Skip to content

Commit 725071e

Browse files
committed
Fix pekko route naming
1 parent 11b59d0 commit 725071e

File tree

10 files changed

+256
-166
lines changed

10 files changed

+256
-166
lines changed

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

+10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import static io.opentelemetry.javaagent.instrumentation.pekkohttp.v1_0.server.PekkoHttpServerSingletons.instrumenter;
1010

1111
import io.opentelemetry.context.Context;
12+
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute;
13+
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource;
1214
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder;
1315
import io.opentelemetry.javaagent.instrumentation.pekkohttp.v1_0.server.route.PekkoRouteHolder;
1416
import java.util.ArrayDeque;
@@ -143,6 +145,14 @@ public void onPush() {
143145
if (!headers.isEmpty()) {
144146
response = (HttpResponse) response.addHeaders(headers);
145147
}
148+
PekkoRouteHolder routeHolder = tracingRequest.context.get(PekkoRouteHolder.KEY);
149+
if (routeHolder != null) {
150+
routeHolder.pushIfNotCompletelyMatched("**");
151+
HttpServerRoute.update(
152+
tracingRequest.context,
153+
HttpServerRouteSource.CONTROLLER,
154+
routeHolder.route());
155+
}
146156

147157
instrumenter().end(tracingRequest.context, tracingRequest.request, response, null);
148158
}

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

-41
This file was deleted.

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

+11-8
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
import static net.bytebuddy.matcher.ElementMatchers.named;
1010
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
1111

12+
import io.opentelemetry.context.Context;
1213
import io.opentelemetry.instrumentation.api.util.VirtualField;
14+
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
1315
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
1416
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
1517
import net.bytebuddy.asm.Advice;
@@ -18,7 +20,6 @@
1820
import org.apache.pekko.http.scaladsl.model.Uri;
1921
import org.apache.pekko.http.scaladsl.server.PathMatcher;
2022
import org.apache.pekko.http.scaladsl.server.PathMatchers;
21-
import org.apache.pekko.http.scaladsl.server.PathMatchers$;
2223

2324
public class PathMatcherStaticInstrumentation implements TypeInstrumentation {
2425
@Override
@@ -43,11 +44,13 @@ public static void onExit(
4344
@Advice.Argument(0) Uri.Path path,
4445
@Advice.Return PathMatcher.Matching<?> result) {
4546
// result is either matched or unmatched, we only care about the matches
47+
Context context = Java8BytecodeBridge.currentContext();
48+
PekkoRouteHolder routeHolder = context.get(PekkoRouteHolder.KEY);
49+
if (routeHolder == null) {
50+
return;
51+
}
4652
if (result.getClass() == PathMatcher.Matched.class) {
47-
if (PathMatchers$.PathEnd$.class == pathMatcher.getClass()) {
48-
PekkoRouteHolder.endMatched();
49-
return;
50-
}
53+
PathMatcher.Matched<?> match = (PathMatcher.Matched<?>) result;
5154
// if present use the matched path that was remembered in PathMatcherInstrumentation,
5255
// otherwise just use a *
5356
String prefix = VirtualField.find(PathMatcher.class, String.class).get(pathMatcher);
@@ -58,9 +61,9 @@ public static void onExit(
5861
prefix = "*";
5962
}
6063
}
61-
if (prefix != null) {
62-
PekkoRouteHolder.push(prefix);
63-
}
64+
routeHolder.push(path, match.pathRest(), prefix);
65+
} else {
66+
routeHolder.didNotMatch();
6467
}
6568
}
6669
}

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ public List<TypeInstrumentation> typeInstrumentations() {
3434
return asList(
3535
new PathMatcherInstrumentation(),
3636
new PathMatcherStaticInstrumentation(),
37-
new RouteConcatenationInstrumentation(),
38-
new PathConcatenationInstrumentation());
37+
new RouteConcatenationInstrumentation());
3938
}
4039
}

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

+46-41
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,17 @@
1010
import io.opentelemetry.context.Context;
1111
import io.opentelemetry.context.ContextKey;
1212
import io.opentelemetry.context.ImplicitContextKeyed;
13-
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute;
14-
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource;
15-
import java.util.ArrayDeque;
1613
import java.util.Deque;
14+
import java.util.LinkedList;
15+
import org.apache.pekko.http.scaladsl.model.Uri;
1716

