Skip to content

Commit

Permalink
Implement error.type attribute in HTTP semconv (#9466)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mateusz Rzeszutek authored Oct 10, 2023
1 parent 64e1554 commit b6dd11a
Show file tree
Hide file tree
Showing 25 changed files with 540 additions and 267 deletions.
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(
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 @@ -61,6 +63,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 @@ -86,6 +89,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 @@ -121,6 +125,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

0 comments on commit b6dd11a

Please sign in to comment.