Migrate Spring Web{flux} instrumentation to Decorator

This commit is contained in:
Tyler Benson 2019-03-11 16:41:24 -07:00
parent d31965ff5a
commit d30d715dc8
14 changed files with 223 additions and 127 deletions

View File

@ -122,19 +122,27 @@ public abstract class BaseDecorator {
* @return * @return
*/ */
public String spanNameForMethod(final Method method) { public String spanNameForMethod(final Method method) {
final Class<?> declaringClass = method.getDeclaringClass(); return spanNameForClass(method.getDeclaringClass()) + "." + method.getName();
String className; }
if (declaringClass.isAnonymousClass()) {
className = declaringClass.getName(); /**
if (declaringClass.getPackage() != null) { * This method is used to generate an acceptable span (operation) name based on a given class
final String pkgName = declaringClass.getPackage().getName(); * reference. Anonymous classes are named based on their parent.
if (!pkgName.isEmpty()) { *
className = declaringClass.getName().replace(pkgName, "").substring(1); * @param clazz
} * @return
} */
} else { public String spanNameForClass(final Class clazz) {
className = declaringClass.getSimpleName(); if (!clazz.isAnonymousClass()) {
return clazz.getSimpleName();
} }
return className + "." + method.getName(); String className = clazz.getName();
if (clazz.getPackage() != null) {
final String pkgName = clazz.getPackage().getName();
if (!pkgName.isEmpty()) {
className = clazz.getName().replace(pkgName, "").substring(1);
}
}
return className;
} }
} }

View File

