Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion dd-java-agent/instrumentation/instrumentation.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ plugins {
apply from: "${rootDir}/gradle/java.gradle"

Project instr_project = project
subprojects { subProj ->
subprojects {Project subProj ->
apply plugin: "net.bytebuddy.byte-buddy"
apply plugin: 'muzzle'

Expand All @@ -37,6 +37,19 @@ subprojects { subProj ->
}
}

dependencies {
// Apply common dependencies for instrumentation.
compile project(':dd-java-agent:agent-tooling')
compile deps.bytebuddy
compile deps.opentracing
annotationProcessor deps.autoservice
implementation deps.autoservice

testCompile project(':dd-java-agent:testing')
testAnnotationProcessor deps.autoservice
testImplementation deps.autoservice
}

// Make it so all instrumentation subproject tests can be run with a single command.
instr_project.tasks.test.dependsOn(subProj.tasks.test)
}
Expand Down
8 changes: 0 additions & 8 deletions dd-java-agent/instrumentation/netty-4.0/netty-4.0.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,6 @@ testSets {
dependencies {
compileOnly group: 'io.netty', name: 'netty-codec-http', version: '4.0.0.Final'

compile project(':dd-java-agent:agent-tooling')

compile deps.bytebuddy
compile deps.opentracing
annotationProcessor deps.autoservice
implementation deps.autoservice

testCompile project(':dd-java-agent:testing')
testCompile project(':dd-java-agent:instrumentation:java-concurrent')

testCompile group: 'io.netty', name: 'netty-codec-http', version: '4.0.0.Final'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,36 @@
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.SpanContext;
import io.opentracing.Tracer;
import io.opentracing.propagation.Format;
import io.opentracing.util.GlobalTracer;

public class HttpServerRequestTracingHandler extends ChannelInboundHandlerAdapter {

@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
final Tracer tracer = GlobalTracer.get();

if (!(msg instanceof HttpRequest)) {
ctx.fireChannelRead(msg); // superclass does not throw
final Span span = ctx.channel().attr(AttributeKeys.SERVER_ATTRIBUTE_KEY).get();
if (span == null) {
ctx.fireChannelRead(msg); // superclass does not throw
} else {
try (final Scope scope = tracer.scopeManager().activate(span, false)) {
ctx.fireChannelRead(msg); // superclass does not throw
}
}
return;
}

final HttpRequest request = (HttpRequest) msg;

final SpanContext extractedContext =
GlobalTracer.get()
.extract(Format.Builtin.HTTP_HEADERS, new NettyRequestExtractAdapter(request));
tracer.extract(Format.Builtin.HTTP_HEADERS, new NettyRequestExtractAdapter(request));

final Span span =
GlobalTracer.get().buildSpan("netty.request").asChildOf(extractedContext).start();
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
tracer.buildSpan("netty.request").asChildOf(extractedContext).ignoreActiveSpan().start();
try (final Scope scope = tracer.scopeManager().activate(span, false)) {
DECORATE.afterStart(span);
DECORATE.onConnection(span, ctx.channel());
DECORATE.onRequest(span, request);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.utils.OkHttpUtils
import datadog.trace.agent.test.utils.PortUtils
import datadog.trace.api.DDSpanTypes
import datadog.trace.agent.test.base.HttpServerTest
import datadog.trace.instrumentation.netty40.server.NettyHttpServerDecorator
import io.netty.bootstrap.ServerBootstrap
import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInitializer
import io.netty.channel.ChannelPipeline
import io.netty.channel.EventLoopGroup
Expand All @@ -13,153 +12,99 @@ import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.nio.NioServerSocketChannel
import io.netty.handler.codec.http.DefaultFullHttpResponse
import io.netty.handler.codec.http.FullHttpResponse
import io.netty.handler.codec.http.HttpRequest
import io.netty.handler.codec.http.HttpRequestDecoder
import io.netty.handler.codec.http.HttpResponseEncoder
import io.netty.handler.codec.http.HttpResponseStatus
import io.netty.handler.codec.http.HttpServerCodec
import io.netty.handler.codec.http.HttpVersion
import io.netty.handler.codec.http.LastHttpContent
import io.netty.handler.logging.LogLevel
import io.netty.handler.logging.LoggingHandler
import io.netty.util.CharsetUtil
import io.opentracing.tag.Tags
import okhttp3.OkHttpClient
import okhttp3.Request
import spock.lang.Shared

import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1

class Netty40ServerTest extends AgentTestRunner {

class Netty40ServerTest extends HttpServerTest<NettyHttpServerDecorator> {
@Shared
OkHttpClient client = OkHttpUtils.client()

def "test server request/response"() {
setup:
EventLoopGroup eventLoopGroup = new NioEventLoopGroup()
int port = PortUtils.randomOpenPort()
initializeServer(eventLoopGroup, port, handlers, HttpResponseStatus.OK)

def request = new Request.Builder()
.url("http://localhost:$port/")
.header("x-datadog-trace-id", "123")
.header("x-datadog-parent-id", "456")
.get()
.build()
def response = client.newCall(request).execute()

expect:
response.code() == 200
response.body().string() == "Hello World"

and:
assertTraces(1) {
trace(0, 1) {
span(0) {
traceId "123"
parentId "456"
serviceName "unnamed-java-app"
operationName "netty.request"
resourceName "GET /"
spanType DDSpanTypes.HTTP_SERVER
errored false
tags {
"$Tags.COMPONENT.key" "netty"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "http://localhost:$port/"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
defaultTags(true)
}
}
}
}
EventLoopGroup eventLoopGroup

cleanup:
eventLoopGroup.shutdownGracefully()
@Override
void startServer(int port) {
// def handlers = [new HttpServerCodec()]
def handlers = [new HttpRequestDecoder(), new HttpResponseEncoder()]
eventLoopGroup = new NioEventLoopGroup()

where:
handlers | _
[new HttpServerCodec()] | _
[new HttpRequestDecoder(), new HttpResponseEncoder()] | _
}

def "test #responseCode response handling"() {
setup:
EventLoopGroup eventLoopGroup = new NioEventLoopGroup()
int port = PortUtils.randomOpenPort()
initializeServer(eventLoopGroup, port, new HttpServerCodec(), responseCode)

def request = new Request.Builder().url("http://localhost:$port/").get().build()
def response = client.newCall(request).execute()

expect:
response.code() == responseCode.code()
response.body().string() == "Hello World"

and:
assertTraces(1) {
trace(0, 1) {
span(0) {
serviceName "unnamed-java-app"
operationName "netty.request"
resourceName name
spanType DDSpanTypes.HTTP_SERVER
errored error
tags {
"$Tags.COMPONENT.key" "netty"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" responseCode.code()
"$Tags.HTTP_URL.key" "http://localhost:$port/"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
if (error) {
tag("error", true)
}
defaultTags()
}
}
}
}

cleanup:
eventLoopGroup.shutdownGracefully()

where:
responseCode | name | error
HttpResponseStatus.OK | "GET /" | false
HttpResponseStatus.NOT_FOUND | "404" | false
HttpResponseStatus.INTERNAL_SERVER_ERROR | "GET /" | true
}

def initializeServer(eventLoopGroup, port, handlers, responseCode) {
ServerBootstrap bootstrap = new ServerBootstrap()
.group(eventLoopGroup)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler([
initChannel: { ch ->
ChannelPipeline pipeline = ch.pipeline()
handlers.each { pipeline.addLast(it) }
pipeline.addLast([
channelRead0 : { ctx, msg ->
if (msg instanceof LastHttpContent) {
ByteBuf content = Unpooled.copiedBuffer("Hello World", CharsetUtil.UTF_8)
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, responseCode, content)
initChannel: { ch ->
ChannelPipeline pipeline = ch.pipeline()
handlers.each { pipeline.addLast(it) }
pipeline.addLast([
channelRead0 : { ctx, msg ->
if (msg instanceof HttpRequest) {
ServerEndpoint endpoint = ServerEndpoint.forPath((msg as HttpRequest).uri)
ctx.write controller(endpoint) {
ByteBuf content = null
FullHttpResponse response = null
switch (endpoint) {
case SUCCESS:
case ERROR:
content = Unpooled.copiedBuffer(endpoint.body, CharsetUtil.UTF_8)
response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status), content)
break
case REDIRECT:
response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status))
response.headers().set(HttpHeaderNames.LOCATION, endpoint.body)
break
case EXCEPTION:
throw new Exception(endpoint.body)
default:
content = Unpooled.copiedBuffer(NOT_FOUND.body, CharsetUtil.UTF_8)
response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(NOT_FOUND.status), content)
break
}
response.headers().set(CONTENT_TYPE, "text/plain")
if (content) {
response.headers().set(CONTENT_LENGTH, content.readableBytes())
}
return response
}
}
},
exceptionCaught : { ChannelHandlerContext ctx, Throwable cause ->
ByteBuf content = Unpooled.copiedBuffer(cause.message, CharsetUtil.UTF_8)
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR, content)
response.headers().set(CONTENT_TYPE, "text/plain")
response.headers().set(CONTENT_LENGTH, content.readableBytes())
ctx.write(response)
}
},
channelReadComplete: { it.flush() }
] as SimpleChannelInboundHandler)
}
] as ChannelInitializer).channel(NioServerSocketChannel)
},
channelReadComplete: { it.flush() }
] as SimpleChannelInboundHandler)
}
] as ChannelInitializer).channel(NioServerSocketChannel)
bootstrap.bind(port).sync()
}

