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 cbef725ea140..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,35 +8,19 @@ 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 io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics.createOrGetWithParentContext; 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 io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; -import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientEntityStorage; -import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientRequest; -import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.HttpOtelContext; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.logging.Logger; +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.ContentDecoder; -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; @@ -81,7 +65,7 @@ public static void methodEnter( if (httpContext == null) { httpContext = new BasicHttpContext(); } - HttpOtelContext httpOtelContext = HttpOtelContext.adapt(httpContext); + ApacheHttpClientOtelContext httpOtelContext = ApacheHttpClientOtelContext.adapt(httpContext); httpOtelContext.markAsyncClient(); WrappedFutureCallback wrappedFutureCallback = @@ -93,309 +77,4 @@ public static void methodEnter( futureCallback = wrappedFutureCallback; } } - - public static 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(); - } - } - - public static 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); - - if (instrumenter().shouldStart(parentContext, otelRequest)) { - Context context = instrumenter().start(parentContext, otelRequest); - 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 - ApacheHttpClientEntityStorage.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(); - } - } - - public static 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(); - } - } - - public static 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(); - } - } - - 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 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, getFinalRequest(), getFinalResponse(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 - instrumenter().end(context, getFinalRequest(), getFinalResponse(), 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, getFinalRequest(), getFinalResponse(), 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() { - ApacheHttpClientEntityStorage.clearOtelAttributes(httpContext); - } - - private HttpResponse getFinalResponse() { - return getFinalResponse(null); - } - - private HttpResponse getFinalResponse(T result) { - return ApacheHttpClientEntityStorage.getFinalResponse(result, context); - } - - private ApacheHttpClientRequest getFinalRequest() { - return ApacheHttpClientEntityStorage.getFinalRequest(otelRequest, context); - } - } } 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 4e2b2358d466..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,53 +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 io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientContentLengthAttributesGetter; -import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientHttpAttributesGetter; -import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientNetAttributesGetter; -import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientRequest; -import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.HttpHeaderSetter; -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 { - 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())) - .addAttributesExtractor(new ApacheHttpClientContentLengthAttributesGetter()) - .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/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..77ea2e4f32fc --- /dev/null +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedContentDecoder.java @@ -0,0 +1,33 @@ +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..c51877c2670b --- /dev/null +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedContentEncoder.java @@ -0,0 +1,36 @@ +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..59e6af48e798 --- /dev/null +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedFutureCallback.java @@ -0,0 +1,121 @@ +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..74000165e2af --- /dev/null +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedRequestProducer.java @@ -0,0 +1,92 @@ +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..1eb209a3fd5f --- /dev/null +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/WrappedResponseConsumer.java @@ -0,0 +1,68 @@ +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-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 3993cc04003a..45517e6607fb 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,8 +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.ApacheHttpClientInstrumentationHelper.endInstrumentation; -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.ApacheHttpClientInstrumentationHelper.startInstrumentation; +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; @@ -136,7 +135,7 @@ public static void methodEnter( @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); otelRequest = new ApacheHttpClientRequest(parentContext, request); - context = startInstrumentation(parentContext, request, otelRequest); + context = helper().startInstrumentation(parentContext, request, otelRequest); if (context == null) { return; @@ -158,7 +157,7 @@ public static void methodExit( } scope.close(); - endInstrumentation(context, otelRequest, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } @@ -174,7 +173,7 @@ public static void methodEnter( @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); otelRequest = new ApacheHttpClientRequest(parentContext, request); - context = startInstrumentation(parentContext, request, otelRequest); + context = helper().startInstrumentation(parentContext, request, otelRequest); if (context == null) { return; @@ -203,7 +202,7 @@ public static void methodExit( } scope.close(); - endInstrumentation(context, otelRequest, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } @@ -219,7 +218,7 @@ public static void methodEnter( @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); otelRequest = new ApacheHttpClientRequest(parentContext, host, request); - context = startInstrumentation(parentContext, request, otelRequest); + context = helper().startInstrumentation(parentContext, request, otelRequest); if (context == null) { return; @@ -240,7 +239,7 @@ public static void methodExit( } scope.close(); - endInstrumentation(context, otelRequest, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } @@ -257,7 +256,7 @@ public static void methodEnter( @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); otelRequest = new ApacheHttpClientRequest(parentContext, host, request); - context = startInstrumentation(parentContext, request, otelRequest); + context = helper().startInstrumentation(parentContext, request, otelRequest); if (context == null) { return; @@ -285,7 +284,7 @@ public static void methodExit( } scope.close(); - endInstrumentation(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/ApacheHttpClientInstrumentationHelper.java b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientInstrumentationHelper.java deleted file mode 100644 index 2ba49ba4179b..000000000000 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientInstrumentationHelper.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.commons.BytesTransferMetrics.createOrGetWithParentContext; -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.ApacheHttpClientSingletons.instrumenter; -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientEntityStorage.getFinalRequest; -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientEntityStorage.getFinalResponse; - -import io.opentelemetry.context.Context; -import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; -import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientRequest; -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 { - @Nullable - public static 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 static void endInstrumentation( - Context context, ApacheHttpClientRequest otelRequest, T result, Throwable throwable) { - ApacheHttpClientRequest finalRequest = getFinalRequest(otelRequest, context); - HttpResponse finalResponse = getFinalResponse(result, context); - instrumenter().end(context, finalRequest, finalResponse, throwable); - } - - private ApacheHttpClientInstrumentationHelper() {} -} 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 ddaac226aacc..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,53 +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 io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientContentLengthAttributesGetter; -import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientHttpAttributesGetter; -import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientNetAttributesGetter; -import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.ApacheHttpClientRequest; -import io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons.HttpHeaderSetter; -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())) - .addAttributesExtractor(new ApacheHttpClientContentLengthAttributesGetter()) - .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/WrappingStatusSettingResponseHandler.java b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/WrappingStatusSettingResponseHandler.java index 7664e59eaf84..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,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0; -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.ApacheHttpClientInstrumentationHelper.endInstrumentation; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.ApacheHttpClientSingletons.helper; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; @@ -33,7 +33,7 @@ public WrappingStatusSettingResponseHandler( @Override public T handleResponse(HttpResponse response) throws IOException { - endInstrumentation(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 the synchronous http client callback // underneath HttpClient.execute(..), in order to not attribute other CLIENT span timings that 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 7e1b407ccc33..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,38 +8,20 @@ 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.commons.BytesTransferMetrics.createOrGetWithParentContext; -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientInstrumentationHelper.endInstrumentation; -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientInstrumentationHelper.startInstrumentation; -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 io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.List; -import java.util.logging.Logger; 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.Header; -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.AsyncResponseConsumer; -import org.apache.hc.core5.http.nio.CapacityChannel; -import org.apache.hc.core5.http.nio.DataStreamChannel; -import org.apache.hc.core5.http.nio.RequestChannel; import org.apache.hc.core5.http.protocol.BasicHttpContext; import org.apache.hc.core5.http.protocol.HttpContext; @@ -83,7 +65,7 @@ public static void methodEnter( if (httpContext == null) { httpContext = new BasicHttpContext(); } - HttpOtelContext httpOtelContext = HttpOtelContext.adapt(httpContext); + ApacheHttpClientOtelContext httpOtelContext = ApacheHttpClientOtelContext.adapt(httpContext); httpOtelContext.markAsyncClient(); WrappedFutureCallback wrappedFutureCallback = @@ -94,282 +76,4 @@ public static void methodEnter( futureCallback = wrappedFutureCallback; } } - - public static 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(); - } - } - - public static 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(); - } - } - - public static 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); - } - } - - public static 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 = 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 - ApacheHttpClientEntityStorage.setCurrentContext(httpContext, context); - } - - delegate.sendRequest(request, entityDetails, httpContext); - } - } - - 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 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; - } - - 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 - 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 - 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() { - ApacheHttpClientEntityStorage.clearOtelAttributes(httpContext); - } - } } 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..6b2372c4c840 --- /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,20 @@ +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/ApacheHttpClientEntityStorage.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientEntityStorage.java deleted file mode 100644 index 1c0799000750..000000000000 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientEntityStorage.java +++ /dev/null @@ -1,103 +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.bootstrap.Java8BytecodeBridge.currentContext; - -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.api.internal.SpanKey; -import io.opentelemetry.instrumentation.api.util.VirtualField; -import javax.annotation.Nullable; -import org.apache.hc.core5.http.HttpRequest; -import org.apache.hc.core5.http.HttpResponse; -import org.apache.hc.core5.http.protocol.HttpContext; - -public class ApacheHttpClientEntityStorage { - private static final VirtualField httpRequestByOtelContext; - private static final VirtualField httpResponseByOtelContext; - - public ApacheHttpClientEntityStorage() {} - - static { - httpRequestByOtelContext = VirtualField.find(Context.class, HttpRequest.class); - httpResponseByOtelContext = VirtualField.find(Context.class, HttpResponse.class); - } - - // 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 static void storeHttpRequest(Context context, HttpRequest httpRequest) { - if (httpRequest != null) { - httpRequestByOtelContext.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 static void storeHttpResponse(Context context, HttpResponse httpResponse) { - if (httpResponse != null) { - httpResponseByOtelContext.set(context, httpResponse); - } - } - - public static ApacheHttpClientRequest getFinalRequest( - ApacheHttpClientRequest request, Context context) { - HttpRequest internalRequest = httpRequestByOtelContext.get(context); - if (internalRequest != null) { - return request.withHttpRequest(internalRequest); - } - return request; - } - - public static HttpResponse getFinalResponse(T response, Context context) { - HttpResponse internalResponse = httpResponseByOtelContext.get(context); - if (internalResponse != null) { - return internalResponse; - } - if (response instanceof HttpResponse) { - return (HttpResponse) response; - } - return null; - } - - public static void setCurrentContext(HttpContext httpContext, Context context) { - if (httpContext != null) { - HttpOtelContext httpOtelContext = HttpOtelContext.adapt(httpContext); - httpOtelContext.setContext(context); - } - } - - public static void clearOtelAttributes(HttpContext httpContext) { - if (httpContext != null) { - HttpOtelContext.adapt(httpContext).clear(); - } - } - - @Nullable - public static Context getCurrentContext(HttpContext httpContext) { - if (httpContext == null) { - return null; - } - HttpOtelContext httpOtelContext = HttpOtelContext.adapt(httpContext); - Context otelContext = httpOtelContext.getContext(); - if (otelContext == null) { - // for async clients, the contexts should always be set by their instrumentation - if (httpOtelContext.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/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/ApacheHttpClientHttpAttributesGetter.java deleted file mode 100644 index d3615f694e38..000000000000 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientHttpAttributesGetter.java +++ /dev/null @@ -1,52 +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.http.HttpClientAttributesGetter; -import java.util.List; -import javax.annotation.Nullable; -import org.apache.hc.core5.http.HttpResponse; - -public 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.getCode(); - } - - @Override - @Nullable - public String getFlavor(ApacheHttpClientRequest request, @Nullable HttpResponse response) { - String flavor = request.getFlavor(); - if (flavor == null && response != null) { - flavor = ApacheHttpClientAttributesHelper.getFlavor(response.getVersion()); - } - return flavor; - } - - @Override - public List getResponseHeader( - ApacheHttpClientRequest request, HttpResponse response, String name) { - return ApacheHttpClientAttributesHelper.getHeader(response, name); - } -} 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..a468b935da5e --- /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,26 @@ +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 91e423d1eec2..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,8 +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.ApacheHttpClientInstrumentationHelper.endInstrumentation; -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientInstrumentationHelper.startInstrumentation; +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; @@ -133,7 +132,7 @@ public static void methodEnter( @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); otelRequest = new ApacheHttpClientRequest(parentContext, request); - context = startInstrumentation(parentContext, request, otelRequest); + context = helper().startInstrumentation(parentContext, request, otelRequest); if (context == null) { return; @@ -154,7 +153,7 @@ public static void methodExit( } scope.close(); - endInstrumentation(context, otelRequest, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } @@ -170,7 +169,7 @@ public static void methodEnter( @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); otelRequest = new ApacheHttpClientRequest(parentContext, request); - context = startInstrumentation(parentContext, request, otelRequest); + context = helper().startInstrumentation(parentContext, request, otelRequest); if (context == null) { return; @@ -198,7 +197,7 @@ public static void methodExit( } scope.close(); - endInstrumentation(context, otelRequest, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } @@ -214,7 +213,7 @@ public static void methodEnter( @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); otelRequest = new ApacheHttpClientRequest(parentContext, request); - context = startInstrumentation(parentContext, request, otelRequest); + context = helper().startInstrumentation(parentContext, request, otelRequest); if (context == null) { return; @@ -242,7 +241,7 @@ public static void methodExit( } scope.close(); - endInstrumentation(context, otelRequest, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } @@ -258,7 +257,7 @@ public static void methodEnter( @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); otelRequest = new ApacheHttpClientRequest(parentContext, new RequestWithHost(host, request)); - context = startInstrumentation(parentContext, request, otelRequest); + context = helper().startInstrumentation(parentContext, request, otelRequest); if (context == null) { return; @@ -279,7 +278,7 @@ public static void methodExit( } scope.close(); - endInstrumentation(context, otelRequest, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } @@ -296,7 +295,7 @@ public static void methodEnter( @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); otelRequest = new ApacheHttpClientRequest(parentContext, new RequestWithHost(host, request)); - context = startInstrumentation(parentContext, request, otelRequest); + context = helper().startInstrumentation(parentContext, request, otelRequest); if (context == null) { return; @@ -324,7 +323,7 @@ public static void methodExit( } scope.close(); - endInstrumentation(context, otelRequest, result, throwable); + helper().endInstrumentation(context, otelRequest, result, throwable); } } @@ -341,7 +340,7 @@ public static void methodEnter( @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); otelRequest = new ApacheHttpClientRequest(parentContext, new RequestWithHost(host, request)); - context = startInstrumentation(parentContext, request, otelRequest); + context = helper().startInstrumentation(parentContext, request, otelRequest); if (context == null) { return; @@ -369,7 +368,7 @@ public static void methodExit( } scope.close(); - endInstrumentation(context, otelRequest, 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 index 220d1d0686e7..0eb9849c25e0 100644 --- 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 @@ -6,12 +6,13 @@ 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.ApacheHttpClientEntityStorage.getFinalRequest; -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientEntityStorage.getFinalResponse; -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientSingletons.instrumenter; +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; @@ -19,10 +20,16 @@ import org.apache.hc.core5.http.HttpResponse; public final class ApacheHttpClientInstrumentationHelper { + private final Instrumenter instrumenter; + + public ApacheHttpClientInstrumentationHelper(Instrumenter instrumenter) { + this.instrumenter = instrumenter; + } + @Nullable - public static Context startInstrumentation( + public Context startInstrumentation( Context parentContext, HttpRequest request, ApacheHttpClientRequest otelRequest) { - if (!instrumenter().shouldStart(parentContext, otelRequest)) { + if (!instrumenter.shouldStart(parentContext, otelRequest)) { return null; } @@ -35,15 +42,32 @@ public static Context startInstrumentation( } } - return instrumenter().start(parentContext, otelRequest); + return instrumenter.start(parentContext, otelRequest); } - public static void endInstrumentation( + public void endInstrumentation( Context context, ApacheHttpClientRequest otelRequest, T result, Throwable throwable) { - ApacheHttpClientRequest finalRequest = getFinalRequest(otelRequest, context); - HttpResponse finalResponse = getFinalResponse(result, context); - instrumenter().end(context, finalRequest, finalResponse, throwable); + OtelHttpRequest finalRequest = getFinalRequest(otelRequest, context); + OtelHttpResponse finalResponse = getFinalResponse(result, context); + instrumenter.end(context, finalRequest, finalResponse, throwable); } - private ApacheHttpClientInstrumentationHelper() {} + 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/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 985c493f12d7..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,30 +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.HttpResponse; - -public final class ApacheHttpClientNetAttributesGetter - implements NetClientAttributesGetter { - @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(); - } -} 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 index 93abc3c57ff2..7b7231961111 100644 --- 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 @@ -7,6 +7,8 @@ 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; @@ -31,7 +33,7 @@ * 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. */ -class ApacheHttpClientProcessorInstrumentation implements TypeInstrumentation { +public final class ApacheHttpClientProcessorInstrumentation implements TypeInstrumentation { @Override public ElementMatcher classLoaderOptimization() { return hasClassesNamed("org.apache.hc.core5.http.protocol.HttpProcessor"); @@ -74,9 +76,9 @@ public static void methodEnter( @Advice.Argument(value = 0) HttpRequest httpRequest, @Advice.Argument(value = 1) EntityDetails entityDetails, @Advice.Argument(value = 2) HttpContext httpContext) { - Context context = ApacheHttpClientEntityStorage.getCurrentContext(httpContext); + Context context = httpContextManager().getCurrentContext(httpContext); if (context != null) { - ApacheHttpClientEntityStorage.storeHttpRequest(context, httpRequest); + storage().storeHttpRequest(context, httpRequest); } } } @@ -88,9 +90,9 @@ public static void methodEnter( @Advice.Argument(value = 0) HttpResponse httpResponse, @Advice.Argument(value = 1) EntityDetails entityDetails, @Advice.Argument(value = 2) HttpContext httpContext) { - Context context = ApacheHttpClientEntityStorage.getCurrentContext(httpContext); + Context context = httpContextManager().getCurrentContext(httpContext); if (context != null) { - ApacheHttpClientEntityStorage.storeHttpResponse(context, httpResponse); + 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 index 1950b5c73e81..f5cf13cb821e 100644 --- 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 @@ -7,10 +7,11 @@ 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 { +public final class ApacheHttpClientRequest implements OtelHttpRequest { private final Context parentContext; private final HttpRequest httpRequest; @@ -23,38 +24,47 @@ 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..738718ad9bf9 --- /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,33 @@ +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 870853706991..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.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())) - .addAttributesExtractor(new ApacheHttpClientContentLengthAttributesGetter()) - .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/HttpOtelContext.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/HttpOtelContext.java deleted file mode 100644 index 5554336ad2bb..000000000000 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/HttpOtelContext.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.util.Objects; -import org.apache.hc.core5.http.protocol.HttpContext; -import org.apache.hc.core5.http.protocol.HttpCoreContext; - -public final class HttpOtelContext { - private static final String CONTEXT_ATTRIBUTE = "@otel.context"; - private static final String ASYNC_CLIENT_ATTRIBUTE = "@otel.async.client"; - - private final HttpCoreContext httpContext; - - private HttpOtelContext(HttpContext httpContext) { - this.httpContext = HttpCoreContext.adapt(httpContext); - } - - public static HttpOtelContext adapt(HttpContext httpContext) { - return new HttpOtelContext(httpContext); - } - - public void setContext(Context context) { - httpContext.setAttribute(CONTEXT_ATTRIBUTE, context); - } - - public void markAsyncClient() { - httpContext.setAttribute(ASYNC_CLIENT_ATTRIBUTE, true); - } - - public Context getContext() { - return httpContext.getAttribute(CONTEXT_ATTRIBUTE, Context.class); - } - - public boolean isAsyncClient() { - Boolean attribute = httpContext.getAttribute(ASYNC_CLIENT_ATTRIBUTE, Boolean.class); - return Objects.equals(attribute, Boolean.TRUE); - } - - public void clear() { - httpContext.removeAttribute(CONTEXT_ATTRIBUTE); - httpContext.removeAttribute(ASYNC_CLIENT_ATTRIBUTE); - } -} 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..b677cf114457 --- /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,43 @@ +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..6abf3205cd54 --- /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,121 @@ +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/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..9a1915f009f2 --- /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,47 @@ +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..aabcfb56262d --- /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,58 @@ +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..46ac8fe0d765 --- /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,72 @@ +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 96f5a7f8e699..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,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientInstrumentationHelper.endInstrumentation; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientSingletons.helper; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; @@ -33,7 +33,7 @@ public WrappingStatusSettingResponseHandler( @Override public T handleResponse(ClassicHttpResponse response) throws IOException, HttpException { - endInstrumentation(context, otelRequest, 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 the synchronous http client callback // underneath HttpClient.execute(..), in order to not attribute other CLIENT span timings that diff --git a/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientContentLengthAttributesGetter.java b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientContentLengthAttributesGetter.java deleted file mode 100644 index 59af6ee2e14b..000000000000 --- a/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientContentLengthAttributesGetter.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.getFirstHeader; - -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import javax.annotation.Nonnull; -import org.apache.http.HttpResponse; - -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 ApacheHttpClientRequest otelRequest) {} - - @Override - public void onEnd( - @Nonnull AttributesBuilder attributes, - @Nonnull Context context, - @Nonnull ApacheHttpClientRequest otelRequest, - HttpResponse 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( - ApacheHttpClientRequest request, BytesTransferMetrics metrics) { - String requestContentLength = request.getFirstHeader(CONTENT_LENGTH_HEADER); - if (requestContentLength != null) { - return null; - } - return metrics.getRequestContentLength(); - } - - private static Long getContentLength(HttpResponse response, BytesTransferMetrics metrics) { - if (response == null) { - return null; - } - String responseContentLength = getFirstHeader(response, CONTENT_LENGTH_HEADER); - if (responseContentLength != null) { - return null; - } - return metrics.getResponseContentLength(); - } -} 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..4c03c77a0919 --- /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,20 @@ +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/ApacheHttpClientEntityStorage.java b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientEntityStorage.java deleted file mode 100644 index 8a115b211855..000000000000 --- a/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientEntityStorage.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.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 io.opentelemetry.instrumentation.api.util.VirtualField; -import javax.annotation.Nullable; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.protocol.HttpContext; - -public final class ApacheHttpClientEntityStorage { - private static final VirtualField httpRequestByOtelContext; - private static final VirtualField httpResponseByOtelContext; - - public ApacheHttpClientEntityStorage() {} - - static { - httpRequestByOtelContext = VirtualField.find(Context.class, HttpRequest.class); - httpResponseByOtelContext = VirtualField.find(Context.class, HttpResponse.class); - } - - // 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 static void storeHttpRequest(Context context, HttpRequest httpRequest) { - if (httpRequest != null) { - httpRequestByOtelContext.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 static void storeHttpResponse(Context context, HttpResponse httpResponse) { - if (httpResponse != null) { - httpResponseByOtelContext.set(context, httpResponse); - } - } - - public static ApacheHttpClientRequest getFinalRequest( - ApacheHttpClientRequest request, Context context) { - HttpRequest internalRequest = httpRequestByOtelContext.get(context); - if (internalRequest != null) { - return request.withHttpRequest(internalRequest); - } - return request; - } - - public static HttpResponse getFinalResponse(T response, Context context) { - HttpResponse internalResponse = httpResponseByOtelContext.get(context); - if (internalResponse != null) { - return internalResponse; - } - if (response instanceof HttpResponse) { - return (HttpResponse) response; - } - return null; - } - - public static void setCurrentContext(HttpContext httpContext, Context context) { - if (httpContext != null) { - HttpOtelContext httpOtelContext = HttpOtelContext.adapt(httpContext); - httpOtelContext.setContext(context); - } - } - - public static void clearOtelAttributes(HttpContext httpContext) { - if (httpContext != null) { - HttpOtelContext.adapt(httpContext).clear(); - } - } - - @Nullable - public static Context getCurrentContext(HttpContext httpContext) { - if (httpContext == null) { - return null; - } - HttpOtelContext httpOtelContext = HttpOtelContext.adapt(httpContext); - Context otelContext = httpOtelContext.getContext(); - if (otelContext == null) { - // for async clients, the contexts should always be set by their instrumentation - if (httpOtelContext.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-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientHttpAttributesGetter.java b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientHttpAttributesGetter.java deleted file mode 100644 index 5d9feb998e40..000000000000 --- a/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientHttpAttributesGetter.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons; - -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.ProtocolVersion; - -public 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) { - String flavor = request.getFlavor(); - if (flavor == null && response != null && response.getStatusLine() != null) { - ProtocolVersion protocolVersion = response.getStatusLine().getProtocolVersion(); - flavor = ApacheHttpClientAttributesHelper.getFlavor(protocolVersion); - } - return flavor; - } - - @Override - public List getResponseHeader( - ApacheHttpClientRequest request, HttpResponse response, String name) { - return ApacheHttpClientAttributesHelper.getHeader(response, name); - } -} 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..3ef74d6f69a8 --- /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,26 @@ +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..a70d11a7fe0d --- /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 index f86bdaf91ab7..7b95ecdfcecf 100644 --- 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 @@ -7,6 +7,8 @@ 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; @@ -65,9 +67,9 @@ public static class HttpRequestProcessorAdvice { public static void methodEnter( @Advice.Argument(value = 0) HttpRequest httpRequest, @Advice.Argument(value = 1) HttpContext httpContext) { - Context context = ApacheHttpClientEntityStorage.getCurrentContext(httpContext); + Context context = httpContextManager().getCurrentContext(httpContext); if (context != null) { - ApacheHttpClientEntityStorage.storeHttpRequest(context, httpRequest); + storage().storeHttpRequest(context, httpRequest); } } } @@ -78,9 +80,9 @@ public static class HttpResponseProcessorAdvice { public static void methodEnter( @Advice.Argument(value = 0) HttpResponse httpResponse, @Advice.Argument(value = 1) HttpContext httpContext) { - Context context = ApacheHttpClientEntityStorage.getCurrentContext(httpContext); + Context context = httpContextManager().getCurrentContext(httpContext); if (context != null) { - ApacheHttpClientEntityStorage.storeHttpResponse(context, httpResponse); + 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 index e8913c32f630..faccae8adae5 100644 --- 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 @@ -9,6 +9,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; +import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.OtelHttpRequest; import java.net.InetSocketAddress; import java.net.URI; import java.util.List; @@ -17,7 +18,7 @@ import org.apache.http.HttpRequest; import org.apache.http.client.methods.HttpUriRequest; -public final class ApacheHttpClientRequest { +public final class ApacheHttpClientRequest implements OtelHttpRequest { private final Context parentContext; @Nullable private final URI uri; @Nullable private final HttpHost target; @@ -43,18 +44,22 @@ public ApacheHttpClientRequest withHttpRequest(HttpRequest httpRequest) { return new ApacheHttpClientRequest(parentContext, uri, target, httpRequest); } + @Override public BytesTransferMetrics getBytesTransferMetrics() { return BytesTransferMetrics.getBytesTransferMetrics(parentContext); } + @Override public String getMethod() { return httpRequest.getRequestLine().getMethod(); } + @Override public String getUrl() { - return uri != null ? uri.toString() : null; + return uri == null ? null : uri.toString(); } + @Override public String getFlavor() { return ApacheHttpClientAttributesHelper.getFlavor(httpRequest.getProtocolVersion()); } @@ -69,16 +74,23 @@ public Integer getPeerPort() { return ApacheHttpClientAttributesHelper.getPeerPort(uri); } + @Nullable + public InetSocketAddress getPeerSocketAddress() { + return ApacheHttpClientAttributesHelper.getPeerSocketAddress(target); + } + + @Override public List getHeader(String name) { return ApacheHttpClientAttributesHelper.getHeader(httpRequest, name); } + @Override public String getFirstHeader(String name) { return ApacheHttpClientAttributesHelper.getFirstHeader(httpRequest, name); } - @Nullable - public InetSocketAddress peerSocketAddress() { - return ApacheHttpClientAttributesHelper.getPeerSocketAddress(target); + @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..ffe7878e6958 --- /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,33 @@ +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/HttpHeaderSetter.java b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/HttpHeaderSetter.java deleted file mode 100644 index e744e53f7340..000000000000 --- a/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/HttpHeaderSetter.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons; - -import io.opentelemetry.context.propagation.TextMapSetter; -import javax.annotation.Nullable; - -public enum HttpHeaderSetter implements TextMapSetter { - INSTANCE; - - @Override - public void set(@Nullable ApacheHttpClientRequest carrier, String key, String value) { - if (carrier != null) { - carrier.setHeader(key, value); - } - } -} diff --git a/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/HttpOtelContext.java b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/HttpOtelContext.java deleted file mode 100644 index b440a2a66ea5..000000000000 --- a/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/HttpOtelContext.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 java.util.Objects; -import org.apache.http.protocol.HttpContext; - -public final class HttpOtelContext { - private static final String CONTEXT_ATTRIBUTE = "@otel.context"; - private static final String ASYNC_CLIENT_ATTRIBUTE = "@otel.async.client"; - - private final HttpContext httpContext; - - private HttpOtelContext(HttpContext httpContext) { - this.httpContext = httpContext; - } - - public static HttpOtelContext adapt(HttpContext httpContext) { - return new HttpOtelContext(httpContext); - } - - public void setContext(Context context) { - httpContext.setAttribute(CONTEXT_ATTRIBUTE, context); - } - - public void markAsyncClient() { - httpContext.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); - } - - private T getAttribute(String attributeName, Class clazz) { - Object attribute = httpContext.getAttribute(attributeName); - if (attribute == null) { - return null; - } - return clazz.cast(attribute); - } - - public void clear() { - httpContext.removeAttribute(CONTEXT_ATTRIBUTE); - httpContext.removeAttribute(ASYNC_CLIENT_ATTRIBUTE); - } -} diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/WrappedHttpEntity.java b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/WrappedHttpEntity.java similarity index 92% rename from instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/WrappedHttpEntity.java rename to instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/WrappedHttpEntity.java index ab794d27fce9..c58228612901 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/WrappedHttpEntity.java +++ b/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/WrappedHttpEntity.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 io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; import io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons.CountingOutputStream; @@ -15,8 +15,8 @@ public final class WrappedHttpEntity extends HttpEntityWrapper { private final BytesTransferMetrics metrics; - public WrappedHttpEntity(BytesTransferMetrics metrics, HttpEntity delegate) { - super(delegate); + public WrappedHttpEntity(BytesTransferMetrics metrics, HttpEntity wrappedEntity) { + super(wrappedEntity); this.metrics = metrics; } diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientContentLengthAttributesGetter.java b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientContentLengthAttributesGetter.java similarity index 68% rename from instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientContentLengthAttributesGetter.java rename to instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientContentLengthAttributesGetter.java index f5a397e5c4a8..d629bed61d1b 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientContentLengthAttributesGetter.java +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientContentLengthAttributesGetter.java @@ -3,34 +3,30 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; - -import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientAttributesHelper.getFirstHeader; +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.javaagent.instrumentation.apachehttpclient.commons.BytesTransferMetrics; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import javax.annotation.Nonnull; -import org.apache.hc.core5.http.HttpResponse; public final class ApacheHttpClientContentLengthAttributesGetter - implements AttributesExtractor { + implements AttributesExtractor { private static final String CONTENT_LENGTH_HEADER = "content-length"; @Override public void onStart( @Nonnull AttributesBuilder attributes, @Nonnull Context parentContext, - @Nonnull ApacheHttpClientRequest otelRequest) {} + @Nonnull OtelHttpRequest otelRequest) {} @Override public void onEnd( @Nonnull AttributesBuilder attributes, @Nonnull Context context, - @Nonnull ApacheHttpClientRequest otelRequest, - HttpResponse response, + @Nonnull OtelHttpRequest otelRequest, + OtelHttpResponse response, Throwable error) { BytesTransferMetrics metrics = otelRequest.getBytesTransferMetrics(); if (metrics != null) { @@ -46,8 +42,7 @@ public void onEnd( } } - private static Long getContentLength( - ApacheHttpClientRequest request, BytesTransferMetrics metrics) { + private static Long getContentLength(OtelHttpRequest request, BytesTransferMetrics metrics) { String requestContentLength = request.getFirstHeader(CONTENT_LENGTH_HEADER); if (requestContentLength != null) { return null; @@ -55,11 +50,11 @@ private static Long getContentLength( return metrics.getRequestContentLength(); } - private static Long getContentLength(HttpResponse response, BytesTransferMetrics metrics) { + private static Long getContentLength(OtelHttpResponse response, BytesTransferMetrics metrics) { if (response == null) { return null; } - String responseContentLength = getFirstHeader(response, CONTENT_LENGTH_HEADER); + String responseContentLength = response.getFirstHeader(CONTENT_LENGTH_HEADER); if (responseContentLength != null) { return null; } 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..174ef18195ef --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientHttpAttributesGetter.java @@ -0,0 +1,50 @@ +/* + * 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..e87bc4757d6f --- /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/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientNetAttributesGetter.java b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientNetAttributesGetter.java similarity index 55% rename from instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientNetAttributesGetter.java rename to instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientNetAttributesGetter.java index 2b4ccf9a324a..460a2cd8d38c 100644 --- a/instrumentation/apache-httpclient/commons-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/commons/ApacheHttpClientNetAttributesGetter.java +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/ApacheHttpClientNetAttributesGetter.java @@ -3,38 +3,35 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.commons; +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons; import io.opentelemetry.instrumentation.api.instrumenter.net.InetSocketAddressNetClientAttributesGetter; -import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.net.InetSocketAddress; import javax.annotation.Nullable; -import org.apache.http.HttpResponse; public final class ApacheHttpClientNetAttributesGetter extends - InetSocketAddressNetClientAttributesGetter { - + InetSocketAddressNetClientAttributesGetter { @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(); } @Nullable @Override public InetSocketAddress getPeerSocketAddress( - ApacheHttpClientRequest request, @Nullable HttpResponse response) { - return request.peerSocketAddress(); + OtelHttpRequest request, @Nullable OtelHttpResponse response) { + return request.getPeerSocketAddress(); } } 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/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/HttpHeaderSetter.java similarity index 63% rename from instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/HttpHeaderSetter.java rename to instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/HttpHeaderSetter.java index 4dc3f0d4f752..48c35835c341 100644 --- 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/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/HttpHeaderSetter.java @@ -3,16 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons; import io.opentelemetry.context.propagation.TextMapSetter; import javax.annotation.Nullable; -public enum HttpHeaderSetter implements TextMapSetter { - INSTANCE; - +public final class HttpHeaderSetter implements TextMapSetter { @Override - public void set(@Nullable ApacheHttpClientRequest carrier, String key, String value) { + 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..00a20c9fefe1 --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpContext.java @@ -0,0 +1,36 @@ +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..5cfe535898cb --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpContextManager.java @@ -0,0 +1,54 @@ +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..9f76685893ec --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpInternalEntityStorage.java @@ -0,0 +1,45 @@ +/* + * 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..6756e987bb37 --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpRequest.java @@ -0,0 +1,26 @@ +package io.opentelemetry.javaagent.instrumentation.apachehttpclient.commons; + +import java.net.InetSocketAddress; +import java.util.List; + +public interface OtelHttpRequest { + BytesTransferMetrics getBytesTransferMetrics(); + + String getPeerName(); + + Integer getPeerPort(); + + InetSocketAddress getPeerSocketAddress(); + + 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..a7915ffd0640 --- /dev/null +++ b/instrumentation/apache-httpclient/commons/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/commons/OtelHttpResponse.java @@ -0,0 +1,13 @@ +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); +}