@ -1,6 +1,7 @@
package datadog.trace.instrumentation.springweb; package datadog.trace.instrumentation.springweb;
import static io.opentracing.log.Fields.ERROR_OBJECT; import static datadog.trace.instrumentation.springweb.SpringWebHttpServerDecorator.DECORATE;
import static datadog.trace.instrumentation.springweb.SpringWebHttpServerDecorator.DECORATE_RENDER;
import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isProtected; import static net.bytebuddy.matcher.ElementMatchers.isProtected;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
@ -11,10 +12,8 @@ import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.Instrumenter;
import io.opentracing.Scope; import io.opentracing.Scope;
import io.opentracing.Span; import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.tag.Tags; import io.opentracing.tag.Tags;
import io.opentracing.util.GlobalTracer; import io.opentracing.util.GlobalTracer;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.Advice;
@ -35,6 +34,17 @@ public final class DispatcherServletInstrumentation extends Instrumenter.Default
return named("org.springframework.web.servlet.DispatcherServlet"); return named("org.springframework.web.servlet.DispatcherServlet");
} }
@Override
public String[] helperClassNames() {
return new String[] {
"datadog.trace.agent.decorator.BaseDecorator",
"datadog.trace.agent.decorator.ServerDecorator",
"datadog.trace.agent.decorator.HttpServerDecorator",
packageName + ".SpringWebHttpServerDecorator",
packageName + ".SpringWebHttpServerDecorator$1",
};
}
@Override @Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() { public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
final Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>(); final Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>();
@ -57,29 +67,17 @@ public final class DispatcherServletInstrumentation extends Instrumenter.Default
@Advice.OnMethodEnter(suppress = Throwable.class) @Advice.OnMethodEnter(suppress = Throwable.class)
public static Scope startSpan(@Advice.Argument(0) final ModelAndView mv) { public static Scope startSpan(@Advice.Argument(0) final ModelAndView mv) {
final Scope scope = GlobalTracer.get().buildSpan("response.render").startActive(true);
final Tracer.SpanBuilder builder = DECORATE_RENDER.afterStart(scope);
GlobalTracer.get() DECORATE_RENDER.onRender(scope, mv);
.buildSpan("response.render") return scope;
.withTag(Tags.COMPONENT.getKey(), "spring-webmvc");
if (mv.getViewName() != null) {
builder.withTag("view.name", mv.getViewName());
}
if (mv.getView() != null) {
builder.withTag("view.type", mv.getView().getClass().getName());
}
return builder.startActive(true);
} }
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan( public static void stopSpan(
@Advice.Enter final Scope scope, @Advice.Thrown final Throwable throwable) { @Advice.Enter final Scope scope, @Advice.Thrown final Throwable throwable) {
DECORATE_RENDER.onError(scope, throwable);
if (throwable != null) { DECORATE_RENDER.beforeFinish(scope);
final Span span = scope.span();
Tags.ERROR.set(span, true);
span.log(Collections.singletonMap(ERROR_OBJECT, throwable));
}
scope.close(); scope.close();
} }
} }
@ -90,7 +88,7 @@ public final class DispatcherServletInstrumentation extends Instrumenter.Default
final Scope scope = GlobalTracer.get().scopeManager().active(); final Scope scope = GlobalTracer.get().scopeManager().active();
if (scope != null && exception != null) { if (scope != null && exception != null) {
final Span span = scope.span(); final Span span = scope.span();
span.log(Collections.singletonMap(ERROR_OBJECT, exception)); DECORATE.onError(span, exception);
// We want to capture the stacktrace, but that doesn't mean it should be an error. // We want to capture the stacktrace, but that doesn't mean it should be an error.
// We rely on a decorator to set the error state based on response code. (5xx -> error) // We rely on a decorator to set the error state based on response code. (5xx -> error)
Tags.ERROR.set(span, false); Tags.ERROR.set(span, false);

View File

@ -1,7 +1,7 @@
package datadog.trace.instrumentation.springweb; package datadog.trace.instrumentation.springweb;
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType; import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static io.opentracing.log.Fields.ERROR_OBJECT; import static datadog.trace.instrumentation.springweb.SpringWebHttpServerDecorator.DECORATE;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isMethod;
@ -14,11 +14,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import com.google.auto.service.AutoService; import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.api.DDSpanTypes;
import datadog.trace.api.DDTags;
import io.opentracing.Scope; import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.tag.Tags;
import io.opentracing.util.GlobalTracer; import io.opentracing.util.GlobalTracer;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Map; import java.util.Map;
@ -30,7 +26,6 @@ import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatcher;
import org.springframework.web.HttpRequestHandler; import org.springframework.web.HttpRequestHandler;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.Controller; import org.springframework.web.servlet.mvc.Controller;
@AutoService(Instrumenter.class) @AutoService(Instrumenter.class)
@ -46,6 +41,17 @@ public final class HandlerAdapterInstrumentation extends Instrumenter.Default {
.and(safeHasSuperType(named("org.springframework.web.servlet.HandlerAdapter"))); .and(safeHasSuperType(named("org.springframework.web.servlet.HandlerAdapter")));
} }
@Override
public String[] helperClassNames() {
return new String[] {
"datadog.trace.agent.decorator.BaseDecorator",
"datadog.trace.agent.decorator.ServerDecorator",
"datadog.trace.agent.decorator.HttpServerDecorator",
packageName + ".SpringWebHttpServerDecorator",
packageName + ".SpringWebHttpServerDecorator$1",
};
}
@Override @Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() { public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap( return singletonMap(
@ -66,15 +72,8 @@ public final class HandlerAdapterInstrumentation extends Instrumenter.Default {
// Name the parent span based on the matching pattern // Name the parent span based on the matching pattern
// This is likely the servlet.request span. // This is likely the servlet.request span.
final Scope parentScope = GlobalTracer.get().scopeManager().active(); final Scope parentScope = GlobalTracer.get().scopeManager().active();
if (parentScope != null && request != null) { if (parentScope != null) {
final String method = request.getMethod(); DECORATE.onRequest(parentScope.span(), request);
final Object bestMatchingPattern =
request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
if (method != null && bestMatchingPattern != null) {
final String resourceName = method + " " + bestMatchingPattern;
parentScope.span().setTag(DDTags.RESOURCE_NAME, resourceName);
parentScope.span().setTag(DDTags.SPAN_TYPE, DDSpanTypes.HTTP_SERVER);
}
} }
// Now create a span for controller execution. // Now create a span for controller execution.
@ -105,33 +104,18 @@ public final class HandlerAdapterInstrumentation extends Instrumenter.Default {
methodName = "<annotation>"; methodName = "<annotation>";
} }
String className = clazz.getSimpleName(); final String operationName = DECORATE.spanNameForClass(clazz) + "." + methodName;
if (className.isEmpty()) {
className = clazz.getName();
if (clazz.getPackage() != null) {
final String pkgName = clazz.getPackage().getName();
if (!pkgName.isEmpty()) {
className = clazz.getName().replace(pkgName, "").substring(1);
}
}
}
final String operationName = className + "." + methodName; final Scope scope = GlobalTracer.get().buildSpan(operationName).startActive(true);
DECORATE.afterStart(scope);
return GlobalTracer.get() return scope;
.buildSpan(operationName)
.withTag(Tags.COMPONENT.getKey(), "spring-web-controller")
.startActive(true);
} }
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan( public static void stopSpan(
@Advice.Enter final Scope scope, @Advice.Thrown final Throwable throwable) { @Advice.Enter final Scope scope, @Advice.Thrown final Throwable throwable) {
if (throwable != null) { DECORATE.onError(scope, throwable);
final Span span = scope.span(); DECORATE.beforeFinish(scope);
Tags.ERROR.set(span, true);
span.log(singletonMap(ERROR_OBJECT, throwable));
}
scope.close(); scope.close();
} }
} }