@Override
void stopServer() {
eventLoopGroup?.shutdownGracefully()
}

@Override
NettyHttpServerDecorator decorator() {
NettyHttpServerDecorator.DECORATE
}

String expectedOperationName() {
"netty.request"
}
}
Original file line number Diff line number Diff line change
@@ -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("io.netty.handler.codec.ByteToMessageDecoder"))
.transform(
new AgentBuilder.Transformer.ForAdvice()
.advice(
named("fireChannelRead"),
HttpServerTestAdvice.ServerEntryAdvice.class.getName()));
}
}
8 changes: 0 additions & 8 deletions dd-java-agent/instrumentation/netty-4.1/netty-4.1.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,6 @@ testSets {
dependencies {
compileOnly group: 'io.netty', name: 'netty-codec-http', version: '4.1.0.Final'

compile project(':dd-java-agent:agent-tooling')

compile deps.bytebuddy
compile deps.opentracing
annotationProcessor deps.autoservice
implementation deps.autoservice

testCompile project(':dd-java-agent:testing')
testCompile project(':dd-java-agent:instrumentation:java-concurrent')
testCompile group: 'io.netty', name: 'netty-codec-http', version: '4.1.0.Final'
testCompile group: 'org.asynchttpclient', name: 'async-http-client', version: '2.1.0'
Expand Down
Loading