Convert java-http-client to use Instrumenter API (#3790)

This commit is contained in:
jack-berg 2021-08-09 23:58:57 -05:00 committed by GitHub
parent 6dcf1bbb87
commit f5be16bc7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 251 additions and 116 deletions

View File

@ -8,7 +8,7 @@ package io.opentelemetry.javaagent.instrumentation.httpclient;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.instrumentation.httpclient.JdkHttpClientTracer.tracer;
import static io.opentelemetry.javaagent.instrumentation.httpclient.JdkHttpClientSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
@ -71,17 +71,18 @@ public class HttpClientInstrumentation implements TypeInstrumentation {
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
Context parentContext = currentContext();
if (!tracer().shouldStartSpan(parentContext)) {
if (!instrumenter().shouldStart(parentContext, httpRequest)) {
return;
}
context = tracer().startSpan(parentContext, httpRequest, httpRequest);
context = instrumenter().start(parentContext, httpRequest);
scope = context.makeCurrent();
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit(
@Advice.Return HttpResponse<?> result,
@Advice.Argument(0) HttpRequest httpRequest,
@Advice.Return HttpResponse<?> httpResponse,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
@ -90,11 +91,7 @@ public class HttpClientInstrumentation implements TypeInstrumentation {
}
scope.close();
if (throwable == null) {
tracer().end(context, result);
} else {
tracer().endExceptionally(context, result, throwable);
}
instrumenter().end(context, httpRequest, httpResponse, throwable);
}
}
@ -112,16 +109,17 @@ public class HttpClientInstrumentation implements TypeInstrumentation {
if (bodyHandler != null) {
bodyHandler = new BodyHandlerWrapper(bodyHandler, parentContext);
}
if (!tracer().shouldStartSpan(parentContext)) {
if (!instrumenter().shouldStart(parentContext, httpRequest)) {
return;
}
context = tracer().startSpan(parentContext, httpRequest, httpRequest);
context = instrumenter().start(parentContext, httpRequest);
scope = context.makeCurrent();
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit(
@Advice.Argument(value = 0) HttpRequest httpRequest,
@Advice.Return(readOnly = false) CompletableFuture<HttpResponse<?>> future,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context,
@ -133,9 +131,9 @@ public class HttpClientInstrumentation implements TypeInstrumentation {
scope.close();
if (throwable != null) {
tracer().endExceptionally(context, null, throwable);
instrumenter().end(context, httpRequest, null, throwable);
} else {
future = future.whenComplete(new ResponseConsumer(context));
future = future.whenComplete(new ResponseConsumer(context, httpRequest));
future = CompletableFutureWrapper.wrap(future, parentContext);
}
}

View File

@ -5,15 +5,40 @@
package io.opentelemetry.javaagent.instrumentation.httpclient;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapSetter;
import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** Context propagation is implemented via {@link HttpHeadersInstrumentation}. */
/** Context propagation is initiated via {@link HttpHeadersInstrumentation}. */
public class HttpHeadersInjectAdapter implements TextMapSetter<HttpRequest> {
public static final HttpHeadersInjectAdapter SETTER = new HttpHeadersInjectAdapter();
private final ContextPropagators contextPropagators;
public HttpHeadersInjectAdapter(ContextPropagators contextPropagators) {
this.contextPropagators = contextPropagators;
}
@Override
public void set(HttpRequest carrier, String key, String value) {
// Don't do anything because headers are immutable
}
public HttpHeaders inject(HttpHeaders original) {
Map<String, List<String>> headerMap = new HashMap<>(original.map());
contextPropagators
.getTextMapPropagator()
.inject(
Context.current(),
headerMap,
(carrier, key, value) -> carrier.put(key, Collections.singletonList(value)));
return HttpHeaders.of(headerMap, (s, s2) -> true);
}
}

View File

@ -6,7 +6,7 @@
package io.opentelemetry.javaagent.instrumentation.httpclient;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass;
import static io.opentelemetry.javaagent.instrumentation.httpclient.JdkHttpClientTracer.tracer;
import static io.opentelemetry.javaagent.instrumentation.httpclient.JdkHttpClientSingletons.setter;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.named;
@ -39,7 +39,7 @@ public class HttpHeadersInstrumentation implements TypeInstrumentation {
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit(@Advice.Return(readOnly = false) HttpHeaders headers) {
headers = tracer().inject(headers);
headers = setter().inject(headers);
}
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.httpclient;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpAttributesExtractor;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import org.checkerframework.checker.nullness.qual.Nullable;
class JdkHttpAttributesExtractor extends HttpAttributesExtractor<HttpRequest, HttpResponse<?>> {
@Override
protected String method(HttpRequest httpRequest) {
return httpRequest.method();
}
@Override
protected String url(HttpRequest httpRequest) {
return httpRequest.uri().toString();
}
@Override
protected @Nullable String target(HttpRequest httpRequest) {
return null;
}
@Override
protected @Nullable String host(HttpRequest httpRequest) {
return httpRequest.uri().getHost();
}
@Override
protected @Nullable String scheme(HttpRequest httpRequest) {
return httpRequest.uri().getScheme();
}
@Override
protected @Nullable String userAgent(HttpRequest httpRequest) {
return httpRequest.headers().firstValue("User-Agent").orElse(null);
}
@Override
protected @Nullable Long requestContentLength(
HttpRequest httpRequest, @Nullable HttpResponse<?> httpResponse) {
return null;
}
@Override
protected @Nullable Long requestContentLengthUncompressed(
HttpRequest httpRequest, @Nullable HttpResponse<?> httpResponse) {
return null;
}
@Override
protected Integer statusCode(HttpRequest httpRequest, HttpResponse<?> httpResponse) {
return httpResponse.statusCode();
}
@Override
protected String flavor(HttpRequest httpRequest, @Nullable HttpResponse<?> httpResponse) {
if (httpResponse != null && httpResponse.version() == Version.HTTP_2) {
return SemanticAttributes.HttpFlavorValues.HTTP_2_0;
}
return SemanticAttributes.HttpFlavorValues.HTTP_1_1;
}
@Override
protected @Nullable Long responseContentLength(
HttpRequest httpRequest, HttpResponse<?> httpResponse) {
return null;
}
@Override
protected @Nullable Long responseContentLengthUncompressed(
HttpRequest httpRequest, HttpResponse<?> httpResponse) {
return null;
}
@Override
protected @Nullable String serverName(
HttpRequest httpRequest, @Nullable HttpResponse<?> httpResponse) {
return null;
}
@Override
protected @Nullable String route(HttpRequest httpRequest) {
return null;
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.httpclient;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
import io.opentelemetry.javaagent.instrumentation.api.instrumenter.PeerServiceAttributesExtractor;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class JdkHttpClientSingletons {
private static final HttpHeadersInjectAdapter SETTER;
private static final Instrumenter<HttpRequest, HttpResponse<?>> INSTRUMENTER;
static {
SETTER = new HttpHeadersInjectAdapter(GlobalOpenTelemetry.getPropagators());
JdkHttpAttributesExtractor httpAttributesExtractor = new JdkHttpAttributesExtractor();
SpanNameExtractor<HttpRequest> spanNameExtractor =
HttpSpanNameExtractor.create(httpAttributesExtractor);
SpanStatusExtractor<HttpRequest, HttpResponse<?>> spanStatusExtractor =
HttpSpanStatusExtractor.create(httpAttributesExtractor);
JdkHttpNetAttributesExtractor netAttributesExtractor = new JdkHttpNetAttributesExtractor();
INSTRUMENTER =
Instrumenter.<HttpRequest, HttpResponse<?>>newBuilder(
GlobalOpenTelemetry.get(), "io.opentelemetry.java-http-client", spanNameExtractor)
.setSpanStatusExtractor(spanStatusExtractor)
.addAttributesExtractor(httpAttributesExtractor)
.addAttributesExtractor(netAttributesExtractor)
.addAttributesExtractor(PeerServiceAttributesExtractor.create(netAttributesExtractor))
.addRequestMetrics(HttpClientMetrics.get())
.newClientInstrumenter(SETTER);
}
public static Instrumenter<HttpRequest, HttpResponse<?>> instrumenter() {
return INSTRUMENTER;
}
public static HttpHeadersInjectAdapter setter() {
return SETTER;
}
private JdkHttpClientSingletons() {}
}

View File

@ -1,92 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.httpclient;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapSetter;
import io.opentelemetry.instrumentation.api.tracer.HttpClientTracer;
import io.opentelemetry.instrumentation.api.tracer.net.NetPeerAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.net.URI;
import java.net.http.HttpClient.Version;
import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JdkHttpClientTracer
extends HttpClientTracer<HttpRequest, HttpRequest, HttpResponse<?>> {
private static final JdkHttpClientTracer TRACER = new JdkHttpClientTracer();
private JdkHttpClientTracer() {
super(NetPeerAttributes.INSTANCE);
}
public static JdkHttpClientTracer tracer() {
return TRACER;
}
@Override
protected String getInstrumentationName() {
return "io.opentelemetry.java-http-client";
}
@Override
protected String method(HttpRequest httpRequest) {
return httpRequest.method();
}
@Override
protected URI url(HttpRequest httpRequest) {
return httpRequest.uri();
}
@Override
protected Integer status(HttpResponse<?> httpResponse) {
return httpResponse.statusCode();
}
@Override
protected String requestHeader(HttpRequest httpRequest, String name) {
return httpRequest.headers().firstValue(name).orElse(null);
}
@Override
protected String responseHeader(HttpResponse<?> httpResponse, String name) {
return httpResponse.headers().firstValue(name).orElse(null);
}
@Override
protected void onResponse(Span span, HttpResponse<?> httpResponse) {
super.onResponse(span, httpResponse);
if (httpResponse != null) {
span.setAttribute(
SemanticAttributes.HTTP_FLAVOR,
httpResponse.version() == Version.HTTP_1_1 ? "1.1" : "2.0");
}
}
@Override
protected TextMapSetter<HttpRequest> getSetter() {
return HttpHeadersInjectAdapter.SETTER;
}
public HttpHeaders inject(HttpHeaders original) {
Map<String, List<String>> headerMap = new HashMap<>(original.map());
inject(
Context.current(),
headerMap,
(carrier, key, value) -> carrier.put(key, Collections.singletonList(value)));
return HttpHeaders.of(headerMap, (s, s2) -> true);
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.httpclient;
import io.opentelemetry.instrumentation.api.instrumenter.net.NetAttributesExtractor;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JdkHttpNetAttributesExtractor
extends NetAttributesExtractor<HttpRequest, HttpResponse<?>> {
private static final Logger logger = LoggerFactory.getLogger(JdkHttpNetAttributesExtractor.class);
@Override
public String transport(HttpRequest httpRequest) {
return SemanticAttributes.NetTransportValues.IP_TCP;
}
@Override
public @Nullable String peerName(
HttpRequest httpRequest, @Nullable HttpResponse<?> httpResponse) {
return httpRequest.uri().getHost();
}
@Override
public @Nullable Integer peerPort(
HttpRequest httpRequest, @Nullable HttpResponse<?> httpResponse) {
int port = httpRequest.uri().getPort();
if (port != -1) {
return port;
}
String scheme = httpRequest.uri().getScheme();
if (scheme == null) {
return 80;
}
switch (scheme) {
case "http":
return 80;
case "https":
return 443;
default:
logger.debug("no default port mapping for scheme: {}", scheme);
return null;
}
}
@Override
public @Nullable String peerIp(HttpRequest httpRequest, @Nullable HttpResponse<?> httpResponse) {
return null;
}
}

View File

@ -5,25 +5,24 @@
package io.opentelemetry.javaagent.instrumentation.httpclient;
import static io.opentelemetry.javaagent.instrumentation.httpclient.JdkHttpClientTracer.tracer;
import static io.opentelemetry.javaagent.instrumentation.httpclient.JdkHttpClientSingletons.instrumenter;
import io.opentelemetry.context.Context;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.function.BiConsumer;
public class ResponseConsumer implements BiConsumer<HttpResponse<?>, Throwable> {
private final Context context;
private final HttpRequest httpRequest;
public ResponseConsumer(Context context) {
public ResponseConsumer(Context context, HttpRequest httpRequest) {
this.context = context;
this.httpRequest = httpRequest;
}
@Override
public void accept(HttpResponse<?> httpResponse, Throwable throwable) {
if (throwable == null) {
tracer().end(context, httpResponse);
} else {
tracer().endExceptionally(context, httpResponse, throwable);
}
instrumenter().end(context, httpRequest, httpResponse, throwable);
}
}