Skip to content

Commit

Permalink
Implement error.type attribute in HTTP semconv
Browse files Browse the repository at this point in the history
  • Loading branch information
Mateusz Rzeszutek committed Sep 18, 2023
1 parent b70cbc1 commit b5baecf
Show file tree
Hide file tree
Showing 23 changed files with 619 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,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 @@ -32,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 @@ -89,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, HttpAttributes.HTTP_RESPONSE_STATUS_CODE, (long) statusCode);
Expand All @@ -115,6 +119,23 @@ public void onEnd(
}
}
}

if (SemconvStability.emitStableHttpSemconv()) {
String errorType;
if (statusCode != null) {
errorType = statusCodeConverter.getErrorType(statusCode);
} 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 @@ -31,6 +31,7 @@ static void applyStableClientDurationAdvice(DoubleHistogramBuilder builder) {
asList(
HttpAttributes.HTTP_REQUEST_METHOD,
HttpAttributes.HTTP_RESPONSE_STATUS_CODE,
HttpAttributes.ERROR_TYPE,
NetworkAttributes.NETWORK_PROTOCOL_NAME,
NetworkAttributes.NETWORK_PROTOCOL_VERSION,
NetworkAttributes.SERVER_ADDRESS,
Expand Down Expand Up @@ -70,6 +71,7 @@ static void applyClientRequestSizeAdvice(LongHistogramBuilder builder) {
// stable attributes
HttpAttributes.HTTP_REQUEST_METHOD,
HttpAttributes.HTTP_RESPONSE_STATUS_CODE,
HttpAttributes.ERROR_TYPE,
NetworkAttributes.NETWORK_PROTOCOL_NAME,
NetworkAttributes.NETWORK_PROTOCOL_VERSION,
NetworkAttributes.SERVER_ADDRESS,
Expand Down Expand Up @@ -97,6 +99,7 @@ static void applyStableServerDurationAdvice(DoubleHistogramBuilder builder) {
SemanticAttributes.HTTP_ROUTE,
HttpAttributes.HTTP_REQUEST_METHOD,
HttpAttributes.HTTP_RESPONSE_STATUS_CODE,
HttpAttributes.ERROR_TYPE,
NetworkAttributes.NETWORK_PROTOCOL_NAME,
NetworkAttributes.NETWORK_PROTOCOL_VERSION,
UrlAttributes.URL_SCHEME)));
Expand Down Expand Up @@ -136,6 +139,7 @@ static void applyServerRequestSizeAdvice(LongHistogramBuilder builder) {
SemanticAttributes.HTTP_ROUTE,
HttpAttributes.HTTP_REQUEST_METHOD,
HttpAttributes.HTTP_RESPONSE_STATUS_CODE,
HttpAttributes.ERROR_TYPE,
NetworkAttributes.NETWORK_PROTOCOL_NAME,
NetworkAttributes.NETWORK_PROTOCOL_VERSION,
UrlAttributes.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,7 +59,7 @@ public void extract(
if (response != null) {
Integer statusCode = getter.getHttpResponseStatusCode(request, response, error);
if (statusCode != null) {
StatusCode statusCodeObj = statusConverter.statusFromHttpStatus(statusCode);
StatusCode statusCodeObj = statusCodeConverter.getSpanStatus(statusCode);
if (statusCodeObj == StatusCode.ERROR) {
spanStatusBuilder.setStatus(statusCodeObj);
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

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

import static java.util.Collections.unmodifiableMap;

import io.opentelemetry.api.trace.StatusCode;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;

// https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/http/http-spans.md#status
enum HttpStatusCodeConverter {
SERVER {

@Override
StatusCode getSpanStatus(int responseStatusCode) {
if (responseStatusCode >= 100 && responseStatusCode < 500) {
return StatusCode.UNSET;
}

return StatusCode.ERROR;
}

@Nullable
@Override
String getErrorType(int responseStatusCode) {
return serverErrorTypes.get(responseStatusCode);
}
},
CLIENT {
@Override
StatusCode getSpanStatus(int responseStatusCode) {
if (responseStatusCode >= 100 && responseStatusCode < 400) {
return StatusCode.UNSET;
}

return StatusCode.ERROR;
}

@Nullable
@Override
String getErrorType(int responseStatusCode) {
return clientErrorTypes.get(responseStatusCode);
}
};

abstract StatusCode getSpanStatus(int responseStatusCode);

@Nullable
abstract String getErrorType(int responseStatusCode);

private static final Map<Integer, String> serverErrorTypes;
private static final Map<Integer, String> clientErrorTypes;

// https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
static {
Map<Integer, String> serverPhrases = new HashMap<>();
serverPhrases.put(500, "Internal Server Error");
serverPhrases.put(501, "Not Implemented");
serverPhrases.put(502, "Bad Gateway");
serverPhrases.put(503, "Service Unavailable");
serverPhrases.put(504, "Gateway Timeout");
serverPhrases.put(505, "HTTP Version Not Supported");
serverPhrases.put(506, "Variant Also Negotiates");
serverPhrases.put(507, "Insufficient Storage");
serverPhrases.put(508, "Loop Detected");
serverPhrases.put(510, "Not Extended");
serverPhrases.put(511, "Network Authentication Required");
serverErrorTypes = unmodifiableMap(serverPhrases);

// include all server error types
Map<Integer, String> clientPhrases = new HashMap<>(serverPhrases);
clientPhrases.put(400, "Bad Request");
clientPhrases.put(401, "Unauthorized");
clientPhrases.put(402, "Payment Required");
clientPhrases.put(403, "Forbidden");
clientPhrases.put(404, "Not Found");
clientPhrases.put(405, "Method Not Allowed");
clientPhrases.put(406, "Not Acceptable");
clientPhrases.put(407, "Proxy Authentication Required");
clientPhrases.put(408, "Request Timeout");
clientPhrases.put(409, "Conflict");
clientPhrases.put(410, "Gone");
clientPhrases.put(411, "Length Required");
clientPhrases.put(412, "Precondition Failed");
clientPhrases.put(413, "Content Too Large");
clientPhrases.put(414, "URI Too Long");
clientPhrases.put(415, "Unsupported Media Type");
clientPhrases.put(416, "Range Not Satisfiable");
clientPhrases.put(417, "Expectation Failed");
clientPhrases.put(418, "I'm a teapot");
clientPhrases.put(421, "Misdirected Request");
clientPhrases.put(422, "Unprocessable Content");
clientPhrases.put(423, "Locked");
clientPhrases.put(424, "Failed Dependency");
clientPhrases.put(425, "Too Early");
clientPhrases.put(426, "Upgrade Required");
clientPhrases.put(428, "Precondition Required");
clientPhrases.put(429, "Too Many Requests");
clientPhrases.put(431, "Request Header Fields Too Large");
clientPhrases.put(451, "Unavailable For Legal Reasons");
clientErrorTypes = unmodifiableMap(clientPhrases);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,7 @@ public final class HttpAttributes {
public static final AttributeKey<Long> HTTP_RESPONSE_STATUS_CODE =
longKey("http.response.status_code");

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

private HttpAttributes() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class HttpSpanStatusExtractorTest {
@ParameterizedTest
@ValueSource(ints = {1, 100, 101, 200, 201, 300, 301, 500, 501, 600, 601})
void hasServerStatus(int statusCode) {
StatusCode expectedStatusCode = HttpStatusConverter.SERVER.statusFromHttpStatus(statusCode);
StatusCode expectedStatusCode = HttpStatusCodeConverter.SERVER.getSpanStatus(statusCode);
when(serverGetter.getHttpResponseStatusCode(anyMap(), anyMap(), isNull()))
.thenReturn(statusCode);

Expand All @@ -51,7 +51,7 @@ void hasServerStatus(int statusCode) {
@ParameterizedTest
@ValueSource(ints = {1, 100, 101, 200, 201, 300, 301, 400, 401, 500, 501, 600, 601})
void hasClientStatus(int statusCode) {
StatusCode expectedStatusCode = HttpStatusConverter.CLIENT.statusFromHttpStatus(statusCode);
StatusCode expectedStatusCode = HttpStatusCodeConverter.CLIENT.getSpanStatus(statusCode);
when(clientGetter.getHttpResponseStatusCode(anyMap(), anyMap(), isNull()))
.thenReturn(statusCode);

Expand Down
Loading

0 comments on commit b5baecf

Please sign in to comment.