View File

@ -0,0 +1,87 @@
package datadog.trace.instrumentation.springweb;
import datadog.trace.agent.decorator.HttpServerDecorator;
import datadog.trace.api.DDSpanTypes;
import datadog.trace.api.DDTags;
import io.opentracing.Scope;
import io.opentracing.Span;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
@Slf4j
public class SpringWebHttpServerDecorator
extends HttpServerDecorator<HttpServletRequest, HttpServletResponse> {
public static final SpringWebHttpServerDecorator DECORATE = new SpringWebHttpServerDecorator();
public static final SpringWebHttpServerDecorator DECORATE_RENDER =
new SpringWebHttpServerDecorator() {
@Override
protected String component() {
return "spring-webmvc";
}
};
@Override
protected String[] instrumentationNames() {
return new String[] {"spring-web"};
}
@Override
protected String component() {
return "spring-web-controller";
}
@Override
protected String method(final HttpServletRequest httpServletRequest) {
return httpServletRequest.getMethod();
}
@Override
protected String url(final HttpServletRequest httpServletRequest) {
return httpServletRequest.getRequestURL().toString();
}
@Override
protected String hostname(final HttpServletRequest httpServletRequest) {
return httpServletRequest.getServerName();
}
@Override
protected Integer port(final HttpServletRequest httpServletRequest) {
return httpServletRequest.getServerPort();
}
@Override
protected Integer status(final HttpServletResponse httpServletResponse) {
return httpServletResponse.getStatus();
}
@Override
public Span onRequest(final Span span, final HttpServletRequest request) {
super.onRequest(span, request);
if (request != null) {
final String method = request.getMethod();
final Object bestMatchingPattern =
request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
if (method != null && bestMatchingPattern != null) {
final String resourceName = method + " " + bestMatchingPattern;
span.setTag(DDTags.RESOURCE_NAME, resourceName);
span.setTag(DDTags.SPAN_TYPE, DDSpanTypes.HTTP_SERVER);
}
}
return span;
}
public Scope onRender(final Scope scope, final ModelAndView mv) {
final Span span = scope.span();
if (mv.getViewName() != null) {
span.setTag("view.name", mv.getViewName());
}
if (mv.getView() != null) {
span.setTag("view.type", mv.getView().getClass().getName());
}
return scope;
}
}

View File