1817
public class PekkoRouteHolder implements ImplicitContextKeyed {
19-
private static final ContextKey<PekkoRouteHolder> KEY = named("opentelemetry-pekko-route");
18+
public static final ContextKey<PekkoRouteHolder> KEY = named("opentelemetry-pekko-route");
2019

21-
private String route = "";
22-
private boolean newSegment = true;
23-
private boolean endMatched;
24-
private final Deque<String> stack = new ArrayDeque<>();
20+
private StringBuilder route = new StringBuilder();
21+
private Uri.Path lastUnmatchedPath = null;
22+
private boolean lastWasMatched = false;
23+
private final Deque<State> savedStates = new LinkedList<>();
2524

2625
public static Context init(Context context) {
2726
if (context.get(KEY) != null) {
@@ -30,51 +29,47 @@ public static Context init(Context context) {
3029
return context.with(new PekkoRouteHolder());
3130
}
3231

33-
public static void push(String path) {
34-
PekkoRouteHolder holder = Context.current().get(KEY);
35-
if (holder != null && holder.newSegment && !holder.endMatched) {
36-
holder.route += path;
37-
holder.newSegment = false;
32+
public void push(Uri.Path beforeMatch, Uri.Path afterMatch, String pathToPush) {
33+
// Only accept the suggested 'pathToPush' if:
34+
// - either this is the first match, or
35+
// - the unmatched part of the path from the previous match is what the current match
36+
// acted upon. This avoids pushes from PathMatchers that compose other PathMatchers,
37+
// instead only accepting pushes from leaf-nodes in the PathMatcher hierarchy that actually
38+
// act on the path.
39+
// AND:
40+
// - some part of the path has now been matched by this matcher
41+
if ((lastUnmatchedPath == null || lastUnmatchedPath.equals(beforeMatch))
42+
&& !afterMatch.equals(beforeMatch)) {
43+
route.append(pathToPush);
44+
lastUnmatchedPath = afterMatch;
3845
}
46+
lastWasMatched = true;
3947
}
4048

41-
public static void startSegment() {
42-
PekkoRouteHolder holder = Context.current().get(KEY);
43-
if (holder != null) {
44-
holder.newSegment = true;
45-
}
49+
public void didNotMatch() {
50+
lastWasMatched = false;
4651
}
4752

48-
public static void endMatched() {
49-
Context context = Context.current();
50-
PekkoRouteHolder holder = context.get(KEY);
51-
if (holder != null) {
52-
holder.endMatched = true;
53-
HttpServerRoute.update(context, HttpServerRouteSource.CONTROLLER, holder.route);
53+
public void pushIfNotCompletelyMatched(String pathToPush) {
54+
if (lastUnmatchedPath != null && !lastUnmatchedPath.isEmpty()) {
55+
route.append(pathToPush);
5456
}
5557
}
5658

57-
public static void save() {
58-
PekkoRouteHolder holder = Context.current().get(KEY);
59-
if (holder != null) {
60-
holder.stack.push(holder.route);
61-
holder.newSegment = true;
62-
}
59+
public String route() {
60+
return lastWasMatched ? route.toString() : null;
6361
}
6462

65-
public static void reset() {
66-
PekkoRouteHolder holder = Context.current().get(KEY);
67-
if (holder != null) {
68-
holder.route = holder.stack.peek();
69-
holder.newSegment = true;
70-
}
63+
public void save() {
64+
savedStates.add(new State(lastUnmatchedPath, route));
65+
route = new StringBuilder(route);
7166
}
7267

73-
public static void restore() {
74-
PekkoRouteHolder holder = Context.current().get(KEY);
75-
if (holder != null) {
76-
holder.route = holder.stack.pop();
77-
holder.newSegment = true;
68+
public void restore() {
69+
State popped = savedStates.pollLast();
70+
if (popped != null) {
71+
lastUnmatchedPath = popped.lastUnmatchedPath;
72+
route = popped.route;
7873
}
7974
}
8075

@@ -84,4 +79,14 @@ public Context storeInContext(Context context) {
8479
}
8580

8681
private PekkoRouteHolder() {}
82+
83+
private static class State {
84+
private final Uri.Path lastUnmatchedPath;
85+
private final StringBuilder route;
86+
87+
private State(Uri.Path lastUnmatchedPath, StringBuilder route) {
88+
this.lastUnmatchedPath = lastUnmatchedPath;
89+
this.route = route;
90+
}
91+
}
8792
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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 io.opentelemetry.context.Context;
9+
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
10+
import org.apache.pekko.http.scaladsl.server.RequestContext;
11+
import org.apache.pekko.http.scaladsl.server.RouteResult;
12+
import scala.Function1;
13+
import scala.concurrent.Future;
14+
15+
public class PekkoRouteWrapper implements Function1<RequestContext, Future<RouteResult>> {
16+
private final Function1<RequestContext, Future<RouteResult>> route;
17+
18+
public PekkoRouteWrapper(Function1<RequestContext, Future<RouteResult>> route) {
19+
this.route = route;
20+
}
21+
22+
@Override
23+
public Future<RouteResult> apply(RequestContext ctx) {
24+
Context context = Java8BytecodeBridge.currentContext();
25+
PekkoRouteHolder routeHolder = context.get(PekkoRouteHolder.KEY);
26+
if (routeHolder == null) {
27+
return route.apply(ctx);
28+
} else {
29+
routeHolder.save();
30+
return route
31+
.apply(ctx)
32+
.map(
33+
result -> {
34+
if (result.getClass() == RouteResult.Rejected.class) {
35+
routeHolder.restore();
36+
}
37+
return result;
38+
},
39+
ctx.executionContext());
40+
}
41+
}
42+
}

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

-24
This file was deleted.

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

+8-30
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
1111
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
1212
import net.bytebuddy.asm.Advice;
13+
import net.bytebuddy.description.method.MethodDescription;
1314
import net.bytebuddy.description.type.TypeDescription;
1415
import net.bytebuddy.matcher.ElementMatcher;
1516
import org.apache.pekko.http.scaladsl.server.RequestContext;
1617
import org.apache.pekko.http.scaladsl.server.RouteResult;
18+
import scala.Function1;
1719
import scala.concurrent.Future;
1820

1921
public class RouteConcatenationInstrumentation implements TypeInstrumentation {
@@ -25,41 +27,17 @@ public ElementMatcher<TypeDescription> typeMatcher() {
2527
@Override
2628
public void transform(TypeTransformer transformer) {
2729
transformer.applyAdviceToMethod(
28-
named("$anonfun$$tilde$1"), this.getClass().getName() + "$ApplyAdvice");
29-
transformer.applyAdviceToMethod(
30-
named("$anonfun$$tilde$2"), this.getClass().getName() + "$Apply2Advice");
30+
MethodDescription::isConstructor, this.getClass().getName() + "$ApplyAdvice");
31+
transformer.applyAdviceToMethod(named("$tilde"), this.getClass().getName() + "$ApplyAdvice");
3132
}
3233

33-
@SuppressWarnings("unused")
3434
public static class ApplyAdvice {
3535

3636
@Advice.OnMethodEnter(suppress = Throwable.class)
37-
public static void onEnter() {
38-
// when routing dsl uses concat(path(...) {...}, path(...) {...}) we'll restore the currently
39-
// matched route after each matcher so that match attempts that failed wouldn't get recorded
40-
// in the route
41-
PekkoRouteHolder.save();
42-
}
43-
44-
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
45-
public static void onExit(
46-
@Advice.Argument(value = 2) RequestContext ctx,
47-
@Advice.Return(readOnly = false) Future<RouteResult> future,
48-
@Advice.Thrown Throwable throwable) {
49-
if (throwable != null) {
50-
PekkoRouteHolder.restore();
51-
} else {
52-
future = future.andThen(new RestoreOnExit(), ctx.executionContext());
53-
}
54-
}
55-
}
56-
57-
@SuppressWarnings("unused")
58-
public static class Apply2Advice {
59-
60-
@Advice.OnMethodEnter(suppress = Throwable.class)
61-
public static void onEnter() {
62-
PekkoRouteHolder.reset();
37+
public static void onEnter(
38+
@Advice.Argument(value = 0, readOnly = false)
39+
Function1<RequestContext, Future<RouteResult>> route) {
40+
route = new PekkoRouteWrapper(route);
6341
}
6442
}
6543
}

0 commit comments

Comments
 (0)