Address comments & add better resource naming for webflux spans.
Spans will now be named with the controller class name and method for request mapping (annotation based API), and lambda functions will be named with target class.lambda.
MODIFY
dd-java-agent/instrumentation/spring-webflux/spring-webflux.gradle
dd-java-agent/instrumentation/spring-webflux/spring-webflux.gradle
@@ -15,11 +15,6 @@ sourceSets {
}
}
-compileMain_java8Java {
- sourceCompatibility = 1.8
- targetCompatibility = 1.8
-}
-
apply plugin: 'org.unbroken-dome.test-sets'
testSets {
@@ -28,6 +23,21 @@ testSets {
}
}
+compileTestJava {
+ sourceCompatibility = "1.8"
+ targetCompatibility = "1.8"
+}
+
+latestDepTest {
+ sourceCompatibility = "1.8"
+ targetCompatibility = "1.8"
+}
+
+compileMain_java8Java {
+ sourceCompatibility = 1.8
+ targetCompatibility = 1.8
+}
+
dependencies {
main_java8CompileOnly group: 'org.springframework', name: 'spring-webflux', version: '5.0.0.RELEASE'
@@ -71,7 +81,3 @@ configurations.latestDepTestCompile {
testJava8Only += '**/*Test.class'
testJava8Only += '**/*TestApplication.class'
-testJava8Only += '**/EchoHandler.class'
-testJava8Only += '**/EchoHandlerFunction.class'
-testJava8Only += '**/FooModel.class'
-testJava8Only += '**/TestController.class'

MODIFY
dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/HandlerFunctionAdapterInstrumentation.java
dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/HandlerFunctionAdapterInstrumentation.java
@@ -17,12 +17,16 @@ import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.reactive.function.server.HandlerFunction;
@AutoService(Instrumenter.class)
public final class HandlerFunctionAdapterInstrumentation extends Instrumenter.Default {
+ static final String LAMBDA_CLASS_NAME = "$$Lambda$";
+
public HandlerFunctionAdapterInstrumentation() {
- super("spring-webflux");
+ super("spring-webflux", "spring-webflux-functional");
}
@Override
@@ -50,7 +54,30 @@ public final class HandlerFunctionAdapterInstrumentation extends Instrumenter.De
final Span activeSpan = GlobalTracer.get().activeSpan();
if (activeSpan != null && handler != null) {
- activeSpan.setTag("handler_function.class", handler.getClass().getName());
+ final Class clazz = handler.getClass();
+
+ String className = clazz.getSimpleName();
+ 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);
+ }
+ }
+ }
+ LoggerFactory.getLogger(HandlerFunction.class).warn(className);
+ final String operationName;
+ final int lambdaIdx = className.indexOf(LAMBDA_CLASS_NAME);
+
+ if (lambdaIdx > -1) {
+ operationName = className.substring(0, lambdaIdx) + ".lambda";
+ } else {
+ operationName = className + ".handle";
+ }
+ activeSpan.setOperationName(operationName);
+ activeSpan.setTag("handler.type", clazz.getName());
+
if (throwable != null) {
Tags.ERROR.set(activeSpan, true);
activeSpan.log(Collections.singletonMap(ERROR_OBJECT, throwable));

ADD
dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/RequestMappingInfoHandlerMappingInstrumentation.java
+package datadog.trace.instrumentation.springwebflux;
+
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.isProtected;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+import com.google.auto.service.AutoService;
+import datadog.trace.agent.tooling.Instrumenter;
+import java.util.Collections;
+import java.util.Map;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+// Provides a way to get the URL with path variables
+@AutoService(Instrumenter.class)
+public final class RequestMappingInfoHandlerMappingInstrumentation extends Instrumenter.Default {
+
+ public static final String PACKAGE =
+ RequestMappingInfoHandlerMappingInstrumentation.class.getPackage().getName();
+
+ public RequestMappingInfoHandlerMappingInstrumentation() {
+ super("spring-webflux", "spring-webflux-annotation");
+ }
+
+ @Override
+ public String[] helperClassNames() {
+ return new String[] {PACKAGE + ".DispatcherHandlerMonoBiConsumer"};
+ }
+
+ @Override
+ public ElementMatcher<TypeDescription> typeMatcher() {
+ return named("org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping");
+ }
+
+ @Override
+ public Map<ElementMatcher, String> transformers() {
+ return Collections.<ElementMatcher, String>singletonMap(
+ isMethod()
+ .and(isProtected())
+ .and(named("handleMatch"))
+ .and(
+ takesArgument(
+ 0, named("org.springframework.web.reactive.result.method.RequestMappingInfo")))
+ .and(takesArgument(1, named("org.springframework.web.method.HandlerMethod")))
+ .and(takesArgument(2, named("org.springframework.web.server.ServerWebExchange")))
+ .and(takesArguments(3)),
+ // Cannot reference class directly here because it would lead to class load failure on Java7
+ PACKAGE + ".RequestMappingInfoHandlerMappingAdvice");
+ }
+}

MODIFY
dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/RouterFunctionInstrumentation.java
dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/RouterFunctionInstrumentation.java
@@ -23,15 +23,12 @@ public final class RouterFunctionInstrumentation extends Instrumenter.Default {
public static final String PACKAGE = RouterFunctionInstrumentation.class.getPackage().getName();
public RouterFunctionInstrumentation() {
- super("spring-webflux");
+ super("spring-webflux", "spring-webflux-functional");
}
@Override
public String[] helperClassNames() {
- return new String[] {
- RouterFunctionInstrumentation.class.getPackage().getName()
- + ".DispatcherHandlerMonoBiConsumer"
- };
+ return new String[] {PACKAGE + ".DispatcherHandlerMonoBiConsumer"};
}
@Override

ADD
dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/RequestMappingInfoHandlerMappingAdvice.java
+package datadog.trace.instrumentation.springwebflux;
+
+import static io.opentracing.log.Fields.ERROR_OBJECT;
+
+import io.opentracing.Scope;
+import io.opentracing.Span;
+import io.opentracing.tag.Tags;
+import io.opentracing.util.GlobalTracer;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import net.bytebuddy.asm.Advice;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.reactive.HandlerMapping;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.util.pattern.PathPattern;
+
+public class RequestMappingInfoHandlerMappingAdvice {
+
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
+ public static void nameSpan(
+ @Advice.Argument(1) final HandlerMethod handlerMethod,
+ @Advice.Argument(2) final ServerWebExchange serverWebExchange,
+ @Advice.Thrown final Throwable throwable) {
+
+ final Scope scope = GlobalTracer.get().scopeManager().active();
+ if (scope != null) {
+ final Span span = scope.span();
+ final PathPattern bestPattern =
+ serverWebExchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
+
+ if (bestPattern != null) {
+ DispatcherHandlerMonoBiConsumer.setTLPathUrl(
+ serverWebExchange.getRequest().getMethodValue() + " " + bestPattern.getPatternString());
+ }
+
+ if (handlerMethod != null) {
+ final Method method;
+ final Class clazz;
+ final String methodName;
+ clazz = handlerMethod.getMethod().getDeclaringClass();
+ method = handlerMethod.getMethod();
+ methodName = method.getName();
+ span.setTag("handler.type", clazz.getName());
+
+ String className = clazz.getSimpleName();
+ 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);
+ }
+ }
+ }
+ span.setOperationName(className + "." + methodName);
+ }
+
+ if (throwable != null) {
+ Tags.ERROR.set(span, true);
+ span.log(Collections.singletonMap(ERROR_OBJECT, throwable));
+ }
+ }
+ }
+}