@ -368,10 +368,12 @@ class SpringBootBasedTest extends AgentTestRunner {
serviceName "unnamed-java-app" serviceName "unnamed-java-app"
operationName name operationName name
resourceName name resourceName name
spanType DDSpanTypes.HTTP_SERVER
childOf(trace.span(0)) childOf(trace.span(0))
errored errorType != null errored errorType != null
tags { tags {
"$Tags.COMPONENT.key" "spring-web-controller" "$Tags.COMPONENT.key" "spring-web-controller"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
if (errorType) { if (errorType) {
"error.msg" String "error.msg" String
errorTags(errorType) errorTags(errorType)

View File

@ -4,8 +4,6 @@ import datadog.trace.agent.tooling.Instrumenter;
public abstract class AbstractWebfluxInstrumentation extends Instrumenter.Default { public abstract class AbstractWebfluxInstrumentation extends Instrumenter.Default {
public static final String PACKAGE = AbstractWebfluxInstrumentation.class.getPackage().getName();
public AbstractWebfluxInstrumentation(final String... additionalNames) { public AbstractWebfluxInstrumentation(final String... additionalNames) {
super("spring-webflux", additionalNames); super("spring-webflux", additionalNames);
} }
@ -13,11 +11,14 @@ public abstract class AbstractWebfluxInstrumentation extends Instrumenter.Defaul
@Override @Override
public String[] helperClassNames() { public String[] helperClassNames() {
return new String[] { return new String[] {
"datadog.trace.agent.decorator.BaseDecorator",
"datadog.trace.agent.decorator.ServerDecorator",
packageName + ".SpringWebfluxHttpServerDecorator",
// Some code comes from reactor's instrumentation's helper // Some code comes from reactor's instrumentation's helper
"datadog.trace.instrumentation.reactor.core.ReactorCoreAdviceUtils", "datadog.trace.instrumentation.reactor.core.ReactorCoreAdviceUtils",
"datadog.trace.instrumentation.reactor.core.ReactorCoreAdviceUtils$TracingSubscriber", "datadog.trace.instrumentation.reactor.core.ReactorCoreAdviceUtils$TracingSubscriber",
PACKAGE + ".AdviceUtils", packageName + ".AdviceUtils",
PACKAGE + ".RouteOnSuccessOrError" packageName + ".RouteOnSuccessOrError"
}; };
} }
} }

View File

@ -31,6 +31,6 @@ public final class DispatcherHandlerInstrumentation extends AbstractWebfluxInstr
.and(takesArgument(0, named("org.springframework.web.server.ServerWebExchange"))) .and(takesArgument(0, named("org.springframework.web.server.ServerWebExchange")))
.and(takesArguments(1)), .and(takesArguments(1)),
// Cannot reference class directly here because it would lead to class load failure on Java7 // Cannot reference class directly here because it would lead to class load failure on Java7
PACKAGE + ".DispatcherHandlerAdvice"); packageName + ".DispatcherHandlerAdvice");
} }
} }

View File

@ -38,6 +38,6 @@ public final class HandlerAdapterInstrumentation extends AbstractWebfluxInstrume
.and(takesArgument(1, named("java.lang.Object"))) .and(takesArgument(1, named("java.lang.Object")))
.and(takesArguments(2)), .and(takesArguments(2)),
// Cannot reference class directly here because it would lead to class load failure on Java7 // Cannot reference class directly here because it would lead to class load failure on Java7
PACKAGE + ".HandlerAdapterAdvice"); packageName + ".HandlerAdapterAdvice");
} }
} }

View File

@ -45,6 +45,6 @@ public final class RouterFunctionInstrumentation extends AbstractWebfluxInstrume
0, named("org.springframework.web.reactive.function.server.ServerRequest"))) 0, named("org.springframework.web.reactive.function.server.ServerRequest")))
.and(takesArguments(1)), .and(takesArguments(1)),
// Cannot reference class directly here because it would lead to class load failure on Java7 // Cannot reference class directly here because it would lead to class load failure on Java7
PACKAGE + ".RouterFunctionAdvice"); packageName + ".RouterFunctionAdvice");
} }
} }

View File

@ -1,5 +1,7 @@
package datadog.trace.instrumentation.springwebflux; package datadog.trace.instrumentation.springwebflux;
import static datadog.trace.instrumentation.springwebflux.SpringWebfluxHttpServerDecorator.DECORATE;
import datadog.trace.instrumentation.reactor.core.ReactorCoreAdviceUtils; import datadog.trace.instrumentation.reactor.core.ReactorCoreAdviceUtils;
import io.opentracing.Span; import io.opentracing.Span;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -11,10 +13,10 @@ public class AdviceUtils {
public static final String SPAN_ATTRIBUTE = "datadog.trace.instrumentation.springwebflux.Span"; public static final String SPAN_ATTRIBUTE = "datadog.trace.instrumentation.springwebflux.Span";
public static final String PARENT_SPAN_ATTRIBUTE = public static final String PARENT_SPAN_ATTRIBUTE =
"datadog.trace.instrumentation.springwebflux.ParentSpan"; "datadog.trace.instrumentation.springwebflux.ParentSpan";
public static String parseOperationName(final Object handler) { public static String parseOperationName(final Object handler) {
final String className = parseClassName(handler.getClass()); final String className = DECORATE.spanNameForClass(handler.getClass());
final String operationName; final String operationName;
final int lambdaIdx = className.indexOf("$$Lambda$"); final int lambdaIdx = className.indexOf("$$Lambda$");
@ -26,29 +28,15 @@ public class AdviceUtils {
return operationName; return operationName;
} }
public static String parseClassName(final Class clazz) { public static void finishSpanIfPresent(
String className = clazz.getSimpleName(); final ServerWebExchange exchange, final Throwable throwable) {
if (className.isEmpty()) { ReactorCoreAdviceUtils.finishSpanIfPresent(
className = clazz.getName(); (Span) exchange.getAttributes().remove(SPAN_ATTRIBUTE), throwable);
if (clazz.getPackage() != null) {
final String pkgName = clazz.getPackage().getName();
if (!pkgName.isEmpty()) {
className = clazz.getName().replace(pkgName, "").substring(1);
}
}
}
return className;
} }
public static void finishSpanIfPresent( public static void finishSpanIfPresent(
final ServerWebExchange exchange, final Throwable throwable) { final ServerRequest serverRequest, final Throwable throwable) {
ReactorCoreAdviceUtils.finishSpanIfPresent( ReactorCoreAdviceUtils.finishSpanIfPresent(
(Span) exchange.getAttributes().remove(SPAN_ATTRIBUTE), throwable); (Span) serverRequest.attributes().remove(SPAN_ATTRIBUTE), throwable);
}
public static void finishSpanIfPresent(
final ServerRequest serverRequest, final Throwable throwable) {
ReactorCoreAdviceUtils.finishSpanIfPresent(
(Span) serverRequest.attributes().remove(SPAN_ATTRIBUTE), throwable);
} }
} }

