Convert java-http-client to use Instrumenter API (#3790)
This commit is contained in:
parent
6dcf1bbb87
commit
f5be16bc7c
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue