Convert java-http-client to use Instrumenter API
MODIFY
instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/HttpClientInstrumentation.java
instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/HttpClientInstrumentation.java
@@ -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);
}
}

MODIFY
instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/HttpHeadersInjectAdapter.java
instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/HttpHeadersInjectAdapter.java
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);
+ }
}

MODIFY
instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/HttpHeadersInstrumentation.java
instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/HttpHeadersInstrumentation.java
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);
}
}
}

ADD
instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/JdkHttpAttributesExtractor.java
+/*
+ * 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;
+ }
+}

ADD
instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/JdkHttpClientSingletons.java
+/*
+ * 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() {}
+}

DELETE
instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/JdkHttpClientTracer.java
-/*
- * 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);
- }
-}

ADD
instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/JdkHttpNetAttributesExtractor.java
+/*
+ * 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;
+ }
+}

MODIFY
instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/ResponseConsumer.java
instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/ResponseConsumer.java
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);
}
}

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