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

Use HttpRouteHolder in spring-webflux instrumentation #5239

Merged
Show file tree
Hide file tree
Changes from all commits
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
Expand Up @@ -46,6 +46,24 @@ public static <REQUEST> ContextCustomizer<REQUEST> get() {

private HttpRouteHolder() {}

/**
* Updates the {@code http.route} attribute in the received {@code context}.
*
* <p>If there is a server span in the context, and the context has been customized with a {@link
* HttpRouteHolder}, then this method will update the route using the provided {@code httpRoute}
* if and only if the last {@link HttpRouteSource} to update the route using this method has
* strictly lower priority than the provided {@link HttpRouteSource}, and the pased value is
* non-null.
*
* <p>If there is a server span in the context, and the context has NOT been customized with a
* {@link HttpRouteHolder}, then this method will update the route using the provided value if it
* is non-null.
*/
public static void updateHttpRoute(
Context context, HttpRouteSource source, @Nullable String httpRoute) {
Comment on lines +62 to +63
Copy link
Member

@trask trask Jan 26, 2022

Choose a reason for hiding this comment

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

do you think this will be used by other instrumentations? also, wondering if HttpRouteGetter0(?) would be more general for this case, which takes Context but no other args, then could use (non-capturing) simple lambda context -> httpRoute in this case (though this requires naming, so ...). or maybe Consumer<Context>? sorry for just throwing random ideas here, I don't have any strong opinions on this

Copy link
Member Author

Choose a reason for hiding this comment

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

There are a few instrumentations that would make use of this method: ratpack, restlet. I thought that just passing the string would be the simplest solution in this case (and there's no naming problems with just passing a string 😄 ).

updateHttpRoute(context, source, ConstantAdapter.INSTANCE, httpRoute);
}

/**
* Updates the {@code http.route} attribute in the received {@code context}.
*
Expand Down Expand Up @@ -154,4 +172,15 @@ public String get(Context context, T arg, HttpRouteGetter<T> httpRouteGetter) {
return httpRouteGetter.get(context, arg);
}
}

private static final class ConstantAdapter implements HttpRouteGetter<String> {

private static final ConstantAdapter INSTANCE = new ConstantAdapter();

@Nullable
@Override
public String get(Context context, String route) {
return route;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ muzzle {

dependencies {
implementation(project(":instrumentation:spring:spring-webflux-5.0:library"))
bootstrap(project(":instrumentation:servlet:servlet-common:bootstrap"))

compileOnly("org.springframework:spring-webflux:5.0.0.RELEASE")
compileOnly("io.projectreactor.ipc:reactor-netty:0.7.0.RELEASE")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static io.opentelemetry.javaagent.instrumentation.spring.webflux.server.WebfluxSingletons.httpRouteGetter;
import static io.opentelemetry.javaagent.instrumentation.spring.webflux.server.WebfluxSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
Expand All @@ -16,19 +17,16 @@
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.server.ServerSpan;
import io.opentelemetry.javaagent.bootstrap.servlet.ServletContextPath;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteHolder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource;
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;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;

public class HandlerAdapterInstrumentation implements TypeInstrumentation {

Expand Down Expand Up @@ -67,14 +65,8 @@ public static void methodEnter(

Context parentContext = Context.current();

Span serverSpan = ServerSpan.fromContextOrNull(parentContext);

PathPattern bestPattern =
exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
if (serverSpan != null && bestPattern != null) {
serverSpan.updateName(
ServletContextPath.prepend(Context.current(), bestPattern.toString()));
}
HttpRouteHolder.updateHttpRoute(
parentContext, HttpRouteSource.CONTROLLER, httpRouteGetter(), exchange);

if (handler == null) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

package io.opentelemetry.javaagent.instrumentation.spring.webflux.server;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.server.ServerSpan;
import io.opentelemetry.javaagent.bootstrap.servlet.ServletContextPath;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteHolder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.RouterFunction;

Expand All @@ -21,30 +21,20 @@ public class RouteOnSuccessOrError implements BiConsumer<HandlerFunction<?>, Thr
private static final Pattern METHOD_REGEX =
Pattern.compile("^(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH) ");

private final RouterFunction<?> routerFunction;
@Nullable private final String route;

public RouteOnSuccessOrError(RouterFunction<?> routerFunction) {
this.routerFunction = routerFunction;
this.route = parseRoute(parsePredicateString(routerFunction));
}

@Override
public void accept(HandlerFunction<?> handler, Throwable throwable) {
if (handler != null) {
String predicateString = parsePredicateString();
if (predicateString != null) {
Context context = Context.current();
if (context != null) {
Span serverSpan = ServerSpan.fromContextOrNull(context);
if (serverSpan != null) {
// TODO should update SERVER span name/route using ServerSpanNaming
serverSpan.updateName(ServletContextPath.prepend(context, parseRoute(predicateString)));
}
}
}
HttpRouteHolder.updateHttpRoute(Context.current(), HttpRouteSource.CONTROLLER, route);
}
}

private String parsePredicateString() {
private static String parsePredicateString(RouterFunction<?> routerFunction) {
String routerFunctionString = routerFunction.toString();
// Router functions containing lambda predicates should not end up in span tags since they are
// confusing
Expand All @@ -56,7 +46,11 @@ private String parsePredicateString() {
}
}

private static String parseRoute(String routerString) {
@Nullable
private static String parseRoute(@Nullable String routerString) {
if (routerString == null) {
return null;
}
return METHOD_REGEX
.matcher(
SPACES_REGEX
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
import io.opentelemetry.instrumentation.api.config.ExperimentalConfig;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteGetter;
import io.opentelemetry.javaagent.instrumentation.spring.webflux.SpringWebfluxConfig;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;

public final class WebfluxSingletons {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spring-webflux-5.0";
Expand All @@ -33,5 +37,13 @@ public static Instrumenter<Object, Void> instrumenter() {
return INSTRUMENTER;
}

public static HttpRouteGetter<ServerWebExchange> httpRouteGetter() {
return (context, exchange) -> {
PathPattern bestPattern =
exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
return bestPattern == null ? null : bestPattern.getPatternString();
};
}

private WebfluxSingletons() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class SpringWebfluxTest extends AgentInstrumentationSpecification {
"$SemanticAttributes.HTTP_SCHEME" "http"
"$SemanticAttributes.HTTP_FLAVOR" "1.1"
"$SemanticAttributes.HTTP_USER_AGENT" String
"$SemanticAttributes.HTTP_ROUTE" urlPathWithVariables
}
}
span(1) {
Expand Down Expand Up @@ -157,6 +158,7 @@ class SpringWebfluxTest extends AgentInstrumentationSpecification {
"$SemanticAttributes.HTTP_SCHEME" "http"
"$SemanticAttributes.HTTP_FLAVOR" "1.1"
"$SemanticAttributes.HTTP_USER_AGENT" String
"$SemanticAttributes.HTTP_ROUTE" urlPathWithVariables
}
}
span(1) {
Expand Down Expand Up @@ -243,6 +245,7 @@ class SpringWebfluxTest extends AgentInstrumentationSpecification {
"$SemanticAttributes.HTTP_SCHEME" "http"
"$SemanticAttributes.HTTP_FLAVOR" "1.1"
"$SemanticAttributes.HTTP_USER_AGENT" String
"$SemanticAttributes.HTTP_ROUTE" urlPathWithVariables
}
}
span(1) {
Expand Down Expand Up @@ -307,6 +310,7 @@ class SpringWebfluxTest extends AgentInstrumentationSpecification {
"$SemanticAttributes.HTTP_SCHEME" "http"
"$SemanticAttributes.HTTP_FLAVOR" "1.1"
"$SemanticAttributes.HTTP_USER_AGENT" String
"$SemanticAttributes.HTTP_ROUTE" "/**"
}
}
span(1) {
Expand Down Expand Up @@ -350,6 +354,7 @@ class SpringWebfluxTest extends AgentInstrumentationSpecification {
"$SemanticAttributes.HTTP_SCHEME" "http"
"$SemanticAttributes.HTTP_FLAVOR" "1.1"
"$SemanticAttributes.HTTP_USER_AGENT" String
"$SemanticAttributes.HTTP_ROUTE" "/echo"
}
}
span(1) {
Expand Down Expand Up @@ -398,6 +403,7 @@ class SpringWebfluxTest extends AgentInstrumentationSpecification {
"$SemanticAttributes.HTTP_SCHEME" "http"
"$SemanticAttributes.HTTP_FLAVOR" "1.1"
"$SemanticAttributes.HTTP_USER_AGENT" String
"$SemanticAttributes.HTTP_ROUTE" urlPathWithVariables
}
}
span(1) {
Expand Down Expand Up @@ -461,6 +467,7 @@ class SpringWebfluxTest extends AgentInstrumentationSpecification {
"$SemanticAttributes.HTTP_SCHEME" "http"
"$SemanticAttributes.HTTP_FLAVOR" "1.1"
"$SemanticAttributes.HTTP_USER_AGENT" String
"$SemanticAttributes.HTTP_ROUTE" "/double-greet-redirect"
}
}
span(1) {
Expand Down Expand Up @@ -492,6 +499,7 @@ class SpringWebfluxTest extends AgentInstrumentationSpecification {
"$SemanticAttributes.HTTP_SCHEME" "http"
"$SemanticAttributes.HTTP_FLAVOR" "1.1"
"$SemanticAttributes.HTTP_USER_AGENT" String
"$SemanticAttributes.HTTP_ROUTE" "/double-greet"
}
}
span(1) {
Expand Down Expand Up @@ -538,6 +546,7 @@ class SpringWebfluxTest extends AgentInstrumentationSpecification {
"$SemanticAttributes.HTTP_SCHEME" "http"
"$SemanticAttributes.HTTP_FLAVOR" "1.1"
"$SemanticAttributes.HTTP_USER_AGENT" String
"$SemanticAttributes.HTTP_ROUTE" urlPathWithVariables
}
}
span(1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@

package server.base

import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.instrumentation.test.AgentTestTrait
import io.opentelemetry.instrumentation.test.base.HttpServerTest
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
import org.springframework.boot.SpringApplication
import org.springframework.context.ConfigurableApplicationContext

Expand Down Expand Up @@ -36,21 +34,14 @@ abstract class SpringWebFluxServerTest extends HttpServerTest<ConfigurableApplic
}

@Override
Set<AttributeKey<?>> httpAttributes(ServerEndpoint endpoint) {
def attributes = super.httpAttributes(endpoint)
attributes.remove(SemanticAttributes.HTTP_ROUTE)
attributes
}

@Override
String expectedServerSpanName(ServerEndpoint endpoint, String method) {
String expectedHttpRoute(ServerEndpoint endpoint) {
switch (endpoint) {
case PATH_PARAM:
return getContextPath() + "/path/{id}/param"
case NOT_FOUND:
return "/**"
default:
return endpoint.resolvePath(address).path
return super.expectedHttpRoute(endpoint)
}
}

Expand Down