Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement error.type attribute in HTTP semconv #9466

Merged
merged 5 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public static <REQUEST, RESPONSE> HttpClientAttributesExtractorBuilder<REQUEST,
HttpClientAttributesExtractor(HttpClientAttributesExtractorBuilder<REQUEST, RESPONSE> builder) {
super(
builder.httpAttributesGetter,
HttpStatusCodeConverter.CLIENT,
builder.capturedRequestHeaders,
builder.capturedResponseHeaders,
builder.knownMethods);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes;
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
import io.opentelemetry.semconv.SemanticAttributes;
import java.util.HashSet;
Expand All @@ -31,16 +32,19 @@ abstract class HttpCommonAttributesExtractor<
implements AttributesExtractor<REQUEST, RESPONSE> {

final GETTER getter;
private final HttpStatusCodeConverter statusCodeConverter;
private final List<String> capturedRequestHeaders;
private final List<String> capturedResponseHeaders;
private final Set<String> knownMethods;

HttpCommonAttributesExtractor(
GETTER getter,
HttpStatusCodeConverter statusCodeConverter,
List<String> capturedRequestHeaders,
List<String> capturedResponseHeaders,
Set<String> knownMethods) {
this.getter = getter;
this.statusCodeConverter = statusCodeConverter;
this.capturedRequestHeaders = lowercase(capturedRequestHeaders);
this.capturedResponseHeaders = lowercase(capturedResponseHeaders);
this.knownMethods = new HashSet<>(knownMethods);
Expand Down Expand Up @@ -88,8 +92,9 @@ public void onEnd(
internalSet(attributes, SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, requestBodySize);
}

Integer statusCode = null;
if (response != null) {
Integer statusCode = getter.getHttpResponseStatusCode(request, response, error);
statusCode = getter.getHttpResponseStatusCode(request, response, error);
if (statusCode != null && statusCode > 0) {
if (SemconvStability.emitStableHttpSemconv()) {
internalSet(attributes, SemanticAttributes.HTTP_RESPONSE_STATUS_CODE, (long) statusCode);
Expand All @@ -114,6 +119,25 @@ public void onEnd(
}
}
}

if (SemconvStability.emitStableHttpSemconv()) {
String errorType = null;
if (statusCode != null && statusCode > 0) {
if (statusCodeConverter.isError(statusCode)) {
errorType = statusCode.toString();
}
} else {
errorType = getter.getErrorType(request, response, error);
// fall back to exception class name & _OTHER
if (errorType == null && error != null) {
errorType = error.getClass().getName();
}
if (errorType == null) {
errorType = _OTHER;
}
}
internalSet(attributes, HttpAttributes.ERROR_TYPE, errorType);
}
}

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,25 @@ public interface HttpCommonAttributesGetter<REQUEST, RESPONSE> {
* returned instead.
*/
List<String> getHttpResponseHeader(REQUEST request, RESPONSE response, String name);

/**
* Returns a description of a class of error the operation ended with.
*
* <p>This method is only called if the request failed before response status code was sent or
* received.
*
* <p>If this method is not implemented, or if it returns {@code null}, the exception class name
* (if any was caught) or the value {@code _OTHER} will be used as error type.
*
* <p>The cardinality of the error type should be low. The instrumentations implementing this
* method are recommended to document the custom values they support.
*
* <p>Examples: {@code Bad Request}, {@code java.net.UnknownHostException}, {@code request
* cancelled}, {@code _OTHER}.
*/
@Nullable
default String getErrorType(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about creating a separate extractor just for that, but decided against it -- let's first see if there are any other use cases for this attribute, and we can retroactively implement a new getter interface if there's a need to do so.

REQUEST request, @Nullable RESPONSE response, @Nullable Throwable throwable) {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleHistogramBuilder;
import io.opentelemetry.extension.incubator.metrics.ExtendedLongHistogramBuilder;
import io.opentelemetry.extension.incubator.metrics.ExtendedLongUpDownCounterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes;
import io.opentelemetry.semconv.SemanticAttributes;

final class HttpMetricsAdvice {
Expand All @@ -26,6 +27,7 @@ static void applyStableClientDurationAdvice(DoubleHistogramBuilder builder) {
asList(
SemanticAttributes.HTTP_REQUEST_METHOD,
SemanticAttributes.HTTP_RESPONSE_STATUS_CODE,
HttpAttributes.ERROR_TYPE,
SemanticAttributes.NETWORK_PROTOCOL_NAME,
SemanticAttributes.NETWORK_PROTOCOL_VERSION,
SemanticAttributes.SERVER_ADDRESS,
Expand Down Expand Up @@ -60,6 +62,7 @@ static void applyClientRequestSizeAdvice(LongHistogramBuilder builder) {
// stable attributes
SemanticAttributes.HTTP_REQUEST_METHOD,
SemanticAttributes.HTTP_RESPONSE_STATUS_CODE,
HttpAttributes.ERROR_TYPE,
SemanticAttributes.NETWORK_PROTOCOL_NAME,
SemanticAttributes.NETWORK_PROTOCOL_VERSION,
SemanticAttributes.SERVER_ADDRESS,
Expand All @@ -84,6 +87,7 @@ static void applyStableServerDurationAdvice(DoubleHistogramBuilder builder) {
SemanticAttributes.HTTP_ROUTE,
SemanticAttributes.HTTP_REQUEST_METHOD,
SemanticAttributes.HTTP_RESPONSE_STATUS_CODE,
HttpAttributes.ERROR_TYPE,
SemanticAttributes.NETWORK_PROTOCOL_NAME,
SemanticAttributes.NETWORK_PROTOCOL_VERSION,
SemanticAttributes.URL_SCHEME));
Expand Down Expand Up @@ -119,6 +123,7 @@ static void applyServerRequestSizeAdvice(LongHistogramBuilder builder) {
SemanticAttributes.HTTP_ROUTE,
SemanticAttributes.HTTP_REQUEST_METHOD,
SemanticAttributes.HTTP_RESPONSE_STATUS_CODE,
HttpAttributes.ERROR_TYPE,
SemanticAttributes.NETWORK_PROTOCOL_NAME,
SemanticAttributes.NETWORK_PROTOCOL_VERSION,
SemanticAttributes.URL_SCHEME,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ public static <REQUEST, RESPONSE> HttpServerAttributesExtractorBuilder<REQUEST,
HttpServerAttributesExtractor(HttpServerAttributesExtractorBuilder<REQUEST, RESPONSE> builder) {
super(
builder.httpAttributesGetter,
HttpStatusCodeConverter.SERVER,
builder.capturedRequestHeaders,
builder.capturedResponseHeaders,
builder.knownMethods);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public final class HttpSpanStatusExtractor<REQUEST, RESPONSE>
*/
public static <REQUEST, RESPONSE> SpanStatusExtractor<REQUEST, RESPONSE> create(
HttpClientAttributesGetter<? super REQUEST, ? super RESPONSE> getter) {
return new HttpSpanStatusExtractor<>(getter, HttpStatusConverter.CLIENT);
return new HttpSpanStatusExtractor<>(getter, HttpStatusCodeConverter.CLIENT);
}

/**
Expand All @@ -36,17 +36,17 @@ public static <REQUEST, RESPONSE> SpanStatusExtractor<REQUEST, RESPONSE> create(
*/
public static <REQUEST, RESPONSE> SpanStatusExtractor<REQUEST, RESPONSE> create(
HttpServerAttributesGetter<? super REQUEST, ? super RESPONSE> getter) {
return new HttpSpanStatusExtractor<>(getter, HttpStatusConverter.SERVER);
return new HttpSpanStatusExtractor<>(getter, HttpStatusCodeConverter.SERVER);
}

private final HttpCommonAttributesGetter<? super REQUEST, ? super RESPONSE> getter;
private final HttpStatusConverter statusConverter;
private final HttpStatusCodeConverter statusCodeConverter;

private HttpSpanStatusExtractor(
HttpCommonAttributesGetter<? super REQUEST, ? super RESPONSE> getter,
HttpStatusConverter statusConverter) {
HttpStatusCodeConverter statusCodeConverter) {
this.getter = getter;
this.statusConverter = statusConverter;
this.statusCodeConverter = statusCodeConverter;
}

@Override
Expand All @@ -59,9 +59,8 @@ public void extract(
if (response != null) {
Integer statusCode = getter.getHttpResponseStatusCode(request, response, error);
if (statusCode != null) {
StatusCode statusCodeObj = statusConverter.statusFromHttpStatus(statusCode);
if (statusCodeObj == StatusCode.ERROR) {
spanStatusBuilder.setStatus(statusCodeObj);
if (statusCodeConverter.isError(statusCode)) {
spanStatusBuilder.setStatus(StatusCode.ERROR);
return;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.http;

// https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/http/http-spans.md#status
enum HttpStatusCodeConverter {
SERVER {
@Override
boolean isError(int responseStatusCode) {
return responseStatusCode >= 500
||
// invalid status code, does not exists
responseStatusCode < 100;
}
},
CLIENT {
@Override
boolean isError(int responseStatusCode) {
return responseStatusCode >= 400
||
// invalid status code, does not exists
responseStatusCode < 100;
}
};

abstract boolean isError(int responseStatusCode);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.http.internal;

import static io.opentelemetry.api.common.AttributeKey.stringKey;

import io.opentelemetry.api.common.AttributeKey;

/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public final class HttpAttributes {

// FIXME: remove this class and replace its usages with SemanticAttributes once schema 1.22 is
// released

public static final AttributeKey<String> ERROR_TYPE = stringKey("error.type");

private HttpAttributes() {}
}

This file was deleted.

Loading
Loading