MODIFY
dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/RouterFunctionAdvice.java
dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/RouterFunctionAdvice.java
@@ -39,12 +39,7 @@ public class RouterFunctionAdvice {
// names that arise from webflux error handling
final String resourceName =
- predicateString
- .replace("&&", "")
- .replace("||", "")
- .replace("(", "")
- .replace(")", "")
- .replaceAll("[ \\t]+", " ");
+ predicateString.replaceAll("[\\(\\)&|]", "").replaceAll("[ \\t]+", " ");
// to be used as resource name by netty span, most likely
DispatcherHandlerMonoBiConsumer.setTLPathUrl(resourceName);
@@ -52,7 +47,7 @@ public class RouterFunctionAdvice {
// should be the dispatcher handler span
final Scope activeScope = GlobalTracer.get().scopeManager().active();
if (activeScope != null) {
- activeScope.span().setTag("predicate_string", predicateString);
+ activeScope.span().setTag("request.predicate", predicateString);
activeScope.span().setTag(DDTags.SPAN_TYPE, DDSpanTypes.HTTP_SERVER);
}
}

MODIFY
dd-java-agent/instrumentation/spring-webflux/src/test/groovy/SpringWebfluxTest.groovy
dd-java-agent/instrumentation/spring-webflux/src/test/groovy/SpringWebfluxTest.groovy
@@ -2,9 +2,10 @@ import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.utils.OkHttpUtils
import datadog.trace.api.DDSpanTypes
import datadog.trace.api.DDTags
-import datadog.trace.instrumentation.springwebflux.EchoHandler
+import datadog.trace.instrumentation.springwebflux.EchoHandlerFunction
import datadog.trace.instrumentation.springwebflux.FooModel
import datadog.trace.instrumentation.springwebflux.SpringWebFluxTestApplication
+import datadog.trace.instrumentation.springwebflux.TestController
import io.opentracing.tag.Tags
import okhttp3.OkHttpClient
import okhttp3.Request
@@ -21,6 +22,7 @@ class SpringWebfluxTest extends AgentTestRunner {
static final okhttp3.MediaType PLAIN_TYPE = okhttp3.MediaType.parse("text/plain; charset=utf-8")
static final String INNER_HANDLER_FUNCTION_CLASS_TAG_PREFIX = SpringWebFluxTestApplication.getName() + "\$"
+ static final String SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX = SpringWebFluxTestApplication.getSimpleName() + "\$"
@LocalServerPort
private int port
@@ -28,11 +30,10 @@ class SpringWebfluxTest extends AgentTestRunner {
OkHttpClient client = OkHttpUtils.client()
@Unroll
- def "Basic GET test #testName"() {
+ def "Basic GET test #testName to functional API"() {
setup:
String url = "http://localhost:$port/greet$urlSuffix"
def request = new Request.Builder().url(url).get().build()
-
when:
def response = client.newCall(request).execute()
@@ -42,15 +43,15 @@ class SpringWebfluxTest extends AgentTestRunner {
assertTraces(TEST_WRITER, 1) {
trace(0, 2) {
span(0) {
- resourceName "DispatcherHandler.handle"
- operationName "DispatcherHandler.handle"
+ resourceNameContains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle")
+ operationNameContains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle")
spanType DDSpanTypes.HTTP_SERVER
childOf(span(1))
tags {
"$Tags.COMPONENT.key" "spring-webflux-controller"
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
- "predicate_string" "(GET && /greet$pathVariableUrlSuffix)"
- "handler_function.class" { String tagVal ->
+ "request.predicate" "(GET && /greet$pathVariableUrlSuffix)"
+ "handler.type" { String tagVal ->
return tagVal.contains(INNER_HANDLER_FUNCTION_CLASS_TAG_PREFIX)
}
defaultTags()
@@ -80,7 +81,59 @@ class SpringWebfluxTest extends AgentTestRunner {
testName | urlSuffix | pathVariableUrlSuffix | expectedResponseBody
"without paramaters" | "" | "" | SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE
"with one parameter" | "/WORLD" | "/{name}" | SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE + " WORLD"
- "with 2 parameters" | "/World/Test1" | "/{name}/{word}" | SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE + " World Test1"
+ "with two parameters" | "/World/Test1" | "/{name}/{word}" | SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE + " World Test1"
+ }
+
+ @Unroll
+ def "Basic GET test #testName to annotations API"() {
+ setup:
+ String url = "http://localhost:$port/foo$urlSuffix"
+ def request = new Request.Builder().url(url).get().build()
+ when:
+ def response = client.newCall(request).execute()
+
+ then:
+ response.code == 200
+ response.body().string() == expectedResponseBody
+ assertTraces(TEST_WRITER, 1) {
+ trace(0, 2) {
+ span(0) {
+ resourceName TestController.getSimpleName() + ".getFooModel"
+ operationName TestController.getSimpleName() + ".getFooModel"
+ spanType DDSpanTypes.HTTP_SERVER
+ childOf(span(1))
+ tags {
+ "$Tags.COMPONENT.key" "spring-webflux-controller"
+ "$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
+ "handler.type" TestController.getName()
+ defaultTags()
+ }
+ }
+ span(1) {
+ resourceName "GET /foo$pathVariableUrlSuffix"
+ operationName "netty.request"
+ spanType DDSpanTypes.HTTP_SERVER
+ parent()
+ tags {
+ "$Tags.COMPONENT.key" "netty"
+ "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
+ "$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
+ "$Tags.PEER_HOSTNAME.key" "localhost"
+ "$Tags.PEER_PORT.key" Integer
+ "$Tags.HTTP_METHOD.key" "GET"
+ "$Tags.HTTP_STATUS.key" 200
+ "$Tags.HTTP_URL.key" url
+ defaultTags()
+ }
+ }
+ }
+ }
+
+ where:
+ testName | urlSuffix | pathVariableUrlSuffix | expectedResponseBody
+ "without parameters" | "" | "" | new FooModel(0L, "DEFAULT").toString()
+ "with one parameter" | "/1" | "/{id}" | new FooModel(1, "pass").toString()
+ "with two parameters" | "/2/world" | "/{id}/{name}" | new FooModel(2, "world").toString()
}
def "404 GET test"() {
@@ -145,16 +198,16 @@ class SpringWebfluxTest extends AgentTestRunner {
assertTraces(TEST_WRITER, 1) {
trace(0, 2) {
span(0) {
- resourceName "DispatcherHandler.handle"
- operationName "DispatcherHandler.handle"
+ resourceName EchoHandlerFunction.getSimpleName() + ".handle"
+ operationName EchoHandlerFunction.getSimpleName() + ".handle"
spanType DDSpanTypes.HTTP_SERVER
childOf(span(1))
tags {
"$Tags.COMPONENT.key" "spring-webflux-controller"
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
- "predicate_string" "(POST && /echo)"
- "handler_function.class" { String tagVal ->
- return tagVal.contains(EchoHandler.getName())
+ "request.predicate" "(POST && /echo)"
+ "handler.type" { String tagVal ->
+ return tagVal.contains(EchoHandlerFunction.getName())
}
defaultTags()
}
@@ -193,7 +246,7 @@ class SpringWebfluxTest extends AgentTestRunner {
assertTraces(TEST_WRITER, 1) {
trace(0, 2) {
span(0) {
- resourceName "GET /failfoo/?"
+ resourceName "GET /failfoo/{id}"
operationName "netty.request"
spanType DDSpanTypes.HTTP_SERVER
errored true
@@ -212,14 +265,15 @@ class SpringWebfluxTest extends AgentTestRunner {
}
}
span(1) {
- resourceName "DispatcherHandler.handle"
- operationName "DispatcherHandler.handle"
+ resourceName "TestController.getFooModelFail"
+ operationName "TestController.getFooModelFail"
spanType DDSpanTypes.HTTP_SERVER
childOf(span(0))
errored true
tags {
"$Tags.COMPONENT.key" "spring-webflux-controller"
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
+ "handler.type" TestController.getName()
errorTags(ArithmeticException, String)
defaultTags()
}
@@ -262,16 +316,16 @@ class SpringWebfluxTest extends AgentTestRunner {
}
}
span(1) {
- resourceName "DispatcherHandler.handle"
- operationName "DispatcherHandler.handle"
+ resourceNameContains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle")
+ operationNameContains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle")
spanType DDSpanTypes.HTTP_SERVER
childOf(span(0))
errored true
tags {
"$Tags.COMPONENT.key" "spring-webflux-controller"
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
- "predicate_string" "(POST && /fail-echo)"
- "handler_function.class" { String tagVal ->
+ "request.predicate" "(POST && /fail-echo)"
+ "handler.type" { String tagVal ->
return tagVal.contains(INNER_HANDLER_FUNCTION_CLASS_TAG_PREFIX)
}
errorTags(NullPointerException, String)
@@ -312,17 +366,17 @@ class SpringWebfluxTest extends AgentTestRunner {
}
}
span(1) {
- resourceName "DispatcherHandler.handle"
- operationName "DispatcherHandler.handle"
+ resourceName "RedirectComponent.lambda"
+ operationName "RedirectComponent.lambda"
spanType DDSpanTypes.HTTP_SERVER
childOf(span(0))
tags {
"$Tags.COMPONENT.key" "spring-webflux-controller"
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
- "predicate_string" "(GET && /double-greet-redirect)"
- "handler_function.class" { String tagVal -> // lambda function, loosen restriction on this tag
+ "request.predicate" "(GET && /double-greet-redirect)"
+ "handler.type" { String tagVal ->
return (tagVal.contains(INNER_HANDLER_FUNCTION_CLASS_TAG_PREFIX)
- || tagVal.contains("Lambda") || tagVal.contains("Proxy"))
+ || tagVal.contains("Lambda")) // || tagVal.contains("Proxy"))
}
defaultTags()
}
@@ -330,15 +384,15 @@ class SpringWebfluxTest extends AgentTestRunner {
}
trace(1, 2) {
span(0) {
- resourceName "DispatcherHandler.handle"
- operationName "DispatcherHandler.handle"
+ resourceNameContains(SpringWebFluxTestApplication.getSimpleName() + "\$", ".handle")
+ operationNameContains(SpringWebFluxTestApplication.getSimpleName() + "\$", ".handle")
spanType DDSpanTypes.HTTP_SERVER
childOf(span(1))
tags {
"$Tags.COMPONENT.key" "spring-webflux-controller"
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
- "predicate_string" "(GET && /double-greet)"
- "handler_function.class" { String tagVal ->
+ "request.predicate" "(GET && /double-greet)"
+ "handler.type" { String tagVal ->
return tagVal.contains(INNER_HANDLER_FUNCTION_CLASS_TAG_PREFIX)
}
defaultTags()
@@ -381,15 +435,15 @@ class SpringWebfluxTest extends AgentTestRunner {
assertTraces(TEST_WRITER, 1) {
trace(0, 2) {
span(0) {
- resourceName "DispatcherHandler.handle"
- operationName "DispatcherHandler.handle"
+ resourceNameContains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle")
+ operationNameContains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle")
spanType DDSpanTypes.HTTP_SERVER
childOf(span(1))
tags {
"$Tags.COMPONENT.key" "spring-webflux-controller"
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
- "predicate_string" "(GET && /greet-counter/{count})"
- "handler_function.class" { String tagVal ->
+ "request.predicate" "(GET && /greet-counter/{count})"
+ "handler.type" { String tagVal ->
return tagVal.contains(INNER_HANDLER_FUNCTION_CLASS_TAG_PREFIX)
}
defaultTags()
@@ -435,18 +489,19 @@ class SpringWebfluxTest extends AgentTestRunner {
assertTraces(TEST_WRITER, 1) {
trace(0, 2) {
span(0) {
- resourceName "DispatcherHandler.handle"
- operationName "DispatcherHandler.handle"
+ resourceName TestController.getSimpleName() + ".getXFooModels"
+ operationName TestController.getSimpleName() + ".getXFooModels"
spanType DDSpanTypes.HTTP_SERVER
childOf(span(1))
tags {
"$Tags.COMPONENT.key" "spring-webflux-controller"
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
+ "handler.type" TestController.getName()
defaultTags()
}
}
span(1) {
- resourceName "GET /annotation-foos/?"
+ resourceName "GET /annotation-foos/{count}"
operationName "netty.request"
spanType DDSpanTypes.HTTP_SERVER
parent()

MODIFY
dd-java-agent/instrumentation/spring-webflux/src/test/groovy/datadog/trace/instrumentation/springwebflux/SpringWebFluxTestApplication.groovy
dd-java-agent/instrumentation/spring-webflux/src/test/groovy/datadog/trace/instrumentation/springwebflux/SpringWebFluxTestApplication.groovy
@@ -40,12 +40,6 @@ class SpringWebFluxTestApplication {
}
}
- @Bean
- RouterFunction<ServerResponse> redirectRouterFunction() {
- return route(GET("/double-greet-redirect"), {
- req -> ServerResponse.temporaryRedirect(URI.create("/double-greet")).build()
- })
- }
@Bean
RouterFunction<ServerResponse> echoRouterFunction(EchoHandler echoHandler) {

MODIFY
dd-java-agent/instrumentation/spring-webflux/src/test/groovy/datadog/trace/instrumentation/springwebflux/TestController.groovy
dd-java-agent/instrumentation/spring-webflux/src/test/groovy/datadog/trace/instrumentation/springwebflux/TestController.groovy
package datadog.trace.instrumentation.springwebflux
-
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController
@@ -15,8 +14,23 @@ class TestController {
return Mono.just(new FooModel((id / 0), "fail"))
}
+ @GetMapping("/foo")
+ Mono<FooModel> getFooModel() {
+ return Mono.just(new FooModel(0L, "DEFAULT"))
+ }
+
+ @GetMapping("/foo/{id}")
+ Mono<FooModel> getFooModel(@PathVariable("id") long id) {
+ return Mono.just(new FooModel(id, "pass"))
+ }
+
+ @GetMapping("/foo/{id}/{name}")
+ Mono<FooModel> getFooModel(@PathVariable("id") long id, @PathVariable("name") String name) {
+ return Mono.just(new FooModel(id, name))
+ }
+
@GetMapping(value = "/annotation-foos/{count}")
- Flux<FooModel> getXFooModelsWithDelay(@PathVariable("count") long count) {
+ Flux<FooModel> getXFooModels(@PathVariable("count") long count) {
FooModel[] foos = FooModel.createXFooModels(count)
return Flux.just(foos)
}

ADD
dd-java-agent/instrumentation/spring-webflux/src/test/java/datadog/trace/instrumentation/springwebflux/RedirectComponent.java
+package datadog.trace.instrumentation.springwebflux;
+
+import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
+import static org.springframework.web.reactive.function.server.RouterFunctions.route;
+
+import java.net.URI;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.ServerResponse;
+
+@Component
+public class RedirectComponent {
+ @Bean
+ public RouterFunction<ServerResponse> redirectRouterFunction() {
+ return route(
+ GET("/double-greet-redirect"),
+ req -> ServerResponse.temporaryRedirect(URI.create("/double-greet")).build());
+ }
+}

MODIFY
dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/asserts/SpanAssert.groovy
dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/asserts/SpanAssert.groovy
@@ -20,6 +20,12 @@ class SpanAssert {
clone(asserter)
}
+ def assertSpanNameContains(String spanName, String... shouldContainArr) {
+ for (String shouldContain : shouldContainArr) {
+ assert spanName.contains(shouldContain)
+ }
+ }
+
def serviceName(String name) {
assert span.serviceName == name
}
@@ -28,10 +34,18 @@ class SpanAssert {
assert span.operationName == name
}
+ def operationNameContains(String... operationNameParts) {
+ assertSpanNameContains(span.operationName, operationNameParts)
+ }
+
def resourceName(String name) {
assert span.resourceName == name
}
+ def resourceNameContains(String... resourceNameParts) {
+ assertSpanNameContains(span.resourceName, resourceNameParts)
+ }
+
def spanType(String type) {
assert span.spanType == type
assert span.tags["span.type"] == type

Address comments & add better resource naming for webflux spans.

Spans will now be named with the controller class name and method for request mapping (annotation based API), and lambda functions will be named with target class.lambda.