Skip to content

Commit 0c32f85

Browse files
authored
Add code attributes to spring webmvc controller spans (#12839)
1 parent 80ccda1 commit 0c32f85

File tree

5 files changed

+51
-24
lines changed

5 files changed

+51
-24
lines changed

instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/boot/SpringBootBasedTest.java

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE;
1212
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE;
1313
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE;
14+
import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_FUNCTION;
15+
import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_NAMESPACE;
1416
import static org.assertj.core.api.Assertions.assertThat;
1517

1618
import com.google.common.collect.ImmutableMap;
@@ -27,6 +29,7 @@
2729
import org.junit.jupiter.api.extension.RegisterExtension;
2830
import org.springframework.boot.SpringApplication;
2931
import org.springframework.context.ConfigurableApplicationContext;
32+
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
3033

3134
class SpringBootBasedTest extends AbstractSpringBootBasedTest {
3235

@@ -73,6 +76,9 @@ protected SpanDataAssert assertHandlerSpan(
7376
span.hasName(handlerSpanName)
7477
.hasKind(SpanKind.INTERNAL)
7578
.hasStatus(StatusData.error())
79+
.hasAttributesSatisfyingExactly(
80+
equalTo(CODE_NAMESPACE, ResourceHttpRequestHandler.class.getName()),
81+
equalTo(CODE_FUNCTION, "handleRequest"))
7682
.hasEventsSatisfyingExactly(
7783
event ->
7884
event
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,50 @@
55

66
package io.opentelemetry.javaagent.instrumentation.spring.webmvc;
77

8-
import io.opentelemetry.instrumentation.api.incubator.semconv.util.SpanNames;
9-
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
8+
import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter;
109
import java.lang.reflect.Method;
1110
import javax.annotation.Nullable;
1211
import org.springframework.web.HttpRequestHandler;
1312
import org.springframework.web.method.HandlerMethod;
1413
import org.springframework.web.servlet.mvc.Controller;
1514

16-
public class HandlerSpanNameExtractor implements SpanNameExtractor<Object> {
15+
public class HandlerCodeAttributesGetter implements CodeAttributesGetter<Object> {
1716

1817
@Nullable private static final Class<?> JAVAX_SERVLET = loadOrNull("javax.servlet.Servlet");
1918
@Nullable private static final Class<?> JAKARTA_SERVLET = loadOrNull("jakarta.servlet.Servlet");
2019

20+
@Nullable
2121
@Override
22-
public String extract(Object handler) {
23-
Class<?> clazz;
24-
String methodName;
22+
public Class<?> getCodeClass(Object handler) {
23+
if (handler instanceof HandlerMethod) {
24+
// name span based on the class and method name defined in the handler
25+
Method method = ((HandlerMethod) handler).getMethod();
26+
return method.getDeclaringClass();
27+
} else {
28+
return handler.getClass();
29+
}
30+
}
2531

32+
@Nullable
33+
@Override
34+
public String getMethodName(Object handler) {
2635
if (handler instanceof HandlerMethod) {
2736
// name span based on the class and method name defined in the handler
2837
Method method = ((HandlerMethod) handler).getMethod();
29-
clazz = method.getDeclaringClass();
30-
methodName = method.getName();
38+
return method.getName();
3139
} else if (handler instanceof HttpRequestHandler) {
3240
// org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter
33-
clazz = handler.getClass();
34-
methodName = "handleRequest";
41+
return "handleRequest";
3542
} else if (handler instanceof Controller) {
3643
// org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter
37-
clazz = handler.getClass();
38-
methodName = "handleRequest";
44+
return "handleRequest";
3945
} else if (isServlet(handler)) {
4046
// org.springframework.web.servlet.handler.SimpleServletHandlerAdapter
41-
clazz = handler.getClass();
42-
methodName = "service";
47+
return "service";
4348
} else {
4449
// perhaps org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
45-
clazz = handler.getClass();
46-
methodName = "<annotation>";
50+
return "<annotation>";
4751
}
48-
49-
return SpanNames.fromMethod(clazz, methodName);
5052
}
5153

5254
private static boolean isServlet(Object handler) {

instrumentation/spring/spring-webmvc/spring-webmvc-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/SpringWebMvcInstrumenterFactory.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
package io.opentelemetry.javaagent.instrumentation.spring.webmvc;
77

88
import io.opentelemetry.api.GlobalOpenTelemetry;
9+
import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor;
10+
import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeSpanNameExtractor;
911
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
1012
import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig;
1113
import org.springframework.web.servlet.ModelAndView;
@@ -19,8 +21,12 @@ public SpringWebMvcInstrumenterFactory(String instrumentationName) {
1921
}
2022

2123
public Instrumenter<Object, Void> createHandlerInstrumenter() {
24+
HandlerCodeAttributesGetter codeAttributesGetter = new HandlerCodeAttributesGetter();
2225
return Instrumenter.<Object, Void>builder(
23-
GlobalOpenTelemetry.get(), instrumentationName, new HandlerSpanNameExtractor())
26+
GlobalOpenTelemetry.get(),
27+
instrumentationName,
28+
CodeSpanNameExtractor.create(codeAttributesGetter))
29+
.addAttributesExtractor(CodeAttributesExtractor.create(codeAttributesGetter))
2430
.setEnabled(ExperimentalConfig.get().controllerTelemetryEnabled())
2531
.buildInstrumenter();
2632
}

instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/boot/AbstractSpringBootBasedTest.java

+11-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import static org.assertj.core.api.Assertions.assertThat;
2525

2626
import io.opentelemetry.api.common.AttributeKey;
27-
import io.opentelemetry.api.common.Attributes;
2827
import io.opentelemetry.api.trace.SpanKind;
2928
import io.opentelemetry.instrumentation.api.internal.HttpConstants;
3029
import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest;
@@ -46,6 +45,7 @@
4645
import org.junit.jupiter.params.provider.ValueSource;
4746
import org.springframework.context.ConfigurableApplicationContext;
4847
import org.springframework.security.web.util.OnCommittedResponseWrapper;
48+
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
4949
import org.springframework.web.servlet.view.RedirectView;
5050

5151
public abstract class AbstractSpringBootBasedTest
@@ -149,7 +149,9 @@ protected List<Consumer<SpanDataAssert>> errorPageSpanAssertions(
149149
span ->
150150
span.hasName("BasicErrorController.error")
151151
.hasKind(SpanKind.INTERNAL)
152-
.hasAttributes(Attributes.empty()));
152+
.hasAttributesSatisfyingExactly(
153+
satisfies(CODE_NAMESPACE, v -> v.endsWith(".BasicErrorController")),
154+
equalTo(CODE_FUNCTION, "error")));
153155
return spanAssertions;
154156
}
155157

@@ -196,10 +198,16 @@ protected SpanDataAssert assertRenderSpan(
196198
protected SpanDataAssert assertHandlerSpan(
197199
SpanDataAssert span, String method, ServerEndpoint endpoint) {
198200
String handlerSpanName = getHandlerSpanName(endpoint);
201+
String codeNamespace = TestController.class.getName();
199202
if (endpoint == NOT_FOUND) {
200203
handlerSpanName = "ResourceHttpRequestHandler.handleRequest";
204+
codeNamespace = ResourceHttpRequestHandler.class.getName();
201205
}
202-
span.hasName(handlerSpanName).hasKind(SpanKind.INTERNAL);
206+
String codeFunction = handlerSpanName.substring(handlerSpanName.indexOf('.') + 1);
207+
span.hasName(handlerSpanName)
208+
.hasKind(SpanKind.INTERNAL)
209+
.hasAttributesSatisfyingExactly(
210+
equalTo(CODE_NAMESPACE, codeNamespace), equalTo(CODE_FUNCTION, codeFunction));
203211
if (endpoint == EXCEPTION) {
204212
span.hasStatus(StatusData.error());
205213
span.hasEventsSatisfyingExactly(

instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/filter/AbstractServletFilterTest.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM;
1414
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM;
1515
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT;
16+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
17+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
18+
import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_FUNCTION;
19+
import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_NAMESPACE;
1620
import static org.assertj.core.api.Assertions.assertThat;
1721

18-
import io.opentelemetry.api.common.Attributes;
1922
import io.opentelemetry.api.trace.SpanKind;
2023
import io.opentelemetry.instrumentation.api.internal.HttpConstants;
2124
import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest;
@@ -97,7 +100,9 @@ protected List<Consumer<SpanDataAssert>> errorPageSpanAssertions(
97100
span ->
98101
span.hasName("BasicErrorController.error")
99102
.hasKind(SpanKind.INTERNAL)
100-
.hasAttributes(Attributes.empty()));
103+
.hasAttributesSatisfyingExactly(
104+
satisfies(CODE_NAMESPACE, v -> v.endsWith(".BasicErrorController")),
105+
equalTo(CODE_FUNCTION, "error")));
101106
return spanAssertions;
102107
}
103108

0 commit comments

Comments
 (0)