Migrate Spring Web{flux} instrumentation to Decorator
This commit is contained in:
parent
d31965ff5a
commit
d30d715dc8
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)"
|
||||||
|
|
Loading…
Reference in New Issue