Skip to content

Commit cf26591

Browse files
committed
add tapir path matching instrumentation
1 parent 20cb8c8 commit cf26591

File tree

3 files changed

+103
-0
lines changed

3 files changed

+103
-0
lines changed

instrumentation/pekko/pekko-http-1.0/javaagent/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ muzzle {
3030
dependencies {
3131
library("org.apache.pekko:pekko-http_2.12:1.0.0")
3232
library("org.apache.pekko:pekko-stream_2.12:1.0.1")
33+
library("com.softwaremill.sttp.tapir:tapir-pekko-http-server_2.12:1.7.0")
3334

3435
testImplementation("com.softwaremill.sttp.tapir:tapir-pekko-http-server_2.12:1.7.0")
3536

@@ -38,6 +39,7 @@ dependencies {
3839

3940
latestDepTestLibrary("org.apache.pekko:pekko-http_2.13:+")
4041
latestDepTestLibrary("org.apache.pekko:pekko-stream_2.13:+")
42+
latestDepTestLibrary("com.softwaremill.sttp.tapir:tapir-pekko-http-server_2.12:+")
4143
}
4244

4345
tasks {

instrumentation/pekko/pekko-http-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pekkohttp/v1_0/server/route/PekkoHttpServerRouteInstrumentationModule.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public String getModuleGroup() {
3232
@Override
3333
public List<TypeInstrumentation> typeInstrumentations() {
3434
return asList(
35+
new TapirPathInstrumentation(),
3536
new PathMatcherInstrumentation(),
3637
new PathMatcherStaticInstrumentation(),
3738
new RouteConcatenationInstrumentation(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.pekkohttp.v1_0.server.route;
7+
8+
import static net.bytebuddy.matcher.ElementMatchers.named;
9+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
10+
11+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
12+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
13+
import net.bytebuddy.asm.Advice;
14+
import net.bytebuddy.description.type.TypeDescription;
15+
import net.bytebuddy.matcher.ElementMatcher;
16+
import org.apache.pekko.http.scaladsl.server.RequestContext;
17+
import org.apache.pekko.http.scaladsl.server.RouteResult;
18+
import scala.Function1;
19+
import scala.Function2;
20+
import scala.Option;
21+
import scala.PartialFunction;
22+
import scala.Unit;
23+
import scala.concurrent.Future;
24+
import scala.util.Try;
25+
import sttp.tapir.EndpointInput;
26+
import sttp.tapir.server.ServerEndpoint;
27+
28+
public class TapirPathInstrumentation implements TypeInstrumentation {
29+
30+
@Override
31+
public ElementMatcher<TypeDescription> typeMatcher() {
32+
return named("sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter");
33+
}
34+
35+
@Override
36+
public void transform(TypeTransformer transformer) {
37+
transformer.applyAdviceToMethod(
38+
named("toRoute").and(takesArgument(0, named("sttp.tapir.server.ServerEndpoint"))),
39+
this.getClass().getName() + "$ApplyAdvice");
40+
}
41+
42+
public static class RouteWrapper implements Function1<RequestContext, Future<RouteResult>> {
43+
private final Function1<RequestContext, Future<RouteResult>> route;
44+
private final ServerEndpoint<?, ?> serverEndpoint;
45+
46+
public RouteWrapper(
47+
ServerEndpoint<?, ?> serverEndpoint, Function1<RequestContext, Future<RouteResult>> route) {
48+
this.route = route;
49+
this.serverEndpoint = serverEndpoint;
50+
}
51+
52+
public class Finalizer implements PartialFunction<Try<RouteResult>, Unit> {
53+
@Override
54+
public boolean isDefinedAt(Try<RouteResult> x) {
55+
return true;
56+
}
57+
58+
@Override
59+
public Unit apply(Try<RouteResult> tryResult) {
60+
if (tryResult.isSuccess()) {
61+
RouteResult result = tryResult.get();
62+
if (result.getClass() == RouteResult.Complete.class) {
63+
String path =
64+
serverEndpoint.showPathTemplate(
65+
(index, pc) ->
66+
pc.name().isDefined()
67+
? "{" + pc.name().get() + "}"
68+
: "{param" + index + "}",
69+
Option.apply(
70+
(Function2<Object, EndpointInput.Query<?>, String>)
71+
(index, q) -> q.name() + "={" + q.name() + "}"),
72+
true,
73+
"*",
74+
Option.apply("*"),
75+
Option.apply("*"));
76+
77+
PekkoRouteHolder.push(path);
78+
PekkoRouteHolder.endMatched();
79+
}
80+
}
81+
return null;
82+
}
83+
}
84+
85+
@Override
86+
public Future<RouteResult> apply(RequestContext ctx) {
87+
return route.apply(ctx).andThen(new Finalizer(), ctx.executionContext());
88+
}
89+
}
90+
91+
@SuppressWarnings("unused")
92+
public static class ApplyAdvice {
93+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
94+
public static void onExit(
95+
@Advice.Argument(0) ServerEndpoint<?, ?> endpoint,
96+
@Advice.Return(readOnly = false) Function1<RequestContext, Future<RouteResult>> route) {
97+
route = new RouteWrapper(endpoint, route);
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)