Skip to content

Commit c096540

Browse files
authored
Fix pekko route naming (#13491)
1 parent 93013f9 commit c096540

File tree

11 files changed

+287
-175
lines changed

11 files changed

+287
-175
lines changed

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

Lines changed: 10 additions & 0 deletions
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 = PekkoRouteHolder.get(tracingRequest.context);
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

Lines changed: 0 additions & 41 deletions
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

Lines changed: 11 additions & 8 deletions
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 = PekkoRouteHolder.get(context);
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

Lines changed: 1 addition & 2 deletions
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

Lines changed: 48 additions & 39 deletions
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;
1513
import java.util.ArrayDeque;
1614
import java.util.Deque;
15+
import org.apache.pekko.http.scaladsl.model.Uri;
1716

1817
public class PekkoRouteHolder implements ImplicitContextKeyed {
1918
private 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 ArrayDeque<>();
2524

2625
public static Context init(Context context) {
2726
if (context.get(KEY) != null) {
@@ -30,51 +29,51 @@ 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;
38-
}
32+
public static PekkoRouteHolder get(Context context) {
33+
return context.get(KEY);
3934
}
4035

41-
public static void startSegment() {
42-
PekkoRouteHolder holder = Context.current().get(KEY);
43-
if (holder != null) {
44-
holder.newSegment = true;
36+
public void push(Uri.Path beforeMatch, Uri.Path afterMatch, String pathToPush) {
37+
// Only accept the suggested 'pathToPush' if:
38+
// - either this is the first match, or
39+
// - the unmatched part of the path from the previous match is what the current match
40+
// acted upon. This avoids pushes from PathMatchers that compose other PathMatchers,
41+
// instead only accepting pushes from leaf-nodes in the PathMatcher hierarchy that actually
42+
// act on the path.
43+
// AND:
44+
// - some part of the path has now been matched by this matcher
45+
if ((lastUnmatchedPath == null || lastUnmatchedPath.equals(beforeMatch))
46+
&& !afterMatch.equals(beforeMatch)) {
47+
route.append(pathToPush);
48+
lastUnmatchedPath = afterMatch;
4549
}
50+
lastWasMatched = true;
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);
54-
}
53+
public void didNotMatch() {
54+
lastWasMatched = false;
5555
}
5656

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;
57+
public void pushIfNotCompletelyMatched(String pathToPush) {
58+
if (lastUnmatchedPath != null && !lastUnmatchedPath.isEmpty()) {
59+
route.append(pathToPush);
6260
}
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 String route() {
64+
return lastWasMatched ? route.toString() : null;
65+
}
66+
67+
public void save() {
68+
savedStates.add(new State(lastUnmatchedPath, route));
69+
route = new StringBuilder(route);
7170
}
7271

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;
72+
public void restore() {
73+
State popped = savedStates.pollLast();
74+
if (popped != null) {
75+
lastUnmatchedPath = popped.lastUnmatchedPath;
76+
route = popped.route;
7877
}
7978
}
8079

@@ -84,4 +83,14 @@ public Context storeInContext(Context context) {
8483
}
8584

8685
private PekkoRouteHolder() {}
86+
87+
private static class State {
88+
private final Uri.Path lastUnmatchedPath;
89+
private final StringBuilder route;
90+
91+
private State(Uri.Path lastUnmatchedPath, StringBuilder route) {
92+
this.lastUnmatchedPath = lastUnmatchedPath;
93+
this.route = route;
94+
}
95+
}
8796
}
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 = PekkoRouteHolder.get(context);
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

Lines changed: 0 additions & 24 deletions
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

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
package io.opentelemetry.javaagent.instrumentation.pekkohttp.v1_0.server.route;
77

8+
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
89
import static net.bytebuddy.matcher.ElementMatchers.named;
910

1011
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
@@ -14,6 +15,7 @@
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 {
@@ -24,42 +26,18 @@ public ElementMatcher<TypeDescription> typeMatcher() {
2426

2527
@Override
2628
public void transform(TypeTransformer transformer) {
27-
transformer.applyAdviceToMethod(
28-
named("$anonfun$$tilde$1"), this.getClass().getName() + "$ApplyAdvice");
29-
transformer.applyAdviceToMethod(
30-
named("$anonfun$$tilde$2"), this.getClass().getName() + "$Apply2Advice");
29+
transformer.applyAdviceToMethod(isConstructor(), this.getClass().getName() + "$ApplyAdvice");
30+
transformer.applyAdviceToMethod(named("$tilde"), this.getClass().getName() + "$ApplyAdvice");
3131
}
3232

3333
@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)