From 4ea025739aff38417b41459ba1d8a9338b7b9f91 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Fri, 8 Dec 2023 19:11:50 +0200 Subject: [PATCH 1/4] Capture http.route for akka-http --- .../akkahttp/server/AkkaFlowWrapper.java | 1 + .../AkkaHttpServerInstrumentationModule.java | 8 +- .../akkahttp/server/AkkaRouteHolder.java | 79 +++++++++++++ .../PathConcatenationInstrumentation.java | 43 +++++++ .../server/PathMatcherInstrumentation.java | 46 ++++++++ .../PathMatcherStaticInstrumentation.java | 64 ++++++++++ .../RouteConcatenationInstrumentation.java | 46 ++++++++ ...bstractHttpServerInstrumentationTest.scala | 11 +- .../AkkaHttpServerInstrumentationTest.scala | 76 +++++++++++- .../akkahttp/AkkaHttpTestWebServer.scala | 109 +++++++++++------- .../tooling/ignore/IgnoredTypesMatcher.java | 2 +- 11 files changed, 440 insertions(+), 45 deletions(-) create mode 100644 instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaRouteHolder.java create mode 100644 instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathConcatenationInstrumentation.java create mode 100644 instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathMatcherInstrumentation.java create mode 100644 instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathMatcherStaticInstrumentation.java create mode 100644 instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/RouteConcatenationInstrumentation.java diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaFlowWrapper.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaFlowWrapper.java index 51b31549ab5a..fce6056e941b 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaFlowWrapper.java +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaFlowWrapper.java @@ -118,6 +118,7 @@ public void onPush() { Context parentContext = currentContext(); if (instrumenter().shouldStart(parentContext, request)) { Context context = instrumenter().start(parentContext, request); + context = AkkaRouteHolder.init(context); tracingRequest = new TracingRequest(context, request); } // event if span wasn't started we need to push TracingRequest to match response diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaHttpServerInstrumentationModule.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaHttpServerInstrumentationModule.java index 12e5de05edff..b3e4d89a4fdc 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaHttpServerInstrumentationModule.java +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaHttpServerInstrumentationModule.java @@ -29,6 +29,12 @@ public ElementMatcher.Junction classLoaderMatcher() { @Override public List typeInstrumentations() { - return asList(new HttpExtServerInstrumentation(), new GraphInterpreterInstrumentation()); + return asList( + new HttpExtServerInstrumentation(), + new GraphInterpreterInstrumentation(), + new PathMatcherInstrumentation(), + new PathMatcherStaticInstrumentation(), + new RouteConcatenationInstrumentation(), + new PathConcatenationInstrumentation()); } } diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaRouteHolder.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaRouteHolder.java new file mode 100644 index 000000000000..32cbb50f019a --- /dev/null +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaRouteHolder.java @@ -0,0 +1,79 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.akkahttp.server; + +import static io.opentelemetry.context.ContextKey.named; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextKey; +import io.opentelemetry.context.ImplicitContextKeyed; +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute; +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource; +import java.util.ArrayDeque; +import java.util.Deque; + +public class AkkaRouteHolder implements ImplicitContextKeyed { + private static final ContextKey KEY = named("opentelemetry-akka-route"); + + private String route = ""; + private boolean newSegment; + private boolean endMatched; + private final Deque stack = new ArrayDeque<>(); + + public static Context init(Context context) { + if (context.get(KEY) != null) { + return context; + } + return context.with(new AkkaRouteHolder()); + } + + public static void push(String path) { + AkkaRouteHolder holder = Context.current().get(KEY); + if (holder != null && holder.newSegment && !holder.endMatched) { + holder.route += path; + holder.newSegment = false; + } + } + + public static void startSegment() { + AkkaRouteHolder holder = Context.current().get(KEY); + if (holder != null) { + holder.newSegment = true; + } + } + + public static void endMatched() { + Context context = Context.current(); + AkkaRouteHolder holder = context.get(KEY); + if (holder != null) { + holder.endMatched = true; + HttpServerRoute.update(context, HttpServerRouteSource.CONTROLLER, holder.route); + } + } + + public static void save() { + AkkaRouteHolder holder = Context.current().get(KEY); + if (holder != null) { + holder.stack.push(holder.route); + holder.newSegment = true; + } + } + + public static void restore() { + AkkaRouteHolder holder = Context.current().get(KEY); + if (holder != null) { + holder.route = holder.stack.pop(); + holder.newSegment = true; + } + } + + @Override + public Context storeInContext(Context context) { + return context.with(KEY, this); + } + + private AkkaRouteHolder() {} +} diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathConcatenationInstrumentation.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathConcatenationInstrumentation.java new file mode 100644 index 000000000000..4f04c5c3136e --- /dev/null +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathConcatenationInstrumentation.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.akkahttp.server; + +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class PathConcatenationInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return namedOneOf( + "akka.http.scaladsl.server.PathMatcher$$anonfun$$tilde$1", + "akka.http.scaladsl.server.PathMatcher"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + namedOneOf("apply", "$anonfun$append$1"), this.getClass().getName() + "$ApplyAdvice"); + } + + @SuppressWarnings("unused") + public static class ApplyAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter() { + // https://github.com/akka/akka-http/blob/0fedb87671ecc450e7378713105ea1dc1d9d0c7d/akka-http/src/main/scala/akka/http/scaladsl/server/PathMatcher.scala#L43 + // https://github.com/akka/akka-http/blob/0fedb87671ecc450e7378713105ea1dc1d9d0c7d/akka-http/src/main/scala/akka/http/scaladsl/server/PathMatcher.scala#L47 + // when routing dsl uses path("path1" / "path2") we are concatenating 3 segments "path1" and / + // and "path2" we need to notify the matcher that a new segment has started, so it could be + // captured in the route + AkkaRouteHolder.startSegment(); + } + } +} diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathMatcherInstrumentation.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathMatcherInstrumentation.java new file mode 100644 index 000000000000..132d883785ba --- /dev/null +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathMatcherInstrumentation.java @@ -0,0 +1,46 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.akkahttp.server; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import akka.http.scaladsl.model.Uri; +import akka.http.scaladsl.server.PathMatcher; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class PathMatcherInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("akka.http.scaladsl.server.PathMatcher$"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("apply") + .and(takesArgument(0, named("akka.http.scaladsl.model.Uri$Path"))) + .and(returns(named("akka.http.scaladsl.server.PathMatcher"))), + this.getClass().getName() + "$ApplyAdvice"); + } + + @SuppressWarnings("unused") + public static class ApplyAdvice { + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onEnter( + @Advice.Argument(0) Uri.Path prefix, @Advice.Return PathMatcher result) { + // remember the path for given matcher, so it could be used for constructing the route + VirtualField.find(PathMatcher.class, String.class).set(result, prefix.toString()); + } + } +} diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathMatcherStaticInstrumentation.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathMatcherStaticInstrumentation.java new file mode 100644 index 000000000000..44a8202b5bd7 --- /dev/null +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathMatcherStaticInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.akkahttp.server; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import akka.http.scaladsl.model.Uri; +import akka.http.scaladsl.server.PathMatcher; +import akka.http.scaladsl.server.PathMatchers; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class PathMatcherStaticInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return extendsClass(named("akka.http.scaladsl.server.PathMatcher")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("apply").and(takesArgument(0, named("akka.http.scaladsl.model.Uri$Path"))), + this.getClass().getName() + "$ApplyAdvice"); + } + + @SuppressWarnings("unused") + public static class ApplyAdvice { + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.This PathMatcher pathMatcher, + @Advice.Argument(0) Uri.Path path, + @Advice.Return PathMatcher.Matching result) { + // result is either matched or unmatched, we only care about the matches + if (result.getClass() == PathMatcher.Matched.class) { + if (PathMatchers.PathEnd$.class == pathMatcher.getClass()) { + AkkaRouteHolder.endMatched(); + return; + } + // for remember the matched path in PathMatcherInstrumentation, otherwise we just use a * + String prefix = VirtualField.find(PathMatcher.class, String.class).get(pathMatcher); + if (prefix == null) { + if (PathMatchers.Slash$.class == pathMatcher.getClass()) { + prefix = "/"; + } else { + prefix = "*"; + } + } + if (prefix != null) { + AkkaRouteHolder.push(prefix); + } + } + } + } +} diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/RouteConcatenationInstrumentation.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/RouteConcatenationInstrumentation.java new file mode 100644 index 000000000000..bb6b376d6106 --- /dev/null +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/RouteConcatenationInstrumentation.java @@ -0,0 +1,46 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.akkahttp.server; + +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class RouteConcatenationInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return namedOneOf( + "akka.http.scaladsl.server.RouteConcatenation$RouteWithConcatenation$$anonfun$$tilde$1", + "akka.http.scaladsl.server.RouteConcatenation$RouteWithConcatenation"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + namedOneOf("apply", "$anonfun$$tilde$1"), this.getClass().getName() + "$ApplyAdvice"); + } + + @SuppressWarnings("unused") + public static class ApplyAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter() { + // when routing dsl uses concat(path(...) {...}, path(...) {...}) we'll restore the currently + // matched route after each matcher so that match attempts that failed wouldn't get recorded + // in the route + AkkaRouteHolder.save(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit() { + AkkaRouteHolder.restore(); + } + } +} diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/akkahttp/AbstractHttpServerInstrumentationTest.scala b/instrumentation/akka/akka-http-10.0/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/akkahttp/AbstractHttpServerInstrumentationTest.scala index 8cbba92195ac..28d79b823ca0 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/akkahttp/AbstractHttpServerInstrumentationTest.scala +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/akkahttp/AbstractHttpServerInstrumentationTest.scala @@ -11,9 +11,9 @@ import io.opentelemetry.instrumentation.testing.junit.http.{ HttpServerTestOptions, ServerEndpoint } +import io.opentelemetry.semconv.SemanticAttributes import java.util -import java.util.Collections import java.util.function.{Function, Predicate} abstract class AbstractHttpServerInstrumentationTest @@ -25,8 +25,13 @@ abstract class AbstractHttpServerInstrumentationTest options.setTestCaptureHttpHeaders(false) options.setHttpAttributes( new Function[ServerEndpoint, util.Set[AttributeKey[_]]] { - override def apply(v1: ServerEndpoint): util.Set[AttributeKey[_]] = - Collections.emptySet() + override def apply(v1: ServerEndpoint): util.Set[AttributeKey[_]] = { + val set = new util.HashSet[AttributeKey[_]]( + HttpServerTestOptions.DEFAULT_HTTP_ATTRIBUTES + ) + set.remove(SemanticAttributes.HTTP_ROUTE) + set + } } ) options.setHasResponseCustomizer( diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/akkahttp/AkkaHttpServerInstrumentationTest.scala b/instrumentation/akka/akka-http-10.0/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/akkahttp/AkkaHttpServerInstrumentationTest.scala index 2001f3335f9f..8a340318475b 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/akkahttp/AkkaHttpServerInstrumentationTest.scala +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/akkahttp/AkkaHttpServerInstrumentationTest.scala @@ -5,13 +5,26 @@ package io.opentelemetry.javaagent.instrumentation.akkahttp +import io.opentelemetry.api.common.AttributeKey import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS import io.opentelemetry.instrumentation.testing.junit.http.{ HttpServerInstrumentationExtension, - HttpServerTestOptions + HttpServerTestOptions, + ServerEndpoint } +import io.opentelemetry.sdk.testing.assertj.{SpanDataAssert, TraceAssert} +import io.opentelemetry.testing.internal.armeria.common.{ + AggregatedHttpRequest, + HttpMethod +} +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension +import java.util +import java.util.function.{BiFunction, Consumer, Function} + class AkkaHttpServerInstrumentationTest extends AbstractHttpServerInstrumentationTest { @RegisterExtension val extension: InstrumentationExtension = @@ -31,5 +44,66 @@ class AkkaHttpServerInstrumentationTest super.configure(options) // exception doesn't propagate options.setTestException(false) + options.setTestPathParam(true) + + options.setHttpAttributes( + new Function[ServerEndpoint, util.Set[AttributeKey[_]]] { + override def apply(v1: ServerEndpoint): util.Set[AttributeKey[_]] = { + HttpServerTestOptions.DEFAULT_HTTP_ATTRIBUTES + } + } + ) + + val expectedRoute = new BiFunction[ServerEndpoint, String, String] { + def apply(endpoint: ServerEndpoint, method: String): String = { + if (endpoint eq ServerEndpoint.PATH_PARAM) + return "/path/*/param" + expectedHttpRoute(endpoint, method) + } + } + options.setExpectedHttpRoute(expectedRoute) + } + + @Test def testPathMatchers(): Unit = { + // /test1 / IntNumber / HexIntNumber / LongNumber / HexLongNumber / DoubleNumber / JavaUUID / Remaining + val request = AggregatedHttpRequest.of( + HttpMethod.GET, + address + .resolve( + "/test1/1/a1/2/b2/3.0/e58ed763-928c-4155-bee9-fdbaaadc15f3/remaining" + ) + .toString + ) + val response = client.execute(request).aggregate.join + assertThat(response.status.code).isEqualTo(SUCCESS.getStatus) + assertThat(response.contentUtf8).isEqualTo(SUCCESS.getBody) + + testing.waitAndAssertTraces(new Consumer[TraceAssert] { + override def accept(trace: TraceAssert): Unit = + trace.hasSpansSatisfyingExactly(new Consumer[SpanDataAssert] { + override def accept(span: SpanDataAssert): Unit = { + span.hasName("GET /test1/*/*/*/*/*/*/*") + } + }) + }) + } + + @Test def testConcat(): Unit = { + val request = AggregatedHttpRequest.of( + HttpMethod.GET, + address.resolve("/test2/second").toString + ) + val response = client.execute(request).aggregate.join + assertThat(response.status.code).isEqualTo(SUCCESS.getStatus) + assertThat(response.contentUtf8).isEqualTo(SUCCESS.getBody) + + testing.waitAndAssertTraces(new Consumer[TraceAssert] { + override def accept(trace: TraceAssert): Unit = + trace.hasSpansSatisfyingExactly(new Consumer[SpanDataAssert] { + override def accept(span: SpanDataAssert): Unit = { + span.hasName("GET /test2/second") + } + }) + }) } } diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/akkahttp/AkkaHttpTestWebServer.scala b/instrumentation/akka/akka-http-10.0/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/akkahttp/AkkaHttpTestWebServer.scala index 69fffed25314..b17c3b65a934 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/akkahttp/AkkaHttpTestWebServer.scala +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/akkahttp/AkkaHttpTestWebServer.scala @@ -8,14 +8,10 @@ package io.opentelemetry.javaagent.instrumentation.akkahttp import akka.actor.ActorSystem import akka.http.scaladsl.Http import akka.http.scaladsl.Http.ServerBinding -import akka.http.scaladsl.model._ +import akka.http.scaladsl.model.StatusCodes.Found import akka.http.scaladsl.server.Directives._ -import akka.http.scaladsl.server.ExceptionHandler import akka.stream.ActorMaterializer -import io.opentelemetry.instrumentation.testing.junit.http.{ - AbstractHttpServerTest, - ServerEndpoint -} +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint._ import java.util.function.Supplier @@ -27,43 +23,70 @@ object AkkaHttpTestWebServer { // needed for the future flatMap/onComplete in the end implicit val executionContext = system.dispatcher - val exceptionHandler = ExceptionHandler { case ex: Exception => - complete( - HttpResponse(status = EXCEPTION.getStatus).withEntity(ex.getMessage) - ) - } - - val route = handleExceptions(exceptionHandler) { - extractUri { uri => - val endpoint = ServerEndpoint.forPath(uri.path.toString()) - complete { - AbstractHttpServerTest.controller( - endpoint, - new Supplier[HttpResponse] { - def get(): HttpResponse = { - val resp = HttpResponse(status = endpoint.getStatus) - endpoint match { - case SUCCESS => resp.withEntity(endpoint.getBody) - case INDEXED_CHILD => - INDEXED_CHILD.collectSpanAttributes(new UrlParameterProvider { - override def getParameter(name: String): String = - uri.query().get(name).orNull - }) - resp.withEntity("") - case QUERY_PARAM => resp.withEntity(uri.queryString().orNull) - case REDIRECT => - resp.withHeaders(headers.Location(endpoint.getBody)) - case ERROR => resp.withEntity(endpoint.getBody) - case EXCEPTION => throw new Exception(endpoint.getBody) - case _ => - HttpResponse(status = NOT_FOUND.getStatus) - .withEntity(NOT_FOUND.getBody) - } + var route = get { + concat( + path(SUCCESS.rawPath()) { + complete( + AbstractHttpServerTest.controller(SUCCESS, supplier(SUCCESS.getBody)) + ) + }, + path(INDEXED_CHILD.rawPath()) { + parameterMap { map => + val supplier = new Supplier[String] { + def get(): String = { + INDEXED_CHILD.collectSpanAttributes(new UrlParameterProvider { + override def getParameter(name: String): String = + map.get(name).orNull + }) + "" } } + complete(AbstractHttpServerTest.controller(INDEXED_CHILD, supplier)) + } + }, + path(QUERY_PARAM.rawPath()) { + extractUri { uri => + complete( + AbstractHttpServerTest + .controller(INDEXED_CHILD, supplier(uri.queryString().orNull)) + ) + } + }, + path(REDIRECT.rawPath()) { + redirect( + AbstractHttpServerTest + .controller(REDIRECT, supplier(REDIRECT.getBody)), + Found + ) + }, + path(ERROR.rawPath()) { + complete( + 500 -> AbstractHttpServerTest + .controller(ERROR, supplier(ERROR.getBody)) + ) + }, + path("path" / LongNumber / "param") { id => + complete( + AbstractHttpServerTest.controller(PATH_PARAM, supplier(id.toString)) + ) + }, + path( + "test1" / IntNumber / HexIntNumber / LongNumber / HexLongNumber / + DoubleNumber / JavaUUID / Remaining + ) { (_, _, _, _, _, _, _) => + complete(SUCCESS.getBody) + }, + pathPrefix("test2") { + concat( + path("first") { + complete(SUCCESS.getBody) + }, + path("second") { + complete(SUCCESS.getBody) + } ) } - } + ) } private var binding: ServerBinding = null @@ -83,4 +106,12 @@ object AkkaHttpTestWebServer { binding = null } } + + def supplier(string: String): Supplier[String] = { + new Supplier[String] { + def get(): String = { + string + } + } + } } diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/IgnoredTypesMatcher.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/IgnoredTypesMatcher.java index c864f4c5fd2b..f199641a48f3 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/IgnoredTypesMatcher.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/IgnoredTypesMatcher.java @@ -31,7 +31,7 @@ public boolean matches(TypeDescription target) { // bytecode proxies typically have $$ in their name if (name.contains("$$") && !name.contains("$$Lambda$") && !name.endsWith("$$Lambda")) { // allow scala anonymous classes - return !name.contains("$$anon$"); + return !name.contains("$$anon$") && !name.contains("$$anonfun$"); } if (name.contains("$JaxbAccessor") From 76458e5aeb4b3cca1acd7957285e40921e7dee61 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Fri, 8 Dec 2023 21:46:32 +0200 Subject: [PATCH 2/4] split route handling into separate instrumentation module --- .../akkahttp/server/AkkaFlowWrapper.java | 1 + .../AkkaHttpServerInstrumentationModule.java | 15 +++---- ...aHttpServerRouteInstrumentationModule.java | 40 +++++++++++++++++++ .../server/{ => route}/AkkaRouteHolder.java | 2 +- .../PathConcatenationInstrumentation.java | 2 +- .../PathMatcherInstrumentation.java | 2 +- .../PathMatcherStaticInstrumentation.java | 2 +- .../RouteConcatenationInstrumentation.java | 2 +- 8 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/AkkaHttpServerRouteInstrumentationModule.java rename instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/{ => route}/AkkaRouteHolder.java (99%) rename instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/{ => route}/PathConcatenationInstrumentation.java (99%) rename instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/{ => route}/PathMatcherInstrumentation.java (99%) rename instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/{ => route}/PathMatcherStaticInstrumentation.java (99%) rename instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/{ => route}/RouteConcatenationInstrumentation.java (99%) diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaFlowWrapper.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaFlowWrapper.java index fce6056e941b..1880275ceade 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaFlowWrapper.java +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaFlowWrapper.java @@ -24,6 +24,7 @@ import akka.stream.stage.OutHandler; import io.opentelemetry.context.Context; import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder; +import io.opentelemetry.javaagent.instrumentation.akkahttp.server.route.AkkaRouteHolder; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaHttpServerInstrumentationModule.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaHttpServerInstrumentationModule.java index b3e4d89a4fdc..18ffe19770b8 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaHttpServerInstrumentationModule.java +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaHttpServerInstrumentationModule.java @@ -27,14 +27,15 @@ public ElementMatcher.Junction classLoaderMatcher() { return hasClassesNamed("akka.http.scaladsl.HttpExt"); } + @Override + public boolean isIndyModule() { + // AkkaHttpServerInstrumentationModule and AkkaHttpServerRouteInstrumentationModule share + // AkkaRouteHolder class + return false; + } + @Override public List typeInstrumentations() { - return asList( - new HttpExtServerInstrumentation(), - new GraphInterpreterInstrumentation(), - new PathMatcherInstrumentation(), - new PathMatcherStaticInstrumentation(), - new RouteConcatenationInstrumentation(), - new PathConcatenationInstrumentation()); + return asList(new HttpExtServerInstrumentation(), new GraphInterpreterInstrumentation()); } } diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/AkkaHttpServerRouteInstrumentationModule.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/AkkaHttpServerRouteInstrumentationModule.java new file mode 100644 index 000000000000..6c484eb968ed --- /dev/null +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/AkkaHttpServerRouteInstrumentationModule.java @@ -0,0 +1,40 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.akkahttp.server.route; + +import static java.util.Arrays.asList; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.List; + +/** + * This instrumentation applies to classes in akka-http.jar while + * AkkaHttpServerInstrumentationModule applies to classes in akka-http-core.jar + */ +@AutoService(InstrumentationModule.class) +public class AkkaHttpServerRouteInstrumentationModule extends InstrumentationModule { + public AkkaHttpServerRouteInstrumentationModule() { + super("akka-http", "akka-http-10.0", "akka-http-server", "akka-http-server-route"); + } + + @Override + public boolean isIndyModule() { + // AkkaHttpServerInstrumentationModule and AkkaHttpServerRouteInstrumentationModule share + // AkkaRouteHolder class + return false; + } + + @Override + public List typeInstrumentations() { + return asList( + new PathMatcherInstrumentation(), + new PathMatcherStaticInstrumentation(), + new RouteConcatenationInstrumentation(), + new PathConcatenationInstrumentation()); + } +} diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaRouteHolder.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/AkkaRouteHolder.java similarity index 99% rename from instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaRouteHolder.java rename to instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/AkkaRouteHolder.java index 32cbb50f019a..9fa98d8c2add 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaRouteHolder.java +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/AkkaRouteHolder.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.akkahttp.server; +package io.opentelemetry.javaagent.instrumentation.akkahttp.server.route; import static io.opentelemetry.context.ContextKey.named; diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathConcatenationInstrumentation.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathConcatenationInstrumentation.java similarity index 99% rename from instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathConcatenationInstrumentation.java rename to instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathConcatenationInstrumentation.java index 4f04c5c3136e..8897a9ebd3bf 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathConcatenationInstrumentation.java +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathConcatenationInstrumentation.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.akkahttp.server; +package io.opentelemetry.javaagent.instrumentation.akkahttp.server.route; import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathMatcherInstrumentation.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathMatcherInstrumentation.java similarity index 99% rename from instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathMatcherInstrumentation.java rename to instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathMatcherInstrumentation.java index 132d883785ba..6c1c8f295848 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathMatcherInstrumentation.java +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathMatcherInstrumentation.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.akkahttp.server; +package io.opentelemetry.javaagent.instrumentation.akkahttp.server.route; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.returns; diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathMatcherStaticInstrumentation.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathMatcherStaticInstrumentation.java similarity index 99% rename from instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathMatcherStaticInstrumentation.java rename to instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathMatcherStaticInstrumentation.java index 44a8202b5bd7..42e180328e51 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/PathMatcherStaticInstrumentation.java +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathMatcherStaticInstrumentation.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.akkahttp.server; +package io.opentelemetry.javaagent.instrumentation.akkahttp.server.route; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass; import static net.bytebuddy.matcher.ElementMatchers.named; diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/RouteConcatenationInstrumentation.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/RouteConcatenationInstrumentation.java similarity index 99% rename from instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/RouteConcatenationInstrumentation.java rename to instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/RouteConcatenationInstrumentation.java index bb6b376d6106..de13aba9fb30 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/RouteConcatenationInstrumentation.java +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/RouteConcatenationInstrumentation.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.akkahttp.server; +package io.opentelemetry.javaagent.instrumentation.akkahttp.server.route; import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; From a1ea00fff2ca3acb03841a58fba0c2e1cc0b0330 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Sat, 9 Dec 2023 14:22:31 +0200 Subject: [PATCH 3/4] Update instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathMatcherInstrumentation.java Co-authored-by: jason plumb <75337021+breedx-splk@users.noreply.github.com> --- .../akkahttp/server/route/PathMatcherInstrumentation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathMatcherInstrumentation.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathMatcherInstrumentation.java index 6c1c8f295848..36255ef06906 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathMatcherInstrumentation.java +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathMatcherInstrumentation.java @@ -39,7 +39,7 @@ public static class ApplyAdvice { @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void onEnter( @Advice.Argument(0) Uri.Path prefix, @Advice.Return PathMatcher result) { - // remember the path for given matcher, so it could be used for constructing the route + // store the path being matched inside a VirtualField on the given matcher, so it can be used for constructing the route VirtualField.find(PathMatcher.class, String.class).set(result, prefix.toString()); } } From df345962ccc950acb5d03e0a6cd99290990941a8 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Sat, 9 Dec 2023 14:43:48 +0200 Subject: [PATCH 4/4] spotless --- .../akkahttp/server/route/PathMatcherInstrumentation.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathMatcherInstrumentation.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathMatcherInstrumentation.java index 36255ef06906..c0e3ef261c37 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathMatcherInstrumentation.java +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/PathMatcherInstrumentation.java @@ -39,7 +39,8 @@ public static class ApplyAdvice { @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void onEnter( @Advice.Argument(0) Uri.Path prefix, @Advice.Return PathMatcher result) { - // store the path being matched inside a VirtualField on the given matcher, so it can be used for constructing the route + // store the path being matched inside a VirtualField on the given matcher, so it can be used + // for constructing the route VirtualField.find(PathMatcher.class, String.class).set(result, prefix.toString()); } }