diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/scala/datadog/trace/instrumentation/akkahttp/AkkaHttpClientInstrumentation.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/scala/datadog/trace/instrumentation/akkahttp/AkkaHttpClientInstrumentation.java index 72f2d4919e..10ac93b274 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/scala/datadog/trace/instrumentation/akkahttp/AkkaHttpClientInstrumentation.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/scala/datadog/trace/instrumentation/akkahttp/AkkaHttpClientInstrumentation.java @@ -15,6 +15,7 @@ import datadog.trace.api.DDSpanTypes; import datadog.trace.api.DDTags; import io.opentracing.Scope; import io.opentracing.Span; +import io.opentracing.Tracer; import io.opentracing.propagation.Format; import io.opentracing.propagation.TextMap; import io.opentracing.tag.Tags; @@ -79,31 +80,45 @@ public final class AkkaHttpClientInstrumentation extends Instrumenter.Default { @Advice.OnMethodEnter(suppress = Throwable.class) public static Scope methodEnter( @Advice.Argument(value = 0, readOnly = false) HttpRequest request) { - Scope scope = + Tracer.SpanBuilder builder = GlobalTracer.get() .buildSpan("akka-http.request") .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT) - .withTag(Tags.HTTP_METHOD.getKey(), request.method().value()) .withTag(DDTags.SPAN_TYPE, DDSpanTypes.HTTP_CLIENT) - .withTag(Tags.COMPONENT.getKey(), "akka-http-client") - .withTag(Tags.HTTP_URL.getKey(), request.getUri().toString()) - .startActive(false); + .withTag(Tags.COMPONENT.getKey(), "akka-http-client"); + if (request != null) { + builder = + builder + .withTag(Tags.HTTP_METHOD.getKey(), request.method().value()) + .withTag(Tags.HTTP_URL.getKey(), request.getUri().toString()); + } + Scope scope = builder.startActive(false); - AkkaHttpHeaders headers = new AkkaHttpHeaders(request); - GlobalTracer.get().inject(scope.span().context(), Format.Builtin.HTTP_HEADERS, headers); - // Request is immutable, so we have to assign new value once we update headers - request = headers.getRequest(); + if (request != null) { + AkkaHttpHeaders headers = new AkkaHttpHeaders(request); + GlobalTracer.get().inject(scope.span().context(), Format.Builtin.HTTP_HEADERS, headers); + // Request is immutable, so we have to assign new value once we update headers + request = headers.getRequest(); + } return scope; } - @Advice.OnMethodExit(suppress = Throwable.class) + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void methodExit( @Advice.Argument(value = 0) final HttpRequest request, @Advice.This final HttpExt thiz, @Advice.Return final Future responseFuture, - @Advice.Enter final Scope scope) { - responseFuture.onComplete(new OnCompleteHandler(scope.span()), thiz.system().dispatcher()); + @Advice.Enter final Scope scope, + @Advice.Thrown final Throwable throwable) { + Span span = scope.span(); + if (throwable == null) { + responseFuture.onComplete(new OnCompleteHandler(span), thiz.system().dispatcher()); + } else { + Tags.ERROR.set(span, Boolean.TRUE); + span.log(Collections.singletonMap(ERROR_OBJECT, throwable)); + span.finish(); + } scope.close(); } } diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy b/dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy index dbb5950507..d63b329596 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy @@ -153,6 +153,36 @@ class AkkaHttpClientInstrumentationTest extends AgentTestRunner { } } + def "singleRequest exception trace" () { + when: + // Passing null causes NPE in singleRequest + Http.get(system).singleRequest(null, materializer) + + then: + thrown NullPointerException + assertTraces(TEST_WRITER, 1) { + trace(0, 1) { + span(0) { + parent() + serviceName "unnamed-java-app" + operationName "akka-http.request" + resourceName "akka-http.request" + errored true + tags { + defaultTags() + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + "$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT + "$Tags.COMPONENT.key" "akka-http-client" + "$Tags.ERROR.key" true + errorTags(NullPointerException) + } + } + } + } + + } + + def "#route pool request trace" () { setup: def url = server.address.resolve("/" + route).toURL()