From 6c4a01171aa2129cc2d1321441363d13bf74289c Mon Sep 17 00:00:00 2001 From: Yashwant Date: Sun, 3 Aug 2025 15:59:13 +0530 Subject: [PATCH 1/3] netty-4.1's fix to correct context prop --- .../NettyChannelPipelineInstrumentation.java | 6 +- .../OtelHttpClientRequestTracingHandler.java | 121 ++++++++++++++++++ .../HttpServerRequestTracingHandler.java | 78 ++++++----- 3 files changed, 169 insertions(+), 36 deletions(-) create mode 100644 instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/client/OtelHttpClientRequestTracingHandler.java diff --git a/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/NettyChannelPipelineInstrumentation.java b/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/NettyChannelPipelineInstrumentation.java index 1e55f9a3..7e152aaa 100644 --- a/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/NettyChannelPipelineInstrumentation.java +++ b/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/NettyChannelPipelineInstrumentation.java @@ -36,6 +36,7 @@ import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_1.client.HttpClientRequestTracingHandler; import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_1.client.HttpClientResponseTracingHandler; import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_1.client.HttpClientTracingHandler; +import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_1.client.OtelHttpClientRequestTracingHandler; import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_1.server.HttpServerBlockingRequestHandler; import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_1.server.HttpServerRequestTracingHandler; import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_1.server.HttpServerResponseTracingHandler; @@ -133,14 +134,13 @@ public static void addHandler( HttpClientTracingHandler.class.getName(), new HttpClientTracingHandler()); - // add OTEL request handler to start spans + // add our custom request handler to start spans with proper context propagation pipeline.addAfter( HttpClientTracingHandler.class.getName(), io.opentelemetry.instrumentation.netty.v4_1.internal.client.HttpClientTracingHandler .class .getName(), - new io.opentelemetry.instrumentation.netty.v4_1.internal.client - .HttpClientRequestTracingHandler(NettyClientSingletons.instrumenter())); + new OtelHttpClientRequestTracingHandler(NettyClientSingletons.instrumenter())); } else if (handler instanceof HttpRequestEncoder) { pipeline.addLast( HttpClientRequestTracingHandler.class.getName(), diff --git a/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/client/OtelHttpClientRequestTracingHandler.java b/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/client/OtelHttpClientRequestTracingHandler.java new file mode 100644 index 00000000..44420b41 --- /dev/null +++ b/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/client/OtelHttpClientRequestTracingHandler.java @@ -0,0 +1,121 @@ +/* + * Copyright The Hypertrace Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_1.client; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel; +import io.opentelemetry.instrumentation.netty.v4_1.internal.AttributeKeys; +import io.opentelemetry.instrumentation.netty.v4_1.internal.client.HttpClientRequestTracingHandler; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Custom extension of OpenTelemetry's HttpClientRequestTracingHandler that ensures proper context + * propagation by using Context.current() as the parent context. + */ +public class OtelHttpClientRequestTracingHandler extends HttpClientRequestTracingHandler { + + // Store the server context for each thread + private static final ThreadLocal SERVER_CONTEXT = new ThreadLocal<>(); + + // Store the mapping from thread ID to server span context (for cross-thread scenarios) + private static final ConcurrentHashMap THREAD_TO_SPAN_CONTEXT = + new ConcurrentHashMap<>(); + + // Maximum size for the thread map before triggering cleanup + private static final int MAX_THREAD_MAP_SIZE = 1000; + + // Cleanup flag to avoid excessive synchronized blocks + private static volatile boolean cleanupNeeded = false; + + public OtelHttpClientRequestTracingHandler( + Instrumenter instrumenter) { + super(instrumenter); + } + + /** + * Stores the current context as the server context for this thread. This should be called from + * the server handler. + */ + public static void storeServerContext(Context context) { + SERVER_CONTEXT.set(context); + + // Also store the span context by thread ID for cross-thread lookup + Span span = Span.fromContext(context); + if (span != null && span.getSpanContext().isValid()) { + THREAD_TO_SPAN_CONTEXT.put(Thread.currentThread().getId(), span.getSpanContext()); + + // Check if we need to clean up the map + if (THREAD_TO_SPAN_CONTEXT.size() > MAX_THREAD_MAP_SIZE) { + cleanupNeeded = true; + } + } + } + + /** + * Perform cleanup of the thread map if it has grown too large. This is done in a synchronized + * block to prevent concurrent modification issues. + */ + private static void cleanupThreadMapIfNeeded() { + if (cleanupNeeded) { + synchronized (THREAD_TO_SPAN_CONTEXT) { + if (THREAD_TO_SPAN_CONTEXT.size() > MAX_THREAD_MAP_SIZE) { + THREAD_TO_SPAN_CONTEXT.clear(); + cleanupNeeded = false; + } + } + } + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise prm) { + try { + if (!(msg instanceof HttpRequest)) { + super.write(ctx, msg, prm); + return; + } + + Context parentContext = SERVER_CONTEXT.get(); + + // Fallback -> If no context in thread local, try Context.current() + if (parentContext == null) { + parentContext = Context.current(); + } + + // Store the parent context in the channel attributes + // This is used by the Opentelemetry's HttpClientRequestTracingHandler in propagating correct + // context. + ctx.channel().attr(AttributeKeys.CLIENT_PARENT_CONTEXT).set(parentContext); + + // Call the parent implementation which will use our stored parent context + super.write(ctx, msg, prm); + + // Clean up after use to prevent memory leaks + SERVER_CONTEXT.remove(); + THREAD_TO_SPAN_CONTEXT.remove(Thread.currentThread().getId()); + cleanupThreadMapIfNeeded(); + + } catch (Exception ignored) { + } + } +} diff --git a/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/server/HttpServerRequestTracingHandler.java b/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/server/HttpServerRequestTracingHandler.java index 6fce19a2..15bd3a1b 100644 --- a/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/server/HttpServerRequestTracingHandler.java +++ b/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/server/HttpServerRequestTracingHandler.java @@ -26,9 +26,12 @@ import io.netty.util.Attribute; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.netty.v4_1.internal.ServerContext; import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_1.AttributeKeys; import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_1.DataCaptureUtils; +import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_1.client.OtelHttpClientRequestTracingHandler; import java.nio.charset.Charset; import java.util.Deque; import java.util.HashMap; @@ -57,50 +60,59 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { ctx.fireChannelRead(msg); return; } - Span span = Span.fromContext(serverContexts.element().context()); - if (msg instanceof HttpRequest) { - HttpRequest httpRequest = (HttpRequest) msg; + Context context = serverContexts.element().context(); - Map headersMap = headersToMap(httpRequest); - if (instrumentationConfig.httpHeaders().request()) { - headersMap.forEach(span::setAttribute); - } - // used by blocking handler - channel.attr(AttributeKeys.REQUEST_HEADERS).set(headersMap); + // Store the server context in our ThreadLocal for later use by client handlers + // This is CRITICAL for proper context propagation to client spans + OtelHttpClientRequestTracingHandler.storeServerContext(context); + + try (Scope ignored = context.makeCurrent()) { + Span span = Span.fromContext(context); + + if (msg instanceof HttpRequest) { + HttpRequest httpRequest = (HttpRequest) msg; + + Map headersMap = headersToMap(httpRequest); + if (instrumentationConfig.httpHeaders().request()) { + headersMap.forEach(span::setAttribute); + } + // used by blocking handler + channel.attr(AttributeKeys.REQUEST_HEADERS).set(headersMap); - CharSequence contentType = DataCaptureUtils.getContentType(httpRequest); - if (instrumentationConfig.httpBody().request() - && contentType != null - && ContentTypeUtils.shouldCapture(contentType.toString())) { + CharSequence contentType = DataCaptureUtils.getContentType(httpRequest); + if (instrumentationConfig.httpBody().request() + && contentType != null + && ContentTypeUtils.shouldCapture(contentType.toString())) { - CharSequence contentLengthHeader = DataCaptureUtils.getContentLength(httpRequest); - int contentLength = ContentLengthUtils.parseLength(contentLengthHeader); + CharSequence contentLengthHeader = DataCaptureUtils.getContentLength(httpRequest); + int contentLength = ContentLengthUtils.parseLength(contentLengthHeader); - String charsetString = ContentTypeUtils.parseCharset(contentType.toString()); - Charset charset = ContentTypeCharsetUtils.toCharset(charsetString); + String charsetString = ContentTypeUtils.parseCharset(contentType.toString()); + Charset charset = ContentTypeCharsetUtils.toCharset(charsetString); - // set the buffer to capture response body - // the buffer is used byt captureBody method - Attribute bufferAttr = - ctx.channel().attr(AttributeKeys.REQUEST_BODY_BUFFER); - bufferAttr.set(BoundedBuffersFactory.createStream(contentLength, charset)); + // set the buffer to capture response body + // the buffer is used byt captureBody method + Attribute bufferAttr = + ctx.channel().attr(AttributeKeys.REQUEST_BODY_BUFFER); + bufferAttr.set(BoundedBuffersFactory.createStream(contentLength, charset)); - channel.attr(AttributeKeys.PROVIDED_CHARSET).set(charset); + channel.attr(AttributeKeys.PROVIDED_CHARSET).set(charset); + } } - } - if ((msg instanceof HttpContent || msg instanceof ByteBuf) - && instrumentationConfig.httpBody().request()) { - Charset charset = channel.attr(AttributeKeys.PROVIDED_CHARSET).get(); - if (charset == null) { - charset = ContentTypeCharsetUtils.getDefaultCharset(); + if ((msg instanceof HttpContent || msg instanceof ByteBuf) + && instrumentationConfig.httpBody().request()) { + Charset charset = channel.attr(AttributeKeys.PROVIDED_CHARSET).get(); + if (charset == null) { + charset = ContentTypeCharsetUtils.getDefaultCharset(); + } + DataCaptureUtils.captureBody( + span, channel, AttributeKeys.REQUEST_BODY_BUFFER, msg, null, charset); } - DataCaptureUtils.captureBody( - span, channel, AttributeKeys.REQUEST_BODY_BUFFER, msg, null, charset); - } - ctx.fireChannelRead(msg); + ctx.fireChannelRead(msg); + } } private static Map headersToMap(HttpMessage httpMessage) { From d906bc205f93a110c2b24eba6aff8161c2c38bd7 Mon Sep 17 00:00:00 2001 From: Yashwant Date: Sun, 3 Aug 2025 16:14:56 +0530 Subject: [PATCH 2/3] netty-4.0's fix to correct context prop --- .../NettyChannelPipelineInstrumentation.java | 6 +- .../OtelHttpClientRequestTracingHandler.java | 120 ++++++++++++++++++ .../HttpServerRequestTracingHandler.java | 73 ++++++----- .../HttpServerRequestTracingHandler.java | 2 +- 4 files changed, 166 insertions(+), 35 deletions(-) create mode 100644 instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/client/OtelHttpClientRequestTracingHandler.java diff --git a/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/NettyChannelPipelineInstrumentation.java b/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/NettyChannelPipelineInstrumentation.java index c979ccc0..08bed322 100644 --- a/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/NettyChannelPipelineInstrumentation.java +++ b/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/NettyChannelPipelineInstrumentation.java @@ -36,6 +36,7 @@ import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_0.client.HttpClientRequestTracingHandler; import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_0.client.HttpClientResponseTracingHandler; import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_0.client.HttpClientTracingHandler; +import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_0.client.OtelHttpClientRequestTracingHandler; import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_0.server.HttpServerBlockingRequestHandler; import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_0.server.HttpServerRequestTracingHandler; import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_0.server.HttpServerResponseTracingHandler; @@ -135,14 +136,13 @@ public static void addHandler( HttpClientTracingHandler.class.getName(), new HttpClientTracingHandler()); - // add OTEL request handler to start spans + // add our custom request handler to start spans with proper context propagation pipeline.addAfter( HttpClientTracingHandler.class.getName(), io.opentelemetry.javaagent.instrumentation.netty.v4_0.client .HttpClientRequestTracingHandler.class .getName(), - new io.opentelemetry.javaagent.instrumentation.netty.v4_0.client - .HttpClientRequestTracingHandler()); + new OtelHttpClientRequestTracingHandler()); } else if (handler instanceof HttpRequestEncoder) { pipeline.addLast( HttpClientRequestTracingHandler.class.getName(), diff --git a/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/client/OtelHttpClientRequestTracingHandler.java b/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/client/OtelHttpClientRequestTracingHandler.java new file mode 100644 index 00000000..94aa15bd --- /dev/null +++ b/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/client/OtelHttpClientRequestTracingHandler.java @@ -0,0 +1,120 @@ +/* + * Copyright The Hypertrace Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_0.client; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel; +import java.util.concurrent.ConcurrentHashMap; +import io.opentelemetry.javaagent.instrumentation.netty.v4_0.AttributeKeys; +import io.opentelemetry.javaagent.instrumentation.netty.v4_0.client.HttpClientRequestTracingHandler; + +/** + * Custom extension of OpenTelemetry's HttpClientRequestTracingHandler that ensures proper context + * propagation by using Context.current() as the parent context. + */ +public class OtelHttpClientRequestTracingHandler extends HttpClientRequestTracingHandler { + + // Store the server context for each thread + private static final ThreadLocal SERVER_CONTEXT = new ThreadLocal<>(); + + // Store the mapping from thread ID to server span context (for cross-thread scenarios) + private static final ConcurrentHashMap THREAD_TO_SPAN_CONTEXT = + new ConcurrentHashMap<>(); + + // Maximum size for the thread map before triggering cleanup + private static final int MAX_THREAD_MAP_SIZE = 1000; + + // Cleanup flag to avoid excessive synchronized blocks + private static volatile boolean cleanupNeeded = false; + + public OtelHttpClientRequestTracingHandler() { + super(); + } + + /** + * Stores the current context as the server context for this thread. This should be called from + * the server handler. + */ + public static void storeServerContext(Context context) { + SERVER_CONTEXT.set(context); + + // Also store the span context by thread ID for cross-thread lookup + Span span = Span.fromContext(context); + if (span != null && span.getSpanContext().isValid()) { + THREAD_TO_SPAN_CONTEXT.put(Thread.currentThread().getId(), span.getSpanContext()); + + // Check if we need to clean up the map + if (THREAD_TO_SPAN_CONTEXT.size() > MAX_THREAD_MAP_SIZE) { + cleanupNeeded = true; + } + } + } + + /** + * Perform cleanup of the thread map if it has grown too large. This is done in a synchronized + * block to prevent concurrent modification issues. + */ + private static void cleanupThreadMapIfNeeded() { + if (cleanupNeeded) { + synchronized (THREAD_TO_SPAN_CONTEXT) { + if (THREAD_TO_SPAN_CONTEXT.size() > MAX_THREAD_MAP_SIZE) { + THREAD_TO_SPAN_CONTEXT.clear(); + cleanupNeeded = false; + } + } + } + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise prm) { + try { + if (!(msg instanceof HttpRequest)) { + super.write(ctx, msg, prm); + return; + } + + Context parentContext = SERVER_CONTEXT.get(); + + // Fallback -> If no context in thread local, try Context.current() + if (parentContext == null) { + parentContext = Context.current(); + } + + // Store the parent context in the channel attributes + // This is used by the Opentelemetry's HttpClientRequestTracingHandler in propagating correct + // context. + ctx.channel().attr(AttributeKeys.CLIENT_PARENT_CONTEXT).set(parentContext); + + // Call the parent implementation which will use our stored parent context + super.write(ctx, msg, prm); + + // Clean up after use to prevent memory leaks + SERVER_CONTEXT.remove(); + THREAD_TO_SPAN_CONTEXT.remove(Thread.currentThread().getId()); + cleanupThreadMapIfNeeded(); + + } catch (Exception ignored) { + } + } +} diff --git a/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/server/HttpServerRequestTracingHandler.java b/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/server/HttpServerRequestTracingHandler.java index 815a9cf6..00b9c66e 100644 --- a/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/server/HttpServerRequestTracingHandler.java +++ b/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/server/HttpServerRequestTracingHandler.java @@ -27,11 +27,14 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_0.AttributeKeys; import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_0.DataCaptureUtils; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; + +import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_0.client.OtelHttpClientRequestTracingHandler; import org.hypertrace.agent.core.config.InstrumentationConfig; import org.hypertrace.agent.core.instrumentation.HypertraceSemanticAttributes; import org.hypertrace.agent.core.instrumentation.buffer.BoundedBuffersFactory; @@ -57,47 +60,55 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { ctx.fireChannelRead(msg); return; } - Span span = Span.fromContext(context); - if (msg instanceof HttpRequest) { - HttpRequest httpRequest = (HttpRequest) msg; - Map headersMap = headersToMap(httpRequest); - if (instrumentationConfig.httpHeaders().request()) { - headersMap.forEach(span::setAttribute); - } - // used by blocking handler - channel.attr(AttributeKeys.REQUEST_HEADERS).set(headersMap); + // Store the server context in our ThreadLocal for later use by client handlers + // This is CRITICAL for proper context propagation to client spans + OtelHttpClientRequestTracingHandler.storeServerContext(context); + + try (Scope ignored = context.makeCurrent()) { + Span span = Span.fromContext(context); - CharSequence contentType = DataCaptureUtils.getContentType(httpRequest); - if (instrumentationConfig.httpBody().request() - && contentType != null - && ContentTypeUtils.shouldCapture(contentType.toString())) { + if (msg instanceof HttpRequest) { + HttpRequest httpRequest = (HttpRequest) msg; - CharSequence contentLengthHeader = DataCaptureUtils.getContentLength(httpRequest); - int contentLength = ContentLengthUtils.parseLength(contentLengthHeader); + Map headersMap = headersToMap(httpRequest); + if (instrumentationConfig.httpHeaders().request()) { + headersMap.forEach(span::setAttribute); + } + // used by blocking handler + channel.attr(AttributeKeys.REQUEST_HEADERS).set(headersMap); - String charsetString = ContentTypeUtils.parseCharset(contentType.toString()); - Charset charset = ContentTypeCharsetUtils.toCharset(charsetString); + CharSequence contentType = DataCaptureUtils.getContentType(httpRequest); + if (instrumentationConfig.httpBody().request() + && contentType != null + && ContentTypeUtils.shouldCapture(contentType.toString())) { - // set the buffer to capture response body - // the buffer is used byt captureBody method - Attribute bufferAttr = - ctx.channel().attr(AttributeKeys.REQUEST_BODY_BUFFER); - bufferAttr.set(BoundedBuffersFactory.createStream(contentLength, charset)); + CharSequence contentLengthHeader = DataCaptureUtils.getContentLength(httpRequest); + int contentLength = ContentLengthUtils.parseLength(contentLengthHeader); - channel.attr(AttributeKeys.CHARSET).set(charset); + String charsetString = ContentTypeUtils.parseCharset(contentType.toString()); + Charset charset = ContentTypeCharsetUtils.toCharset(charsetString); + + // set the buffer to capture response body + // the buffer is used byt captureBody method + Attribute bufferAttr = + ctx.channel().attr(AttributeKeys.REQUEST_BODY_BUFFER); + bufferAttr.set(BoundedBuffersFactory.createStream(contentLength, charset)); + + channel.attr(AttributeKeys.CHARSET).set(charset); + } } - } - if ((msg instanceof HttpContent || msg instanceof ByteBuf) - && instrumentationConfig.httpBody().request()) { - Charset charset = channel.attr(AttributeKeys.CHARSET).get(); - if (charset == null) { - charset = ContentTypeCharsetUtils.getDefaultCharset(); + if ((msg instanceof HttpContent || msg instanceof ByteBuf) + && instrumentationConfig.httpBody().request()) { + Charset charset = channel.attr(AttributeKeys.CHARSET).get(); + if (charset == null) { + charset = ContentTypeCharsetUtils.getDefaultCharset(); + } + DataCaptureUtils.captureBody( + span, channel, AttributeKeys.REQUEST_BODY_BUFFER, msg, null, charset); } - DataCaptureUtils.captureBody( - span, channel, AttributeKeys.REQUEST_BODY_BUFFER, msg, null, charset); } ctx.fireChannelRead(msg); diff --git a/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/server/HttpServerRequestTracingHandler.java b/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/server/HttpServerRequestTracingHandler.java index 15bd3a1b..83b47d48 100644 --- a/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/server/HttpServerRequestTracingHandler.java +++ b/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/server/HttpServerRequestTracingHandler.java @@ -111,8 +111,8 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { span, channel, AttributeKeys.REQUEST_BODY_BUFFER, msg, null, charset); } - ctx.fireChannelRead(msg); } + ctx.fireChannelRead(msg); } private static Map headersToMap(HttpMessage httpMessage) { From 26c5c07c0c35a44f17e750cf34867b97deafd8d5 Mon Sep 17 00:00:00 2001 From: Yashwant Date: Mon, 25 Aug 2025 12:18:59 +0530 Subject: [PATCH 3/3] spotless apply --- .../NettyChannelPipelineInstrumentation.java | 2 +- .../OtelHttpClientRequestTracingHandler.java | 137 +++++++++--------- .../HttpServerRequestTracingHandler.java | 14 +- .../HttpServerRequestTracingHandler.java | 1 - 4 files changed, 74 insertions(+), 80 deletions(-) diff --git a/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/NettyChannelPipelineInstrumentation.java b/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/NettyChannelPipelineInstrumentation.java index 08bed322..af4878f7 100644 --- a/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/NettyChannelPipelineInstrumentation.java +++ b/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/NettyChannelPipelineInstrumentation.java @@ -142,7 +142,7 @@ public static void addHandler( io.opentelemetry.javaagent.instrumentation.netty.v4_0.client .HttpClientRequestTracingHandler.class .getName(), - new OtelHttpClientRequestTracingHandler()); + new OtelHttpClientRequestTracingHandler()); } else if (handler instanceof HttpRequestEncoder) { pipeline.addLast( HttpClientRequestTracingHandler.class.getName(), diff --git a/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/client/OtelHttpClientRequestTracingHandler.java b/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/client/OtelHttpClientRequestTracingHandler.java index 94aa15bd..768c3679 100644 --- a/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/client/OtelHttpClientRequestTracingHandler.java +++ b/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/client/OtelHttpClientRequestTracingHandler.java @@ -19,15 +19,12 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel; -import java.util.concurrent.ConcurrentHashMap; import io.opentelemetry.javaagent.instrumentation.netty.v4_0.AttributeKeys; import io.opentelemetry.javaagent.instrumentation.netty.v4_0.client.HttpClientRequestTracingHandler; +import java.util.concurrent.ConcurrentHashMap; /** * Custom extension of OpenTelemetry's HttpClientRequestTracingHandler that ensures proper context @@ -35,86 +32,86 @@ */ public class OtelHttpClientRequestTracingHandler extends HttpClientRequestTracingHandler { - // Store the server context for each thread - private static final ThreadLocal SERVER_CONTEXT = new ThreadLocal<>(); + // Store the server context for each thread + private static final ThreadLocal SERVER_CONTEXT = new ThreadLocal<>(); - // Store the mapping from thread ID to server span context (for cross-thread scenarios) - private static final ConcurrentHashMap THREAD_TO_SPAN_CONTEXT = - new ConcurrentHashMap<>(); + // Store the mapping from thread ID to server span context (for cross-thread scenarios) + private static final ConcurrentHashMap THREAD_TO_SPAN_CONTEXT = + new ConcurrentHashMap<>(); - // Maximum size for the thread map before triggering cleanup - private static final int MAX_THREAD_MAP_SIZE = 1000; + // Maximum size for the thread map before triggering cleanup + private static final int MAX_THREAD_MAP_SIZE = 1000; - // Cleanup flag to avoid excessive synchronized blocks - private static volatile boolean cleanupNeeded = false; + // Cleanup flag to avoid excessive synchronized blocks + private static volatile boolean cleanupNeeded = false; - public OtelHttpClientRequestTracingHandler() { - super(); - } + public OtelHttpClientRequestTracingHandler() { + super(); + } - /** - * Stores the current context as the server context for this thread. This should be called from - * the server handler. - */ - public static void storeServerContext(Context context) { - SERVER_CONTEXT.set(context); - - // Also store the span context by thread ID for cross-thread lookup - Span span = Span.fromContext(context); - if (span != null && span.getSpanContext().isValid()) { - THREAD_TO_SPAN_CONTEXT.put(Thread.currentThread().getId(), span.getSpanContext()); - - // Check if we need to clean up the map - if (THREAD_TO_SPAN_CONTEXT.size() > MAX_THREAD_MAP_SIZE) { - cleanupNeeded = true; - } - } - } + /** + * Stores the current context as the server context for this thread. This should be called from + * the server handler. + */ + public static void storeServerContext(Context context) { + SERVER_CONTEXT.set(context); + + // Also store the span context by thread ID for cross-thread lookup + Span span = Span.fromContext(context); + if (span != null && span.getSpanContext().isValid()) { + THREAD_TO_SPAN_CONTEXT.put(Thread.currentThread().getId(), span.getSpanContext()); - /** - * Perform cleanup of the thread map if it has grown too large. This is done in a synchronized - * block to prevent concurrent modification issues. - */ - private static void cleanupThreadMapIfNeeded() { - if (cleanupNeeded) { - synchronized (THREAD_TO_SPAN_CONTEXT) { - if (THREAD_TO_SPAN_CONTEXT.size() > MAX_THREAD_MAP_SIZE) { - THREAD_TO_SPAN_CONTEXT.clear(); - cleanupNeeded = false; - } - } + // Check if we need to clean up the map + if (THREAD_TO_SPAN_CONTEXT.size() > MAX_THREAD_MAP_SIZE) { + cleanupNeeded = true; + } + } + } + + /** + * Perform cleanup of the thread map if it has grown too large. This is done in a synchronized + * block to prevent concurrent modification issues. + */ + private static void cleanupThreadMapIfNeeded() { + if (cleanupNeeded) { + synchronized (THREAD_TO_SPAN_CONTEXT) { + if (THREAD_TO_SPAN_CONTEXT.size() > MAX_THREAD_MAP_SIZE) { + THREAD_TO_SPAN_CONTEXT.clear(); + cleanupNeeded = false; } + } } + } - @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise prm) { - try { - if (!(msg instanceof HttpRequest)) { - super.write(ctx, msg, prm); - return; - } + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise prm) { + try { + if (!(msg instanceof HttpRequest)) { + super.write(ctx, msg, prm); + return; + } - Context parentContext = SERVER_CONTEXT.get(); + Context parentContext = SERVER_CONTEXT.get(); - // Fallback -> If no context in thread local, try Context.current() - if (parentContext == null) { - parentContext = Context.current(); - } + // Fallback -> If no context in thread local, try Context.current() + if (parentContext == null) { + parentContext = Context.current(); + } - // Store the parent context in the channel attributes - // This is used by the Opentelemetry's HttpClientRequestTracingHandler in propagating correct - // context. - ctx.channel().attr(AttributeKeys.CLIENT_PARENT_CONTEXT).set(parentContext); + // Store the parent context in the channel attributes + // This is used by the Opentelemetry's HttpClientRequestTracingHandler in propagating correct + // context. + ctx.channel().attr(AttributeKeys.CLIENT_PARENT_CONTEXT).set(parentContext); - // Call the parent implementation which will use our stored parent context - super.write(ctx, msg, prm); + // Call the parent implementation which will use our stored parent context + super.write(ctx, msg, prm); - // Clean up after use to prevent memory leaks - SERVER_CONTEXT.remove(); - THREAD_TO_SPAN_CONTEXT.remove(Thread.currentThread().getId()); - cleanupThreadMapIfNeeded(); + // Clean up after use to prevent memory leaks + SERVER_CONTEXT.remove(); + THREAD_TO_SPAN_CONTEXT.remove(Thread.currentThread().getId()); + cleanupThreadMapIfNeeded(); - } catch (Exception ignored) { - } + } catch (Exception ignored) { } + } } diff --git a/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/server/HttpServerRequestTracingHandler.java b/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/server/HttpServerRequestTracingHandler.java index 00b9c66e..2dcd6df3 100644 --- a/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/server/HttpServerRequestTracingHandler.java +++ b/instrumentation/netty/netty-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/server/HttpServerRequestTracingHandler.java @@ -30,11 +30,10 @@ import io.opentelemetry.context.Scope; import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_0.AttributeKeys; import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_0.DataCaptureUtils; +import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_0.client.OtelHttpClientRequestTracingHandler; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; - -import io.opentelemetry.javaagent.instrumentation.hypertrace.netty.v4_0.client.OtelHttpClientRequestTracingHandler; import org.hypertrace.agent.core.config.InstrumentationConfig; import org.hypertrace.agent.core.instrumentation.HypertraceSemanticAttributes; import org.hypertrace.agent.core.instrumentation.buffer.BoundedBuffersFactory; @@ -61,7 +60,6 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { return; } - // Store the server context in our ThreadLocal for later use by client handlers // This is CRITICAL for proper context propagation to client spans OtelHttpClientRequestTracingHandler.storeServerContext(context); @@ -81,8 +79,8 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { CharSequence contentType = DataCaptureUtils.getContentType(httpRequest); if (instrumentationConfig.httpBody().request() - && contentType != null - && ContentTypeUtils.shouldCapture(contentType.toString())) { + && contentType != null + && ContentTypeUtils.shouldCapture(contentType.toString())) { CharSequence contentLengthHeader = DataCaptureUtils.getContentLength(httpRequest); int contentLength = ContentLengthUtils.parseLength(contentLengthHeader); @@ -93,7 +91,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { // set the buffer to capture response body // the buffer is used byt captureBody method Attribute bufferAttr = - ctx.channel().attr(AttributeKeys.REQUEST_BODY_BUFFER); + ctx.channel().attr(AttributeKeys.REQUEST_BODY_BUFFER); bufferAttr.set(BoundedBuffersFactory.createStream(contentLength, charset)); channel.attr(AttributeKeys.CHARSET).set(charset); @@ -101,13 +99,13 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { } if ((msg instanceof HttpContent || msg instanceof ByteBuf) - && instrumentationConfig.httpBody().request()) { + && instrumentationConfig.httpBody().request()) { Charset charset = channel.attr(AttributeKeys.CHARSET).get(); if (charset == null) { charset = ContentTypeCharsetUtils.getDefaultCharset(); } DataCaptureUtils.captureBody( - span, channel, AttributeKeys.REQUEST_BODY_BUFFER, msg, null, charset); + span, channel, AttributeKeys.REQUEST_BODY_BUFFER, msg, null, charset); } } diff --git a/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/server/HttpServerRequestTracingHandler.java b/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/server/HttpServerRequestTracingHandler.java index 83b47d48..320e5306 100644 --- a/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/server/HttpServerRequestTracingHandler.java +++ b/instrumentation/netty/netty-4.1/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/server/HttpServerRequestTracingHandler.java @@ -110,7 +110,6 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { DataCaptureUtils.captureBody( span, channel, AttributeKeys.REQUEST_BODY_BUFFER, msg, null, charset); } - } ctx.fireChannelRead(msg); }