diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/build.gradle.kts b/instrumentation/apache-httpasyncclient-4.1/javaagent/build.gradle.kts index 9f4aefdd36e6..c5560b34af9c 100644 --- a/instrumentation/apache-httpasyncclient-4.1/javaagent/build.gradle.kts +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/build.gradle.kts @@ -14,5 +14,7 @@ muzzle { } dependencies { + implementation(project(":instrumentation:apache-httpclient:commons:javaagent")) + implementation(project(":instrumentation:apache-httpclient:commons-4.0:javaagent")) library("org.apache.httpcomponents:httpasyncclient:4.1") } diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientHttpAttributesGetter.java b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientHttpAttributesGetter.java deleted file mode 100644 index 0d4bdf237107..000000000000 --- a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientHttpAttributesGetter.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient; - -import static io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient.ApacheHttpClientRequest.headersToList; - -import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesGetter; -import java.util.List; -import javax.annotation.Nullable; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; - -final class ApacheHttpAsyncClientHttpAttributesGetter - implements HttpClientAttributesGetter { - - @Override - public String getMethod(ApacheHttpClientRequest request) { - return request.getMethod(); - } - - @Override - public String getUrl(ApacheHttpClientRequest request) { - return request.getUrl(); - } - - @Override - public List getRequestHeader(ApacheHttpClientRequest request, String name) { - return request.getHeader(name); - } - - @Override - @Nullable - public Integer getStatusCode( - ApacheHttpClientRequest request, HttpResponse response, @Nullable Throwable error) { - StatusLine statusLine = response.getStatusLine(); - return statusLine != null ? statusLine.getStatusCode() : null; - } - - @Override - @Nullable - public String getFlavor(ApacheHttpClientRequest request, @Nullable HttpResponse response) { - return request.getFlavor(); - } - - @Override - public List getResponseHeader( - ApacheHttpClientRequest request, HttpResponse response, String name) { - return headersToList(response.getHeaders(name)); - } -} diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientInstrumentation.java b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientInstrumentation.java index 53b9cffb58a7..a23f8204841c 100644 --- a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientInstrumentation.java +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientInstrumentation.java @@ -8,34 +8,25 @@ import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -import static io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient.ApacheHttpAsyncClientSingletons.instrumenter; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import java.io.IOException; -import java.util.logging.Logger; -import javax.annotation.Nullable; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientOtelContext; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; -import org.apache.http.HttpException; -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; import org.apache.http.concurrent.FutureCallback; -import org.apache.http.nio.ContentEncoder; -import org.apache.http.nio.IOControl; import org.apache.http.nio.protocol.HttpAsyncRequestProducer; +import org.apache.http.nio.protocol.HttpAsyncResponseConsumer; +import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; -import org.apache.http.protocol.HttpCoreContext; -public class ApacheHttpAsyncClientInstrumentation implements TypeInstrumentation { +public final class ApacheHttpAsyncClientInstrumentation implements TypeInstrumentation { @Override public ElementMatcher classLoaderOptimization() { @@ -66,193 +57,24 @@ public static class ClientAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void methodEnter( @Advice.Argument(value = 0, readOnly = false) HttpAsyncRequestProducer requestProducer, - @Advice.Argument(2) HttpContext httpContext, + @Advice.Argument(value = 1, readOnly = false) HttpAsyncResponseConsumer responseConsumer, + @Advice.Argument(value = 2, readOnly = false) HttpContext httpContext, @Advice.Argument(value = 3, readOnly = false) FutureCallback futureCallback) { Context parentContext = currentContext(); + if (httpContext == null) { + httpContext = new BasicHttpContext(); + } + ApacheHttpClientOtelContext httpOtelContext = ApacheHttpClientOtelContext.adapt(httpContext); + httpOtelContext.markAsyncClient(); WrappedFutureCallback wrappedFutureCallback = new WrappedFutureCallback<>(parentContext, httpContext, futureCallback); requestProducer = - new DelegatingRequestProducer(parentContext, requestProducer, wrappedFutureCallback); + new WrappedRequestProducer( + parentContext, httpContext, requestProducer, wrappedFutureCallback); + responseConsumer = new WrappedResponseConsumer<>(parentContext, responseConsumer); futureCallback = wrappedFutureCallback; } } - - public static class DelegatingRequestProducer implements HttpAsyncRequestProducer { - private final Context parentContext; - private final HttpAsyncRequestProducer delegate; - private final WrappedFutureCallback wrappedFutureCallback; - - public DelegatingRequestProducer( - Context parentContext, - HttpAsyncRequestProducer delegate, - WrappedFutureCallback wrappedFutureCallback) { - this.parentContext = parentContext; - this.delegate = delegate; - this.wrappedFutureCallback = wrappedFutureCallback; - } - - @Override - public HttpHost getTarget() { - return delegate.getTarget(); - } - - @Override - public HttpRequest generateRequest() throws IOException, HttpException { - HttpHost target = delegate.getTarget(); - HttpRequest request = delegate.generateRequest(); - - ApacheHttpClientRequest otelRequest = new ApacheHttpClientRequest(target, request); - - if (instrumenter().shouldStart(parentContext, otelRequest)) { - wrappedFutureCallback.context = instrumenter().start(parentContext, otelRequest); - wrappedFutureCallback.otelRequest = otelRequest; - } - - return request; - } - - @Override - public void produceContent(ContentEncoder encoder, IOControl ioctrl) throws IOException { - delegate.produceContent(encoder, ioctrl); - } - - @Override - public void requestCompleted(HttpContext context) { - delegate.requestCompleted(context); - } - - @Override - public void failed(Exception ex) { - delegate.failed(ex); - } - - @Override - public boolean isRepeatable() { - return delegate.isRepeatable(); - } - - @Override - public void resetRequest() throws IOException { - delegate.resetRequest(); - } - - @Override - public void close() throws IOException { - delegate.close(); - } - } - - public static class WrappedFutureCallback implements FutureCallback { - - private static final Logger logger = Logger.getLogger(WrappedFutureCallback.class.getName()); - - private final Context parentContext; - @Nullable private final HttpContext httpContext; - private final FutureCallback delegate; - - private volatile Context context; - private volatile ApacheHttpClientRequest otelRequest; - - public WrappedFutureCallback( - Context parentContext, HttpContext httpContext, FutureCallback delegate) { - this.parentContext = parentContext; - this.httpContext = httpContext; - // Note: this can be null in real life, so we have to handle this carefully - this.delegate = delegate; - } - - @Override - public void completed(T result) { - if (context == null) { - // this is unexpected - logger.fine("context was never set"); - completeDelegate(result); - return; - } - - instrumenter().end(context, otelRequest, getResponseFromHttpContext(), null); - - if (parentContext == null) { - completeDelegate(result); - return; - } - - try (Scope ignored = parentContext.makeCurrent()) { - completeDelegate(result); - } - } - - @Override - public void failed(Exception ex) { - if (context == null) { - // this is unexpected - logger.fine("context was never set"); - failDelegate(ex); - return; - } - - // end span before calling delegate - instrumenter().end(context, otelRequest, getResponseFromHttpContext(), ex); - - if (parentContext == null) { - failDelegate(ex); - return; - } - - try (Scope ignored = parentContext.makeCurrent()) { - failDelegate(ex); - } - } - - @Override - public void cancelled() { - if (context == null) { - // this is unexpected - logger.fine("context was never set"); - cancelDelegate(); - return; - } - - // TODO (trask) add "canceled" span attribute - // end span before calling delegate - instrumenter().end(context, otelRequest, getResponseFromHttpContext(), null); - - if (parentContext == null) { - cancelDelegate(); - return; - } - - try (Scope ignored = parentContext.makeCurrent()) { - cancelDelegate(); - } - } - - private void completeDelegate(T result) { - if (delegate != null) { - delegate.completed(result); - } - } - - private void failDelegate(Exception ex) { - if (delegate != null) { - delegate.failed(ex); - } - } - - private void cancelDelegate() { - if (delegate != null) { - delegate.cancelled(); - } - } - - @Nullable - private HttpResponse getResponseFromHttpContext() { - if (httpContext == null) { - return null; - } - return (HttpResponse) httpContext.getAttribute(HttpCoreContext.HTTP_RESPONSE); - } - } } diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientInstrumentationModule.java b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientInstrumentationModule.java index 40291335cbde..ed4c2d9d780d 100644 --- a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientInstrumentationModule.java +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientInstrumentationModule.java @@ -5,21 +5,24 @@ package io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient; -import static java.util.Collections.singletonList; - import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientProcessorInstrumentation; +import java.util.ArrayList; import java.util.List; @AutoService(InstrumentationModule.class) -public class ApacheHttpAsyncClientInstrumentationModule extends InstrumentationModule { +public final class ApacheHttpAsyncClientInstrumentationModule extends InstrumentationModule { public ApacheHttpAsyncClientInstrumentationModule() { super("apache-httpasyncclient", "apache-httpasyncclient-4.1"); } @Override public List typeInstrumentations() { - return singletonList(new ApacheHttpAsyncClientInstrumentation()); + List instrumentationList = new ArrayList<>(); + instrumentationList.add(new ApacheHttpAsyncClientInstrumentation()); + instrumentationList.add(new ApacheHttpClientProcessorInstrumentation()); + return instrumentationList; } } diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientNetAttributesGetter.java b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientNetAttributesGetter.java deleted file mode 100644 index 817783cb8584..000000000000 --- a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientNetAttributesGetter.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient; - -import io.opentelemetry.instrumentation.api.instrumenter.net.InetSocketAddressNetClientAttributesGetter; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.net.InetSocketAddress; -import javax.annotation.Nullable; -import org.apache.http.HttpResponse; - -final class ApacheHttpAsyncClientNetAttributesGetter - extends InetSocketAddressNetClientAttributesGetter { - - @Override - public String getTransport(ApacheHttpClientRequest request, @Nullable HttpResponse response) { - return SemanticAttributes.NetTransportValues.IP_TCP; - } - - @Override - @Nullable - public String getPeerName(ApacheHttpClientRequest request) { - return request.getPeerName(); - } - - @Override - public Integer getPeerPort(ApacheHttpClientRequest request) { - return request.getPeerPort(); - } - - @Nullable - @Override - protected InetSocketAddress getPeerSocketAddress( - ApacheHttpClientRequest request, @Nullable HttpResponse response) { - return request.peerSocketAddress(); - } -} diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientSingletons.java b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientSingletons.java index 152b9c7ca740..e658d7fb60d6 100644 --- a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientSingletons.java +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientSingletons.java @@ -5,47 +5,25 @@ package io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; -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.instrumentation.api.instrumenter.net.PeerServiceAttributesExtractor; -import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig; -import org.apache.http.HttpResponse; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.ApacheHttpClientInstrumenter; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpRequest; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpResponse; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientInstrumentationHelper; public final class ApacheHttpAsyncClientSingletons { private static final String INSTRUMENTATION_NAME = "io.opentelemetry.apache-httpasyncclient-4.1"; - private static final Instrumenter INSTRUMENTER; + private static final ApacheHttpClientInstrumentationHelper HELPER; static { - ApacheHttpAsyncClientHttpAttributesGetter httpAttributesGetter = - new ApacheHttpAsyncClientHttpAttributesGetter(); - ApacheHttpAsyncClientNetAttributesGetter netAttributesGetter = - new ApacheHttpAsyncClientNetAttributesGetter(); - - INSTRUMENTER = - Instrumenter.builder( - GlobalOpenTelemetry.get(), - INSTRUMENTATION_NAME, - HttpSpanNameExtractor.create(httpAttributesGetter)) - .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) - .addAttributesExtractor( - HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) - .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) - .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) - .build()) - .addAttributesExtractor( - PeerServiceAttributesExtractor.create( - netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(HttpHeaderSetter.INSTANCE); + Instrumenter intrumenter; + intrumenter = ApacheHttpClientInstrumenter.create(INSTRUMENTATION_NAME); + HELPER = new ApacheHttpClientInstrumentationHelper(intrumenter); } - public static Instrumenter instrumenter() { - return INSTRUMENTER; + public static ApacheHttpClientInstrumentationHelper helper() { + return HELPER; } private ApacheHttpAsyncClientSingletons() {} diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpClientRequest.java b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpClientRequest.java deleted file mode 100644 index e9b1c6f27e0d..000000000000 --- a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpClientRequest.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient; - -import static java.util.logging.Level.FINE; - -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.logging.Logger; -import javax.annotation.Nullable; -import org.apache.http.Header; -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.ProtocolVersion; - -public final class ApacheHttpClientRequest { - - private static final Logger logger = Logger.getLogger(ApacheHttpClientRequest.class.getName()); - - @Nullable private final URI uri; - - private final HttpRequest delegate; - @Nullable private final HttpHost target; - - public ApacheHttpClientRequest(@Nullable HttpHost httpHost, HttpRequest httpRequest) { - URI calculatedUri = getUri(httpRequest); - if (calculatedUri != null && httpHost != null) { - uri = getCalculatedUri(httpHost, calculatedUri); - } else { - uri = calculatedUri; - } - delegate = httpRequest; - target = httpHost; - } - - public List getHeader(String name) { - return headersToList(delegate.getHeaders(name)); - } - - // minimize memory overhead by not using streams - static List headersToList(Header[] headers) { - if (headers.length == 0) { - return Collections.emptyList(); - } - List headersList = new ArrayList<>(headers.length); - for (int i = 0; i < headers.length; ++i) { - headersList.add(headers[i].getValue()); - } - return headersList; - } - - public void setHeader(String name, String value) { - delegate.setHeader(name, value); - } - - public String getMethod() { - return delegate.getRequestLine().getMethod(); - } - - public String getUrl() { - return uri != null ? uri.toString() : null; - } - - public String getFlavor() { - ProtocolVersion protocolVersion = delegate.getProtocolVersion(); - String protocol = protocolVersion.getProtocol(); - if (!protocol.equals("HTTP")) { - return null; - } - int major = protocolVersion.getMajor(); - int minor = protocolVersion.getMinor(); - if (major == 1 && minor == 0) { - return SemanticAttributes.HttpFlavorValues.HTTP_1_0; - } - if (major == 1 && minor == 1) { - return SemanticAttributes.HttpFlavorValues.HTTP_1_1; - } - if (major == 2 && minor == 0) { - return SemanticAttributes.HttpFlavorValues.HTTP_2_0; - } - logger.log(FINE, "unexpected http protocol version: {0}", protocolVersion); - return null; - } - - public String getPeerName() { - return uri != null ? uri.getHost() : null; - } - - public Integer getPeerPort() { - if (uri == null) { - return null; - } - int port = uri.getPort(); - if (port != -1) { - return port; - } - switch (uri.getScheme()) { - case "http": - return 80; - case "https": - return 443; - default: - logger.log(FINE, "no default port mapping for scheme: {0}", uri.getScheme()); - return null; - } - } - - @Nullable - private static URI getUri(HttpRequest httpRequest) { - try { - // this can be relative or absolute - return new URI(httpRequest.getRequestLine().getUri()); - } catch (URISyntaxException e) { - logger.log(FINE, e.getMessage(), e); - return null; - } - } - - @Nullable - private static URI getCalculatedUri(HttpHost httpHost, URI uri) { - try { - String path = uri.getPath(); - if (!path.startsWith("/")) { - // elasticsearch RestClient sends relative urls - // TODO(trask) add test for this and extend to Apache 4, 4.3 and 5 - path = "/" + path; - } - return new URI( - httpHost.getSchemeName(), - null, - httpHost.getHostName(), - httpHost.getPort(), - path, - uri.getQuery(), - uri.getFragment()); - } catch (URISyntaxException e) { - logger.log(FINE, e.getMessage(), e); - return null; - } - } - - @Nullable - public InetSocketAddress peerSocketAddress() { - if (target == null) { - return null; - } - InetAddress inetAddress = target.getAddress(); - return inetAddress == null ? null : new InetSocketAddress(inetAddress, target.getPort()); - } -} diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/HttpHeaderSetter.java b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/HttpHeaderSetter.java deleted file mode 100644 index 38be9af79a37..000000000000 --- a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/HttpHeaderSetter.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient; - -import io.opentelemetry.context.propagation.TextMapSetter; - -enum HttpHeaderSetter implements TextMapSetter { - INSTANCE; - - @Override - public void set(ApacheHttpClientRequest carrier, String key, String value) { - carrier.setHeader(key, value); - } -} diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedContentDecoder.java b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedContentDecoder.java new file mode 100644 index 000000000000..c7f3535e3e15 --- /dev/null +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedContentDecoder.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient; + +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics.createOrGetWithParentContext; + +import io.opentelemetry.context.Context; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; +import java.io.IOException; +import java.nio.ByteBuffer; +import org.apache.http.nio.ContentDecoder; + +public final class WrappedContentDecoder implements ContentDecoder { + private final Context parentContext; + private final ContentDecoder delegate; + + public WrappedContentDecoder(Context parentContext, ContentDecoder delegate) { + this.delegate = delegate; + this.parentContext = parentContext; + } + + @Override + public int read(ByteBuffer byteBuffer) throws IOException { + if (byteBuffer.hasRemaining()) { + BytesTransferMetrics metrics = createOrGetWithParentContext(parentContext); + metrics.addResponseBytes(byteBuffer.limit()); + } + return delegate.read(byteBuffer); + } + + @Override + public boolean isCompleted() { + return delegate.isCompleted(); + } +} diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedContentEncoder.java b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedContentEncoder.java new file mode 100644 index 000000000000..736fd4131605 --- /dev/null +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedContentEncoder.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient; + +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics.createOrGetWithParentContext; + +import io.opentelemetry.context.Context; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; +import java.io.IOException; +import java.nio.ByteBuffer; +import org.apache.http.nio.ContentEncoder; + +public final class WrappedContentEncoder implements ContentEncoder { + private final Context parentContext; + private final ContentEncoder delegate; + + public WrappedContentEncoder(Context parentContext, ContentEncoder delegate) { + this.parentContext = parentContext; + this.delegate = delegate; + } + + @Override + public int write(ByteBuffer byteBuffer) throws IOException { + BytesTransferMetrics metrics = createOrGetWithParentContext(parentContext); + metrics.addRequestBytes(byteBuffer.limit()); + return delegate.write(byteBuffer); + } + + @Override + public void complete() throws IOException { + delegate.complete(); + } + + @Override + public boolean isCompleted() { + return delegate.isCompleted(); + } +} diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedFutureCallback.java b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedFutureCallback.java new file mode 100644 index 000000000000..31ba101006db --- /dev/null +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedFutureCallback.java @@ -0,0 +1,126 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient; + +import static io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient.ApacheHttpAsyncClientSingletons.helper; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientContextManager.httpContextManager; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientRequest; +import java.util.logging.Logger; +import org.apache.http.concurrent.FutureCallback; +import org.apache.http.protocol.HttpContext; + +public final class WrappedFutureCallback implements FutureCallback { + private static final Logger logger = Logger.getLogger(WrappedFutureCallback.class.getName()); + + private final Context parentContext; + private final HttpContext httpContext; + private final FutureCallback delegate; + + volatile Context context; + volatile ApacheHttpClientRequest otelRequest; + + public WrappedFutureCallback( + Context parentContext, HttpContext httpContext, FutureCallback delegate) { + this.parentContext = parentContext; + this.httpContext = httpContext; + // Note: this can be null in real life, so we have to handle this carefully + this.delegate = delegate; + } + + @Override + public void completed(T result) { + if (context == null) { + // this is unexpected + logger.fine("context was never set"); + completeDelegate(result); + return; + } + + helper().endInstrumentation(context, otelRequest, result, null); + + if (parentContext == null) { + completeDelegate(result); + return; + } + + try (Scope ignored = parentContext.makeCurrent()) { + completeDelegate(result); + } + } + + @Override + public void failed(Exception ex) { + if (context == null) { + // this is unexpected + logger.fine("context was never set"); + failDelegate(ex); + return; + } + + // end span before calling delegate + helper().endInstrumentation(context, otelRequest, null, ex); + + if (parentContext == null) { + failDelegate(ex); + return; + } + + try (Scope ignored = parentContext.makeCurrent()) { + failDelegate(ex); + } + } + + @Override + public void cancelled() { + if (context == null) { + // this is unexpected + logger.fine("context was never set"); + cancelDelegate(); + return; + } + + // TODO (trask) add "canceled" span attribute + // end span before calling delegate + helper().endInstrumentation(context, otelRequest, null, null); + + if (parentContext == null) { + cancelDelegate(); + return; + } + + try (Scope ignored = parentContext.makeCurrent()) { + cancelDelegate(); + } + } + + private void completeDelegate(T result) { + removeOtelAttributes(); + if (delegate != null) { + delegate.completed(result); + } + } + + private void failDelegate(Exception ex) { + removeOtelAttributes(); + if (delegate != null) { + delegate.failed(ex); + } + } + + private void cancelDelegate() { + removeOtelAttributes(); + if (delegate != null) { + delegate.cancelled(); + } + } + + private void removeOtelAttributes() { + httpContextManager().clearOtelAttributes(httpContext); + } +} diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedRequestProducer.java b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedRequestProducer.java new file mode 100644 index 000000000000..832cbbc5f3c4 --- /dev/null +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedRequestProducer.java @@ -0,0 +1,95 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient; + +import static io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient.ApacheHttpAsyncClientSingletons.helper; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientContextManager.httpContextManager; + +import io.opentelemetry.context.Context; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientRequest; +import java.io.IOException; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.nio.ContentEncoder; +import org.apache.http.nio.IOControl; +import org.apache.http.nio.protocol.HttpAsyncRequestProducer; +import org.apache.http.protocol.HttpContext; + +public final class WrappedRequestProducer implements HttpAsyncRequestProducer { + private final Context parentContext; + private final HttpContext httpContext; + private final HttpAsyncRequestProducer delegate; + private final WrappedFutureCallback wrappedFutureCallback; + + public WrappedRequestProducer( + Context parentContext, + HttpContext httpContext, + HttpAsyncRequestProducer delegate, + WrappedFutureCallback wrappedFutureCallback) { + this.parentContext = parentContext; + this.httpContext = httpContext; + this.delegate = delegate; + this.wrappedFutureCallback = wrappedFutureCallback; + } + + @Override + public HttpHost getTarget() { + return delegate.getTarget(); + } + + @Override + public HttpRequest generateRequest() throws IOException, HttpException { + HttpHost target = delegate.getTarget(); + HttpRequest request = delegate.generateRequest(); + + ApacheHttpClientRequest otelRequest; + otelRequest = new ApacheHttpClientRequest(parentContext, target, request); + Context context = helper().startInstrumentation(parentContext, request, otelRequest); + + if (context != null) { + wrappedFutureCallback.context = context; + wrappedFutureCallback.otelRequest = otelRequest; + + // As the http processor instrumentation is going to be called asynchronously, + // we will need to store the otel context variables in http context for the + // http processor instrumentation to use + httpContextManager().setCurrentContext(httpContext, context); + } + + return request; + } + + @Override + public void produceContent(ContentEncoder encoder, IOControl ioctrl) throws IOException { + delegate.produceContent(new WrappedContentEncoder(parentContext, encoder), ioctrl); + } + + @Override + public void requestCompleted(HttpContext context) { + delegate.requestCompleted(context); + } + + @Override + public void failed(Exception ex) { + delegate.failed(ex); + } + + @Override + public boolean isRepeatable() { + return delegate.isRepeatable(); + } + + @Override + public void resetRequest() throws IOException { + delegate.resetRequest(); + } + + @Override + public void close() throws IOException { + delegate.close(); + } +} diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedResponseConsumer.java b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedResponseConsumer.java new file mode 100644 index 000000000000..51b96677e8d9 --- /dev/null +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedResponseConsumer.java @@ -0,0 +1,71 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient; + +import io.opentelemetry.context.Context; +import java.io.IOException; +import org.apache.http.HttpException; +import org.apache.http.HttpResponse; +import org.apache.http.nio.ContentDecoder; +import org.apache.http.nio.IOControl; +import org.apache.http.nio.protocol.HttpAsyncResponseConsumer; +import org.apache.http.protocol.HttpContext; + +public final class WrappedResponseConsumer implements HttpAsyncResponseConsumer { + private final Context parentContext; + private final HttpAsyncResponseConsumer delegate; + + public WrappedResponseConsumer(Context parentContext, HttpAsyncResponseConsumer delegate) { + this.parentContext = parentContext; + this.delegate = delegate; + } + + @Override + public void responseReceived(HttpResponse httpResponse) throws IOException, HttpException { + delegate.responseReceived(httpResponse); + } + + @Override + public void consumeContent(ContentDecoder contentDecoder, IOControl ioControl) + throws IOException { + delegate.consumeContent(new WrappedContentDecoder(parentContext, contentDecoder), ioControl); + } + + @Override + public void responseCompleted(HttpContext httpContext) { + delegate.responseCompleted(httpContext); + } + + @Override + public void failed(Exception e) { + delegate.failed(e); + } + + @Override + public Exception getException() { + return delegate.getException(); + } + + @Override + public T getResult() { + return delegate.getResult(); + } + + @Override + public boolean isDone() { + return delegate.isDone(); + } + + @Override + public void close() throws IOException { + delegate.close(); + } + + @Override + public boolean cancel() { + return delegate.cancel(); + } +} diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientTest.java b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientTest.java index fe67a5e829a8..d364817252a9 100644 --- a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientTest.java +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientTest.java @@ -228,14 +228,14 @@ void configureTest(HttpClientTestOptions.Builder optionsBuilder) { optionsBuilder.setUserAgent("httpasyncclient"); optionsBuilder.setResponseCodeOnRedirectError(302); optionsBuilder.enableTestReadTimeout(); - optionsBuilder.setHttpAttributes( - endpoint -> { - Set> attributes = - new HashSet<>(HttpClientTestOptions.DEFAULT_HTTP_ATTRIBUTES); - attributes.add(SemanticAttributes.HTTP_SCHEME); - attributes.add(SemanticAttributes.HTTP_TARGET); - return attributes; - }); + optionsBuilder.setHttpAttributes(ApacheHttpAsyncClientTest::getHttpAttributes); + } + + private static Set> getHttpAttributes(URI endpoint) { + Set> attributes = new HashSet<>(HttpClientTestOptions.DEFAULT_HTTP_ATTRIBUTES); + attributes.add(SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH); + attributes.add(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH); + return attributes; } static String fullPathFromUri(URI uri) { diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/build.gradle.kts b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/build.gradle.kts index afabfa6580a2..16ceeaf0fad5 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/build.gradle.kts +++ b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/build.gradle.kts @@ -24,5 +24,8 @@ muzzle { } dependencies { - library("org.apache.httpcomponents:httpclient:4.0") + implementation(project(":instrumentation:apache-httpclient:commons:javaagent")) + implementation(project(":instrumentation:apache-httpclient:commons-4.0:javaagent")) + // 4.0.x uses GuardedBy which interferes with compiling tests + library("org.apache.httpcomponents:httpclient:4.1") } diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientHelper.java b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientHelper.java deleted file mode 100644 index bdfc241a81fd..000000000000 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientHelper.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0; - -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.ApacheHttpClientSingletons.instrumenter; - -import io.opentelemetry.context.Context; -import org.apache.http.HttpResponse; - -public final class ApacheHttpClientHelper { - - public static void doMethodExit( - Context context, ApacheHttpClientRequest request, Object result, Throwable throwable) { - if (throwable != null) { - instrumenter().end(context, request, null, throwable); - } else if (result instanceof HttpResponse) { - instrumenter().end(context, request, (HttpResponse) result, null); - } else { - // ended in WrappingStatusSettingResponseHandler - } - } - - private ApacheHttpClientHelper() {} -} diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientHttpAttributesGetter.java b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientHttpAttributesGetter.java deleted file mode 100644 index 41dd0c2a07bd..000000000000 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientHttpAttributesGetter.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0; - -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.ApacheHttpClientRequest.headersToList; - -import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesGetter; -import java.util.List; -import javax.annotation.Nullable; -import org.apache.http.HttpResponse; - -final class ApacheHttpClientHttpAttributesGetter - implements HttpClientAttributesGetter { - - @Override - public String getMethod(ApacheHttpClientRequest request) { - return request.getMethod(); - } - - @Override - public String getUrl(ApacheHttpClientRequest request) { - return request.getUrl(); - } - - @Override - public List getRequestHeader(ApacheHttpClientRequest request, String name) { - return request.getHeader(name); - } - - @Override - public Integer getStatusCode( - ApacheHttpClientRequest request, HttpResponse response, @Nullable Throwable error) { - return response.getStatusLine().getStatusCode(); - } - - @Override - @Nullable - public String getFlavor(ApacheHttpClientRequest request, @Nullable HttpResponse response) { - return request.getFlavor(); - } - - @Override - public List getResponseHeader( - ApacheHttpClientRequest request, HttpResponse response, String name) { - return headersToList(response.getHeaders(name)); - } -} diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientInstrumentation.java b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientInstrumentation.java index 09b38bd89f3b..8094429a4863 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientInstrumentation.java +++ b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientInstrumentation.java @@ -8,7 +8,7 @@ import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.ApacheHttpClientSingletons.instrumenter; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.ApacheHttpClientSingletons.helper; import static net.bytebuddy.matcher.ElementMatchers.isAbstract; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.named; @@ -20,6 +20,7 @@ import io.opentelemetry.context.Scope; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientRequest; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -28,7 +29,7 @@ import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpUriRequest; -public class ApacheHttpClientInstrumentation implements TypeInstrumentation { +public final class ApacheHttpClientInstrumentation implements TypeInstrumentation { @Override public ElementMatcher classLoaderOptimization() { return hasClassesNamed("org.apache.http.client.HttpClient"); @@ -133,14 +134,13 @@ public static void methodEnter( @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); + otelRequest = new ApacheHttpClientRequest(parentContext, request.getURI(), request); + context = helper().startInstrumentation(parentContext, request, otelRequest); - otelRequest = new ApacheHttpClientRequest(request); - - if (!instrumenter().shouldStart(parentContext, otelRequest)) { + if (context == null) { return; } - context = instrumenter().start(parentContext, otelRequest); scope = context.makeCurrent(); } @@ -157,7 +157,7 @@ public static void methodExit( } scope.close(); - ApacheHttpClientHelper.doMethodExit(context, otelRequest, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } @@ -172,14 +172,13 @@ public static void methodEnter( @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); + otelRequest = new ApacheHttpClientRequest(parentContext, request.getURI(), request); + context = helper().startInstrumentation(parentContext, request, otelRequest); - otelRequest = new ApacheHttpClientRequest(request); - - if (!instrumenter().shouldStart(parentContext, otelRequest)) { + if (context == null) { return; } - context = instrumenter().start(parentContext, otelRequest); scope = context.makeCurrent(); // Wrap the handler so we capture the status code @@ -203,7 +202,7 @@ public static void methodExit( } scope.close(); - ApacheHttpClientHelper.doMethodExit(context, otelRequest, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } @@ -218,14 +217,13 @@ public static void methodEnter( @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); + otelRequest = new ApacheHttpClientRequest(parentContext, host, request); + context = helper().startInstrumentation(parentContext, request, otelRequest); - otelRequest = new ApacheHttpClientRequest(host, request); - - if (!instrumenter().shouldStart(parentContext, otelRequest)) { + if (context == null) { return; } - context = instrumenter().start(parentContext, otelRequest); scope = context.makeCurrent(); } @@ -241,7 +239,7 @@ public static void methodExit( } scope.close(); - ApacheHttpClientHelper.doMethodExit(context, otelRequest, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } @@ -257,14 +255,13 @@ public static void methodEnter( @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); + otelRequest = new ApacheHttpClientRequest(parentContext, host, request); + context = helper().startInstrumentation(parentContext, request, otelRequest); - otelRequest = new ApacheHttpClientRequest(host, request); - - if (!instrumenter().shouldStart(parentContext, otelRequest)) { + if (context == null) { return; } - context = instrumenter().start(parentContext, otelRequest); scope = context.makeCurrent(); // Wrap the handler so we capture the status code @@ -287,7 +284,7 @@ public static void methodExit( } scope.close(); - ApacheHttpClientHelper.doMethodExit(context, otelRequest, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } } diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientInstrumentationModule.java b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientInstrumentationModule.java index b0821b7dcfc3..ef7c10ae524b 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientInstrumentationModule.java +++ b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientInstrumentationModule.java @@ -5,22 +5,24 @@ package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0; -import static java.util.Collections.singletonList; - import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientProcessorInstrumentation; +import java.util.ArrayList; import java.util.List; @AutoService(InstrumentationModule.class) -public class ApacheHttpClientInstrumentationModule extends InstrumentationModule { - +public final class ApacheHttpClientInstrumentationModule extends InstrumentationModule { public ApacheHttpClientInstrumentationModule() { super("apache-httpclient", "apache-httpclient-4.0"); } @Override public List typeInstrumentations() { - return singletonList(new ApacheHttpClientInstrumentation()); + List instrumentationList = new ArrayList<>(); + instrumentationList.add(new ApacheHttpClientInstrumentation()); + instrumentationList.add(new ApacheHttpClientProcessorInstrumentation()); + return instrumentationList; } } diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientSingletons.java b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientSingletons.java index 095f132776d6..0a56f5af2c36 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientSingletons.java +++ b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientSingletons.java @@ -5,47 +5,25 @@ package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; -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.instrumentation.api.instrumenter.net.PeerServiceAttributesExtractor; -import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig; -import org.apache.http.HttpResponse; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.ApacheHttpClientInstrumenter; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpRequest; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpResponse; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientInstrumentationHelper; public final class ApacheHttpClientSingletons { private static final String INSTRUMENTATION_NAME = "io.opentelemetry.apache-httpclient-4.0"; - private static final Instrumenter INSTRUMENTER; + private static final ApacheHttpClientInstrumentationHelper HELPER; static { - ApacheHttpClientHttpAttributesGetter httpAttributesGetter = - new ApacheHttpClientHttpAttributesGetter(); - ApacheHttpClientNetAttributesGetter netAttributesGetter = - new ApacheHttpClientNetAttributesGetter(); - - INSTRUMENTER = - Instrumenter.builder( - GlobalOpenTelemetry.get(), - INSTRUMENTATION_NAME, - HttpSpanNameExtractor.create(httpAttributesGetter)) - .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) - .addAttributesExtractor( - HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) - .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) - .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) - .build()) - .addAttributesExtractor( - PeerServiceAttributesExtractor.create( - netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(HttpHeaderSetter.INSTANCE); + Instrumenter intrumenter; + intrumenter = ApacheHttpClientInstrumenter.create(INSTRUMENTATION_NAME); + HELPER = new ApacheHttpClientInstrumentationHelper(intrumenter); } - public static Instrumenter instrumenter() { - return INSTRUMENTER; + public static ApacheHttpClientInstrumentationHelper helper() { + return HELPER; } private ApacheHttpClientSingletons() {} diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/HttpHeaderSetter.java b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/HttpHeaderSetter.java deleted file mode 100644 index 2147c40b31df..000000000000 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/HttpHeaderSetter.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0; - -import io.opentelemetry.context.propagation.TextMapSetter; - -enum HttpHeaderSetter implements TextMapSetter { - INSTANCE; - - @Override - public void set(ApacheHttpClientRequest carrier, String key, String value) { - carrier.setHeader(key, value); - } -} diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/WrappingStatusSettingResponseHandler.java b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/WrappingStatusSettingResponseHandler.java index 7e8ef4316b73..873464cd98bf 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/WrappingStatusSettingResponseHandler.java +++ b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/WrappingStatusSettingResponseHandler.java @@ -5,10 +5,11 @@ package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0; -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.ApacheHttpClientSingletons.instrumenter; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.ApacheHttpClientSingletons.helper; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientRequest; import java.io.IOException; import org.apache.http.HttpResponse; import org.apache.http.client.ResponseHandler; @@ -32,9 +33,9 @@ public WrappingStatusSettingResponseHandler( @Override public T handleResponse(HttpResponse response) throws IOException { - instrumenter().end(context, request, response, null); + helper().endInstrumentation(context, request, response, null); // ending the span before executing the callback handler (and scoping the callback handler to - // the parent context), even though we are inside of a synchronous http client callback + // the parent context), even though we are inside the synchronous http client callback // underneath HttpClient.execute(..), in order to not attribute other CLIENT span timings that // may be performed in the callback handler to the http client span (and so we don't end up with // nested CLIENT spans, which we currently suppress) diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/test/groovy/ApacheHttpClientTest.groovy b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/test/groovy/ApacheHttpClientTest.groovy deleted file mode 100644 index 995e2cce6074..000000000000 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/test/groovy/ApacheHttpClientTest.groovy +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.api.common.AttributeKey -import io.opentelemetry.instrumentation.test.AgentTestTrait -import io.opentelemetry.instrumentation.test.base.HttpClientTest -import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import org.apache.http.HttpHost -import org.apache.http.HttpRequest -import org.apache.http.HttpResponse -import org.apache.http.client.params.ClientPNames -import org.apache.http.conn.ClientConnectionManager -import org.apache.http.conn.ClientConnectionManagerFactory -import org.apache.http.conn.scheme.SchemeRegistry -import org.apache.http.impl.client.DefaultHttpClient -import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager -import org.apache.http.message.BasicHeader -import org.apache.http.message.BasicHttpRequest -import org.apache.http.params.BasicHttpParams -import org.apache.http.params.HttpConnectionParams -import org.apache.http.params.HttpParams -import org.apache.http.protocol.BasicHttpContext -import spock.lang.Shared - -import java.util.function.Consumer - -abstract class ApacheHttpClientTest extends HttpClientTest implements AgentTestTrait { - @Shared - DefaultHttpClient client = buildClient(false) - - @Shared - DefaultHttpClient clientWithReadTimeout = buildClient(true) - - DefaultHttpClient buildClient(boolean readTimeout) { - HttpParams httpParams = new BasicHttpParams() - HttpConnectionParams.setConnectionTimeout(httpParams, CONNECT_TIMEOUT_MS) - if (readTimeout) { - HttpConnectionParams.setSoTimeout(httpParams, READ_TIMEOUT_MS) - } - httpParams.setParameter(ClientPNames.CONNECTION_MANAGER_FACTORY_CLASS_NAME, ThreadSafeClientConnManagerFactory.getName()) - return new DefaultHttpClient(httpParams) - } - - DefaultHttpClient getClient(URI uri) { - if (uri.toString().contains("/read-timeout")) { - return clientWithReadTimeout - } - return client - } - - static class ThreadSafeClientConnManagerFactory implements ClientConnectionManagerFactory { - @Override - ClientConnectionManager newInstance(HttpParams httpParams, SchemeRegistry schemeRegistry) { - return new ThreadSafeClientConnManager(httpParams, schemeRegistry) - } - } - - def cleanupSpec() { - client.getConnectionManager().shutdown() - clientWithReadTimeout.getConnectionManager().shutdown() - } - - @Override - String userAgent() { - return "apachehttpclient" - } - - @Override - boolean testReadTimeout() { - true - } - - @Override - T buildRequest(String method, URI uri, Map headers) { - def request = createRequest(method, uri) - request.addHeader("user-agent", userAgent()) - headers.entrySet().each { - request.setHeader(new BasicHeader(it.key, it.value)) - } - return request - } - - @Override - Set> httpAttributes(URI uri) { - Set> extra = [ - SemanticAttributes.HTTP_SCHEME, - SemanticAttributes.HTTP_TARGET - ] - super.httpAttributes(uri) + extra - } - - // compilation fails with @Override annotation on this method (groovy quirk?) - int sendRequest(T request, String method, URI uri, Map headers) { - def response = executeRequest(request, uri) - response.entity?.content?.close() // Make sure the connection is closed. - return response.statusLine.statusCode - } - - // compilation fails with @Override annotation on this method (groovy quirk?) - void sendRequestWithCallback(T request, String method, URI uri, Map headers, HttpClientResult requestResult) { - try { - executeRequestWithCallback(request, uri) { - it.entity?.content?.close() // Make sure the connection is closed. - requestResult.complete(it.statusLine.statusCode) - } - } catch (Throwable throwable) { - requestResult.complete(throwable) - } - } - - abstract T createRequest(String method, URI uri) - - abstract HttpResponse executeRequest(T request, URI uri) - - abstract void executeRequestWithCallback(T request, URI uri, Consumer callback) - - static String fullPathFromURI(URI uri) { - StringBuilder builder = new StringBuilder() - if (uri.getPath() != null) { - builder.append(uri.getPath()) - } - - if (uri.getQuery() != null) { - builder.append('?') - builder.append(uri.getQuery()) - } - - if (uri.getFragment() != null) { - builder.append('#') - builder.append(uri.getFragment()) - } - return builder.toString() - } -} - -class ApacheClientHostRequest extends ApacheHttpClientTest { - @Override - BasicHttpRequest createRequest(String method, URI uri) { - // also testing with an absolute path below - return new BasicHttpRequest(method, fullPathFromURI(uri)) - } - - @Override - HttpResponse executeRequest(BasicHttpRequest request, URI uri) { - return getClient(uri).execute(new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()), request) - } - - @Override - void executeRequestWithCallback(BasicHttpRequest request, URI uri, Consumer callback) { - getClient(uri).execute(new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()), request) { - callback.accept(it) - } - } -} - -class ApacheClientHostAbsoluteUriRequest extends ApacheHttpClientTest { - @Override - BasicHttpRequest createRequest(String method, URI uri) { - return new BasicHttpRequest(method, uri.toString()) - } - - @Override - HttpResponse executeRequest(BasicHttpRequest request, URI uri) { - return getClient(uri).execute(new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()), request) - } - - @Override - void executeRequestWithCallback(BasicHttpRequest request, URI uri, Consumer callback) { - getClient(uri).execute(new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()), request) { - callback.accept(it) - } - } -} - -class ApacheClientHostRequestContext extends ApacheHttpClientTest { - @Override - BasicHttpRequest createRequest(String method, URI uri) { - // also testing with an absolute path below - return new BasicHttpRequest(method, fullPathFromURI(uri)) - } - - @Override - HttpResponse executeRequest(BasicHttpRequest request, URI uri) { - return getClient(uri).execute(new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()), request, new BasicHttpContext()) - } - - @Override - void executeRequestWithCallback(BasicHttpRequest request, URI uri, Consumer callback) { - getClient(uri).execute(new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()), request, { - callback.accept(it) - }, new BasicHttpContext()) - } -} - -class ApacheClientHostAbsoluteUriRequestContext extends ApacheHttpClientTest { - @Override - BasicHttpRequest createRequest(String method, URI uri) { - return new BasicHttpRequest(method, uri.toString()) - } - - @Override - HttpResponse executeRequest(BasicHttpRequest request, URI uri) { - return getClient(uri).execute(new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()), request, new BasicHttpContext()) - } - - @Override - void executeRequestWithCallback(BasicHttpRequest request, URI uri, Consumer callback) { - getClient(uri).execute(new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()), request, { - callback.accept(it) - }, new BasicHttpContext()) - } -} - -class ApacheClientUriRequest extends ApacheHttpClientTest { - @Override - HttpUriRequest createRequest(String method, URI uri) { - return new HttpUriRequest(method, uri) - } - - @Override - HttpResponse executeRequest(HttpUriRequest request, URI uri) { - return getClient(uri).execute(request) - } - - @Override - void executeRequestWithCallback(HttpUriRequest request, URI uri, Consumer callback) { - getClient(uri).execute(request) { - callback.accept(it) - } - } -} - -class ApacheClientUriRequestContext extends ApacheHttpClientTest { - @Override - HttpUriRequest createRequest(String method, URI uri) { - return new HttpUriRequest(method, uri) - } - - @Override - HttpResponse executeRequest(HttpUriRequest request, URI uri) { - return getClient(uri).execute(request, new BasicHttpContext()) - } - - @Override - void executeRequestWithCallback(HttpUriRequest request, URI uri, Consumer callback) { - getClient(uri).execute(request, { - callback.accept(it) - }, new BasicHttpContext()) - } -} diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/test/groovy/HttpUriRequest.groovy b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/test/groovy/HttpUriRequest.groovy deleted file mode 100644 index 4a3c7bd57bb1..000000000000 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/test/groovy/HttpUriRequest.groovy +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import org.apache.http.client.methods.HttpRequestBase - -class HttpUriRequest extends HttpRequestBase { - - private final String methodName - - HttpUriRequest(final String methodName, final URI uri) { - this.methodName = methodName - setURI(uri) - } - - @Override - String getMethod() { - return methodName - } -} diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/AbstractApacheHttpClientTest.java b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/AbstractApacheHttpClientTest.java new file mode 100644 index 000000000000..09e36294b2fa --- /dev/null +++ b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/AbstractApacheHttpClientTest.java @@ -0,0 +1,109 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.net.URI; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.HttpContext; + +abstract class AbstractApacheHttpClientTest + extends AbstractHttpClientTest { + @Override + protected String userAgent() { + return "apachehttpclient"; + } + + @Override + protected void configure(HttpClientTestOptions.Builder optionsBuilder) { + optionsBuilder.setUserAgent(userAgent()); + optionsBuilder.enableTestReadTimeout(); + optionsBuilder.setHttpAttributes(AbstractApacheHttpClientTest::getHttpAttributes); + optionsBuilder.setResponseCodeOnRedirectError(302); + } + + private static Set> getHttpAttributes(URI endpoint) { + Set> attributes = new HashSet<>(HttpClientTestOptions.DEFAULT_HTTP_ATTRIBUTES); + attributes.add(SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH); + attributes.add(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH); + return attributes; + } + + @Override + public T buildRequest(String method, URI uri, Map headers) { + T request = createRequest(method, uri); + request.addHeader("user-agent", userAgent()); + headers.forEach(request::setHeader); + return request; + } + + @Override + public int sendRequest(T request, String method, URI uri, Map headers) + throws Exception { + return getResponseCode(executeRequest(request, uri)); + } + + @Override + public void sendRequestWithCallback( + T request, + String method, + URI uri, + Map headers, + HttpClientResult requestResult) { + try { + executeRequestWithCallback(request, uri, requestResult); + } catch (Throwable throwable) { + requestResult.complete(throwable); + } + } + + protected HttpHost getHost(URI uri) { + return new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()); + } + + protected HttpContext getContext() { + return new BasicHttpContext(); + } + + protected static String fullPathFromUri(URI uri) { + StringBuilder builder = new StringBuilder(); + if (uri.getPath() != null) { + builder.append(uri.getPath()); + } + + if (uri.getQuery() != null) { + builder.append('?'); + builder.append(uri.getQuery()); + } + + if (uri.getFragment() != null) { + builder.append('#'); + builder.append(uri.getFragment()); + } + return builder.toString(); + } + + abstract T createRequest(String method, URI uri); + + abstract HttpResponse executeRequest(T request, URI uri) throws Exception; + + abstract void executeRequestWithCallback(T request, URI uri, HttpClientResult requestResult) + throws Exception; + + private static int getResponseCode(HttpResponse response) { + return response.getStatusLine().getStatusCode(); + } +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientTest.java b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientTest.java new file mode 100644 index 000000000000..c759ece65d9b --- /dev/null +++ b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientTest.java @@ -0,0 +1,217 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0; + +import static io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest.CONNECTION_TIMEOUT; +import static io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest.READ_TIMEOUT; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult; +import java.io.IOException; +import java.net.URI; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.params.ClientPNames; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ClientConnectionManagerFactory; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; +import org.apache.http.message.BasicHttpRequest; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.RegisterExtension; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class ApacheHttpClientTest { + @RegisterExtension + static final InstrumentationExtension testing = HttpClientInstrumentationExtension.forAgent(); + + private final DefaultHttpClient client = buildClient(false); + private final DefaultHttpClient clientWithReadTimeout = buildClient(true); + + @AfterAll + void tearDown() { + client.getConnectionManager().shutdown(); + clientWithReadTimeout.getConnectionManager().shutdown(); + } + + private static DefaultHttpClient buildClient(boolean readTimeout) { + HttpParams httpParams = new BasicHttpParams(); + HttpConnectionParams.setConnectionTimeout(httpParams, (int) CONNECTION_TIMEOUT.toMillis()); + if (readTimeout) { + HttpConnectionParams.setSoTimeout(httpParams, (int) READ_TIMEOUT.toMillis()); + } + httpParams.setParameter( + ClientPNames.CONNECTION_MANAGER_FACTORY_CLASS_NAME, + ThreadSafeClientConnManagerFactory.class.getName()); + return new DefaultHttpClient(httpParams); + } + + private DefaultHttpClient getClient(URI uri) { + if (uri.toString().contains("/read-timeout")) { + return clientWithReadTimeout; + } + return client; + } + + public static class ThreadSafeClientConnManagerFactory implements ClientConnectionManagerFactory { + @Override + public ClientConnectionManager newInstance( + HttpParams httpParams, SchemeRegistry schemeRegistry) { + return new ThreadSafeClientConnManager(schemeRegistry); + } + } + + @Nested + class ApacheClientHostRequestTest extends AbstractTest { + @Override + public BasicHttpRequest createRequest(String method, URI uri) { + // also testing with an absolute path below + return new BasicHttpRequest(method, fullPathFromUri(uri)); + } + + @Override + public HttpResponse doExecuteRequest(BasicHttpRequest request, URI uri) throws Exception { + return getClient(uri).execute(getHost(uri), request); + } + + @Override + void executeRequestWithCallback(BasicHttpRequest request, URI uri, HttpClientResult result) + throws Exception { + getClient(uri).execute(getHost(uri), request, new HttpResponseHandler(result)); + } + } + + @Nested + class ApacheClientHostAbsoluteUriRequestTest extends AbstractTest { + @Override + BasicHttpRequest createRequest(String method, URI uri) { + return new BasicHttpRequest(method, uri.toString()); + } + + @Override + HttpResponse doExecuteRequest(BasicHttpRequest request, URI uri) throws Exception { + return getClient(uri).execute(getHost(uri), request); + } + + @Override + void executeRequestWithCallback(BasicHttpRequest request, URI uri, HttpClientResult result) + throws Exception { + getClient(uri).execute(getHost(uri), request, new HttpResponseHandler(result)); + } + } + + @Nested + class ApacheClientHostRequestContextTest extends AbstractTest { + @Override + BasicHttpRequest createRequest(String method, URI uri) { + // also testing with an absolute path below + return new BasicHttpRequest(method, fullPathFromUri(uri)); + } + + @Override + HttpResponse doExecuteRequest(BasicHttpRequest request, URI uri) throws Exception { + return getClient(uri).execute(getHost(uri), request, getContext()); + } + + @Override + void executeRequestWithCallback(BasicHttpRequest request, URI uri, HttpClientResult result) + throws Exception { + getClient(uri).execute(getHost(uri), request, new HttpResponseHandler(result), getContext()); + } + } + + @Nested + class ApacheClientHostAbsoluteUriRequestContextTest extends AbstractTest { + @Override + BasicHttpRequest createRequest(String method, URI uri) { + return new BasicHttpRequest(method, uri.toString()); + } + + @Override + HttpResponse doExecuteRequest(BasicHttpRequest request, URI uri) throws Exception { + return getClient(uri).execute(getHost(uri), request, getContext()); + } + + @Override + void executeRequestWithCallback(BasicHttpRequest request, URI uri, HttpClientResult result) + throws Exception { + getClient(uri).execute(getHost(uri), request, new HttpResponseHandler(result), getContext()); + } + } + + @Nested + class ApacheClientUriRequestTest extends AbstractTest { + @Override + HttpUriRequest createRequest(String method, URI uri) { + return new HttpUriRequest(method, uri); + } + + @Override + HttpResponse doExecuteRequest(HttpUriRequest request, URI uri) throws Exception { + return getClient(uri).execute(request); + } + + @Override + void executeRequestWithCallback(HttpUriRequest request, URI uri, HttpClientResult result) + throws Exception { + getClient(uri).execute(request, new HttpResponseHandler(result)); + } + } + + @Nested + class ApacheClientUriRequestContextTest extends AbstractTest { + @Override + HttpUriRequest createRequest(String method, URI uri) { + return new HttpUriRequest(method, uri); + } + + @Override + HttpResponse doExecuteRequest(HttpUriRequest request, URI uri) throws Exception { + return getClient(uri).execute(request, getContext()); + } + + @Override + void executeRequestWithCallback(HttpUriRequest request, URI uri, HttpClientResult result) + throws Exception { + getClient(uri).execute(request, new HttpResponseHandler(result), getContext()); + } + } + + abstract static class AbstractTest + extends AbstractApacheHttpClientTest { + @Override + final HttpResponse executeRequest(T request, URI uri) throws Exception { + HttpResponse httpResponse = doExecuteRequest(request, uri); + httpResponse.getEntity().getContent().close(); + return httpResponse; + } + + abstract HttpResponse doExecuteRequest(T request, URI uri) throws Exception; + } + + private static class HttpResponseHandler implements ResponseHandler { + private final HttpClientResult requestResult; + + public HttpResponseHandler(HttpClientResult requestResult) { + this.requestResult = requestResult; + } + + @Override + public Void handleResponse(HttpResponse response) throws IOException { + response.getEntity().getContent().close(); + requestResult.complete(response.getStatusLine().getStatusCode()); + return null; + } + } +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/HttpUriRequest.java b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/HttpUriRequest.java new file mode 100644 index 000000000000..3c93819482ae --- /dev/null +++ b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/HttpUriRequest.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0; + +import java.net.URI; +import org.apache.http.client.methods.HttpRequestBase; + +final class HttpUriRequest extends HttpRequestBase { + + private final String methodName; + + HttpUriRequest(String methodName, URI uri) { + this.methodName = methodName; + setURI(uri); + } + + @Override + public String getMethod() { + return methodName; + } +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/build.gradle.kts b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/build.gradle.kts index 185eba7481a3..414c7e3e5213 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/build.gradle.kts +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/build.gradle.kts @@ -11,5 +11,6 @@ muzzle { } dependencies { + implementation(project(":instrumentation:apache-httpclient:commons:javaagent")) library("org.apache.httpcomponents.client5:httpclient5:5.0") } diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpAsyncClientInstrumentation.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpAsyncClientInstrumentation.java index 5a6885710e0e..f0183345a6e3 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpAsyncClientInstrumentation.java +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpAsyncClientInstrumentation.java @@ -8,36 +8,24 @@ import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientSingletons.instrumenter; -import static java.util.logging.Level.FINE; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import java.io.IOException; -import java.util.logging.Logger; -import javax.annotation.Nullable; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; import org.apache.hc.core5.concurrent.FutureCallback; -import org.apache.hc.core5.http.EntityDetails; -import org.apache.hc.core5.http.HttpException; -import org.apache.hc.core5.http.HttpRequest; -import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.nio.AsyncRequestProducer; -import org.apache.hc.core5.http.nio.DataStreamChannel; -import org.apache.hc.core5.http.nio.RequestChannel; +import org.apache.hc.core5.http.nio.AsyncResponseConsumer; import org.apache.hc.core5.http.protocol.BasicHttpContext; import org.apache.hc.core5.http.protocol.HttpContext; -import org.apache.hc.core5.http.protocol.HttpCoreContext; -class ApacheHttpAsyncClientInstrumentation implements TypeInstrumentation { +public final class ApacheHttpAsyncClientInstrumentation implements TypeInstrumentation { @Override public ElementMatcher classLoaderOptimization() { @@ -69,6 +57,7 @@ public static class ClientAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void methodEnter( @Advice.Argument(value = 0, readOnly = false) AsyncRequestProducer requestProducer, + @Advice.Argument(value = 1, readOnly = false) AsyncResponseConsumer responseConsumer, @Advice.Argument(value = 3, readOnly = false) HttpContext httpContext, @Advice.Argument(value = 4, readOnly = false) FutureCallback futureCallback) { @@ -76,195 +65,15 @@ public static void methodEnter( if (httpContext == null) { httpContext = new BasicHttpContext(); } + ApacheHttpClientOtelContext httpOtelContext = ApacheHttpClientOtelContext.adapt(httpContext); + httpOtelContext.markAsyncClient(); WrappedFutureCallback wrappedFutureCallback = new WrappedFutureCallback<>(parentContext, httpContext, futureCallback); requestProducer = - new DelegatingRequestProducer(parentContext, requestProducer, wrappedFutureCallback); + new WrappedRequestProducer(parentContext, requestProducer, wrappedFutureCallback); + responseConsumer = new WrappedResponseConsumer<>(parentContext, responseConsumer); futureCallback = wrappedFutureCallback; } } - - public static class DelegatingRequestProducer implements AsyncRequestProducer { - private final Context parentContext; - private final AsyncRequestProducer delegate; - private final WrappedFutureCallback wrappedFutureCallback; - - public DelegatingRequestProducer( - Context parentContext, - AsyncRequestProducer delegate, - WrappedFutureCallback wrappedFutureCallback) { - this.parentContext = parentContext; - this.delegate = delegate; - this.wrappedFutureCallback = wrappedFutureCallback; - } - - @Override - public void failed(Exception ex) { - delegate.failed(ex); - } - - @Override - public void sendRequest(RequestChannel channel, HttpContext context) - throws HttpException, IOException { - DelegatingRequestChannel requestChannel = - new DelegatingRequestChannel(channel, parentContext, wrappedFutureCallback); - delegate.sendRequest(requestChannel, context); - } - - @Override - public boolean isRepeatable() { - return delegate.isRepeatable(); - } - - @Override - public int available() { - return delegate.available(); - } - - @Override - public void produce(DataStreamChannel channel) throws IOException { - delegate.produce(channel); - } - - @Override - public void releaseResources() { - delegate.releaseResources(); - } - } - - public static class DelegatingRequestChannel implements RequestChannel { - private final RequestChannel delegate; - private final Context parentContext; - private final WrappedFutureCallback wrappedFutureCallback; - - public DelegatingRequestChannel( - RequestChannel requestChannel, - Context parentContext, - WrappedFutureCallback wrappedFutureCallback) { - this.delegate = requestChannel; - this.parentContext = parentContext; - this.wrappedFutureCallback = wrappedFutureCallback; - } - - @Override - public void sendRequest(HttpRequest request, EntityDetails entityDetails, HttpContext context) - throws HttpException, IOException { - if (instrumenter().shouldStart(parentContext, request)) { - wrappedFutureCallback.context = instrumenter().start(parentContext, request); - wrappedFutureCallback.httpRequest = request; - } - - delegate.sendRequest(request, entityDetails, context); - } - } - - public static class WrappedFutureCallback implements FutureCallback { - - private static final Logger logger = Logger.getLogger(WrappedFutureCallback.class.getName()); - - private final Context parentContext; - private final HttpContext httpContext; - private final FutureCallback delegate; - - private volatile Context context; - private volatile HttpRequest httpRequest; - - public WrappedFutureCallback( - Context parentContext, HttpContext httpContext, FutureCallback delegate) { - this.parentContext = parentContext; - this.httpContext = httpContext; - // Note: this can be null in real life, so we have to handle this carefully - this.delegate = delegate; - } - - @Override - public void completed(T result) { - if (context == null) { - // this is unexpected - logger.log(FINE, "context was never set"); - completeDelegate(result); - return; - } - - instrumenter().end(context, httpRequest, getResponseFromHttpContext(), null); - - if (parentContext == null) { - completeDelegate(result); - return; - } - - try (Scope ignored = parentContext.makeCurrent()) { - completeDelegate(result); - } - } - - @Override - public void failed(Exception ex) { - if (context == null) { - // this is unexpected - logger.log(FINE, "context was never set"); - failDelegate(ex); - return; - } - - // end span before calling delegate - instrumenter().end(context, httpRequest, getResponseFromHttpContext(), ex); - - if (parentContext == null) { - failDelegate(ex); - return; - } - - try (Scope ignored = parentContext.makeCurrent()) { - failDelegate(ex); - } - } - - @Override - public void cancelled() { - if (context == null) { - // this is unexpected - logger.log(FINE, "context was never set"); - cancelDelegate(); - return; - } - - // TODO (trask) add "canceled" span attribute - // end span before calling delegate - instrumenter().end(context, httpRequest, getResponseFromHttpContext(), null); - - if (parentContext == null) { - cancelDelegate(); - return; - } - - try (Scope ignored = parentContext.makeCurrent()) { - cancelDelegate(); - } - } - - private void completeDelegate(T result) { - if (delegate != null) { - delegate.completed(result); - } - } - - private void failDelegate(Exception ex) { - if (delegate != null) { - delegate.failed(ex); - } - } - - private void cancelDelegate() { - if (delegate != null) { - delegate.cancelled(); - } - } - - @Nullable - private HttpResponse getResponseFromHttpContext() { - return (HttpResponse) httpContext.getAttribute(HttpCoreContext.HTTP_RESPONSE); - } - } } diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientHttpAttributesGetter.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientAttributesHelper.java similarity index 59% rename from instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientHttpAttributesGetter.java rename to instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientAttributesHelper.java index 09d4827005f6..501e57a3b86c 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientHttpAttributesGetter.java +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientAttributesHelper.java @@ -5,79 +5,52 @@ package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; -import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesGetter; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; -import javax.annotation.Nullable; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpRequest; -import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.MessageHeaders; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.net.URIAuthority; -final class ApacheHttpClientHttpAttributesGetter - implements HttpClientAttributesGetter { - private static final Logger logger = - Logger.getLogger(ApacheHttpClientHttpAttributesGetter.class.getName()); +public final class ApacheHttpClientAttributesHelper { + private static final Logger logger; - @Override - public String getMethod(HttpRequest request) { - return request.getMethod(); + static { + logger = Logger.getLogger(ApacheHttpClientAttributesHelper.class.getName()); } - @Override - public String getUrl(HttpRequest request) { - // similar to org.apache.hc.core5.http.message.BasicHttpRequest.getUri() - // not calling getUri() to avoid unnecessary conversion - StringBuilder url = new StringBuilder(); - URIAuthority authority = request.getAuthority(); - if (authority != null) { - String scheme = request.getScheme(); - if (scheme != null) { - url.append(scheme); - url.append("://"); - } else { - url.append("http://"); - } - url.append(authority.getHostName()); - int port = authority.getPort(); - if (port >= 0) { - url.append(":"); - url.append(port); - } - } - String path = request.getPath(); - if (path != null) { - if (url.length() > 0 && !path.startsWith("/")) { - url.append("/"); - } - url.append(path); - } else { - url.append("/"); - } - return url.toString(); + public ApacheHttpClientAttributesHelper() {} + + public static List getHeader(MessageHeaders messageHeaders, String name) { + return headersToList(messageHeaders.getHeaders(name)); } - @Override - public List getRequestHeader(HttpRequest request, String name) { - return getHeader(request, name); + public static String getFirstHeader(MessageHeaders messageHeader, String name) { + Header firstHeader = messageHeader.getFirstHeader(name); + if (firstHeader != null) { + return firstHeader.getValue(); + } + return null; } - @Override - public Integer getStatusCode( - HttpRequest request, HttpResponse response, @Nullable Throwable error) { - return response.getCode(); + // minimize memory overhead by not using streams + private static List headersToList(Header[] headers) { + if (headers.length == 0) { + return Collections.emptyList(); + } + List headersList = new ArrayList<>(headers.length); + for (Header header : headers) { + headersList.add(header.getValue()); + } + return headersList; } - @Override - @Nullable - public String getFlavor(HttpRequest request, @Nullable HttpResponse response) { - ProtocolVersion protocolVersion = getVersion(request, response); + public static String getFlavor(ProtocolVersion protocolVersion) { if (protocolVersion == null) { return null; } @@ -100,32 +73,43 @@ public String getFlavor(HttpRequest request, @Nullable HttpResponse response) { return null; } - @Override - public List getResponseHeader(HttpRequest request, HttpResponse response, String name) { - return getHeader(response, name); - } - - private static ProtocolVersion getVersion(HttpRequest request, @Nullable HttpResponse response) { - ProtocolVersion protocolVersion = request.getVersion(); - if (protocolVersion == null && response != null) { - protocolVersion = response.getVersion(); + public static String getUrl(HttpRequest httpRequest) { + // similar to org.apache.hc.core5.http.message.BasicHttpRequest.getUri() + // not calling getUri() to avoid unnecessary conversion + StringBuilder url = new StringBuilder(); + URIAuthority authority = httpRequest.getAuthority(); + if (authority != null) { + String scheme = httpRequest.getScheme(); + if (scheme != null) { + url.append(scheme); + url.append("://"); + } else { + url.append("http://"); + } + url.append(authority.getHostName()); + int port = authority.getPort(); + if (port >= 0) { + url.append(":"); + url.append(port); + } } - return protocolVersion; + String path = httpRequest.getPath(); + if (path != null) { + if (url.length() > 0 && !path.startsWith("/")) { + url.append("/"); + } + url.append(path); + } else { + url.append("/"); + } + return url.toString(); } - private static List getHeader(MessageHeaders messageHeaders, String name) { - return headersToList(messageHeaders.getHeaders(name)); + public static Integer getPeerPort(HttpRequest httpRequest) { + return httpRequest.getAuthority().getPort(); } - // minimize memory overhead by not using streams - private static List headersToList(Header[] headers) { - if (headers.length == 0) { - return Collections.emptyList(); - } - List headersList = new ArrayList<>(headers.length); - for (Header header : headers) { - headersList.add(header.getValue()); - } - return headersList; + public static String getPeerName(HttpRequest httpRequest) { + return httpRequest.getAuthority().getHostName(); } } diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientContextManager.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientContextManager.java new file mode 100644 index 000000000000..f6a364745708 --- /dev/null +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientContextManager.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; + +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpContextManager; +import org.apache.hc.core5.http.protocol.HttpContext; + +public final class ApacheHttpClientContextManager extends OtelHttpContextManager { + private static final ApacheHttpClientContextManager INSTANCE; + + static { + INSTANCE = new ApacheHttpClientContextManager(); + } + + private ApacheHttpClientContextManager() { + super(ApacheHttpClientOtelContext::adapt); + } + + public static OtelHttpContextManager httpContextManager() { + return INSTANCE; + } +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientHelper.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientHelper.java deleted file mode 100644 index 0a75742dd513..000000000000 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientHelper.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; - -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientSingletons.instrumenter; - -import io.opentelemetry.context.Context; -import org.apache.hc.core5.http.ClassicHttpRequest; -import org.apache.hc.core5.http.HttpResponse; - -public class ApacheHttpClientHelper { - - public static void doMethodExit( - Context context, ClassicHttpRequest request, Object result, Throwable throwable) { - if (throwable != null) { - instrumenter().end(context, request, null, throwable); - } else if (result instanceof HttpResponse) { - instrumenter().end(context, request, (HttpResponse) result, null); - } else { - // ended in WrappingStatusSettingResponseHandler - } - } - - private ApacheHttpClientHelper() {} -} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientInernalEntityStorage.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientInernalEntityStorage.java new file mode 100644 index 000000000000..ce34ea492f57 --- /dev/null +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientInernalEntityStorage.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; + +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpInternalEntityStorage; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpResponse; + +public final class ApacheHttpClientInernalEntityStorage + extends OtelHttpInternalEntityStorage { + private static final ApacheHttpClientInernalEntityStorage INSTANCE; + + static { + INSTANCE = new ApacheHttpClientInernalEntityStorage(); + } + + private ApacheHttpClientInernalEntityStorage() { + super( + VirtualField.find(Context.class, HttpRequest.class), + VirtualField.find(Context.class, HttpResponse.class)); + } + + public static OtelHttpInternalEntityStorage storage() { + return INSTANCE; + } +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientInstrumentation.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientInstrumentation.java index b5897ec9ecc6..6f31cbf5a128 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientInstrumentation.java +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientInstrumentation.java @@ -8,7 +8,7 @@ import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientSingletons.instrumenter; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientSingletons.helper; import static net.bytebuddy.matcher.ElementMatchers.isAbstract; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.named; @@ -27,7 +27,7 @@ import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.io.HttpClientResponseHandler; -class ApacheHttpClientInstrumentation implements TypeInstrumentation { +public final class ApacheHttpClientInstrumentation implements TypeInstrumentation { @Override public ElementMatcher classLoaderOptimization() { return hasClassesNamed("org.apache.hc.client5.http.classic.HttpClient"); @@ -127,22 +127,25 @@ public static class RequestAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void methodEnter( @Advice.Argument(0) ClassicHttpRequest request, + @Advice.Local("otelRequest") ApacheHttpClientRequest otelRequest, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); - if (!instrumenter().shouldStart(parentContext, request)) { + otelRequest = new ApacheHttpClientRequest(parentContext, request); + context = helper().startInstrumentation(parentContext, request, otelRequest); + + if (context == null) { return; } - context = instrumenter().start(parentContext, request); scope = context.makeCurrent(); } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void methodExit( - @Advice.Argument(0) ClassicHttpRequest request, @Advice.Return Object result, @Advice.Thrown Throwable throwable, + @Advice.Local("otelRequest") ApacheHttpClientRequest otelRequest, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { if (scope == null) { @@ -150,7 +153,7 @@ public static void methodExit( } scope.close(); - ApacheHttpClientHelper.doMethodExit(context, request, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } @@ -161,28 +164,32 @@ public static class RequestWithHandlerAdvice { public static void methodEnter( @Advice.Argument(0) ClassicHttpRequest request, @Advice.Argument(value = 1, readOnly = false) HttpClientResponseHandler handler, + @Advice.Local("otelRequest") ApacheHttpClientRequest otelRequest, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); - if (!instrumenter().shouldStart(parentContext, request)) { + otelRequest = new ApacheHttpClientRequest(parentContext, request); + context = helper().startInstrumentation(parentContext, request, otelRequest); + + if (context == null) { return; } - context = instrumenter().start(parentContext, request); scope = context.makeCurrent(); // Wrap the handler so we capture the status code if (handler != null) { handler = - new WrappingStatusSettingResponseHandler<>(context, parentContext, request, handler); + new WrappingStatusSettingResponseHandler<>( + context, parentContext, otelRequest, handler); } } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void methodExit( - @Advice.Argument(0) ClassicHttpRequest request, @Advice.Return Object result, @Advice.Thrown Throwable throwable, + @Advice.Local("otelRequest") ApacheHttpClientRequest otelRequest, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { if (scope == null) { @@ -190,7 +197,7 @@ public static void methodExit( } scope.close(); - ApacheHttpClientHelper.doMethodExit(context, request, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } @@ -201,28 +208,32 @@ public static class RequestWithContextAndHandlerAdvice { public static void methodEnter( @Advice.Argument(0) ClassicHttpRequest request, @Advice.Argument(value = 2, readOnly = false) HttpClientResponseHandler handler, + @Advice.Local("otelRequest") ApacheHttpClientRequest otelRequest, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); - if (!instrumenter().shouldStart(parentContext, request)) { + otelRequest = new ApacheHttpClientRequest(parentContext, request); + context = helper().startInstrumentation(parentContext, request, otelRequest); + + if (context == null) { return; } - context = instrumenter().start(parentContext, request); scope = context.makeCurrent(); // Wrap the handler so we capture the status code if (handler != null) { handler = - new WrappingStatusSettingResponseHandler<>(context, parentContext, request, handler); + new WrappingStatusSettingResponseHandler<>( + context, parentContext, otelRequest, handler); } } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void methodExit( - @Advice.Argument(0) ClassicHttpRequest request, @Advice.Return Object result, @Advice.Thrown Throwable throwable, + @Advice.Local("otelRequest") ApacheHttpClientRequest otelRequest, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { if (scope == null) { @@ -230,7 +241,7 @@ public static void methodExit( } scope.close(); - ApacheHttpClientHelper.doMethodExit(context, request, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } @@ -241,16 +252,17 @@ public static class RequestWithHostAdvice { public static void methodEnter( @Advice.Argument(0) HttpHost host, @Advice.Argument(1) ClassicHttpRequest request, - @Advice.Local("otelFullRequest") ClassicHttpRequest fullRequest, + @Advice.Local("otelRequest") ApacheHttpClientRequest otelRequest, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); - fullRequest = new RequestWithHost(host, request); - if (!instrumenter().shouldStart(parentContext, fullRequest)) { + otelRequest = new ApacheHttpClientRequest(parentContext, new RequestWithHost(host, request)); + context = helper().startInstrumentation(parentContext, request, otelRequest); + + if (context == null) { return; } - context = instrumenter().start(parentContext, fullRequest); scope = context.makeCurrent(); } @@ -258,7 +270,7 @@ public static void methodEnter( public static void methodExit( @Advice.Return Object result, @Advice.Thrown Throwable throwable, - @Advice.Local("otelFullRequest") ClassicHttpRequest fullRequest, + @Advice.Local("otelRequest") ApacheHttpClientRequest otelRequest, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { if (scope == null) { @@ -266,7 +278,7 @@ public static void methodExit( } scope.close(); - ApacheHttpClientHelper.doMethodExit(context, fullRequest, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } @@ -278,24 +290,24 @@ public static void methodEnter( @Advice.Argument(0) HttpHost host, @Advice.Argument(1) ClassicHttpRequest request, @Advice.Argument(value = 2, readOnly = false) HttpClientResponseHandler handler, - @Advice.Local("otelFullRequest") ClassicHttpRequest fullRequest, + @Advice.Local("otelRequest") ApacheHttpClientRequest otelRequest, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { - Context parentContext = currentContext(); - fullRequest = new RequestWithHost(host, request); - if (!instrumenter().shouldStart(parentContext, fullRequest)) { + otelRequest = new ApacheHttpClientRequest(parentContext, new RequestWithHost(host, request)); + context = helper().startInstrumentation(parentContext, request, otelRequest); + + if (context == null) { return; } - context = instrumenter().start(parentContext, fullRequest); scope = context.makeCurrent(); // Wrap the handler so we capture the status code if (handler != null) { handler = new WrappingStatusSettingResponseHandler<>( - context, parentContext, fullRequest, handler); + context, parentContext, otelRequest, handler); } } @@ -303,7 +315,7 @@ public static void methodEnter( public static void methodExit( @Advice.Return Object result, @Advice.Thrown Throwable throwable, - @Advice.Local("otelFullRequest") ClassicHttpRequest fullRequest, + @Advice.Local("otelRequest") ApacheHttpClientRequest otelRequest, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { if (scope == null) { @@ -311,7 +323,7 @@ public static void methodExit( } scope.close(); - ApacheHttpClientHelper.doMethodExit(context, fullRequest, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } @@ -323,24 +335,24 @@ public static void methodEnter( @Advice.Argument(0) HttpHost host, @Advice.Argument(1) ClassicHttpRequest request, @Advice.Argument(value = 3, readOnly = false) HttpClientResponseHandler handler, - @Advice.Local("otelFullRequest") ClassicHttpRequest fullRequest, + @Advice.Local("otelRequest") ApacheHttpClientRequest otelRequest, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { - Context parentContext = currentContext(); - fullRequest = new RequestWithHost(host, request); - if (!instrumenter().shouldStart(parentContext, fullRequest)) { + otelRequest = new ApacheHttpClientRequest(parentContext, new RequestWithHost(host, request)); + context = helper().startInstrumentation(parentContext, request, otelRequest); + + if (context == null) { return; } - context = instrumenter().start(parentContext, fullRequest); scope = context.makeCurrent(); // Wrap the handler so we capture the status code if (handler != null) { handler = new WrappingStatusSettingResponseHandler<>( - context, parentContext, fullRequest, handler); + context, parentContext, otelRequest, handler); } } @@ -348,7 +360,7 @@ public static void methodEnter( public static void methodExit( @Advice.Return Object result, @Advice.Thrown Throwable throwable, - @Advice.Local("otelFullRequest") ClassicHttpRequest fullRequest, + @Advice.Local("otelRequest") ApacheHttpClientRequest otelRequest, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { if (scope == null) { @@ -356,7 +368,7 @@ public static void methodExit( } scope.close(); - ApacheHttpClientHelper.doMethodExit(context, fullRequest, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } } diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientInstrumentationHelper.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientInstrumentationHelper.java new file mode 100644 index 000000000000..270a700d21a0 --- /dev/null +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientInstrumentationHelper.java @@ -0,0 +1,74 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; + +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics.createOrGetWithParentContext; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientInernalEntityStorage.storage; + +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpRequest; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpResponse; +import javax.annotation.Nullable; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpEntityContainer; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpResponse; + +public final class ApacheHttpClientInstrumentationHelper { + private final Instrumenter instrumenter; + + public ApacheHttpClientInstrumentationHelper( + Instrumenter instrumenter) { + this.instrumenter = instrumenter; + } + + @Nullable + public Context startInstrumentation( + Context parentContext, HttpRequest request, ApacheHttpClientRequest otelRequest) { + if (!instrumenter.shouldStart(parentContext, otelRequest)) { + return null; + } + + if (request instanceof HttpEntityContainer) { + HttpEntity originalEntity = ((HttpEntityContainer) request).getEntity(); + if (originalEntity != null && originalEntity.isChunked()) { + BytesTransferMetrics metrics = createOrGetWithParentContext(parentContext); + HttpEntity wrappedHttpEntity = new WrappedHttpEntity(metrics, originalEntity); + ((HttpEntityContainer) request).setEntity(wrappedHttpEntity); + } + } + + return instrumenter.start(parentContext, otelRequest); + } + + public void endInstrumentation( + Context context, ApacheHttpClientRequest otelRequest, T result, Throwable throwable) { + OtelHttpRequest finalRequest = getFinalRequest(otelRequest, context); + OtelHttpResponse finalResponse = getFinalResponse(result, context); + instrumenter.end(context, finalRequest, finalResponse, throwable); + } + + private static OtelHttpRequest getFinalRequest(ApacheHttpClientRequest request, Context context) { + HttpRequest internalRequest = storage().getInternalRequest(context); + if (internalRequest != null) { + return request.withHttpRequest(internalRequest); + } + return request; + } + + private static OtelHttpResponse getFinalResponse(T result, Context context) { + HttpResponse internalResponse = storage().getInternalResponse(context); + if (internalResponse != null) { + return new ApacheHttpClientResponse(internalResponse); + } + if (result instanceof HttpResponse) { + return new ApacheHttpClientResponse((HttpResponse) result); + } + return null; + } +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientInstrumentationModule.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientInstrumentationModule.java index 0e478a395f1c..42b18af5e347 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientInstrumentationModule.java +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientInstrumentationModule.java @@ -8,11 +8,11 @@ import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; @AutoService(InstrumentationModule.class) -public class ApacheHttpClientInstrumentationModule extends InstrumentationModule { +public final class ApacheHttpClientInstrumentationModule extends InstrumentationModule { public ApacheHttpClientInstrumentationModule() { super("apache-httpclient", "apache-httpclient-5.0"); @@ -20,7 +20,10 @@ public ApacheHttpClientInstrumentationModule() { @Override public List typeInstrumentations() { - return Arrays.asList( - new ApacheHttpClientInstrumentation(), new ApacheHttpAsyncClientInstrumentation()); + List instrumentationList = new ArrayList<>(); + instrumentationList.add(new ApacheHttpClientInstrumentation()); + instrumentationList.add(new ApacheHttpAsyncClientInstrumentation()); + instrumentationList.add(new ApacheHttpClientProcessorInstrumentation()); + return instrumentationList; } } diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientNetAttributesGetter.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientNetAttributesGetter.java deleted file mode 100644 index 6d458ca9e69d..000000000000 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientNetAttributesGetter.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; - -import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import javax.annotation.Nullable; -import org.apache.hc.core5.http.HttpRequest; -import org.apache.hc.core5.http.HttpResponse; - -final class ApacheHttpClientNetAttributesGetter - implements NetClientAttributesGetter { - - @Override - public String getTransport(HttpRequest request, @Nullable HttpResponse response) { - return SemanticAttributes.NetTransportValues.IP_TCP; - } - - @Override - @Nullable - public String getPeerName(HttpRequest request) { - return request.getAuthority().getHostName(); - } - - @Override - public Integer getPeerPort(HttpRequest request) { - return request.getAuthority().getPort(); - } -} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientOtelContext.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientOtelContext.java new file mode 100644 index 000000000000..7841ea69349a --- /dev/null +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientOtelContext.java @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; + +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpContext; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.http.protocol.HttpCoreContext; + +public final class ApacheHttpClientOtelContext extends OtelHttpContext { + private final HttpCoreContext httpContext; + + private ApacheHttpClientOtelContext(HttpContext httpContext) { + this.httpContext = HttpCoreContext.adapt(httpContext); + } + + public static ApacheHttpClientOtelContext adapt(HttpContext httpContext) { + return new ApacheHttpClientOtelContext(httpContext); + } + + @Override + protected void setAttribute(String name, T value) { + httpContext.setAttribute(name, value); + } + + @Override + protected T getAttribute(String name, Class type) { + return httpContext.getAttribute(name, type); + } + + @Override + protected void removeAttribute(String name) { + httpContext.removeAttribute(name); + } +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientProcessorInstrumentation.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientProcessorInstrumentation.java new file mode 100644 index 000000000000..7b7231961111 --- /dev/null +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientProcessorInstrumentation.java @@ -0,0 +1,99 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientContextManager.httpContextManager; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientInernalEntityStorage.storage; +import static net.bytebuddy.matcher.ElementMatchers.isAbstract; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.opentelemetry.context.Context; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.protocol.HttpContext; + +/** + * Apache HttpClient makes an internal copy of the request sent by the user and may not give actual + * response in case of errors back to the user. It internally stores this information in it's http + * context. Hence, to fetch the attributes we instrument the client interceptors. + */ +public final class ApacheHttpClientProcessorInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("org.apache.hc.core5.http.protocol.HttpProcessor"); + } + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("org.apache.hc.core5.http.protocol.HttpProcessor")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod() + .and(named("process")) + .and(isPublic()) + .and(not(isAbstract())) + .and(takesArguments(3)) + .and(takesArgument(0, named("org.apache.hc.core5.http.HttpRequest"))) + .and(takesArgument(1, named("org.apache.hc.core5.http.EntityDetails"))) + .and(takesArgument(2, named("org.apache.hc.core5.http.protocol.HttpContext"))), + this.getClass().getName() + "$HttpRequestProcessorAdvice"); + + transformer.applyAdviceToMethod( + isMethod() + .and(named("process")) + .and(isPublic()) + .and(not(isAbstract())) + .and(takesArguments(3)) + .and(takesArgument(0, named("org.apache.hc.core5.http.HttpResponse"))) + .and(takesArgument(1, named("org.apache.hc.core5.http.EntityDetails"))) + .and(takesArgument(2, named("org.apache.hc.core5.http.protocol.HttpContext"))), + this.getClass().getName() + "$HttpResponseProcessorAdvice"); + } + + @SuppressWarnings("unused") + public static class HttpRequestProcessorAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void methodEnter( + @Advice.Argument(value = 0) HttpRequest httpRequest, + @Advice.Argument(value = 1) EntityDetails entityDetails, + @Advice.Argument(value = 2) HttpContext httpContext) { + Context context = httpContextManager().getCurrentContext(httpContext); + if (context != null) { + storage().storeHttpRequest(context, httpRequest); + } + } + } + + @SuppressWarnings("unused") + public static class HttpResponseProcessorAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void methodEnter( + @Advice.Argument(value = 0) HttpResponse httpResponse, + @Advice.Argument(value = 1) EntityDetails entityDetails, + @Advice.Argument(value = 2) HttpContext httpContext) { + Context context = httpContextManager().getCurrentContext(httpContext); + if (context != null) { + storage().storeHttpResponse(context, httpResponse); + } + } + } +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientRequest.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientRequest.java new file mode 100644 index 000000000000..f5cf13cb821e --- /dev/null +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientRequest.java @@ -0,0 +1,71 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; + +import io.opentelemetry.context.Context; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpRequest; +import java.util.List; +import org.apache.hc.core5.http.HttpRequest; + +public final class ApacheHttpClientRequest implements OtelHttpRequest { + private final Context parentContext; + private final HttpRequest httpRequest; + + public ApacheHttpClientRequest(Context parentContext, HttpRequest httpRequest) { + this.parentContext = parentContext; + this.httpRequest = httpRequest; + } + + public ApacheHttpClientRequest withHttpRequest(HttpRequest httpRequest) { + return new ApacheHttpClientRequest(parentContext, httpRequest); + } + + @Override + public BytesTransferMetrics getBytesTransferMetrics() { + return BytesTransferMetrics.getBytesTransferMetrics(parentContext); + } + + @Override + public String getPeerName() { + return httpRequest.getAuthority().getHostName(); + } + + @Override + public Integer getPeerPort() { + return ApacheHttpClientAttributesHelper.getPeerPort(httpRequest); + } + + @Override + public String getMethod() { + return httpRequest.getMethod(); + } + + @Override + public String getUrl() { + return ApacheHttpClientAttributesHelper.getUrl(httpRequest); + } + + @Override + public String getFlavor() { + return ApacheHttpClientAttributesHelper.getFlavor(httpRequest.getVersion()); + } + + @Override + public List getHeader(String name) { + return ApacheHttpClientAttributesHelper.getHeader(httpRequest, name); + } + + @Override + public String getFirstHeader(String name) { + return ApacheHttpClientAttributesHelper.getFirstHeader(httpRequest, name); + } + + @Override + public void setHeader(String key, String value) { + httpRequest.setHeader(key, value); + } +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientResponse.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientResponse.java new file mode 100644 index 000000000000..d68b9436527d --- /dev/null +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientResponse.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; + +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpResponse; +import java.util.List; +import org.apache.hc.core5.http.HttpResponse; + +public final class ApacheHttpClientResponse implements OtelHttpResponse { + private final HttpResponse httpResponse; + + public ApacheHttpClientResponse(HttpResponse httpResponse) { + this.httpResponse = httpResponse; + } + + @Override + public Integer statusCode() { + return httpResponse.getCode(); + } + + @Override + public String getFlavour() { + return ApacheHttpClientAttributesHelper.getFlavor(httpResponse.getVersion()); + } + + @Override + public List getHeader(String name) { + return ApacheHttpClientAttributesHelper.getHeader(httpResponse, name); + } + + @Override + public String getFirstHeader(String name) { + return ApacheHttpClientAttributesHelper.getFirstHeader(httpResponse, name); + } +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientSingletons.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientSingletons.java index ac2405b69754..7ee527f22ab6 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientSingletons.java +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientSingletons.java @@ -5,48 +5,24 @@ package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; -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.instrumentation.api.instrumenter.net.PeerServiceAttributesExtractor; -import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig; -import org.apache.hc.core5.http.HttpRequest; -import org.apache.hc.core5.http.HttpResponse; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.ApacheHttpClientInstrumenter; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpRequest; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpResponse; public final class ApacheHttpClientSingletons { private static final String INSTRUMENTATION_NAME = "io.opentelemetry.apache-httpclient-5.0"; - private static final Instrumenter INSTRUMENTER; + private static final ApacheHttpClientInstrumentationHelper HELPER; static { - ApacheHttpClientHttpAttributesGetter httpAttributesGetter = - new ApacheHttpClientHttpAttributesGetter(); - ApacheHttpClientNetAttributesGetter netAttributesGetter = - new ApacheHttpClientNetAttributesGetter(); - - INSTRUMENTER = - Instrumenter.builder( - GlobalOpenTelemetry.get(), - INSTRUMENTATION_NAME, - HttpSpanNameExtractor.create(httpAttributesGetter)) - .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) - .addAttributesExtractor( - HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) - .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) - .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) - .build()) - .addAttributesExtractor( - PeerServiceAttributesExtractor.create( - netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(HttpHeaderSetter.INSTANCE); + Instrumenter instrumenter; + instrumenter = ApacheHttpClientInstrumenter.create(INSTRUMENTATION_NAME); + HELPER = new ApacheHttpClientInstrumentationHelper(instrumenter); } - public static Instrumenter instrumenter() { - return INSTRUMENTER; + public static ApacheHttpClientInstrumentationHelper helper() { + return HELPER; } private ApacheHttpClientSingletons() {} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/HttpHeaderSetter.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/HttpHeaderSetter.java deleted file mode 100644 index 292d6642dc03..000000000000 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/HttpHeaderSetter.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; - -import io.opentelemetry.context.propagation.TextMapSetter; -import javax.annotation.Nullable; -import org.apache.hc.core5.http.HttpRequest; - -enum HttpHeaderSetter implements TextMapSetter { - INSTANCE; - - @Override - public void set(@Nullable HttpRequest carrier, String key, String value) { - if (carrier == null) { - return; - } - carrier.setHeader(key, value); - } -} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/RequestWithHost.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/RequestWithHost.java index 6fd5767e83a8..4458d8e38545 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/RequestWithHost.java +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/RequestWithHost.java @@ -5,23 +5,23 @@ package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; -import java.net.URI; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.message.HttpRequestWrapper; import org.apache.hc.core5.net.URIAuthority; -public class RequestWithHost extends HttpRequestWrapper implements ClassicHttpRequest { - +public final class RequestWithHost extends HttpRequestWrapper implements ClassicHttpRequest { private final String scheme; private final URIAuthority authority; + private final ClassicHttpRequest httpRequest; public RequestWithHost(HttpHost httpHost, ClassicHttpRequest httpRequest) { super(httpRequest); this.scheme = httpHost.getSchemeName(); this.authority = new URIAuthority(httpHost.getHostName(), httpHost.getPort()); + this.httpRequest = httpRequest; } @Override @@ -34,20 +34,13 @@ public URIAuthority getAuthority() { return authority; } - @Override - public URI getUri() { - // overriding super because it's not correct (doesn't incorporate authority) - // and isn't needed anyways - throw new UnsupportedOperationException(); - } - @Override public HttpEntity getEntity() { - throw new UnsupportedOperationException(); + return httpRequest.getEntity(); } @Override - public void setEntity(HttpEntity entity) { - throw new UnsupportedOperationException(); + public void setEntity(HttpEntity httpEntity) { + httpRequest.setEntity(httpEntity); } } diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedDataStreamChannel.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedDataStreamChannel.java new file mode 100644 index 000000000000..febc1222c75d --- /dev/null +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedDataStreamChannel.java @@ -0,0 +1,48 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; + +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics.createOrGetWithParentContext; + +import io.opentelemetry.context.Context; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.nio.DataStreamChannel; + +public final class WrappedDataStreamChannel implements DataStreamChannel { + private final Context parentContext; + private final DataStreamChannel delegate; + + public WrappedDataStreamChannel(Context parentContext, DataStreamChannel delegate) { + this.parentContext = parentContext; + this.delegate = delegate; + } + + @Override + public void requestOutput() { + delegate.requestOutput(); + } + + @Override + public int write(ByteBuffer byteBuffer) throws IOException { + BytesTransferMetrics metrics = createOrGetWithParentContext(parentContext); + metrics.addRequestBytes(byteBuffer.limit()); + return delegate.write(byteBuffer); + } + + @Override + public void endStream() throws IOException { + delegate.endStream(); + } + + @Override + public void endStream(List list) throws IOException { + delegate.endStream(list); + } +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedFutureCallback.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedFutureCallback.java new file mode 100644 index 000000000000..87a50b0192bb --- /dev/null +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedFutureCallback.java @@ -0,0 +1,126 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; + +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientContextManager.httpContextManager; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientSingletons.helper; +import static java.util.logging.Level.FINE; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import java.util.logging.Logger; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.protocol.HttpContext; + +public final class WrappedFutureCallback implements FutureCallback { + private static final Logger logger = Logger.getLogger(WrappedFutureCallback.class.getName()); + + private final Context parentContext; + private final HttpContext httpContext; + private final FutureCallback delegate; + + volatile Context context; + volatile ApacheHttpClientRequest otelRequest; + + public WrappedFutureCallback( + Context parentContext, HttpContext httpContext, FutureCallback delegate) { + this.parentContext = parentContext; + this.httpContext = httpContext; + // Note: this can be null in real life, so we have to handle this carefully + this.delegate = delegate; + } + + @Override + public void completed(T result) { + if (context == null) { + // this is unexpected + logger.log(FINE, "context was never set"); + completeDelegate(result); + return; + } + + helper().endInstrumentation(context, otelRequest, result, null); + + if (parentContext == null) { + completeDelegate(result); + return; + } + + try (Scope ignored = parentContext.makeCurrent()) { + completeDelegate(result); + } + } + + @Override + public void failed(Exception ex) { + if (context == null) { + // this is unexpected + logger.log(FINE, "context was never set"); + failDelegate(ex); + return; + } + + // end span before calling delegate + helper().endInstrumentation(context, otelRequest, null, ex); + + if (parentContext == null) { + failDelegate(ex); + return; + } + + try (Scope ignored = parentContext.makeCurrent()) { + failDelegate(ex); + } + } + + @Override + public void cancelled() { + if (context == null) { + // this is unexpected + logger.log(FINE, "context was never set"); + cancelDelegate(); + return; + } + + // TODO (trask) add "canceled" span attribute + // end span before calling delegate + helper().endInstrumentation(context, otelRequest, null, null); + + if (parentContext == null) { + cancelDelegate(); + return; + } + + try (Scope ignored = parentContext.makeCurrent()) { + cancelDelegate(); + } + } + + private void completeDelegate(T result) { + removeOtelAttributes(); + if (delegate != null) { + delegate.completed(result); + } + } + + private void failDelegate(Exception ex) { + removeOtelAttributes(); + if (delegate != null) { + delegate.failed(ex); + } + } + + private void cancelDelegate() { + removeOtelAttributes(); + if (delegate != null) { + delegate.cancelled(); + } + } + + private void removeOtelAttributes() { + httpContextManager().clearOtelAttributes(httpContext); + } +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedHttpEntity.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedHttpEntity.java new file mode 100644 index 000000000000..bfbda46ad90f --- /dev/null +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedHttpEntity.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; + +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.CountingOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.io.entity.HttpEntityWrapper; + +public final class WrappedHttpEntity extends HttpEntityWrapper { + private final BytesTransferMetrics metrics; + + public WrappedHttpEntity(BytesTransferMetrics metrics, HttpEntity wrappedEntity) { + super(wrappedEntity); + this.metrics = metrics; + } + + @Override + public void writeTo(OutputStream outStream) throws IOException { + super.writeTo(new CountingOutputStream(metrics, outStream)); + } +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedRequestChannel.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedRequestChannel.java new file mode 100644 index 000000000000..5c78d4516f71 --- /dev/null +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedRequestChannel.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; + +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientContextManager.httpContextManager; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientSingletons.helper; + +import io.opentelemetry.context.Context; +import java.io.IOException; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.nio.RequestChannel; +import org.apache.hc.core5.http.protocol.HttpContext; + +public final class WrappedRequestChannel implements RequestChannel { + private final RequestChannel delegate; + private final Context parentContext; + private final WrappedFutureCallback wrappedFutureCallback; + + public WrappedRequestChannel( + RequestChannel requestChannel, + Context parentContext, + WrappedFutureCallback wrappedFutureCallback) { + this.delegate = requestChannel; + this.parentContext = parentContext; + this.wrappedFutureCallback = wrappedFutureCallback; + } + + @Override + public void sendRequest(HttpRequest request, EntityDetails entityDetails, HttpContext httpContext) + throws HttpException, IOException { + ApacheHttpClientRequest otelRequest = new ApacheHttpClientRequest(parentContext, request); + Context context = helper().startInstrumentation(parentContext, request, otelRequest); + + if (context != null) { + wrappedFutureCallback.context = context; + wrappedFutureCallback.otelRequest = otelRequest; + + // As the http processor instrumentation is going to be called asynchronously, + // we will need to store the otel context variables in http context for the + // http processor instrumentation to use + httpContextManager().setCurrentContext(httpContext, context); + } + + delegate.sendRequest(request, entityDetails, httpContext); + } +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedRequestProducer.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedRequestProducer.java new file mode 100644 index 000000000000..bd946e502a5e --- /dev/null +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedRequestProducer.java @@ -0,0 +1,60 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; + +import io.opentelemetry.context.Context; +import java.io.IOException; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.nio.AsyncRequestProducer; +import org.apache.hc.core5.http.nio.DataStreamChannel; +import org.apache.hc.core5.http.nio.RequestChannel; +import org.apache.hc.core5.http.protocol.HttpContext; + +public final class WrappedRequestProducer implements AsyncRequestProducer { + private final Context parentContext; + private final AsyncRequestProducer delegate; + private final WrappedFutureCallback callback; + + public WrappedRequestProducer( + Context parentContext, AsyncRequestProducer delegate, WrappedFutureCallback callback) { + this.parentContext = parentContext; + this.delegate = delegate; + this.callback = callback; + } + + @Override + public void failed(Exception ex) { + delegate.failed(ex); + } + + @Override + public void sendRequest(RequestChannel channel, HttpContext context) + throws HttpException, IOException { + RequestChannel requestChannel; + requestChannel = new WrappedRequestChannel(channel, parentContext, callback); + delegate.sendRequest(requestChannel, context); + } + + @Override + public boolean isRepeatable() { + return delegate.isRepeatable(); + } + + @Override + public int available() { + return delegate.available(); + } + + @Override + public void produce(DataStreamChannel channel) throws IOException { + delegate.produce(new WrappedDataStreamChannel(parentContext, channel)); + } + + @Override + public void releaseResources() { + delegate.releaseResources(); + } +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedResponseConsumer.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedResponseConsumer.java new file mode 100644 index 000000000000..136093c01f2b --- /dev/null +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappedResponseConsumer.java @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; + +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics.createOrGetWithParentContext; + +import io.opentelemetry.context.Context; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.nio.AsyncResponseConsumer; +import org.apache.hc.core5.http.nio.CapacityChannel; +import org.apache.hc.core5.http.protocol.HttpContext; + +public final class WrappedResponseConsumer implements AsyncResponseConsumer { + private final AsyncResponseConsumer delegate; + private final Context parentContext; + + public WrappedResponseConsumer(Context parentContext, AsyncResponseConsumer delegate) { + this.parentContext = parentContext; + this.delegate = delegate; + } + + @Override + public void consumeResponse( + HttpResponse httpResponse, + EntityDetails entityDetails, + HttpContext httpContext, + FutureCallback futureCallback) + throws HttpException, IOException { + delegate.consumeResponse(httpResponse, entityDetails, httpContext, futureCallback); + } + + @Override + public void informationResponse(HttpResponse httpResponse, HttpContext httpContext) + throws HttpException, IOException { + delegate.informationResponse(httpResponse, httpContext); + } + + @Override + public void failed(Exception e) { + delegate.failed(e); + } + + @Override + public void updateCapacity(CapacityChannel capacityChannel) throws IOException { + delegate.updateCapacity(capacityChannel); + } + + @Override + public void consume(ByteBuffer byteBuffer) throws IOException { + if (byteBuffer.hasRemaining()) { + BytesTransferMetrics metrics = createOrGetWithParentContext(parentContext); + metrics.addResponseBytes(byteBuffer.limit()); + } + delegate.consume(byteBuffer); + } + + @Override + public void streamEnd(List list) throws HttpException, IOException { + delegate.streamEnd(list); + } + + @Override + public void releaseResources() { + delegate.releaseResources(); + } +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappingStatusSettingResponseHandler.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappingStatusSettingResponseHandler.java index 4735955e5775..425474cce7c9 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappingStatusSettingResponseHandler.java +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/WrappingStatusSettingResponseHandler.java @@ -5,38 +5,37 @@ package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientSingletons.instrumenter; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientSingletons.helper; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import java.io.IOException; -import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.io.HttpClientResponseHandler; -public class WrappingStatusSettingResponseHandler implements HttpClientResponseHandler { +public final class WrappingStatusSettingResponseHandler implements HttpClientResponseHandler { final Context context; final Context parentContext; - final ClassicHttpRequest request; + final ApacheHttpClientRequest otelRequest; final HttpClientResponseHandler handler; public WrappingStatusSettingResponseHandler( Context context, Context parentContext, - ClassicHttpRequest request, + ApacheHttpClientRequest otelRequest, HttpClientResponseHandler handler) { this.context = context; this.parentContext = parentContext; - this.request = request; + this.otelRequest = otelRequest; this.handler = handler; } @Override public T handleResponse(ClassicHttpResponse response) throws IOException, HttpException { - instrumenter().end(context, request, response, null); + helper().endInstrumentation(context, otelRequest, response, null); // ending the span before executing the callback handler (and scoping the callback handler to - // the parent context), even though we are inside of a synchronous http client callback + // the parent context), even though we are inside the synchronous http client callback // underneath HttpClient.execute(..), in order to not attribute other CLIENT span timings that // may be performed in the callback handler to the http client span (and so we don't end up with // nested CLIENT spans, which we currently suppress) diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/AbstractApacheHttpClientTest.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/AbstractApacheHttpClientTest.java index 62ae595ac11d..d2ef7859391b 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/AbstractApacheHttpClientTest.java +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/AbstractApacheHttpClientTest.java @@ -37,6 +37,7 @@ protected void configure(HttpClientTestOptions.Builder optionsBuilder) { optionsBuilder.setUserAgent(userAgent()); optionsBuilder.enableTestReadTimeout(); optionsBuilder.setHttpAttributes(this::getHttpAttributes); + optionsBuilder.setResponseCodeOnRedirectError(302); } protected Set> getHttpAttributes(URI endpoint) { @@ -45,6 +46,8 @@ protected Set> getHttpAttributes(URI endpoint) { attributes.add(SemanticAttributes.NET_PEER_PORT); attributes.add(SemanticAttributes.HTTP_URL); attributes.add(SemanticAttributes.HTTP_METHOD); + attributes.add(SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH); + attributes.add(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH); if (endpoint.toString().contains("/success")) { attributes.add(SemanticAttributes.HTTP_FLAVOR); } diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpAsyncClientTest.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpAsyncClientTest.java index 201ec2aa2e02..401ffbd5694e 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpAsyncClientTest.java +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpAsyncClientTest.java @@ -8,7 +8,6 @@ import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult; -import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions; import java.net.URI; import java.util.Map; import java.util.concurrent.CancellationException; @@ -109,12 +108,6 @@ HttpResponse executeRequest(SimpleHttpRequest request, URI uri) throws Exception void executeRequestWithCallback(SimpleHttpRequest request, URI uri, HttpClientResult result) { client.execute(request, getContext(), new ResponseCallback(result)); } - - @Override - protected void configure(HttpClientTestOptions.Builder optionsBuilder) { - super.configure(optionsBuilder); - optionsBuilder.setResponseCodeOnRedirectError(302); - } } private static class ResponseCallback implements FutureCallback { diff --git a/instrumentation/apache-httpclient/commons-4.0/javaagent/build.gradle.kts b/instrumentation/apache-httpclient/commons-4.0/javaagent/build.gradle.kts new file mode 100644 index 000000000000..b7b87921d088 --- /dev/null +++ b/instrumentation/apache-httpclient/commons-4.0/javaagent/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +dependencies { + implementation(project(":instrumentation:apache-httpclient:commons:javaagent")) + compileOnly("org.apache.httpcomponents:httpcore:4.0") +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientRequest.java b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientAttributesHelper.java similarity index 64% rename from instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientRequest.java rename to instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientAttributesHelper.java index a572d4014f4e..ea7d89ae859e 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientRequest.java +++ b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientAttributesHelper.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0; +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons; import static java.util.logging.Level.FINE; @@ -17,39 +17,33 @@ import javax.annotation.Nullable; import org.apache.http.Header; import org.apache.http.HttpHost; +import org.apache.http.HttpMessage; import org.apache.http.HttpRequest; import org.apache.http.ProtocolVersion; -import org.apache.http.client.methods.HttpUriRequest; -public final class ApacheHttpClientRequest { +public final class ApacheHttpClientAttributesHelper { + private static final Logger logger; - private static final Logger logger = Logger.getLogger(ApacheHttpClientRequest.class.getName()); - - @Nullable private final URI uri; - - private final HttpRequest delegate; - - public ApacheHttpClientRequest(HttpHost httpHost, HttpRequest httpRequest) { - URI calculatedUri = getUri(httpRequest); - if (calculatedUri != null && httpHost != null) { - uri = getCalculatedUri(httpHost, calculatedUri); - } else { - uri = calculatedUri; - } - delegate = httpRequest; + static { + logger = Logger.getLogger(ApacheHttpClientAttributesHelper.class.getName()); } - public ApacheHttpClientRequest(HttpUriRequest httpRequest) { - uri = httpRequest.getURI(); - delegate = httpRequest; + public ApacheHttpClientAttributesHelper() {} + + public static List getHeader(HttpMessage httpMessage, String name) { + return headersToList(httpMessage.getHeaders(name)); } - public List getHeader(String name) { - return headersToList(delegate.getHeaders(name)); + public static String getFirstHeader(HttpMessage httpMessage, String name) { + Header firstHeader = httpMessage.getFirstHeader(name); + if (firstHeader != null) { + return firstHeader.getValue(); + } + return null; } // minimize memory overhead by not using streams - static List headersToList(Header[] headers) { + private static List headersToList(Header[] headers) { if (headers == null || headers.length == 0) { return Collections.emptyList(); } @@ -60,20 +54,7 @@ static List headersToList(Header[] headers) { return headersList; } - public void setHeader(String name, String value) { - delegate.setHeader(name, value); - } - - public String getMethod() { - return delegate.getRequestLine().getMethod(); - } - - public String getUrl() { - return uri != null ? uri.toString() : null; - } - - public String getFlavor() { - ProtocolVersion protocolVersion = delegate.getProtocolVersion(); + public static String getFlavor(ProtocolVersion protocolVersion) { String protocol = protocolVersion.getProtocol(); if (!protocol.equals("HTTP")) { return null; @@ -93,14 +74,12 @@ public String getFlavor() { return null; } - @Nullable - public String getPeerName() { - return uri == null ? null : uri.getHost(); - } - - @Nullable - public Integer getPeerPort() { - return uri == null ? null : uri.getPort(); + public static URI getUri(HttpHost target, HttpRequest httpRequest) { + URI calculatedUri = getUri(httpRequest); + if (calculatedUri != null && target != null) { + calculatedUri = getCalculatedUri(target, calculatedUri); + } + return calculatedUri; } @Nullable @@ -114,15 +93,29 @@ private static URI getUri(HttpRequest httpRequest) { } } + public static String getPeerName(URI uri) { + return uri == null ? null : uri.getHost(); + } + + public static Integer getPeerPort(URI uri) { + return uri == null ? null : uri.getPort(); + } + @Nullable private static URI getCalculatedUri(HttpHost httpHost, URI uri) { try { + String path = uri.getPath(); + if (!path.startsWith("/")) { + // elasticsearch RestClient sends relative urls + // TODO(trask) add test for this and extend to Apache 4, 4.3 and 5 + path = "/" + path; + } return new URI( httpHost.getSchemeName(), null, httpHost.getHostName(), httpHost.getPort(), - uri.getPath(), + path, uri.getQuery(), uri.getFragment()); } catch (URISyntaxException e) { diff --git a/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientContextManager.java b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientContextManager.java new file mode 100644 index 000000000000..8c6ef8a867d7 --- /dev/null +++ b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientContextManager.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons; + +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpContextManager; +import org.apache.http.protocol.HttpContext; + +public final class ApacheHttpClientContextManager extends OtelHttpContextManager { + private static final ApacheHttpClientContextManager INSTANCE; + + static { + INSTANCE = new ApacheHttpClientContextManager(); + } + + private ApacheHttpClientContextManager() { + super(ApacheHttpClientOtelContext::adapt); + } + + public static OtelHttpContextManager httpContextManager() { + return INSTANCE; + } +} diff --git a/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientInernalEntityStorage.java b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientInernalEntityStorage.java new file mode 100644 index 000000000000..61232b1914c5 --- /dev/null +++ b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientInernalEntityStorage.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons; + +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpInternalEntityStorage; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; + +public final class ApacheHttpClientInernalEntityStorage + extends OtelHttpInternalEntityStorage { + private static final ApacheHttpClientInernalEntityStorage INSTANCE; + + static { + INSTANCE = new ApacheHttpClientInernalEntityStorage(); + } + + private ApacheHttpClientInernalEntityStorage() { + super( + VirtualField.find(Context.class, HttpRequest.class), + VirtualField.find(Context.class, HttpResponse.class)); + } + + public static OtelHttpInternalEntityStorage storage() { + return INSTANCE; + } +} diff --git a/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientInstrumentationHelper.java b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientInstrumentationHelper.java new file mode 100644 index 000000000000..41fef2cf8478 --- /dev/null +++ b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientInstrumentationHelper.java @@ -0,0 +1,74 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons; + +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics.createOrGetWithParentContext; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientInernalEntityStorage.storage; + +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpRequest; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpResponse; +import javax.annotation.Nullable; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; + +public final class ApacheHttpClientInstrumentationHelper { + private final Instrumenter instrumenter; + + public ApacheHttpClientInstrumentationHelper( + Instrumenter instrumenter) { + this.instrumenter = instrumenter; + } + + @Nullable + public Context startInstrumentation( + Context parentContext, HttpRequest request, ApacheHttpClientRequest otelRequest) { + if (!instrumenter.shouldStart(parentContext, otelRequest)) { + return null; + } + + if (request instanceof HttpEntityEnclosingRequest) { + HttpEntity originalEntity = ((HttpEntityEnclosingRequest) request).getEntity(); + if (originalEntity != null && originalEntity.isChunked()) { + BytesTransferMetrics metrics = createOrGetWithParentContext(parentContext); + HttpEntity wrappedHttpEntity = new WrappedHttpEntity(metrics, originalEntity); + ((HttpEntityEnclosingRequest) request).setEntity(wrappedHttpEntity); + } + } + + return instrumenter.start(parentContext, otelRequest); + } + + public void endInstrumentation( + Context context, ApacheHttpClientRequest otelRequest, T result, Throwable throwable) { + OtelHttpRequest finalRequest = getFinalRequest(otelRequest, context); + OtelHttpResponse finalResponse = getFinalResponse(result, context); + instrumenter.end(context, finalRequest, finalResponse, throwable); + } + + private static OtelHttpRequest getFinalRequest(ApacheHttpClientRequest request, Context context) { + HttpRequest internalRequest = storage().getInternalRequest(context); + if (internalRequest != null) { + return request.withHttpRequest(internalRequest); + } + return request; + } + + private static OtelHttpResponse getFinalResponse(T result, Context context) { + HttpResponse internalResponse = storage().getInternalResponse(context); + if (internalResponse != null) { + return new ApacheHttpClientResponse(internalResponse); + } + if (result instanceof HttpResponse) { + return new ApacheHttpClientResponse((HttpResponse) result); + } + return null; + } +} diff --git a/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientOtelContext.java b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientOtelContext.java new file mode 100644 index 000000000000..7e66a6e0a78c --- /dev/null +++ b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientOtelContext.java @@ -0,0 +1,40 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons; + +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpContext; +import org.apache.http.protocol.HttpContext; + +public final class ApacheHttpClientOtelContext extends OtelHttpContext { + private final HttpContext httpContext; + + private ApacheHttpClientOtelContext(HttpContext httpContext) { + this.httpContext = httpContext; + } + + public static ApacheHttpClientOtelContext adapt(HttpContext httpContext) { + return new ApacheHttpClientOtelContext(httpContext); + } + + @Override + protected void setAttribute(String name, T value) { + httpContext.setAttribute(name, value); + } + + @Override + protected T getAttribute(String attributeName, Class clazz) { + Object attribute = httpContext.getAttribute(attributeName); + if (attribute == null) { + return null; + } + return clazz.cast(attribute); + } + + @Override + protected void removeAttribute(String name) { + httpContext.removeAttribute(name); + } +} diff --git a/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientProcessorInstrumentation.java b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientProcessorInstrumentation.java new file mode 100644 index 000000000000..7b95ecdfcecf --- /dev/null +++ b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientProcessorInstrumentation.java @@ -0,0 +1,89 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientContextManager.httpContextManager; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientInernalEntityStorage.storage; +import static net.bytebuddy.matcher.ElementMatchers.isAbstract; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.opentelemetry.context.Context; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.protocol.HttpContext; + +public final class ApacheHttpClientProcessorInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("org.apache.http.protocol.HttpProcessor"); + } + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("org.apache.http.protocol.HttpProcessor")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod() + .and(named("process")) + .and(isPublic()) + .and(not(isAbstract())) + .and(takesArguments(2)) + .and(takesArgument(0, named("org.apache.http.HttpRequest"))) + .and(takesArgument(1, named("org.apache.http.protocol.HttpContext"))), + this.getClass().getName() + "$HttpRequestProcessorAdvice"); + + transformer.applyAdviceToMethod( + isMethod() + .and(named("process")) + .and(isPublic()) + .and(not(isAbstract())) + .and(takesArguments(2)) + .and(takesArgument(0, named("org.apache.http.HttpResponse"))) + .and(takesArgument(1, named("org.apache.http.protocol.HttpContext"))), + this.getClass().getName() + "$HttpResponseProcessorAdvice"); + } + + @SuppressWarnings("unused") + public static class HttpRequestProcessorAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void methodEnter( + @Advice.Argument(value = 0) HttpRequest httpRequest, + @Advice.Argument(value = 1) HttpContext httpContext) { + Context context = httpContextManager().getCurrentContext(httpContext); + if (context != null) { + storage().storeHttpRequest(context, httpRequest); + } + } + } + + @SuppressWarnings("unused") + public static class HttpResponseProcessorAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void methodEnter( + @Advice.Argument(value = 0) HttpResponse httpResponse, + @Advice.Argument(value = 1) HttpContext httpContext) { + Context context = httpContextManager().getCurrentContext(httpContext); + if (context != null) { + storage().storeHttpResponse(context, httpResponse); + } + } + } +} diff --git a/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientRequest.java b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientRequest.java new file mode 100644 index 000000000000..9d7a5f4e1405 --- /dev/null +++ b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientRequest.java @@ -0,0 +1,84 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons; + +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientAttributesHelper.getUri; + +import io.opentelemetry.context.Context; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpRequest; +import java.net.URI; +import java.util.List; +import javax.annotation.Nullable; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; + +public final class ApacheHttpClientRequest implements OtelHttpRequest { + private final Context parentContext; + @Nullable private final URI uri; + private final HttpRequest httpRequest; + + public ApacheHttpClientRequest(Context parentContext, URI uri, HttpRequest httpRequest) { + this.parentContext = parentContext; + this.uri = uri; + this.httpRequest = httpRequest; + } + + public ApacheHttpClientRequest(Context parentContext, HttpHost target, HttpRequest httpRequest) { + this(parentContext, getUri(target, httpRequest), httpRequest); + } + + public ApacheHttpClientRequest withHttpRequest(HttpRequest httpRequest) { + return new ApacheHttpClientRequest(parentContext, uri, httpRequest); + } + + @Override + public BytesTransferMetrics getBytesTransferMetrics() { + return BytesTransferMetrics.getBytesTransferMetrics(parentContext); + } + + @Override + public String getMethod() { + return httpRequest.getRequestLine().getMethod(); + } + + @Override + public String getUrl() { + return uri == null ? null : uri.toString(); + } + + @Override + public String getFlavor() { + return ApacheHttpClientAttributesHelper.getFlavor(httpRequest.getProtocolVersion()); + } + + @Override + @Nullable + public String getPeerName() { + return ApacheHttpClientAttributesHelper.getPeerName(uri); + } + + @Override + @Nullable + public Integer getPeerPort() { + return ApacheHttpClientAttributesHelper.getPeerPort(uri); + } + + @Override + public List getHeader(String name) { + return ApacheHttpClientAttributesHelper.getHeader(httpRequest, name); + } + + @Override + public String getFirstHeader(String name) { + return ApacheHttpClientAttributesHelper.getFirstHeader(httpRequest, name); + } + + @Override + public void setHeader(String name, String value) { + httpRequest.setHeader(name, value); + } +} diff --git a/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientResponse.java b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientResponse.java new file mode 100644 index 000000000000..e810b99edd36 --- /dev/null +++ b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientResponse.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons; + +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpResponse; +import java.util.List; +import org.apache.http.HttpResponse; + +public final class ApacheHttpClientResponse implements OtelHttpResponse { + private final HttpResponse httpResponse; + + public ApacheHttpClientResponse(HttpResponse httpResponse) { + this.httpResponse = httpResponse; + } + + @Override + public Integer statusCode() { + return httpResponse.getStatusLine().getStatusCode(); + } + + @Override + public String getFlavour() { + return ApacheHttpClientAttributesHelper.getFlavor(httpResponse.getProtocolVersion()); + } + + @Override + public List getHeader(String name) { + return ApacheHttpClientAttributesHelper.getHeader(httpResponse, name); + } + + @Override + public String getFirstHeader(String name) { + return ApacheHttpClientAttributesHelper.getFirstHeader(httpResponse, name); + } +} diff --git a/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/WrappedHttpEntity.java b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/WrappedHttpEntity.java new file mode 100644 index 000000000000..c58228612901 --- /dev/null +++ b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/WrappedHttpEntity.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons; + +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.CountingOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.http.HttpEntity; +import org.apache.http.entity.HttpEntityWrapper; + +public final class WrappedHttpEntity extends HttpEntityWrapper { + private final BytesTransferMetrics metrics; + + public WrappedHttpEntity(BytesTransferMetrics metrics, HttpEntity wrappedEntity) { + super(wrappedEntity); + this.metrics = metrics; + } + + @Override + public void writeTo(OutputStream outStream) throws IOException { + super.writeTo(new CountingOutputStream(metrics, outStream)); + } +} diff --git a/instrumentation/apache-httpclient/commons/javaagent/build.gradle.kts b/instrumentation/apache-httpclient/commons/javaagent/build.gradle.kts new file mode 100644 index 000000000000..66031f03cf04 --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/build.gradle.kts @@ -0,0 +1,6 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +dependencies { +} diff --git a/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientContentLengthAttributesGetter.java b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientContentLengthAttributesGetter.java new file mode 100644 index 000000000000..d629bed61d1b --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientContentLengthAttributesGetter.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons; + +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import javax.annotation.Nonnull; + +public final class ApacheHttpClientContentLengthAttributesGetter + implements AttributesExtractor { + private static final String CONTENT_LENGTH_HEADER = "content-length"; + + @Override + public void onStart( + @Nonnull AttributesBuilder attributes, + @Nonnull Context parentContext, + @Nonnull OtelHttpRequest otelRequest) {} + + @Override + public void onEnd( + @Nonnull AttributesBuilder attributes, + @Nonnull Context context, + @Nonnull OtelHttpRequest otelRequest, + OtelHttpResponse response, + Throwable error) { + BytesTransferMetrics metrics = otelRequest.getBytesTransferMetrics(); + if (metrics != null) { + Long requestContentLength = getContentLength(otelRequest, metrics); + if (requestContentLength != null) { + attributes.put(SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, requestContentLength); + } + + Long responseContentLength = getContentLength(response, metrics); + if (responseContentLength != null) { + attributes.put(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, responseContentLength); + } + } + } + + private static Long getContentLength(OtelHttpRequest request, BytesTransferMetrics metrics) { + String requestContentLength = request.getFirstHeader(CONTENT_LENGTH_HEADER); + if (requestContentLength != null) { + return null; + } + return metrics.getRequestContentLength(); + } + + private static Long getContentLength(OtelHttpResponse response, BytesTransferMetrics metrics) { + if (response == null) { + return null; + } + String responseContentLength = response.getFirstHeader(CONTENT_LENGTH_HEADER); + if (responseContentLength != null) { + return null; + } + return metrics.getResponseContentLength(); + } +} diff --git a/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientHttpAttributesGetter.java b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientHttpAttributesGetter.java new file mode 100644 index 000000000000..c7f23cd9ce1b --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientHttpAttributesGetter.java @@ -0,0 +1,53 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons; + +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesGetter; +import java.util.List; +import javax.annotation.Nullable; + +public final class ApacheHttpClientHttpAttributesGetter + implements HttpClientAttributesGetter { + @Override + public String getMethod(OtelHttpRequest request) { + return request.getMethod(); + } + + @Override + public String getUrl(OtelHttpRequest request) { + return request.getUrl(); + } + + @Override + public List getRequestHeader(OtelHttpRequest request, String name) { + return request.getHeader(name); + } + + @Override + public Integer getStatusCode( + OtelHttpRequest request, OtelHttpResponse response, @Nullable Throwable error) { + return response.statusCode(); + } + + @Override + @Nullable + public String getFlavor(OtelHttpRequest request, @Nullable OtelHttpResponse response) { + String flavor = request.getFlavor(); + if (flavor == null && response != null) { + String responseFlavour = response.getFlavour(); + if (responseFlavour != null) { + flavor = responseFlavour; + } + } + return flavor; + } + + @Override + public List getResponseHeader( + OtelHttpRequest request, OtelHttpResponse response, String name) { + return response.getHeader(name); + } +} diff --git a/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientInstrumenter.java b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientInstrumenter.java new file mode 100644 index 000000000000..9109ec632997 --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientInstrumenter.java @@ -0,0 +1,44 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +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.instrumentation.api.instrumenter.net.PeerServiceAttributesExtractor; +import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig; + +public final class ApacheHttpClientInstrumenter { + public static Instrumenter create(String instrumentationName) { + ApacheHttpClientHttpAttributesGetter httpAttributesGetter; + ApacheHttpClientNetAttributesGetter netAttributesGetter; + + httpAttributesGetter = new ApacheHttpClientHttpAttributesGetter(); + netAttributesGetter = new ApacheHttpClientNetAttributesGetter(); + + return Instrumenter.builder( + GlobalOpenTelemetry.get(), + instrumentationName, + HttpSpanNameExtractor.create(httpAttributesGetter)) + .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) + .addAttributesExtractor( + HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) + .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) + .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .build()) + .addAttributesExtractor( + PeerServiceAttributesExtractor.create( + netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) + .addAttributesExtractor(new ApacheHttpClientContentLengthAttributesGetter()) + .addOperationMetrics(HttpClientMetrics.get()) + .buildClientInstrumenter(new HttpHeaderSetter()); + } + + private ApacheHttpClientInstrumenter() {} +} diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientNetAttributesGetter.java b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientNetAttributesGetter.java similarity index 56% rename from instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientNetAttributesGetter.java rename to instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientNetAttributesGetter.java index f0fea9eda1ca..68fc0a5a9e9d 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientNetAttributesGetter.java +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientNetAttributesGetter.java @@ -3,29 +3,27 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0; +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons; import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import javax.annotation.Nullable; -import org.apache.http.HttpResponse; - -final class ApacheHttpClientNetAttributesGetter - implements NetClientAttributesGetter { +public final class ApacheHttpClientNetAttributesGetter + implements NetClientAttributesGetter { @Override - public String getTransport(ApacheHttpClientRequest request, @Nullable HttpResponse response) { + public String getTransport(OtelHttpRequest request, @Nullable OtelHttpResponse response) { return SemanticAttributes.NetTransportValues.IP_TCP; } @Override @Nullable - public String getPeerName(ApacheHttpClientRequest request) { + public String getPeerName(OtelHttpRequest request) { return request.getPeerName(); } @Override - public Integer getPeerPort(ApacheHttpClientRequest request) { + public Integer getPeerPort(OtelHttpRequest request) { return request.getPeerPort(); } } diff --git a/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/BytesTransferMetrics.java b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/BytesTransferMetrics.java new file mode 100644 index 000000000000..2c5faa5c2590 --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/BytesTransferMetrics.java @@ -0,0 +1,89 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons; + +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import java.util.concurrent.atomic.AtomicLong; + +public final class BytesTransferMetrics { + private static final VirtualField byContext; + + static { + byContext = VirtualField.find(Context.class, BytesTransferMetrics.class); + } + + private final AtomicLong bytesOut = new AtomicLong(); + + private final AtomicLong bytesIn = new AtomicLong(); + + /** + * Add request bytes produced. This may not always represent the request size, for example in case + * when connection is closed while writing request, this will repsent the bytes written till this + * point. + * + * @param byteLength bytes written from request. + */ + public void addRequestBytes(int byteLength) { + bytesOut.addAndGet(byteLength); + } + + /** + * Add response bytes consumed. This may not always represent the response size, for example in + * case when connection is closed while reading response, this will represent the bytes read till + * this point. + * + *

**Note** that this metric may only be applicable for async client, since it fetches response + * eagerly but, sync client fetches response lazily and hence the bytes may even be consumed after + * the end of span. + * + * @param byteLength bytes consumed from response. + */ + public void addResponseBytes(int byteLength) { + bytesIn.addAndGet(byteLength); + } + + /** + * Get request content length, priority is given if explicit content-length is present like in + * case when the request is not chunked, else value is computed using the bytes written. + * + * @return content-length of request, null if content-length is not applicable. + */ + public Long getRequestContentLength() { + long bytesWritten = bytesOut.get(); + if (bytesWritten > 0) { + return bytesWritten; + } + return null; + } + + /** + * Get response content length, priority is given if explicit content-length is present like in + * case when the response is not chunked, else value is computed using the bytes read. + * + * @return content-length of response, null if content-length is not applicable. + */ + public Long getResponseContentLength() { + long bytesRead = bytesIn.get(); + if (bytesRead > 0) { + return bytesRead; + } + return null; + } + + public static BytesTransferMetrics createOrGetWithParentContext(Context parentContext) { + BytesTransferMetrics metrics = byContext.get(parentContext); + if (metrics == null) { + metrics = new BytesTransferMetrics(); + byContext.set(parentContext, metrics); + } + return metrics; + } + + public static BytesTransferMetrics getBytesTransferMetrics(Context parentContext) { + return byContext.get(parentContext); + } +} diff --git a/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/CountingOutputStream.java b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/CountingOutputStream.java new file mode 100644 index 000000000000..5e4e2f165f16 --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/CountingOutputStream.java @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicBoolean; + +public final class CountingOutputStream extends FilterOutputStream { + private final BytesTransferMetrics metrics; + private final AtomicBoolean closed; + + /** + * Wraps another output stream, counting the number of bytes written. + * + * @param out the output stream to be wrapped + */ + public CountingOutputStream(BytesTransferMetrics metrics, OutputStream out) { + super(out); + this.metrics = metrics; + this.closed = new AtomicBoolean(false); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + if (!closed.get()) { + metrics.addRequestBytes(len); + } + } + + @Override + public void write(int b) throws IOException { + out.write(b); + if (!closed.get()) { + metrics.addRequestBytes(1); + } + } + + // Overriding close() because FilterOutputStream's close() method pre-JDK8 has bad behavior: + // it silently ignores any exception thrown by flush(). Instead, just close the delegate stream. + // It should flush itself if necessary. + @Override + public void close() throws IOException { + out.close(); + closed.compareAndSet(false, true); + } +} diff --git a/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/HttpHeaderSetter.java b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/HttpHeaderSetter.java new file mode 100644 index 000000000000..48c35835c341 --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/HttpHeaderSetter.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons; + +import io.opentelemetry.context.propagation.TextMapSetter; +import javax.annotation.Nullable; + +public final class HttpHeaderSetter implements TextMapSetter { + @Override + public void set(@Nullable OtelHttpRequest carrier, String key, String value) { + if (carrier != null) { + carrier.setHeader(key, value); + } + } +} diff --git a/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpContext.java b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpContext.java new file mode 100644 index 000000000000..c289aaf18d4f --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpContext.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons; + +import io.opentelemetry.context.Context; +import java.util.Objects; + +public abstract class OtelHttpContext { + private static final String CONTEXT_ATTRIBUTE = "@otel.context"; + private static final String ASYNC_CLIENT_ATTRIBUTE = "@otel.async.client"; + + protected abstract void setAttribute(String name, T value); + + protected abstract T getAttribute(String name, Class type); + + protected abstract void removeAttribute(String name); + + public void setContext(Context context) { + setAttribute(CONTEXT_ATTRIBUTE, context); + } + + public void markAsyncClient() { + setAttribute(ASYNC_CLIENT_ATTRIBUTE, true); + } + + public Context getContext() { + return getAttribute(CONTEXT_ATTRIBUTE, Context.class); + } + + public boolean isAsyncClient() { + return Objects.equals(getAttribute(ASYNC_CLIENT_ATTRIBUTE, Boolean.class), Boolean.TRUE); + } + + public void clear() { + removeAttribute(CONTEXT_ATTRIBUTE); + removeAttribute(ASYNC_CLIENT_ATTRIBUTE); + } +} diff --git a/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpContextManager.java b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpContextManager.java new file mode 100644 index 000000000000..bd2327a2cc57 --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpContextManager.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons; + +import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.internal.SpanKey; +import java.util.function.Function; +import javax.annotation.Nullable; + +public abstract class OtelHttpContextManager { + private final Function adapter; + + protected OtelHttpContextManager(Function adapter) { + this.adapter = adapter; + } + + public void setCurrentContext(CTX httpContext, Context context) { + if (httpContext != null) { + adapter.apply(httpContext).setContext(context); + } + } + + public void clearOtelAttributes(CTX httpContext) { + if (httpContext != null) { + adapter.apply(httpContext).clear(); + } + } + + @Nullable + public Context getCurrentContext(CTX httpContext) { + if (httpContext == null) { + return null; + } + OtelHttpContext otelHttpContext = adapter.apply(httpContext); + Context otelContext = otelHttpContext.getContext(); + if (otelContext == null) { + // for async clients, the contexts should always be set by their instrumentation + if (otelHttpContext.isAsyncClient()) { + return null; + } + // for classic clients, context will remain same as the caller + otelContext = currentContext(); + } + // verifying if the current context is a http client context + // this eliminates suppressed contexts and http processor cases which ran for + // apache http server also present in the library + Span span = SpanKey.HTTP_CLIENT.fromContextOrNull(otelContext); + if (span == null) { + return null; + } + return otelContext; + } +} diff --git a/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpInternalEntityStorage.java b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpInternalEntityStorage.java new file mode 100644 index 000000000000..0dc7f7531498 --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpInternalEntityStorage.java @@ -0,0 +1,44 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons; + +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.util.VirtualField; + +public abstract class OtelHttpInternalEntityStorage { + private final VirtualField requestStorage; + private final VirtualField responseStorage; + + protected OtelHttpInternalEntityStorage( + VirtualField requestStorage, VirtualField responseStorage) { + this.requestStorage = requestStorage; + this.responseStorage = responseStorage; + } + + // http client internally makes a copy of the user request, we are storing it + // from the interceptor, to be able to fetch actually sent headers by the client + public void storeHttpRequest(Context context, REQ httpRequest) { + if (httpRequest != null) { + requestStorage.set(context, httpRequest); + } + } + + // in cases of failures (like circular redirects), callbacks may not receive the actual response + // from the client, hence we are storing this response from interceptor to fetch attributes + public void storeHttpResponse(Context context, RES httpResponse) { + if (httpResponse != null) { + responseStorage.set(context, httpResponse); + } + } + + public REQ getInternalRequest(Context context) { + return requestStorage.get(context); + } + + public RES getInternalResponse(Context context) { + return responseStorage.get(context); + } +} diff --git a/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpRequest.java b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpRequest.java new file mode 100644 index 000000000000..015da0ea142a --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpRequest.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons; + +import java.util.List; + +public interface OtelHttpRequest { + BytesTransferMetrics getBytesTransferMetrics(); + + String getPeerName(); + + Integer getPeerPort(); + + String getMethod(); + + String getUrl(); + + String getFlavor(); + + List getHeader(String name); + + String getFirstHeader(String name); + + void setHeader(String key, String value); +} diff --git a/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpResponse.java b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpResponse.java new file mode 100644 index 000000000000..874ed9253506 --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpResponse.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons; + +import java.util.List; + +public interface OtelHttpResponse { + Integer statusCode(); + + String getFlavour(); + + List getHeader(String name); + + String getFirstHeader(String name); +} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-5.0/javaagent/src/test/groovy/ElasticsearchRest5Test.groovy b/instrumentation/elasticsearch/elasticsearch-rest-5.0/javaagent/src/test/groovy/ElasticsearchRest5Test.groovy index 7ef9c892deaf..dc34c45b2ef4 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-5.0/javaagent/src/test/groovy/ElasticsearchRest5Test.groovy +++ b/instrumentation/elasticsearch/elasticsearch-rest-5.0/javaagent/src/test/groovy/ElasticsearchRest5Test.groovy @@ -94,6 +94,7 @@ class ElasticsearchRest5Test extends AgentInstrumentationSpecification { "$SemanticAttributes.HTTP_FLAVOR" SemanticAttributes.HttpFlavorValues.HTTP_1_1 "$SemanticAttributes.HTTP_URL" "${httpHost.toURI()}/_cluster/health" "$SemanticAttributes.HTTP_STATUS_CODE" 200 + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" 0L "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" Long } } @@ -167,6 +168,7 @@ class ElasticsearchRest5Test extends AgentInstrumentationSpecification { "$SemanticAttributes.HTTP_FLAVOR" SemanticAttributes.HttpFlavorValues.HTTP_1_1 "$SemanticAttributes.HTTP_URL" "${httpHost.toURI()}/_cluster/health" "$SemanticAttributes.HTTP_STATUS_CODE" 200 + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" 0L "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" Long } } diff --git a/instrumentation/elasticsearch/elasticsearch-rest-6.4/javaagent/src/test/groovy/ElasticsearchRest6Test.groovy b/instrumentation/elasticsearch/elasticsearch-rest-6.4/javaagent/src/test/groovy/ElasticsearchRest6Test.groovy index 5ab11bcadb90..5d73eca4b56d 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-6.4/javaagent/src/test/groovy/ElasticsearchRest6Test.groovy +++ b/instrumentation/elasticsearch/elasticsearch-rest-6.4/javaagent/src/test/groovy/ElasticsearchRest6Test.groovy @@ -88,6 +88,7 @@ class ElasticsearchRest6Test extends AgentInstrumentationSpecification { "$SemanticAttributes.HTTP_FLAVOR" SemanticAttributes.HttpFlavorValues.HTTP_1_1 "$SemanticAttributes.HTTP_URL" "${httpHost.toURI()}/_cluster/health" "$SemanticAttributes.HTTP_STATUS_CODE" 200 + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" 0L "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" Long } } @@ -160,6 +161,7 @@ class ElasticsearchRest6Test extends AgentInstrumentationSpecification { "$SemanticAttributes.HTTP_FLAVOR" SemanticAttributes.HttpFlavorValues.HTTP_1_1 "$SemanticAttributes.HTTP_URL" "${httpHost.toURI()}/_cluster/health" "$SemanticAttributes.HTTP_STATUS_CODE" 200 + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" 0L "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" Long } } diff --git a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/groovy/ElasticsearchRest7Test.groovy b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/groovy/ElasticsearchRest7Test.groovy index 41d8e245d989..2c1688dffeb0 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/groovy/ElasticsearchRest7Test.groovy +++ b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/groovy/ElasticsearchRest7Test.groovy @@ -87,6 +87,7 @@ class ElasticsearchRest7Test extends AgentInstrumentationSpecification { "$SemanticAttributes.HTTP_FLAVOR" SemanticAttributes.HttpFlavorValues.HTTP_1_1 "$SemanticAttributes.HTTP_URL" "${httpHost.toURI()}/_cluster/health" "$SemanticAttributes.HTTP_STATUS_CODE" 200 + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" 0L "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" Long } } @@ -159,6 +160,7 @@ class ElasticsearchRest7Test extends AgentInstrumentationSpecification { "$SemanticAttributes.HTTP_FLAVOR" SemanticAttributes.HttpFlavorValues.HTTP_1_1 "$SemanticAttributes.HTTP_URL" "${httpHost.toURI()}/_cluster/health" "$SemanticAttributes.HTTP_STATUS_CODE" 200 + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" 0L "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" Long } } diff --git a/instrumentation/opensearch/opensearch-rest-1.0/javaagent/src/test/java/OpenSearchRestTest.java b/instrumentation/opensearch/opensearch-rest-1.0/javaagent/src/test/java/OpenSearchRestTest.java index e4a30c77df9a..f16b231bb6f0 100644 --- a/instrumentation/opensearch/opensearch-rest-1.0/javaagent/src/test/java/OpenSearchRestTest.java +++ b/instrumentation/opensearch/opensearch-rest-1.0/javaagent/src/test/java/OpenSearchRestTest.java @@ -112,6 +112,7 @@ void shouldGetStatusWithTraces() throws IOException { equalTo( SemanticAttributes.HTTP_URL, httpHost.toURI() + "/_cluster/health"), equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo(SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, 0L), equalTo(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, 415L)))); } @@ -187,6 +188,7 @@ public void onFailure(Exception e) { equalTo( SemanticAttributes.HTTP_URL, httpHost.toURI() + "/_cluster/health"), equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo(SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, 0L), equalTo(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, 415L)), span -> span.hasName("callback") diff --git a/settings.gradle.kts b/settings.gradle.kts index 5c81aa53f587..7842d3c809e1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -141,6 +141,8 @@ hideFromDependabot(":instrumentation:apache-dubbo-2.7:javaagent") hideFromDependabot(":instrumentation:apache-dubbo-2.7:library-autoconfigure") hideFromDependabot(":instrumentation:apache-dubbo-2.7:testing") hideFromDependabot(":instrumentation:apache-httpasyncclient-4.1:javaagent") +hideFromDependabot(":instrumentation:apache-httpclient:commons:javaagent") +hideFromDependabot(":instrumentation:apache-httpclient:commons-4.0:javaagent") hideFromDependabot(":instrumentation:apache-httpclient:apache-httpclient-2.0:javaagent") hideFromDependabot(":instrumentation:apache-httpclient:apache-httpclient-4.0:javaagent") hideFromDependabot(":instrumentation:apache-httpclient:apache-httpclient-4.3:library") @@ -513,3 +515,5 @@ include(":benchmark-jfr-analyzer") fun hideFromDependabot(projectPath: String) { include(projectPath) } +include("instrumentation:apache-httpclient:commons-4.0:javaagent") +findProject(":instrumentation:apache-httpclient:commons-4.0:javaagent")?.name = "javaagent"