Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix vert.x route containing dupicate segments when RoutingContext.next is used #12260

Merged
merged 2 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.vertx;

import static io.opentelemetry.context.ContextKey.named;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.context.ImplicitContextKeyed;

public final class RouteHolder implements ImplicitContextKeyed {
private static final ContextKey<RouteHolder> KEY = named("opentelemetry-vertx-route");

private String route;

private RouteHolder(String route) {
this.route = route;
}

public static Context with(Context context, String route) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe rename to withIfNotExists?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or simply init?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed to init because init is in a couple of other ImplicitContextKeyed implementations

if (context.get(KEY) != null) {
return context;
}
return context.with(new RouteHolder(route));
}

public static String get(Context context) {
RouteHolder holder = context.get(KEY);
return holder != null ? holder.route : null;
}

public static void set(Context context, String route) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe rename to updateIfExists?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are similar set methods in other ImplicitContextKeyed implementations. If we are going to rename then we should rename all the similar usages and probably the get method also. I think the holder != null check is just defensive coding. It really should not be null unless the instrumented code has changed in an unexpected way, so the IfExists suffix isn't really necessary in my opinion.

RouteHolder holder = context.get(KEY);
if (holder != null) {
holder.route = route;
}
}

@Override
public Context storeInContext(Context context) {
return context.with(KEY, this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@

package io.opentelemetry.javaagent.instrumentation.vertx;

import static io.opentelemetry.context.ContextKey.named;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute;
Expand All @@ -24,8 +21,6 @@
/** This is used to wrap Vert.x Handlers to provide nice user-friendly SERVER span names */
public final class RoutingContextHandlerWrapper implements Handler<RoutingContext> {

private static final ContextKey<String> ROUTE_KEY = named("opentelemetry-vertx-route");

private final Handler<RoutingContext> handler;

public RoutingContextHandlerWrapper(Handler<RoutingContext> handler) {
Expand All @@ -35,13 +30,15 @@ public RoutingContextHandlerWrapper(Handler<RoutingContext> handler) {
@Override
public void handle(RoutingContext context) {
Context otelContext = Context.current();
// remember currently set route so it could be restored
RoutingContextUtil.setRoute(context, RouteHolder.get(otelContext));
String route = getRoute(otelContext, context);
if (route != null && route.endsWith("/")) {
route = route.substring(0, route.length() - 1);
}
HttpServerRoute.update(otelContext, HttpServerRouteSource.NESTED_CONTROLLER, route);

try (Scope ignore = otelContext.with(ROUTE_KEY, route).makeCurrent()) {
try (Scope ignore = RouteHolder.with(otelContext, route).makeCurrent()) {
handler.handle(context);
} catch (Throwable throwable) {
Span serverSpan = LocalRootSpan.fromContextOrNull(otelContext);
Expand All @@ -54,7 +51,7 @@ public void handle(RoutingContext context) {

private static String getRoute(Context otelContext, RoutingContext routingContext) {
String route = routingContext.currentRoute().getPath();
String existingRoute = otelContext.get(ROUTE_KEY);
String existingRoute = RouteHolder.get(otelContext);
return existingRoute != null ? existingRoute + route : route;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.vertx;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments;

import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.vertx.ext.web.RoutingContext;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

public class RoutingContextInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("io.vertx.ext.web.RoutingContext");
}

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return implementsInterface(named("io.vertx.ext.web.RoutingContext"));
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isPublic().and(named("next")).and(takesNoArguments()),
this.getClass().getName() + "$NextAdvice");
}

@SuppressWarnings("unused")
public static class NextAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void next(@Advice.This RoutingContext routingContext) {
// calling next tells router to move to the next matching route
// restore remembered route to remove currently matched route from it
String previousRoute = RoutingContextUtil.getRoute(routingContext);
if (previousRoute != null) {
RouteHolder.set(Java8BytecodeBridge.currentContext(), previousRoute);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.vertx;

import io.opentelemetry.instrumentation.api.util.VirtualField;
import io.vertx.ext.web.RoutingContext;

public final class RoutingContextUtil {
private static final VirtualField<RoutingContext, String> routeField =
VirtualField.find(RoutingContext.class, String.class);

public static void setRoute(RoutingContext routingContext, String route) {
routeField.set(routingContext, route);
}

public static String getRoute(RoutingContext routingContext) {
return routeField.get(routingContext);
}

private RoutingContextUtil() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

package io.opentelemetry.javaagent.instrumentation.vertx;

import static java.util.Collections.singletonList;
import static java.util.Arrays.asList;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
Expand All @@ -21,6 +21,6 @@ public VertxWebInstrumentationModule() {

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new RouteInstrumentation());
return asList(new RouteInstrumentation(), new RoutingContextInstrumentation());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public abstract class AbstractVertxWebServer extends AbstractVerticle {
public Router buildRouter() {
Router router = Router.router(vertx);

// verify that calling RoutingContext::next doesn't affect http.route
router.route(SUCCESS.getPath()).handler(RoutingContext::next);
//noinspection Convert2Lambda
router
.route(SUCCESS.getPath())
Expand Down
Loading