commit
2b5037e2f7
|
@ -0,0 +1,49 @@
|
||||||
|
// Set properties before any plugins get loaded
|
||||||
|
ext {
|
||||||
|
minJavaVersionForTests = JavaVersion.VERSION_1_8
|
||||||
|
maxJavaVersionForTests = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "${rootDir}/gradle/java.gradle"
|
||||||
|
|
||||||
|
muzzle {
|
||||||
|
pass {
|
||||||
|
group = "io.netty"
|
||||||
|
module = "netty"
|
||||||
|
versions = "[3.8.0.Final,4)"
|
||||||
|
assertInverse = true
|
||||||
|
}
|
||||||
|
fail {
|
||||||
|
group = "io.netty"
|
||||||
|
module = "netty-all"
|
||||||
|
versions = "[,]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'org.unbroken-dome.test-sets'
|
||||||
|
|
||||||
|
testSets {
|
||||||
|
latestDepTest
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly group: 'io.netty', name: 'netty', version: '3.8.0.Final'
|
||||||
|
|
||||||
|
testCompile group: 'io.netty', name: 'netty', version: '3.8.0.Final'
|
||||||
|
testCompile group: 'com.ning', name: 'async-http-client', version: '1.8.0'
|
||||||
|
|
||||||
|
latestDepTestCompile group: 'io.netty', name: 'netty', version: '3.10.+'
|
||||||
|
latestDepTestCompile group: 'com.ning', name: 'async-http-client', version: '1.9.+'
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to force the dependency to the earliest supported version because other libraries declare newer versions.
|
||||||
|
configurations.testCompile {
|
||||||
|
resolutionStrategy {
|
||||||
|
eachDependency { DependencyResolveDetails details ->
|
||||||
|
//specifying a fixed version for all libraries with io.netty' group
|
||||||
|
if (details.requested.group == 'io.netty') {
|
||||||
|
details.useVersion "3.8.0.Final"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
import com.ning.http.client.AsyncCompletionHandler
|
||||||
|
import com.ning.http.client.AsyncHttpClient
|
||||||
|
import com.ning.http.client.AsyncHttpClientConfig
|
||||||
|
import com.ning.http.client.Response
|
||||||
|
import datadog.trace.agent.test.base.HttpClientTest
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.Tags
|
||||||
|
import datadog.trace.instrumentation.netty38.client.NettyHttpClientDecorator
|
||||||
|
import spock.lang.AutoCleanup
|
||||||
|
import spock.lang.Shared
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutionException
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
import static datadog.trace.agent.test.utils.PortUtils.UNUSABLE_PORT
|
||||||
|
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
|
||||||
|
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
|
||||||
|
|
||||||
|
class Netty38ClientTest extends HttpClientTest {
|
||||||
|
|
||||||
|
@Shared
|
||||||
|
def clientConfig = new AsyncHttpClientConfig.Builder()
|
||||||
|
.setRequestTimeout(TimeUnit.SECONDS.toMillis(10).toInteger())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@Shared
|
||||||
|
@AutoCleanup
|
||||||
|
AsyncHttpClient asyncHttpClient = new AsyncHttpClient(clientConfig)
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
|
||||||
|
def methodName = "prepare" + method.toLowerCase().capitalize()
|
||||||
|
def requestBuilder = asyncHttpClient."$methodName"(uri.toString())
|
||||||
|
headers.each { requestBuilder.setHeader(it.key, it.value) }
|
||||||
|
def response = requestBuilder.execute(new AsyncCompletionHandler() {
|
||||||
|
@Override
|
||||||
|
Object onCompleted(Response response) throws Exception {
|
||||||
|
callback?.call()
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}).get()
|
||||||
|
blockUntilChildSpansFinished(1)
|
||||||
|
return response.statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String component() {
|
||||||
|
return NettyHttpClientDecorator.DECORATE.component()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String expectedOperationName() {
|
||||||
|
return "netty.client.request"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean testRedirects() {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean testConnectionFailure() {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
def "connection error (unopened port)"() {
|
||||||
|
given:
|
||||||
|
def uri = new URI("http://localhost:$UNUSABLE_PORT/")
|
||||||
|
|
||||||
|
when:
|
||||||
|
runUnderTrace("parent") {
|
||||||
|
doRequest(method, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
then:
|
||||||
|
def ex = thrown(Exception)
|
||||||
|
def thrownException = ex instanceof ExecutionException ? ex.cause : ex
|
||||||
|
|
||||||
|
and:
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 2) {
|
||||||
|
basicSpan(it, 0, "parent", null, thrownException)
|
||||||
|
|
||||||
|
span(1) {
|
||||||
|
operationName "netty.connect"
|
||||||
|
resourceName "netty.connect"
|
||||||
|
childOf span(0)
|
||||||
|
errored true
|
||||||
|
tags {
|
||||||
|
"$Tags.COMPONENT" "netty"
|
||||||
|
Class errorClass = ConnectException
|
||||||
|
try {
|
||||||
|
errorClass = Class.forName('io.netty.channel.AbstractChannel$AnnotatedConnectException')
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
// Older versions use 'java.net.ConnectException' and do not have 'io.netty.channel.AbstractChannel$AnnotatedConnectException'
|
||||||
|
}
|
||||||
|
errorTags errorClass, "Connection refused: localhost/127.0.0.1:$UNUSABLE_PORT"
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
where:
|
||||||
|
method = "GET"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
import datadog.trace.agent.test.base.HttpServerTest
|
||||||
|
import datadog.trace.instrumentation.netty38.server.NettyHttpServerDecorator
|
||||||
|
import org.jboss.netty.bootstrap.ServerBootstrap
|
||||||
|
import org.jboss.netty.buffer.ChannelBuffer
|
||||||
|
import org.jboss.netty.buffer.ChannelBuffers
|
||||||
|
import org.jboss.netty.channel.*
|
||||||
|
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory
|
||||||
|
import org.jboss.netty.handler.codec.http.*
|
||||||
|
import org.jboss.netty.handler.logging.LoggingHandler
|
||||||
|
import org.jboss.netty.logging.InternalLogLevel
|
||||||
|
import org.jboss.netty.util.CharsetUtil
|
||||||
|
|
||||||
|
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.*
|
||||||
|
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*
|
||||||
|
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1
|
||||||
|
|
||||||
|
class Netty38ServerTest extends HttpServerTest<Channel> {
|
||||||
|
|
||||||
|
ChannelPipeline channelPipeline() {
|
||||||
|
ChannelPipeline channelPipeline = new DefaultChannelPipeline()
|
||||||
|
|
||||||
|
channelPipeline.addLast("http-codec", new HttpServerCodec())
|
||||||
|
channelPipeline.addLast("controller", new SimpleChannelHandler() {
|
||||||
|
@Override
|
||||||
|
void messageReceived(ChannelHandlerContext ctx, MessageEvent msg) throws Exception {
|
||||||
|
if (msg.getMessage() instanceof HttpRequest) {
|
||||||
|
def uri = URI.create((msg.getMessage() as HttpRequest).getUri())
|
||||||
|
HttpServerTest.ServerEndpoint endpoint = forPath(uri.path)
|
||||||
|
ctx.sendDownstream controller(endpoint) {
|
||||||
|
HttpResponse response
|
||||||
|
ChannelBuffer responseContent = null
|
||||||
|
switch (endpoint) {
|
||||||
|
case SUCCESS:
|
||||||
|
case ERROR:
|
||||||
|
responseContent = ChannelBuffers.copiedBuffer(endpoint.body, CharsetUtil.UTF_8)
|
||||||
|
response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status))
|
||||||
|
response.setContent(responseContent)
|
||||||
|
break
|
||||||
|
case QUERY_PARAM:
|
||||||
|
responseContent = ChannelBuffers.copiedBuffer(uri.query, CharsetUtil.UTF_8)
|
||||||
|
response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status))
|
||||||
|
response.setContent(responseContent)
|
||||||
|
break
|
||||||
|
case REDIRECT:
|
||||||
|
response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status))
|
||||||
|
response.headers().set(LOCATION, endpoint.body)
|
||||||
|
break
|
||||||
|
case EXCEPTION:
|
||||||
|
throw new Exception(endpoint.body)
|
||||||
|
default:
|
||||||
|
responseContent = ChannelBuffers.copiedBuffer(NOT_FOUND.body, CharsetUtil.UTF_8)
|
||||||
|
response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status))
|
||||||
|
response.setContent(responseContent)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
response.headers().set(CONTENT_TYPE, "text/plain")
|
||||||
|
if (responseContent) {
|
||||||
|
response.headers().set(CONTENT_LENGTH, responseContent.readableBytes())
|
||||||
|
}
|
||||||
|
return new DownstreamMessageEvent(
|
||||||
|
ctx.getChannel(),
|
||||||
|
new SucceededChannelFuture(ctx.getChannel()),
|
||||||
|
response,
|
||||||
|
ctx.getChannel().getRemoteAddress())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent ex) throws Exception {
|
||||||
|
ChannelBuffer buffer = ChannelBuffers.copiedBuffer(ex.getCause().getMessage(), CharsetUtil.UTF_8)
|
||||||
|
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
response.setContent(buffer)
|
||||||
|
response.headers().set(CONTENT_TYPE, "text/plain")
|
||||||
|
response.headers().set(CONTENT_LENGTH, buffer.readableBytes())
|
||||||
|
ctx.sendDownstream(new DownstreamMessageEvent(
|
||||||
|
ctx.getChannel(),
|
||||||
|
new FailedChannelFuture(ctx.getChannel(), ex.getCause()),
|
||||||
|
response,
|
||||||
|
ctx.getChannel().getRemoteAddress()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return channelPipeline
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Channel startServer(int port) {
|
||||||
|
ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory())
|
||||||
|
bootstrap.setParentHandler(new LoggingHandler(InternalLogLevel.INFO))
|
||||||
|
bootstrap.setPipeline(channelPipeline())
|
||||||
|
|
||||||
|
InetSocketAddress address = new InetSocketAddress(port)
|
||||||
|
return bootstrap.bind(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void stopServer(Channel server) {
|
||||||
|
server?.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String component() {
|
||||||
|
NettyHttpServerDecorator.DECORATE.component()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String expectedOperationName() {
|
||||||
|
"netty.request"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import datadog.trace.agent.test.base.HttpServerTestAdvice;
|
||||||
|
import datadog.trace.agent.tooling.Instrumenter;
|
||||||
|
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||||
|
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public class NettyServerTestInstrumentation implements Instrumenter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AgentBuilder instrument(final AgentBuilder agentBuilder) {
|
||||||
|
return agentBuilder
|
||||||
|
.type(named("org.jboss.netty.handler.codec.http.HttpRequestDecoder"))
|
||||||
|
.transform(
|
||||||
|
new AgentBuilder.Transformer.ForAdvice()
|
||||||
|
.advice(
|
||||||
|
named("createMessage"),
|
||||||
|
HttpServerTestAdvice.ServerEntryAdvice.class.getName()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package datadog.trace.instrumentation.netty38;
|
||||||
|
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpHeaders;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpRequest;
|
||||||
|
|
||||||
|
public class AbstractNettyAdvice {
|
||||||
|
public static void muzzleCheck(final HttpRequest httpRequest) {
|
||||||
|
final HttpHeaders headers = httpRequest.headers();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
package datadog.trace.instrumentation.netty38;
|
||||||
|
|
||||||
|
import static datadog.trace.agent.tooling.ClassLoaderMatcher.hasClassesNamed;
|
||||||
|
import static datadog.trace.agent.tooling.bytebuddy.matcher.DDElementMatchers.implementsInterface;
|
||||||
|
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
|
||||||
|
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
|
||||||
|
import static datadog.trace.instrumentation.netty38.server.NettyHttpServerDecorator.DECORATE;
|
||||||
|
import static java.util.Collections.singletonMap;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import datadog.trace.agent.tooling.Instrumenter;
|
||||||
|
import datadog.trace.bootstrap.ContextStore;
|
||||||
|
import datadog.trace.bootstrap.InstrumentationContext;
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.Tags;
|
||||||
|
import datadog.trace.context.TraceScope;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
import net.bytebuddy.description.method.MethodDescription;
|
||||||
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
|
import org.jboss.netty.channel.Channel;
|
||||||
|
import org.jboss.netty.channel.ChannelFuture;
|
||||||
|
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public class ChannelFutureListenerInstrumentation extends Instrumenter.Default {
|
||||||
|
|
||||||
|
public ChannelFutureListenerInstrumentation() {
|
||||||
|
super(
|
||||||
|
NettyChannelPipelineInstrumentation.INSTRUMENTATION_NAME,
|
||||||
|
NettyChannelPipelineInstrumentation.ADDITIONAL_INSTRUMENTATION_NAMES);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<ClassLoader> classLoaderMatcher() {
|
||||||
|
// Optimization for expensive typeMatcher.
|
||||||
|
return hasClassesNamed("org.jboss.netty.channel.ChannelFutureListener");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||||
|
return implementsInterface(named("org.jboss.netty.channel.ChannelFutureListener"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] helperClassNames() {
|
||||||
|
return new String[] {
|
||||||
|
packageName + ".AbstractNettyAdvice",
|
||||||
|
packageName + ".ChannelTraceContext",
|
||||||
|
packageName + ".ChannelTraceContext$Factory",
|
||||||
|
packageName + ".server.NettyHttpServerDecorator",
|
||||||
|
packageName + ".server.NettyRequestExtractAdapter"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||||
|
return singletonMap(
|
||||||
|
isMethod()
|
||||||
|
.and(named("operationComplete"))
|
||||||
|
.and(takesArgument(0, named("org.jboss.netty.channel.ChannelFuture"))),
|
||||||
|
ChannelFutureListenerInstrumentation.class.getName() + "$OperationCompleteAdvice");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> contextStore() {
|
||||||
|
return Collections.singletonMap(
|
||||||
|
"org.jboss.netty.channel.Channel", packageName + ".ChannelTraceContext");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class OperationCompleteAdvice extends AbstractNettyAdvice {
|
||||||
|
@Advice.OnMethodEnter
|
||||||
|
public static TraceScope activateScope(@Advice.Argument(0) final ChannelFuture future) {
|
||||||
|
/*
|
||||||
|
Idea here is:
|
||||||
|
- To return scope only if we have captured it.
|
||||||
|
- To capture scope only in case of error.
|
||||||
|
*/
|
||||||
|
final Throwable cause = future.getCause();
|
||||||
|
if (cause == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ContextStore<Channel, ChannelTraceContext> contextStore =
|
||||||
|
InstrumentationContext.get(Channel.class, ChannelTraceContext.class);
|
||||||
|
|
||||||
|
final TraceScope.Continuation continuation =
|
||||||
|
contextStore
|
||||||
|
.putIfAbsent(future.getChannel(), ChannelTraceContext.Factory.INSTANCE)
|
||||||
|
.getConnectionContinuation();
|
||||||
|
contextStore.get(future.getChannel()).setConnectionContinuation(null);
|
||||||
|
if (continuation == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final TraceScope parentScope = continuation.activate();
|
||||||
|
|
||||||
|
final AgentSpan errorSpan = startSpan("netty.connect").setTag(Tags.COMPONENT, "netty");
|
||||||
|
try (final AgentScope scope = activateSpan(errorSpan, false)) {
|
||||||
|
DECORATE.onError(errorSpan, cause);
|
||||||
|
DECORATE.beforeFinish(errorSpan);
|
||||||
|
errorSpan.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parentScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||||
|
public static void deactivateScope(@Advice.Enter final TraceScope scope) {
|
||||||
|
if (scope != null) {
|
||||||
|
scope.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package datadog.trace.instrumentation.netty38;
|
||||||
|
|
||||||
|
import datadog.trace.bootstrap.ContextStore;
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
|
||||||
|
import datadog.trace.context.TraceScope;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ChannelTraceContext {
|
||||||
|
public static class Factory implements ContextStore.Factory<ChannelTraceContext> {
|
||||||
|
public static final Factory INSTANCE = new Factory();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelTraceContext create() {
|
||||||
|
return new ChannelTraceContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceScope.Continuation connectionContinuation;
|
||||||
|
AgentSpan serverSpan;
|
||||||
|
AgentSpan clientSpan;
|
||||||
|
AgentSpan clientParentSpan;
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package datadog.trace.instrumentation.netty38;
|
||||||
|
|
||||||
|
import static datadog.trace.agent.tooling.ClassLoaderMatcher.hasClassesNamed;
|
||||||
|
import static datadog.trace.agent.tooling.bytebuddy.matcher.DDElementMatchers.implementsInterface;
|
||||||
|
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeScope;
|
||||||
|
import static datadog.trace.instrumentation.netty38.NettyChannelPipelineInstrumentation.ADDITIONAL_INSTRUMENTATION_NAMES;
|
||||||
|
import static datadog.trace.instrumentation.netty38.NettyChannelPipelineInstrumentation.INSTRUMENTATION_NAME;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.returns;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import datadog.trace.agent.tooling.Instrumenter;
|
||||||
|
import datadog.trace.bootstrap.ContextStore;
|
||||||
|
import datadog.trace.bootstrap.InstrumentationContext;
|
||||||
|
import datadog.trace.context.TraceScope;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
import net.bytebuddy.description.method.MethodDescription;
|
||||||
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
|
import org.jboss.netty.channel.Channel;
|
||||||
|
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public class NettyChannelInstrumentation extends Instrumenter.Default {
|
||||||
|
public NettyChannelInstrumentation() {
|
||||||
|
super(INSTRUMENTATION_NAME, ADDITIONAL_INSTRUMENTATION_NAMES);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<ClassLoader> classLoaderMatcher() {
|
||||||
|
// Optimization for expensive typeMatcher.
|
||||||
|
return hasClassesNamed("org.jboss.netty.channel.Channel");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||||
|
return implementsInterface(named("org.jboss.netty.channel.Channel"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] helperClassNames() {
|
||||||
|
return new String[] {
|
||||||
|
packageName + ".AbstractNettyAdvice",
|
||||||
|
packageName + ".ChannelTraceContext",
|
||||||
|
packageName + ".ChannelTraceContext$Factory"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||||
|
final Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>();
|
||||||
|
transformers.put(
|
||||||
|
isMethod()
|
||||||
|
.and(named("connect"))
|
||||||
|
.and(returns(named("org.jboss.netty.channel.ChannelFuture"))),
|
||||||
|
NettyChannelInstrumentation.class.getName() + "$ChannelConnectAdvice");
|
||||||
|
return transformers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> contextStore() {
|
||||||
|
return Collections.singletonMap(
|
||||||
|
"org.jboss.netty.channel.Channel", ChannelTraceContext.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ChannelConnectAdvice extends AbstractNettyAdvice {
|
||||||
|
@Advice.OnMethodEnter
|
||||||
|
public static void addConnectContinuation(@Advice.This final Channel channel) {
|
||||||
|
final TraceScope scope = activeScope();
|
||||||
|
if (scope != null) {
|
||||||
|
final TraceScope.Continuation continuation = scope.capture();
|
||||||
|
if (continuation != null) {
|
||||||
|
final ContextStore<Channel, ChannelTraceContext> contextStore =
|
||||||
|
InstrumentationContext.get(Channel.class, ChannelTraceContext.class);
|
||||||
|
|
||||||
|
if (contextStore
|
||||||
|
.putIfAbsent(channel, ChannelTraceContext.Factory.INSTANCE)
|
||||||
|
.getConnectionContinuation()
|
||||||
|
!= null) {
|
||||||
|
continuation.close();
|
||||||
|
} else {
|
||||||
|
contextStore.get(channel).setConnectionContinuation(continuation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,207 @@
|
||||||
|
package datadog.trace.instrumentation.netty38;
|
||||||
|
|
||||||
|
import static datadog.trace.agent.tooling.ClassLoaderMatcher.hasClassesNamed;
|
||||||
|
import static datadog.trace.agent.tooling.bytebuddy.matcher.DDElementMatchers.implementsInterface;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import datadog.trace.agent.tooling.Instrumenter;
|
||||||
|
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
|
||||||
|
import datadog.trace.bootstrap.ContextStore;
|
||||||
|
import datadog.trace.bootstrap.InstrumentationContext;
|
||||||
|
import datadog.trace.instrumentation.netty38.client.HttpClientRequestTracingHandler;
|
||||||
|
import datadog.trace.instrumentation.netty38.client.HttpClientResponseTracingHandler;
|
||||||
|
import datadog.trace.instrumentation.netty38.client.HttpClientTracingHandler;
|
||||||
|
import datadog.trace.instrumentation.netty38.server.HttpServerRequestTracingHandler;
|
||||||
|
import datadog.trace.instrumentation.netty38.server.HttpServerResponseTracingHandler;
|
||||||
|
import datadog.trace.instrumentation.netty38.server.HttpServerTracingHandler;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
import net.bytebuddy.description.method.MethodDescription;
|
||||||
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
|
import org.jboss.netty.channel.Channel;
|
||||||
|
import org.jboss.netty.channel.ChannelHandler;
|
||||||
|
import org.jboss.netty.channel.ChannelPipeline;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpClientCodec;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpRequestEncoder;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpServerCodec;
|
||||||
|
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public class NettyChannelPipelineInstrumentation extends Instrumenter.Default {
|
||||||
|
|
||||||
|
static final String INSTRUMENTATION_NAME = "netty";
|
||||||
|
static final String[] ADDITIONAL_INSTRUMENTATION_NAMES = {"netty-3.9"};
|
||||||
|
|
||||||
|
public NettyChannelPipelineInstrumentation() {
|
||||||
|
super(INSTRUMENTATION_NAME, ADDITIONAL_INSTRUMENTATION_NAMES);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<ClassLoader> classLoaderMatcher() {
|
||||||
|
// Optimization for expensive typeMatcher.
|
||||||
|
return hasClassesNamed("org.jboss.netty.channel.ChannelPipeline");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||||
|
return implementsInterface(named("org.jboss.netty.channel.ChannelPipeline"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] helperClassNames() {
|
||||||
|
return new String[] {
|
||||||
|
packageName + ".AbstractNettyAdvice",
|
||||||
|
packageName + ".ChannelTraceContext",
|
||||||
|
packageName + ".ChannelTraceContext$Factory",
|
||||||
|
NettyChannelPipelineInstrumentation.class.getName() + "$ChannelPipelineAdviceUtil",
|
||||||
|
// Util
|
||||||
|
packageName + ".util.CombinedSimpleChannelHandler",
|
||||||
|
// client helpers
|
||||||
|
packageName + ".client.NettyHttpClientDecorator",
|
||||||
|
packageName + ".client.NettyResponseInjectAdapter",
|
||||||
|
packageName + ".client.HttpClientRequestTracingHandler",
|
||||||
|
packageName + ".client.HttpClientResponseTracingHandler",
|
||||||
|
packageName + ".client.HttpClientTracingHandler",
|
||||||
|
// server helpers
|
||||||
|
packageName + ".server.NettyHttpServerDecorator",
|
||||||
|
packageName + ".server.NettyRequestExtractAdapter",
|
||||||
|
packageName + ".server.HttpServerRequestTracingHandler",
|
||||||
|
packageName + ".server.HttpServerResponseTracingHandler",
|
||||||
|
packageName + ".server.HttpServerTracingHandler"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||||
|
final Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>();
|
||||||
|
transformers.put(
|
||||||
|
isMethod()
|
||||||
|
.and(nameStartsWith("add"))
|
||||||
|
.and(takesArgument(1, named("org.jboss.netty.channel.ChannelHandler"))),
|
||||||
|
NettyChannelPipelineInstrumentation.class.getName() + "$ChannelPipelineAdd2ArgsAdvice");
|
||||||
|
transformers.put(
|
||||||
|
isMethod()
|
||||||
|
.and(nameStartsWith("add"))
|
||||||
|
.and(takesArgument(2, named("org.jboss.netty.channel.ChannelHandler"))),
|
||||||
|
NettyChannelPipelineInstrumentation.class.getName() + "$ChannelPipelineAdd3ArgsAdvice");
|
||||||
|
return transformers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> contextStore() {
|
||||||
|
return Collections.singletonMap(
|
||||||
|
"org.jboss.netty.channel.Channel", ChannelTraceContext.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When certain handlers are added to the pipeline, we want to add our corresponding tracing
|
||||||
|
* handlers. If those handlers are later removed, we may want to remove our handlers. That is not
|
||||||
|
* currently implemented.
|
||||||
|
*/
|
||||||
|
public static class ChannelPipelineAdviceUtil {
|
||||||
|
public static void wrapHandler(
|
||||||
|
final ContextStore<Channel, ChannelTraceContext> contextStore,
|
||||||
|
final ChannelPipeline pipeline,
|
||||||
|
final ChannelHandler handler) {
|
||||||
|
try {
|
||||||
|
// Server pipeline handlers
|
||||||
|
if (handler instanceof HttpServerCodec) {
|
||||||
|
pipeline.addLast(
|
||||||
|
HttpServerTracingHandler.class.getName(), new HttpServerTracingHandler(contextStore));
|
||||||
|
} else if (handler instanceof HttpRequestDecoder) {
|
||||||
|
pipeline.addLast(
|
||||||
|
HttpServerRequestTracingHandler.class.getName(),
|
||||||
|
new HttpServerRequestTracingHandler(contextStore));
|
||||||
|
} else if (handler instanceof HttpResponseEncoder) {
|
||||||
|
pipeline.addLast(
|
||||||
|
HttpServerResponseTracingHandler.class.getName(),
|
||||||
|
new HttpServerResponseTracingHandler(contextStore));
|
||||||
|
} else
|
||||||
|
// Client pipeline handlers
|
||||||
|
if (handler instanceof HttpClientCodec) {
|
||||||
|
pipeline.addLast(
|
||||||
|
HttpClientTracingHandler.class.getName(), new HttpClientTracingHandler(contextStore));
|
||||||
|
} else if (handler instanceof HttpRequestEncoder) {
|
||||||
|
pipeline.addLast(
|
||||||
|
HttpClientRequestTracingHandler.class.getName(),
|
||||||
|
new HttpClientRequestTracingHandler(contextStore));
|
||||||
|
} else if (handler instanceof HttpResponseDecoder) {
|
||||||
|
pipeline.addLast(
|
||||||
|
HttpClientResponseTracingHandler.class.getName(),
|
||||||
|
new HttpClientResponseTracingHandler(contextStore));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
CallDepthThreadLocalMap.reset(ChannelPipeline.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ChannelPipelineAdd2ArgsAdvice extends AbstractNettyAdvice {
|
||||||
|
@Advice.OnMethodEnter
|
||||||
|
public static int checkDepth(
|
||||||
|
@Advice.This final ChannelPipeline pipeline,
|
||||||
|
@Advice.Argument(1) final ChannelHandler handler) {
|
||||||
|
// Pipelines are created once as a factory and then copied multiple times using the same add
|
||||||
|
// methods as we are hooking. If our handler has already been added we need to remove it so we
|
||||||
|
// don't end up with duplicates (this throws an exception)
|
||||||
|
if (pipeline.get(handler.getClass().getName()) != null) {
|
||||||
|
pipeline.remove(handler.getClass().getName());
|
||||||
|
}
|
||||||
|
return CallDepthThreadLocalMap.incrementCallDepth(ChannelPipeline.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||||
|
public static void addHandler(
|
||||||
|
@Advice.Enter final int depth,
|
||||||
|
@Advice.This final ChannelPipeline pipeline,
|
||||||
|
@Advice.Argument(1) final ChannelHandler handler) {
|
||||||
|
if (depth > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ContextStore<Channel, ChannelTraceContext> contextStore =
|
||||||
|
InstrumentationContext.get(Channel.class, ChannelTraceContext.class);
|
||||||
|
|
||||||
|
ChannelPipelineAdviceUtil.wrapHandler(contextStore, pipeline, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ChannelPipelineAdd3ArgsAdvice extends AbstractNettyAdvice {
|
||||||
|
@Advice.OnMethodEnter
|
||||||
|
public static int checkDepth(
|
||||||
|
@Advice.This final ChannelPipeline pipeline,
|
||||||
|
@Advice.Argument(2) final ChannelHandler handler) {
|
||||||
|
// Pipelines are created once as a factory and then copied multiple times using the same add
|
||||||
|
// methods as we are hooking. If our handler has already been added we need to remove it so we
|
||||||
|
// don't end up with duplicates (this throws an exception)
|
||||||
|
if (pipeline.get(handler.getClass().getName()) != null) {
|
||||||
|
pipeline.remove(handler.getClass().getName());
|
||||||
|
}
|
||||||
|
return CallDepthThreadLocalMap.incrementCallDepth(ChannelPipeline.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||||
|
public static void addHandler(
|
||||||
|
@Advice.Enter final int depth,
|
||||||
|
@Advice.This final ChannelPipeline pipeline,
|
||||||
|
@Advice.Argument(2) final ChannelHandler handler) {
|
||||||
|
if (depth > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ContextStore<Channel, ChannelTraceContext> contextStore =
|
||||||
|
InstrumentationContext.get(Channel.class, ChannelTraceContext.class);
|
||||||
|
|
||||||
|
ChannelPipelineAdviceUtil.wrapHandler(contextStore, pipeline, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package datadog.trace.instrumentation.netty38.client;
|
||||||
|
|
||||||
|
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
|
||||||
|
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan;
|
||||||
|
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.propagate;
|
||||||
|
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
|
||||||
|
import static datadog.trace.instrumentation.netty38.client.NettyHttpClientDecorator.DECORATE;
|
||||||
|
import static datadog.trace.instrumentation.netty38.client.NettyResponseInjectAdapter.SETTER;
|
||||||
|
|
||||||
|
import datadog.trace.bootstrap.ContextStore;
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
|
||||||
|
import datadog.trace.context.TraceScope;
|
||||||
|
import datadog.trace.instrumentation.netty38.ChannelTraceContext;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jboss.netty.channel.Channel;
|
||||||
|
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||||
|
import org.jboss.netty.channel.MessageEvent;
|
||||||
|
import org.jboss.netty.channel.SimpleChannelDownstreamHandler;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpRequest;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class HttpClientRequestTracingHandler extends SimpleChannelDownstreamHandler {
|
||||||
|
|
||||||
|
private final ContextStore<Channel, ChannelTraceContext> contextStore;
|
||||||
|
|
||||||
|
public HttpClientRequestTracingHandler(
|
||||||
|
final ContextStore<Channel, ChannelTraceContext> contextStore) {
|
||||||
|
this.contextStore = contextStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeRequested(final ChannelHandlerContext ctx, final MessageEvent msg)
|
||||||
|
throws Exception {
|
||||||
|
if (!(msg.getMessage() instanceof HttpRequest)) {
|
||||||
|
ctx.sendDownstream(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ChannelTraceContext channelTraceContext =
|
||||||
|
contextStore.putIfAbsent(ctx.getChannel(), ChannelTraceContext.Factory.INSTANCE);
|
||||||
|
|
||||||
|
TraceScope parentScope = null;
|
||||||
|
final TraceScope.Continuation continuation = channelTraceContext.getConnectionContinuation();
|
||||||
|
if (continuation != null) {
|
||||||
|
parentScope = continuation.activate();
|
||||||
|
channelTraceContext.setConnectionContinuation(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
final HttpRequest request = (HttpRequest) msg.getMessage();
|
||||||
|
|
||||||
|
channelTraceContext.setClientParentSpan(activeSpan());
|
||||||
|
|
||||||
|
final AgentSpan span = startSpan("netty.client.request");
|
||||||
|
try (final AgentScope scope = activateSpan(span, false)) {
|
||||||
|
DECORATE.afterStart(span);
|
||||||
|
DECORATE.onRequest(span, request);
|
||||||
|
DECORATE.onPeerConnection(span, (InetSocketAddress) ctx.getChannel().getRemoteAddress());
|
||||||
|
|
||||||
|
propagate().inject(span, request.headers(), SETTER);
|
||||||
|
|
||||||
|
channelTraceContext.setClientSpan(span);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ctx.sendDownstream(msg);
|
||||||
|
} catch (final Throwable throwable) {
|
||||||
|
DECORATE.onError(span, throwable);
|
||||||
|
DECORATE.beforeFinish(span);
|
||||||
|
span.finish();
|
||||||
|
throw throwable;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (parentScope != null) {
|
||||||
|
parentScope.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package datadog.trace.instrumentation.netty38.client;
|
||||||
|
|
||||||
|
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
|
||||||
|
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopSpan;
|
||||||
|
import static datadog.trace.instrumentation.netty38.client.NettyHttpClientDecorator.DECORATE;
|
||||||
|
|
||||||
|
import datadog.trace.bootstrap.ContextStore;
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
|
||||||
|
import datadog.trace.instrumentation.netty38.ChannelTraceContext;
|
||||||
|
import org.jboss.netty.channel.Channel;
|
||||||
|
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||||
|
import org.jboss.netty.channel.MessageEvent;
|
||||||
|
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpResponse;
|
||||||
|
|
||||||
|
public class HttpClientResponseTracingHandler extends SimpleChannelUpstreamHandler {
|
||||||
|
|
||||||
|
private final ContextStore<Channel, ChannelTraceContext> contextStore;
|
||||||
|
|
||||||
|
public HttpClientResponseTracingHandler(
|
||||||
|
final ContextStore<Channel, ChannelTraceContext> contextStore) {
|
||||||
|
this.contextStore = contextStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent msg)
|
||||||
|
throws Exception {
|
||||||
|
final ChannelTraceContext channelTraceContext =
|
||||||
|
contextStore.putIfAbsent(ctx.getChannel(), ChannelTraceContext.Factory.INSTANCE);
|
||||||
|
|
||||||
|
AgentSpan parent = channelTraceContext.getClientParentSpan();
|
||||||
|
if (parent == null) {
|
||||||
|
parent = noopSpan();
|
||||||
|
channelTraceContext.setClientParentSpan(noopSpan());
|
||||||
|
}
|
||||||
|
final AgentSpan span = channelTraceContext.getClientSpan();
|
||||||
|
|
||||||
|
final boolean finishSpan = msg.getMessage() instanceof HttpResponse;
|
||||||
|
|
||||||
|
if (span != null && finishSpan) {
|
||||||
|
try (final AgentScope scope = activateSpan(span, false)) {
|
||||||
|
DECORATE.onResponse(span, (HttpResponse) msg.getMessage());
|
||||||
|
DECORATE.beforeFinish(span);
|
||||||
|
span.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want the callback in the scope of the parent, not the client span
|
||||||
|
try (final AgentScope scope = activateSpan(parent, false)) {
|
||||||
|
scope.setAsyncPropagation(true);
|
||||||
|
ctx.sendUpstream(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package datadog.trace.instrumentation.netty38.client;
|
||||||
|
|
||||||
|
import datadog.trace.bootstrap.ContextStore;
|
||||||
|
import datadog.trace.instrumentation.netty38.ChannelTraceContext;
|
||||||
|
import datadog.trace.instrumentation.netty38.util.CombinedSimpleChannelHandler;
|
||||||
|
import org.jboss.netty.channel.Channel;
|
||||||
|
|
||||||
|
public class HttpClientTracingHandler
|
||||||
|
extends CombinedSimpleChannelHandler<
|
||||||
|
HttpClientResponseTracingHandler, HttpClientRequestTracingHandler> {
|
||||||
|
|
||||||
|
public HttpClientTracingHandler(final ContextStore<Channel, ChannelTraceContext> contextStore) {
|
||||||
|
super(
|
||||||
|
new HttpClientResponseTracingHandler(contextStore),
|
||||||
|
new HttpClientRequestTracingHandler(contextStore));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package datadog.trace.instrumentation.netty38.client;
|
||||||
|
|
||||||
|
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.HOST;
|
||||||
|
|
||||||
|
import datadog.trace.bootstrap.instrumentation.decorator.HttpClientDecorator;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpRequest;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpResponse;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class NettyHttpClientDecorator extends HttpClientDecorator<HttpRequest, HttpResponse> {
|
||||||
|
public static final NettyHttpClientDecorator DECORATE = new NettyHttpClientDecorator();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String[] instrumentationNames() {
|
||||||
|
return new String[] {"netty", "netty-3.9"};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String component() {
|
||||||
|
return "netty-client";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String method(final HttpRequest httpRequest) {
|
||||||
|
return httpRequest.getMethod().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected URI url(final HttpRequest request) throws URISyntaxException {
|
||||||
|
final URI uri = new URI(request.getUri());
|
||||||
|
if ((uri.getHost() == null || uri.getHost().equals("")) && request.headers().contains(HOST)) {
|
||||||
|
return new URI("http://" + request.headers().get(HOST) + request.getUri());
|
||||||
|
} else {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Integer status(final HttpResponse httpResponse) {
|
||||||
|
return httpResponse.getStatus().getCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package datadog.trace.instrumentation.netty38.client;
|
||||||
|
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.AgentPropagation;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpHeaders;
|
||||||
|
|
||||||
|
public class NettyResponseInjectAdapter implements AgentPropagation.Setter<HttpHeaders> {
|
||||||
|
|
||||||
|
public static final NettyResponseInjectAdapter SETTER = new NettyResponseInjectAdapter();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(final HttpHeaders headers, final String key, final String value) {
|
||||||
|
headers.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package datadog.trace.instrumentation.netty38.server;
|
||||||
|
|
||||||
|
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
|
||||||
|
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.propagate;
|
||||||
|
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
|
||||||
|
import static datadog.trace.instrumentation.netty38.server.NettyHttpServerDecorator.DECORATE;
|
||||||
|
import static datadog.trace.instrumentation.netty38.server.NettyRequestExtractAdapter.GETTER;
|
||||||
|
|
||||||
|
import datadog.trace.bootstrap.ContextStore;
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.AgentSpan.Context;
|
||||||
|
import datadog.trace.instrumentation.netty38.ChannelTraceContext;
|
||||||
|
import org.jboss.netty.channel.Channel;
|
||||||
|
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||||
|
import org.jboss.netty.channel.MessageEvent;
|
||||||
|
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpRequest;
|
||||||
|
|
||||||
|
public class HttpServerRequestTracingHandler extends SimpleChannelUpstreamHandler {
|
||||||
|
|
||||||
|
private final ContextStore<Channel, ChannelTraceContext> contextStore;
|
||||||
|
|
||||||
|
public HttpServerRequestTracingHandler(
|
||||||
|
final ContextStore<Channel, ChannelTraceContext> contextStore) {
|
||||||
|
this.contextStore = contextStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent msg)
|
||||||
|
throws Exception {
|
||||||
|
final ChannelTraceContext channelTraceContext =
|
||||||
|
contextStore.putIfAbsent(ctx.getChannel(), ChannelTraceContext.Factory.INSTANCE);
|
||||||
|
|
||||||
|
if (!(msg.getMessage() instanceof HttpRequest)) {
|
||||||
|
final AgentSpan span = channelTraceContext.getServerSpan();
|
||||||
|
if (span == null) {
|
||||||
|
ctx.sendUpstream(msg); // superclass does not throw
|
||||||
|
} else {
|
||||||
|
try (final AgentScope scope = activateSpan(span, false)) {
|
||||||
|
scope.setAsyncPropagation(true);
|
||||||
|
ctx.sendUpstream(msg); // superclass does not throw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final HttpRequest request = (HttpRequest) msg.getMessage();
|
||||||
|
|
||||||
|
final Context context = propagate().extract(request.headers(), GETTER);
|
||||||
|
|
||||||
|
final AgentSpan span = startSpan("netty.request", context);
|
||||||
|
try (final AgentScope scope = activateSpan(span, false)) {
|
||||||
|
DECORATE.afterStart(span);
|
||||||
|
DECORATE.onConnection(span, ctx.getChannel());
|
||||||
|
DECORATE.onRequest(span, request);
|
||||||
|
|
||||||
|
scope.setAsyncPropagation(true);
|
||||||
|
|
||||||
|
channelTraceContext.setServerSpan(span);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ctx.sendUpstream(msg);
|
||||||
|
} catch (final Throwable throwable) {
|
||||||
|
DECORATE.onError(span, throwable);
|
||||||
|
DECORATE.beforeFinish(span);
|
||||||
|
span.finish(); // Finish the span manually since finishSpanOnClose was false
|
||||||
|
throw throwable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package datadog.trace.instrumentation.netty38.server;
|
||||||
|
|
||||||
|
import static datadog.trace.instrumentation.netty38.server.NettyHttpServerDecorator.DECORATE;
|
||||||
|
|
||||||
|
import datadog.trace.bootstrap.ContextStore;
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.Tags;
|
||||||
|
import datadog.trace.instrumentation.netty38.ChannelTraceContext;
|
||||||
|
import org.jboss.netty.channel.Channel;
|
||||||
|
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||||
|
import org.jboss.netty.channel.MessageEvent;
|
||||||
|
import org.jboss.netty.channel.SimpleChannelDownstreamHandler;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpResponse;
|
||||||
|
|
||||||
|
public class HttpServerResponseTracingHandler extends SimpleChannelDownstreamHandler {
|
||||||
|
|
||||||
|
private final ContextStore<Channel, ChannelTraceContext> contextStore;
|
||||||
|
|
||||||
|
public HttpServerResponseTracingHandler(
|
||||||
|
final ContextStore<Channel, ChannelTraceContext> contextStore) {
|
||||||
|
this.contextStore = contextStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeRequested(final ChannelHandlerContext ctx, final MessageEvent msg)
|
||||||
|
throws Exception {
|
||||||
|
final ChannelTraceContext channelTraceContext =
|
||||||
|
contextStore.putIfAbsent(ctx.getChannel(), ChannelTraceContext.Factory.INSTANCE);
|
||||||
|
|
||||||
|
final AgentSpan span = channelTraceContext.getServerSpan();
|
||||||
|
if (span == null || !(msg.getMessage() instanceof HttpResponse)) {
|
||||||
|
ctx.sendDownstream(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final HttpResponse response = (HttpResponse) msg.getMessage();
|
||||||
|
|
||||||
|
try {
|
||||||
|
ctx.sendDownstream(msg);
|
||||||
|
} catch (final Throwable throwable) {
|
||||||
|
DECORATE.onError(span, throwable);
|
||||||
|
span.setTag(Tags.HTTP_STATUS, 500);
|
||||||
|
span.finish(); // Finish the span manually since finishSpanOnClose was false
|
||||||
|
throw throwable;
|
||||||
|
}
|
||||||
|
DECORATE.onResponse(span, response);
|
||||||
|
DECORATE.beforeFinish(span);
|
||||||
|
span.finish(); // Finish the span manually since finishSpanOnClose was false
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package datadog.trace.instrumentation.netty38.server;
|
||||||
|
|
||||||
|
import datadog.trace.bootstrap.ContextStore;
|
||||||
|
import datadog.trace.instrumentation.netty38.ChannelTraceContext;
|
||||||
|
import datadog.trace.instrumentation.netty38.util.CombinedSimpleChannelHandler;
|
||||||
|
import org.jboss.netty.channel.Channel;
|
||||||
|
|
||||||
|
public class HttpServerTracingHandler
|
||||||
|
extends CombinedSimpleChannelHandler<
|
||||||
|
HttpServerRequestTracingHandler, HttpServerResponseTracingHandler> {
|
||||||
|
|
||||||
|
public HttpServerTracingHandler(final ContextStore<Channel, ChannelTraceContext> contextStore) {
|
||||||
|
super(
|
||||||
|
new HttpServerRequestTracingHandler(contextStore),
|
||||||
|
new HttpServerResponseTracingHandler(contextStore));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package datadog.trace.instrumentation.netty38.server;
|
||||||
|
|
||||||
|
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.HOST;
|
||||||
|
|
||||||
|
import datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jboss.netty.channel.Channel;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpRequest;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpResponse;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class NettyHttpServerDecorator
|
||||||
|
extends HttpServerDecorator<HttpRequest, Channel, HttpResponse> {
|
||||||
|
public static final NettyHttpServerDecorator DECORATE = new NettyHttpServerDecorator();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String[] instrumentationNames() {
|
||||||
|
return new String[] {"netty", "netty-3.9"};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String component() {
|
||||||
|
return "netty";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String method(final HttpRequest httpRequest) {
|
||||||
|
return httpRequest.getMethod().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected URI url(final HttpRequest request) throws URISyntaxException {
|
||||||
|
final URI uri = new URI(request.getUri());
|
||||||
|
if ((uri.getHost() == null || uri.getHost().equals("")) && request.headers().contains(HOST)) {
|
||||||
|
return new URI("http://" + request.headers().get(HOST) + request.getUri());
|
||||||
|
} else {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String peerHostIP(final Channel channel) {
|
||||||
|
final SocketAddress socketAddress = channel.getRemoteAddress();
|
||||||
|
if (socketAddress instanceof InetSocketAddress) {
|
||||||
|
return ((InetSocketAddress) socketAddress).getAddress().getHostAddress();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Integer peerPort(final Channel channel) {
|
||||||
|
final SocketAddress socketAddress = channel.getRemoteAddress();
|
||||||
|
if (socketAddress instanceof InetSocketAddress) {
|
||||||
|
return ((InetSocketAddress) socketAddress).getPort();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Integer status(final HttpResponse httpResponse) {
|
||||||
|
return httpResponse.getStatus().getCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package datadog.trace.instrumentation.netty38.server;
|
||||||
|
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.AgentPropagation;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpHeaders;
|
||||||
|
|
||||||
|
public class NettyRequestExtractAdapter implements AgentPropagation.Getter<HttpHeaders> {
|
||||||
|
|
||||||
|
public static final NettyRequestExtractAdapter GETTER = new NettyRequestExtractAdapter();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<String> keys(final HttpHeaders headers) {
|
||||||
|
return headers.names();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get(final HttpHeaders headers, final String key) {
|
||||||
|
return headers.get(key);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
package datadog.trace.instrumentation.netty38.util;
|
||||||
|
|
||||||
|
import org.jboss.netty.channel.ChannelEvent;
|
||||||
|
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||||
|
import org.jboss.netty.channel.ChannelStateEvent;
|
||||||
|
import org.jboss.netty.channel.ChildChannelStateEvent;
|
||||||
|
import org.jboss.netty.channel.ExceptionEvent;
|
||||||
|
import org.jboss.netty.channel.MessageEvent;
|
||||||
|
import org.jboss.netty.channel.SimpleChannelDownstreamHandler;
|
||||||
|
import org.jboss.netty.channel.SimpleChannelHandler;
|
||||||
|
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
|
||||||
|
import org.jboss.netty.channel.WriteCompletionEvent;
|
||||||
|
|
||||||
|
public class CombinedSimpleChannelHandler<
|
||||||
|
Upstream extends SimpleChannelUpstreamHandler,
|
||||||
|
Downstream extends SimpleChannelDownstreamHandler>
|
||||||
|
extends SimpleChannelHandler {
|
||||||
|
|
||||||
|
private final Upstream upstream;
|
||||||
|
private final Downstream downstream;
|
||||||
|
|
||||||
|
public CombinedSimpleChannelHandler(final Upstream upstream, final Downstream downstream) {
|
||||||
|
this.upstream = upstream;
|
||||||
|
this.downstream = downstream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleUpstream(final ChannelHandlerContext ctx, final ChannelEvent e)
|
||||||
|
throws Exception {
|
||||||
|
upstream.handleUpstream(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent e)
|
||||||
|
throws Exception {
|
||||||
|
upstream.messageReceived(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(final ChannelHandlerContext ctx, final ExceptionEvent e)
|
||||||
|
throws Exception {
|
||||||
|
upstream.exceptionCaught(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelOpen(final ChannelHandlerContext ctx, final ChannelStateEvent e)
|
||||||
|
throws Exception {
|
||||||
|
upstream.channelOpen(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelBound(final ChannelHandlerContext ctx, final ChannelStateEvent e)
|
||||||
|
throws Exception {
|
||||||
|
upstream.channelBound(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelConnected(final ChannelHandlerContext ctx, final ChannelStateEvent e)
|
||||||
|
throws Exception {
|
||||||
|
upstream.channelConnected(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInterestChanged(final ChannelHandlerContext ctx, final ChannelStateEvent e)
|
||||||
|
throws Exception {
|
||||||
|
upstream.channelInterestChanged(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelDisconnected(final ChannelHandlerContext ctx, final ChannelStateEvent e)
|
||||||
|
throws Exception {
|
||||||
|
upstream.channelDisconnected(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelUnbound(final ChannelHandlerContext ctx, final ChannelStateEvent e)
|
||||||
|
throws Exception {
|
||||||
|
upstream.channelUnbound(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelClosed(final ChannelHandlerContext ctx, final ChannelStateEvent e)
|
||||||
|
throws Exception {
|
||||||
|
upstream.channelClosed(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeComplete(final ChannelHandlerContext ctx, final WriteCompletionEvent e)
|
||||||
|
throws Exception {
|
||||||
|
upstream.writeComplete(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void childChannelOpen(final ChannelHandlerContext ctx, final ChildChannelStateEvent e)
|
||||||
|
throws Exception {
|
||||||
|
upstream.childChannelOpen(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void childChannelClosed(final ChannelHandlerContext ctx, final ChildChannelStateEvent e)
|
||||||
|
throws Exception {
|
||||||
|
upstream.childChannelClosed(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleDownstream(final ChannelHandlerContext ctx, final ChannelEvent e)
|
||||||
|
throws Exception {
|
||||||
|
downstream.handleDownstream(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeRequested(final ChannelHandlerContext ctx, final MessageEvent e)
|
||||||
|
throws Exception {
|
||||||
|
downstream.writeRequested(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindRequested(final ChannelHandlerContext ctx, final ChannelStateEvent e)
|
||||||
|
throws Exception {
|
||||||
|
downstream.bindRequested(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connectRequested(final ChannelHandlerContext ctx, final ChannelStateEvent e)
|
||||||
|
throws Exception {
|
||||||
|
downstream.connectRequested(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInterestOpsRequested(final ChannelHandlerContext ctx, final ChannelStateEvent e)
|
||||||
|
throws Exception {
|
||||||
|
downstream.setInterestOpsRequested(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnectRequested(final ChannelHandlerContext ctx, final ChannelStateEvent e)
|
||||||
|
throws Exception {
|
||||||
|
downstream.disconnectRequested(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unbindRequested(final ChannelHandlerContext ctx, final ChannelStateEvent e)
|
||||||
|
throws Exception {
|
||||||
|
downstream.unbindRequested(ctx, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeRequested(final ChannelHandlerContext ctx, final ChannelStateEvent e)
|
||||||
|
throws Exception {
|
||||||
|
downstream.closeRequested(ctx, e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
import com.ning.http.client.AsyncCompletionHandler
|
||||||
|
import com.ning.http.client.AsyncHttpClient
|
||||||
|
import com.ning.http.client.AsyncHttpClientConfig
|
||||||
|
import com.ning.http.client.Response
|
||||||
|
import datadog.trace.agent.test.base.HttpClientTest
|
||||||
|
import datadog.trace.bootstrap.instrumentation.api.Tags
|
||||||
|
import datadog.trace.instrumentation.netty38.client.NettyHttpClientDecorator
|
||||||
|
import spock.lang.AutoCleanup
|
||||||
|
import spock.lang.Shared
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutionException
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
import static datadog.trace.agent.test.utils.PortUtils.UNUSABLE_PORT
|
||||||
|
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
|
||||||
|
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
|
||||||
|
|
||||||
|
class Netty38ClientTest extends HttpClientTest {
|
||||||
|
|
||||||
|
@Shared
|
||||||
|
def clientConfig = new AsyncHttpClientConfig.Builder()
|
||||||
|
.setRequestTimeoutInMs(TimeUnit.SECONDS.toMillis(10).toInteger())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@Shared
|
||||||
|
@AutoCleanup
|
||||||
|
AsyncHttpClient asyncHttpClient = new AsyncHttpClient(clientConfig)
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
|
||||||
|
def methodName = "prepare" + method.toLowerCase().capitalize()
|
||||||
|
def requestBuilder = asyncHttpClient."$methodName"(uri.toString())
|
||||||
|
headers.each { requestBuilder.setHeader(it.key, it.value) }
|
||||||
|
def response = requestBuilder.execute(new AsyncCompletionHandler() {
|
||||||
|
@Override
|
||||||
|
Object onCompleted(Response response) throws Exception {
|
||||||
|
callback?.call()
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}).get()
|
||||||
|
blockUntilChildSpansFinished(1)
|
||||||
|
return response.statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String component() {
|
||||||
|
return NettyHttpClientDecorator.DECORATE.component()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String expectedOperationName() {
|
||||||
|
return "netty.client.request"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean testRedirects() {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean testConnectionFailure() {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
def "connection error (unopened port)"() {
|
||||||
|
given:
|
||||||
|
def uri = new URI("http://localhost:$UNUSABLE_PORT/")
|
||||||
|
|
||||||
|
when:
|
||||||
|
runUnderTrace("parent") {
|
||||||
|
doRequest(method, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
then:
|
||||||
|
def ex = thrown(Exception)
|
||||||
|
def thrownException = ex instanceof ExecutionException ? ex.cause : ex
|
||||||
|
|
||||||
|
and:
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 2) {
|
||||||
|
basicSpan(it, 0, "parent", null, thrownException)
|
||||||
|
|
||||||
|
span(1) {
|
||||||
|
operationName "netty.connect"
|
||||||
|
resourceName "netty.connect"
|
||||||
|
childOf span(0)
|
||||||
|
errored true
|
||||||
|
tags {
|
||||||
|
"$Tags.COMPONENT" "netty"
|
||||||
|
Class errorClass = ConnectException
|
||||||
|
try {
|
||||||
|
errorClass = Class.forName('io.netty.channel.AbstractChannel$AnnotatedConnectException')
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
// Older versions use 'java.net.ConnectException' and do not have 'io.netty.channel.AbstractChannel$AnnotatedConnectException'
|
||||||
|
}
|
||||||
|
errorTags errorClass, "Connection refused: localhost/127.0.0.1:$UNUSABLE_PORT"
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
where:
|
||||||
|
method = "GET"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
import datadog.trace.agent.test.base.HttpServerTest
|
||||||
|
import datadog.trace.instrumentation.netty38.server.NettyHttpServerDecorator
|
||||||
|
import org.jboss.netty.bootstrap.ServerBootstrap
|
||||||
|
import org.jboss.netty.buffer.ChannelBuffer
|
||||||
|
import org.jboss.netty.buffer.ChannelBuffers
|
||||||
|
import org.jboss.netty.channel.*
|
||||||
|
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory
|
||||||
|
import org.jboss.netty.handler.codec.http.*
|
||||||
|
import org.jboss.netty.handler.logging.LoggingHandler
|
||||||
|
import org.jboss.netty.logging.InternalLogLevel
|
||||||
|
import org.jboss.netty.util.CharsetUtil
|
||||||
|
|
||||||
|
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.*
|
||||||
|
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*
|
||||||
|
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1
|
||||||
|
|
||||||
|
class Netty38ServerTest extends HttpServerTest<Channel> {
|
||||||
|
|
||||||
|
ChannelPipeline channelPipeline() {
|
||||||
|
ChannelPipeline channelPipeline = new DefaultChannelPipeline()
|
||||||
|
|
||||||
|
channelPipeline.addLast("http-codec", new HttpServerCodec())
|
||||||
|
channelPipeline.addLast("controller", new SimpleChannelHandler() {
|
||||||
|
@Override
|
||||||
|
void messageReceived(ChannelHandlerContext ctx, MessageEvent msg) throws Exception {
|
||||||
|
if (msg.getMessage() instanceof HttpRequest) {
|
||||||
|
def uri = URI.create((msg.getMessage() as HttpRequest).getUri())
|
||||||
|
HttpServerTest.ServerEndpoint endpoint = forPath(uri.path)
|
||||||
|
ctx.sendDownstream controller(endpoint) {
|
||||||
|
HttpResponse response
|
||||||
|
ChannelBuffer responseContent = null
|
||||||
|
switch (endpoint) {
|
||||||
|
case SUCCESS:
|
||||||
|
case ERROR:
|
||||||
|
responseContent = ChannelBuffers.copiedBuffer(endpoint.body, CharsetUtil.UTF_8)
|
||||||
|
response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status))
|
||||||
|
response.setContent(responseContent)
|
||||||
|
break
|
||||||
|
case QUERY_PARAM:
|
||||||
|
responseContent = ChannelBuffers.copiedBuffer(uri.query, CharsetUtil.UTF_8)
|
||||||
|
response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status))
|
||||||
|
response.setContent(responseContent)
|
||||||
|
break
|
||||||
|
case REDIRECT:
|
||||||
|
response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status))
|
||||||
|
response.headers().set(LOCATION, endpoint.body)
|
||||||
|
break
|
||||||
|
case EXCEPTION:
|
||||||
|
throw new Exception(endpoint.body)
|
||||||
|
default:
|
||||||
|
responseContent = ChannelBuffers.copiedBuffer(NOT_FOUND.body, CharsetUtil.UTF_8)
|
||||||
|
response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status))
|
||||||
|
response.setContent(responseContent)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
response.headers().set(CONTENT_TYPE, "text/plain")
|
||||||
|
if (responseContent) {
|
||||||
|
response.headers().set(CONTENT_LENGTH, responseContent.readableBytes())
|
||||||
|
}
|
||||||
|
return new DownstreamMessageEvent(
|
||||||
|
ctx.getChannel(),
|
||||||
|
new SucceededChannelFuture(ctx.getChannel()),
|
||||||
|
response,
|
||||||
|
ctx.getChannel().getRemoteAddress())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent ex) throws Exception {
|
||||||
|
ChannelBuffer buffer = ChannelBuffers.copiedBuffer(ex.getCause().getMessage(), CharsetUtil.UTF_8)
|
||||||
|
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
response.setContent(buffer)
|
||||||
|
response.headers().set(CONTENT_TYPE, "text/plain")
|
||||||
|
response.headers().set(CONTENT_LENGTH, buffer.readableBytes())
|
||||||
|
ctx.sendDownstream(new DownstreamMessageEvent(
|
||||||
|
ctx.getChannel(),
|
||||||
|
new FailedChannelFuture(ctx.getChannel(), ex.getCause()),
|
||||||
|
response,
|
||||||
|
ctx.getChannel().getRemoteAddress()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return channelPipeline
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Channel startServer(int port) {
|
||||||
|
ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory())
|
||||||
|
bootstrap.setParentHandler(new LoggingHandler(InternalLogLevel.INFO))
|
||||||
|
bootstrap.setPipeline(channelPipeline())
|
||||||
|
|
||||||
|
InetSocketAddress address = new InetSocketAddress(port)
|
||||||
|
return bootstrap.bind(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void stopServer(Channel server) {
|
||||||
|
server?.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String component() {
|
||||||
|
NettyHttpServerDecorator.DECORATE.component()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String expectedOperationName() {
|
||||||
|
"netty.request"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import datadog.trace.agent.test.base.HttpServerTestAdvice;
|
||||||
|
import datadog.trace.agent.tooling.Instrumenter;
|
||||||
|
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||||
|
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public class NettyServerTestInstrumentation implements Instrumenter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AgentBuilder instrument(final AgentBuilder agentBuilder) {
|
||||||
|
return agentBuilder
|
||||||
|
.type(named("org.jboss.netty.handler.codec.http.HttpRequestDecoder"))
|
||||||
|
.transform(
|
||||||
|
new AgentBuilder.Transformer.ForAdvice()
|
||||||
|
.advice(
|
||||||
|
named("createMessage"),
|
||||||
|
HttpServerTestAdvice.ServerEntryAdvice.class.getName()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,10 +60,10 @@ public class HttpClientRequestTracingHandler extends ChannelOutboundHandlerAdapt
|
||||||
span.finish();
|
span.finish();
|
||||||
throw throwable;
|
throw throwable;
|
||||||
}
|
}
|
||||||
}
|
} finally {
|
||||||
|
|
||||||
if (null != parentScope) {
|
if (null != parentScope) {
|
||||||
parentScope.close();
|
parentScope.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,10 +60,10 @@ public class HttpClientRequestTracingHandler extends ChannelOutboundHandlerAdapt
|
||||||
span.finish();
|
span.finish();
|
||||||
throw throwable;
|
throw throwable;
|
||||||
}
|
}
|
||||||
}
|
} finally {
|
||||||
|
|
||||||
if (null != parentScope) {
|
if (null != parentScope) {
|
||||||
parentScope.close();
|
parentScope.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,7 @@ include ':dd-java-agent:instrumentation:log4j2'
|
||||||
include ':dd-java-agent:instrumentation:mongo'
|
include ':dd-java-agent:instrumentation:mongo'
|
||||||
include ':dd-java-agent:instrumentation:mongo:driver-3.1'
|
include ':dd-java-agent:instrumentation:mongo:driver-3.1'
|
||||||
include ':dd-java-agent:instrumentation:mongo:driver-async-3.3'
|
include ':dd-java-agent:instrumentation:mongo:driver-async-3.3'
|
||||||
|
include ':dd-java-agent:instrumentation:netty-3.8'
|
||||||
include ':dd-java-agent:instrumentation:netty-4.0'
|
include ':dd-java-agent:instrumentation:netty-4.0'
|
||||||
include ':dd-java-agent:instrumentation:netty-4.1'
|
include ':dd-java-agent:instrumentation:netty-4.1'
|
||||||
include ':dd-java-agent:instrumentation:okhttp-3'
|
include ':dd-java-agent:instrumentation:okhttp-3'
|
||||||
|
|
Loading…
Reference in New Issue