-
Notifications
You must be signed in to change notification settings - Fork 873
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Capture http.route for akka-http (#10039)
Co-authored-by: jason plumb <[email protected]>
- Loading branch information
1 parent
43ba144
commit f69c0e2
Showing
12 changed files
with
482 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
...agent/instrumentation/akkahttp/server/route/AkkaHttpServerRouteInstrumentationModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<TypeInstrumentation> typeInstrumentations() { | ||
return asList( | ||
new PathMatcherInstrumentation(), | ||
new PathMatcherStaticInstrumentation(), | ||
new RouteConcatenationInstrumentation(), | ||
new PathConcatenationInstrumentation()); | ||
} | ||
} |
79 changes: 79 additions & 0 deletions
79
...ava/io/opentelemetry/javaagent/instrumentation/akkahttp/server/route/AkkaRouteHolder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.akkahttp.server.route; | ||
|
||
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<AkkaRouteHolder> KEY = named("opentelemetry-akka-route"); | ||
|
||
private String route = ""; | ||
private boolean newSegment; | ||
private boolean endMatched; | ||
private final Deque<String> 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() {} | ||
} |
43 changes: 43 additions & 0 deletions
43
...try/javaagent/instrumentation/akkahttp/server/route/PathConcatenationInstrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.akkahttp.server.route; | ||
|
||
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<TypeDescription> 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(); | ||
} | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
...telemetry/javaagent/instrumentation/akkahttp/server/route/PathMatcherInstrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.akkahttp.server.route; | ||
|
||
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<TypeDescription> 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) { | ||
// 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()); | ||
} | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
...try/javaagent/instrumentation/akkahttp/server/route/PathMatcherStaticInstrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.akkahttp.server.route; | ||
|
||
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<TypeDescription> 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); | ||
} | ||
} | ||
} | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
...ry/javaagent/instrumentation/akkahttp/server/route/RouteConcatenationInstrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.akkahttp.server.route; | ||
|
||
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<TypeDescription> 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(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.