View File

@ -1,12 +1,11 @@
package datadog.trace.instrumentation.springwebflux; package datadog.trace.instrumentation.springwebflux;
import datadog.trace.api.DDSpanTypes; import static datadog.trace.instrumentation.springwebflux.SpringWebfluxHttpServerDecorator.DECORATE;
import datadog.trace.api.DDTags;
import datadog.trace.context.TraceScope; import datadog.trace.context.TraceScope;
import datadog.trace.instrumentation.reactor.core.ReactorCoreAdviceUtils; import datadog.trace.instrumentation.reactor.core.ReactorCoreAdviceUtils;
import io.opentracing.Scope; import io.opentracing.Scope;
import io.opentracing.Span; import io.opentracing.Span;
import io.opentracing.tag.Tags;
import io.opentracing.util.GlobalTracer; import io.opentracing.util.GlobalTracer;
import java.util.function.Function; import java.util.function.Function;
import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.Advice;
@ -29,12 +28,8 @@ public class DispatcherHandlerAdvice {
if (parentSpan != null) { if (parentSpan != null) {
exchange.getAttributes().put(AdviceUtils.PARENT_SPAN_ATTRIBUTE, parentSpan); exchange.getAttributes().put(AdviceUtils.PARENT_SPAN_ATTRIBUTE, parentSpan);
} }
final Scope scope = final Scope scope = GlobalTracer.get().buildSpan("DispatcherHandler.handle").startActive(false);
GlobalTracer.get() DECORATE.afterStart(scope);
.buildSpan("DispatcherHandler.handle")
.withTag(Tags.COMPONENT.getKey(), "spring-webflux-controller")
.withTag(DDTags.SPAN_TYPE, DDSpanTypes.HTTP_SERVER)
.startActive(false);
((TraceScope) scope).setAsyncPropagation(true); ((TraceScope) scope).setAsyncPropagation(true);
exchange.getAttributes().put(AdviceUtils.SPAN_ATTRIBUTE, scope.span()); exchange.getAttributes().put(AdviceUtils.SPAN_ATTRIBUTE, scope.span());
return scope; return scope;
@ -42,13 +37,13 @@ public class DispatcherHandlerAdvice {
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit( public static void methodExit(
@Advice.Enter final Scope scope, @Advice.Enter final Scope scope,
@Advice.Thrown final Throwable throwable, @Advice.Thrown final Throwable throwable,
@Advice.Argument(0) final ServerWebExchange exchange, @Advice.Argument(0) final ServerWebExchange exchange,
@Advice.Return(readOnly = false) Mono<Object> mono) { @Advice.Return(readOnly = false) Mono<Object> mono) {
if (throwable == null && mono != null) { if (throwable == null && mono != null) {
final Function<? super Mono<Object>, ? extends Publisher<Object>> function = final Function<? super Mono<Object>, ? extends Publisher<Object>> function =
ReactorCoreAdviceUtils.finishSpanNextOrError(); ReactorCoreAdviceUtils.finishSpanNextOrError();
mono = ReactorCoreAdviceUtils.setPublisherSpan(mono, scope.span()); mono = ReactorCoreAdviceUtils.setPublisherSpan(mono, scope.span());
} else if (throwable != null) { } else if (throwable != null) {
AdviceUtils.finishSpanIfPresent(exchange, throwable); AdviceUtils.finishSpanIfPresent(exchange, throwable);

View File

@ -1,5 +1,7 @@
package datadog.trace.instrumentation.springwebflux; package datadog.trace.instrumentation.springwebflux;
import static datadog.trace.instrumentation.springwebflux.SpringWebfluxHttpServerDecorator.DECORATE;
import datadog.trace.api.DDTags; import datadog.trace.api.DDTags;
import datadog.trace.context.TraceScope; import datadog.trace.context.TraceScope;
import io.opentracing.Scope; import io.opentracing.Scope;
@ -15,8 +17,8 @@ public class HandlerAdapterAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class) @Advice.OnMethodEnter(suppress = Throwable.class)
public static Scope methodEnter( public static Scope methodEnter(
@Advice.Argument(0) final ServerWebExchange exchange, @Advice.Argument(0) final ServerWebExchange exchange,
@Advice.Argument(1) final Object handler) { @Advice.Argument(1) final Object handler) {
Scope scope = null; Scope scope = null;
final Span span = exchange.getAttribute(AdviceUtils.SPAN_ATTRIBUTE); final Span span = exchange.getAttribute(AdviceUtils.SPAN_ATTRIBUTE);
@ -27,11 +29,8 @@ public class HandlerAdapterAdvice {
if (handler instanceof HandlerMethod) { if (handler instanceof HandlerMethod) {
// Special case for requests mapped with annotations // Special case for requests mapped with annotations
final HandlerMethod handlerMethod = (HandlerMethod) handler; final HandlerMethod handlerMethod = (HandlerMethod) handler;
final Class handlerClass = handlerMethod.getMethod().getDeclaringClass(); operationName = DECORATE.spanNameForMethod(handlerMethod.getMethod());
handlerType = handlerMethod.getMethod().getDeclaringClass().getName();
operationName =
AdviceUtils.parseClassName(handlerClass) + "." + handlerMethod.getMethod().getName();
handlerType = handlerClass.getName();
} else { } else {
operationName = AdviceUtils.parseOperationName(handler); operationName = AdviceUtils.parseOperationName(handler);
handlerType = handler.getClass().getName(); handlerType = handler.getClass().getName();
@ -46,11 +45,11 @@ public class HandlerAdapterAdvice {
final Span parentSpan = exchange.getAttribute(AdviceUtils.PARENT_SPAN_ATTRIBUTE); final Span parentSpan = exchange.getAttribute(AdviceUtils.PARENT_SPAN_ATTRIBUTE);
final PathPattern bestPattern = final PathPattern bestPattern =
exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
if (parentSpan != null && bestPattern != null) { if (parentSpan != null && bestPattern != null) {
parentSpan.setTag( parentSpan.setTag(
DDTags.RESOURCE_NAME, DDTags.RESOURCE_NAME,
exchange.getRequest().getMethodValue() + " " + bestPattern.getPatternString()); exchange.getRequest().getMethodValue() + " " + bestPattern.getPatternString());
} }
return scope; return scope;
@ -58,9 +57,9 @@ public class HandlerAdapterAdvice {
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit( public static void methodExit(
@Advice.Argument(0) final ServerWebExchange exchange, @Advice.Argument(0) final ServerWebExchange exchange,
@Advice.Enter final Scope scope, @Advice.Enter final Scope scope,
@Advice.Thrown final Throwable throwable) { @Advice.Thrown final Throwable throwable) {
if (throwable != null) { if (throwable != null) {
AdviceUtils.finishSpanIfPresent(exchange, throwable); AdviceUtils.finishSpanIfPresent(exchange, throwable);
} }

View File

@ -0,0 +1,26 @@
package datadog.trace.instrumentation.springwebflux;
import datadog.trace.agent.decorator.ServerDecorator;
import datadog.trace.api.DDSpanTypes;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SpringWebfluxHttpServerDecorator extends ServerDecorator {
public static final SpringWebfluxHttpServerDecorator DECORATE =
new SpringWebfluxHttpServerDecorator();
@Override
protected String[] instrumentationNames() {
return new String[] {"spring-webflux"};
}
@Override
protected String spanType() {
return DDSpanTypes.HTTP_SERVER;
}
@Override
protected String component() {
return "spring-webflux-controller";
}
}

View File

@ -62,6 +62,7 @@ class SpringWebfluxTest extends AgentTestRunner {
childOf(span(1)) childOf(span(1))
tags { tags {
"$Tags.COMPONENT.key" "spring-webflux-controller" "$Tags.COMPONENT.key" "spring-webflux-controller"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
if (annotatedMethod == null) { if (annotatedMethod == null) {
// Functional API // Functional API
"request.predicate" "(GET && $urlPathWithVariables)" "request.predicate" "(GET && $urlPathWithVariables)"
@ -135,6 +136,7 @@ class SpringWebfluxTest extends AgentTestRunner {
childOf(span(1)) childOf(span(1))
tags { tags {
"$Tags.COMPONENT.key" "spring-webflux-controller" "$Tags.COMPONENT.key" "spring-webflux-controller"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
if (annotatedMethod == null) { if (annotatedMethod == null) {
// Functional API // Functional API
"request.predicate" "(GET && $urlPathWithVariables)" "request.predicate" "(GET && $urlPathWithVariables)"
@ -234,6 +236,7 @@ class SpringWebfluxTest extends AgentTestRunner {
errored true errored true
tags { tags {
"$Tags.COMPONENT.key" "spring-webflux-controller" "$Tags.COMPONENT.key" "spring-webflux-controller"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"handler.type" "org.springframework.web.reactive.resource.ResourceWebHandler" "handler.type" "org.springframework.web.reactive.resource.ResourceWebHandler"
errorTags(ResponseStatusException, String) errorTags(ResponseStatusException, String)
defaultTags() defaultTags()
@ -265,6 +268,7 @@ class SpringWebfluxTest extends AgentTestRunner {
childOf(span(1)) childOf(span(1))
tags { tags {
"$Tags.COMPONENT.key" "spring-webflux-controller" "$Tags.COMPONENT.key" "spring-webflux-controller"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"request.predicate" "(POST && /echo)" "request.predicate" "(POST && /echo)"
"handler.type" { String tagVal -> "handler.type" { String tagVal ->
return tagVal.contains(EchoHandlerFunction.getName()) return tagVal.contains(EchoHandlerFunction.getName())
@ -348,6 +352,7 @@ class SpringWebfluxTest extends AgentTestRunner {
errored true errored true
tags { tags {
"$Tags.COMPONENT.key" "spring-webflux-controller" "$Tags.COMPONENT.key" "spring-webflux-controller"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
if (annotatedMethod == null) { if (annotatedMethod == null) {
// Functional API // Functional API
"request.predicate" "(GET && $urlPathWithVariables)" "request.predicate" "(GET && $urlPathWithVariables)"
@ -412,6 +417,7 @@ class SpringWebfluxTest extends AgentTestRunner {
childOf(span(0)) childOf(span(0))
tags { tags {
"$Tags.COMPONENT.key" "spring-webflux-controller" "$Tags.COMPONENT.key" "spring-webflux-controller"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"request.predicate" "(GET && /double-greet-redirect)" "request.predicate" "(GET && /double-greet-redirect)"
"handler.type" { String tagVal -> "handler.type" { String tagVal ->
return (tagVal.contains(INNER_HANDLER_FUNCTION_CLASS_TAG_PREFIX) return (tagVal.contains(INNER_HANDLER_FUNCTION_CLASS_TAG_PREFIX)
@ -429,6 +435,7 @@ class SpringWebfluxTest extends AgentTestRunner {
childOf(span(1)) childOf(span(1))
tags { tags {
"$Tags.COMPONENT.key" "spring-webflux-controller" "$Tags.COMPONENT.key" "spring-webflux-controller"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"request.predicate" "(GET && /double-greet)" "request.predicate" "(GET && /double-greet)"
"handler.type" { String tagVal -> "handler.type" { String tagVal ->
return tagVal.contains(INNER_HANDLER_FUNCTION_CLASS_TAG_PREFIX) return tagVal.contains(INNER_HANDLER_FUNCTION_CLASS_TAG_PREFIX)
@ -485,6 +492,7 @@ class SpringWebfluxTest extends AgentTestRunner {
childOf(span(1)) childOf(span(1))
tags { tags {
"$Tags.COMPONENT.key" "spring-webflux-controller" "$Tags.COMPONENT.key" "spring-webflux-controller"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
if (annotatedMethod == null) { if (annotatedMethod == null) {
// Functional API // Functional API
"request.predicate" "(GET && $urlPathWithVariables)" "request.predicate" "(GET && $urlPathWithVariables)"