Add HttpServerResponseCustomizer support for Netty (#8094)
Add `HttpServerResponseCustomizer` support for Netty 3.8, 4.0 and 4.1 instrumentations and enable testing for it in their respective `HttpServerTest` tests.
This commit is contained in:
parent
2ebed6c466
commit
079e0faa2d
|
@ -11,6 +11,7 @@ import io.opentelemetry.context.Context;
|
|||
import io.opentelemetry.context.Scope;
|
||||
import io.opentelemetry.instrumentation.api.util.VirtualField;
|
||||
import io.opentelemetry.instrumentation.netty.common.internal.NettyErrorHolder;
|
||||
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder;
|
||||
import io.opentelemetry.javaagent.instrumentation.netty.v3_8.HttpRequestAndChannel;
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
|
@ -38,6 +39,7 @@ public class HttpServerResponseTracingHandler extends SimpleChannelDownstreamHan
|
|||
|
||||
Throwable error = null;
|
||||
try (Scope ignored = context.makeCurrent()) {
|
||||
customizeResponse(context, response);
|
||||
super.writeRequested(ctx, msg);
|
||||
} catch (Throwable t) {
|
||||
error = t;
|
||||
|
@ -47,4 +49,13 @@ public class HttpServerResponseTracingHandler extends SimpleChannelDownstreamHan
|
|||
instrumenter().end(context, request, response, error);
|
||||
}
|
||||
}
|
||||
|
||||
private static void customizeResponse(Context context, HttpResponse response) {
|
||||
try {
|
||||
HttpServerResponseCustomizerHolder.getCustomizer()
|
||||
.customize(context, response, NettyHttpResponseMutator.INSTANCE);
|
||||
} catch (Throwable ignore) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.netty.v3_8.server;
|
||||
|
||||
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseMutator;
|
||||
import org.jboss.netty.handler.codec.http.HttpResponse;
|
||||
|
||||
public enum NettyHttpResponseMutator implements HttpServerResponseMutator<HttpResponse> {
|
||||
INSTANCE;
|
||||
|
||||
NettyHttpResponseMutator() {}
|
||||
|
||||
@Override
|
||||
public void appendHeader(HttpResponse response, String name, String value) {
|
||||
response.headers().add(name, value);
|
||||
}
|
||||
}
|
|
@ -89,6 +89,7 @@ class Netty38ServerTest extends AbstractHttpServerTest<ServerBootstrap> {
|
|||
});
|
||||
|
||||
options.setExpectedException(new IllegalArgumentException(ServerEndpoint.EXCEPTION.getBody()));
|
||||
options.setHasResponseCustomizer(serverEndpoint -> true);
|
||||
}
|
||||
|
||||
private static ChannelPipeline channelPipeline() {
|
||||
|
|
|
@ -17,6 +17,7 @@ import io.opentelemetry.context.Context;
|
|||
import io.opentelemetry.context.Scope;
|
||||
import io.opentelemetry.instrumentation.netty.common.internal.NettyErrorHolder;
|
||||
import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel;
|
||||
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder;
|
||||
import io.opentelemetry.javaagent.instrumentation.netty.v4_0.AttributeKeys;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
@ -31,6 +32,7 @@ public class HttpServerResponseTracingHandler extends ChannelOutboundHandlerAdap
|
|||
}
|
||||
|
||||
try (Scope ignored = context.makeCurrent()) {
|
||||
customizeResponse(context, (HttpResponse) msg);
|
||||
ctx.write(msg, prm);
|
||||
end(ctx.channel(), (HttpResponse) msg, null);
|
||||
} catch (Throwable throwable) {
|
||||
|
@ -46,4 +48,13 @@ public class HttpServerResponseTracingHandler extends ChannelOutboundHandlerAdap
|
|||
error = NettyErrorHolder.getOrDefault(context, error);
|
||||
instrumenter().end(context, request, response, error);
|
||||
}
|
||||
|
||||
private static void customizeResponse(Context context, HttpResponse response) {
|
||||
try {
|
||||
HttpServerResponseCustomizerHolder.getCustomizer()
|
||||
.customize(context, response, NettyHttpResponseMutator.INSTANCE);
|
||||
} catch (Throwable ignore) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.netty.v4_0.server;
|
||||
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseMutator;
|
||||
|
||||
public enum NettyHttpResponseMutator implements HttpServerResponseMutator<HttpResponse> {
|
||||
INSTANCE;
|
||||
|
||||
NettyHttpResponseMutator() {}
|
||||
|
||||
@Override
|
||||
public void appendHeader(HttpResponse response, String name, String value) {
|
||||
response.headers().add(name, value);
|
||||
}
|
||||
}
|
|
@ -47,6 +47,11 @@ class Netty40ServerTest extends HttpServerTest<EventLoopGroup> implements AgentT
|
|||
|
||||
static final LoggingHandler LOGGING_HANDLER = new LoggingHandler(SERVER_LOGGER.name, LogLevel.DEBUG)
|
||||
|
||||
@Override
|
||||
boolean hasResponseCustomizer(ServerEndpoint endpoint) {
|
||||
true
|
||||
}
|
||||
|
||||
@Override
|
||||
EventLoopGroup startServer(int port) {
|
||||
def eventLoopGroup = new NioEventLoopGroup()
|
||||
|
|
|
@ -105,11 +105,17 @@ public class NettyChannelPipelineInstrumentation
|
|||
ChannelHandler ourHandler = null;
|
||||
// Server pipeline handlers
|
||||
if (handler instanceof HttpServerCodec) {
|
||||
ourHandler = new HttpServerTracingHandler(NettyServerSingletons.instrumenter());
|
||||
ourHandler =
|
||||
new HttpServerTracingHandler(
|
||||
NettyServerSingletons.instrumenter(),
|
||||
NettyHttpServerResponseBeforeCommitHandler.INSTANCE);
|
||||
} else if (handler instanceof HttpRequestDecoder) {
|
||||
ourHandler = new HttpServerRequestTracingHandler(NettyServerSingletons.instrumenter());
|
||||
} else if (handler instanceof HttpResponseEncoder) {
|
||||
ourHandler = new HttpServerResponseTracingHandler(NettyServerSingletons.instrumenter());
|
||||
ourHandler =
|
||||
new HttpServerResponseTracingHandler(
|
||||
NettyServerSingletons.instrumenter(),
|
||||
NettyHttpServerResponseBeforeCommitHandler.INSTANCE);
|
||||
// Client pipeline handlers
|
||||
} else if (handler instanceof HttpClientCodec) {
|
||||
ourHandler = new HttpClientTracingHandler(NettyClientSingletons.instrumenter());
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.netty.v4_1;
|
||||
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.instrumentation.netty.v4_1.internal.server.HttpServerResponseBeforeCommitHandler;
|
||||
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder;
|
||||
|
||||
public enum NettyHttpServerResponseBeforeCommitHandler
|
||||
implements HttpServerResponseBeforeCommitHandler {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public void handle(Context context, HttpResponse response) {
|
||||
HttpServerResponseCustomizerHolder.getCustomizer()
|
||||
.customize(context, response, NettyHttpServerResponseMutator.INSTANCE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.netty.v4_1;
|
||||
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseMutator;
|
||||
|
||||
public enum NettyHttpServerResponseMutator implements HttpServerResponseMutator<HttpResponse> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public void appendHeader(HttpResponse response, String name, String value) {
|
||||
response.headers().add(name, value);
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import io.netty.channel.ChannelPipeline;
|
|||
import io.opentelemetry.instrumentation.netty.v4_1.AbstractNetty41ServerTest;
|
||||
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
|
||||
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension;
|
||||
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
public class Netty41ServerTest extends AbstractNetty41ServerTest {
|
||||
|
@ -16,6 +17,12 @@ public class Netty41ServerTest extends AbstractNetty41ServerTest {
|
|||
@RegisterExtension
|
||||
static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent();
|
||||
|
||||
@Override
|
||||
protected void configure(HttpServerTestOptions options) {
|
||||
super.configure(options);
|
||||
options.setHasResponseCustomizer(serverEndpoint -> true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configurePipeline(ChannelPipeline channelPipeline) {}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import io.opentelemetry.api.OpenTelemetry;
|
|||
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
||||
import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel;
|
||||
import io.opentelemetry.instrumentation.netty.v4_1.internal.server.HttpServerRequestTracingHandler;
|
||||
import io.opentelemetry.instrumentation.netty.v4_1.internal.server.HttpServerResponseBeforeCommitHandler;
|
||||
import io.opentelemetry.instrumentation.netty.v4_1.internal.server.HttpServerResponseTracingHandler;
|
||||
import io.opentelemetry.instrumentation.netty.v4_1.internal.server.HttpServerTracingHandler;
|
||||
|
||||
|
@ -51,7 +52,8 @@ public final class NettyServerTelemetry {
|
|||
* responses. Must be paired with {@link #createRequestHandler()}.
|
||||
*/
|
||||
public ChannelOutboundHandlerAdapter createResponseHandler() {
|
||||
return new HttpServerResponseTracingHandler(instrumenter);
|
||||
return new HttpServerResponseTracingHandler(
|
||||
instrumenter, HttpServerResponseBeforeCommitHandler.Noop.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,6 +63,7 @@ public final class NettyServerTelemetry {
|
|||
public CombinedChannelDuplexHandler<
|
||||
? extends ChannelInboundHandlerAdapter, ? extends ChannelOutboundHandlerAdapter>
|
||||
createCombinedHandler() {
|
||||
return new HttpServerTracingHandler(instrumenter);
|
||||
return new HttpServerTracingHandler(
|
||||
instrumenter, HttpServerResponseBeforeCommitHandler.Noop.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.instrumentation.netty.v4_1.internal.server;
|
||||
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.opentelemetry.context.Context;
|
||||
|
||||
/**
|
||||
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
|
||||
* any time.
|
||||
*/
|
||||
public interface HttpServerResponseBeforeCommitHandler {
|
||||
void handle(Context context, HttpResponse response);
|
||||
|
||||
/**
|
||||
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
|
||||
* any time.
|
||||
*/
|
||||
enum Noop implements HttpServerResponseBeforeCommitHandler {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public void handle(Context context, HttpResponse response) {}
|
||||
}
|
||||
}
|
|
@ -35,10 +35,13 @@ public class HttpServerResponseTracingHandler extends ChannelOutboundHandlerAdap
|
|||
AttributeKey.valueOf(HttpServerResponseTracingHandler.class, "http-server-response");
|
||||
|
||||
private final Instrumenter<HttpRequestAndChannel, HttpResponse> instrumenter;
|
||||
private final HttpServerResponseBeforeCommitHandler beforeCommitHandler;
|
||||
|
||||
public HttpServerResponseTracingHandler(
|
||||
Instrumenter<HttpRequestAndChannel, HttpResponse> instrumenter) {
|
||||
Instrumenter<HttpRequestAndChannel, HttpResponse> instrumenter,
|
||||
HttpServerResponseBeforeCommitHandler beforeCommitHandler) {
|
||||
this.instrumenter = instrumenter;
|
||||
this.beforeCommitHandler = beforeCommitHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -65,6 +68,7 @@ public class HttpServerResponseTracingHandler extends ChannelOutboundHandlerAdap
|
|||
// Going to finish the span after the write of the last content finishes.
|
||||
if (msg instanceof FullHttpResponse) {
|
||||
// Headers and body all sent together, we have the response information in the msg.
|
||||
beforeCommitHandler.handle(context, (HttpResponse) msg);
|
||||
writePromise.addListener(
|
||||
future -> end(ctx.channel(), (FullHttpResponse) msg, writePromise));
|
||||
} else {
|
||||
|
@ -82,6 +86,7 @@ public class HttpServerResponseTracingHandler extends ChannelOutboundHandlerAdap
|
|||
writePromise = prm;
|
||||
if (msg instanceof HttpResponse) {
|
||||
// Headers before body has been sent, store them to use when finishing the span.
|
||||
beforeCommitHandler.handle(context, (HttpResponse) msg);
|
||||
ctx.channel().attr(HTTP_SERVER_RESPONSE).set((HttpResponse) msg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,11 @@ public class HttpServerTracingHandler
|
|||
extends CombinedChannelDuplexHandler<
|
||||
HttpServerRequestTracingHandler, HttpServerResponseTracingHandler> {
|
||||
|
||||
public HttpServerTracingHandler(Instrumenter<HttpRequestAndChannel, HttpResponse> instrumenter) {
|
||||
public HttpServerTracingHandler(
|
||||
Instrumenter<HttpRequestAndChannel, HttpResponse> instrumenter,
|
||||
HttpServerResponseBeforeCommitHandler responseBeforeCommitHandler) {
|
||||
super(
|
||||
new HttpServerRequestTracingHandler(instrumenter),
|
||||
new HttpServerResponseTracingHandler(instrumenter));
|
||||
new HttpServerResponseTracingHandler(instrumenter, responseBeforeCommitHandler));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue