Skip to content

Commit 8dca278

Browse files
authored
Jersey and cxf server span naming (#2919)
1 parent 53c38ac commit 8dca278

File tree

15 files changed

+250
-36
lines changed

15 files changed

+250
-36
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
7+
8+
public final class JaxRsPathUtil {
9+
private JaxRsPathUtil() {}
10+
11+
public static String normalizePath(String path) {
12+
// ensure that non-empty path starts with /
13+
if (path == null || "/".equals(path)) {
14+
path = "";
15+
} else if (!path.startsWith("/")) {
16+
path = "/" + path;
17+
}
18+
// remove trailing /
19+
if (path.endsWith("/")) {
20+
path = path.substring(0, path.length() - 1);
21+
}
22+
23+
return path;
24+
}
25+
}

instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/CxfInstrumentationModule.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public List<TypeInstrumentation> typeInstrumentations() {
2323
return asList(
2424
new CxfRequestContextInstrumentation(),
2525
new CxfServletControllerInstrumentation(),
26-
new CxfRsHttpListenerInstrumentation());
26+
new CxfRsHttpListenerInstrumentation(),
27+
new CxfJaxRsInvokerInstrumentation());
2728
}
2829
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
7+
8+
import static java.util.Collections.singletonMap;
9+
import static net.bytebuddy.matcher.ElementMatchers.named;
10+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
11+
12+
import io.opentelemetry.context.Context;
13+
import io.opentelemetry.context.Scope;
14+
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
15+
import java.util.Map;
16+
import net.bytebuddy.asm.Advice;
17+
import net.bytebuddy.description.method.MethodDescription;
18+
import net.bytebuddy.description.type.TypeDescription;
19+
import net.bytebuddy.matcher.ElementMatcher;
20+
import org.apache.cxf.message.Exchange;
21+
22+
public class CxfJaxRsInvokerInstrumentation implements TypeInstrumentation {
23+
24+
@Override
25+
public ElementMatcher<TypeDescription> typeMatcher() {
26+
return named("org.apache.cxf.jaxrs.JAXRSInvoker");
27+
}
28+
29+
@Override
30+
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
31+
return singletonMap(
32+
named("invoke")
33+
.and(takesArgument(0, named("org.apache.cxf.message.Exchange")))
34+
.and(takesArgument(1, Object.class))
35+
.and(takesArgument(2, Object.class)),
36+
CxfJaxRsInvokerInstrumentation.class.getName() + "$InvokeAdvice");
37+
}
38+
39+
public static class InvokeAdvice {
40+
41+
@Advice.OnMethodEnter(suppress = Throwable.class)
42+
public static void onEnter(
43+
@Advice.Argument(0) Exchange exchange, @Advice.Local("otelScope") Scope scope) {
44+
Context context = CxfTracingUtil.updateServerSpanName(exchange);
45+
if (context != null) {
46+
scope = context.makeCurrent();
47+
}
48+
}
49+
50+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
51+
public static void stopSpan(@Advice.Local("otelScope") Scope scope) {
52+
if (scope != null) {
53+
scope.close();
54+
}
55+
}
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
7+
8+
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxRsPathUtil.normalizePath;
9+
10+
import io.opentelemetry.api.trace.Span;
11+
import io.opentelemetry.context.Context;
12+
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
13+
import io.opentelemetry.instrumentation.api.servlet.ServletContextPath;
14+
import io.opentelemetry.instrumentation.api.tracer.ServerSpan;
15+
import io.opentelemetry.javaagent.instrumentation.api.jaxrs.JaxrsContextPath;
16+
import org.apache.cxf.jaxrs.model.ClassResourceInfo;
17+
import org.apache.cxf.jaxrs.model.OperationResourceInfo;
18+
import org.apache.cxf.jaxrs.model.URITemplate;
19+
import org.apache.cxf.message.Exchange;
20+
21+
public final class CxfTracingUtil {
22+
23+
private CxfTracingUtil() {}
24+
25+
public static Context updateServerSpanName(Exchange exchange) {
26+
Context context = Context.current();
27+
Span serverSpan = ServerSpan.fromContextOrNull(context);
28+
if (serverSpan == null) {
29+
return null;
30+
}
31+
32+
OperationResourceInfo ori = exchange.get(OperationResourceInfo.class);
33+
ClassResourceInfo cri = ori.getClassResourceInfo();
34+
String name = getName(cri.getURITemplate(), ori.getURITemplate());
35+
if (name.isEmpty()) {
36+
return null;
37+
}
38+
39+
serverSpan.updateName(
40+
ServletContextPath.prepend(context, JaxrsContextPath.prepend(context, name)));
41+
// mark span name as updated from controller to avoid JaxRsAnnotationsTracer updating it
42+
ServerSpanNaming.updateSource(context, ServerSpanNaming.Source.CONTROLLER);
43+
44+
return JaxrsContextPath.init(context, JaxrsContextPath.prepend(context, name));
45+
}
46+
47+
private static String getName(URITemplate classTemplate, URITemplate operationTemplate) {
48+
String classPath = normalize(classTemplate);
49+
String operationPath = normalize(operationTemplate);
50+
51+
return classPath + operationPath;
52+
}
53+
54+
private static String normalize(URITemplate uriTemplate) {
55+
if (uriTemplate == null) {
56+
return "";
57+
}
58+
59+
return normalizePath(uriTemplate.getValue());
60+
}
61+
}

instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/test/groovy/CxfHttpServerTest.groovy

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,4 @@ class CxfHttpServerTest extends JaxRsHttpServerTest<Server> {
3939
void stopServer(Server httpServer) {
4040
httpServer.stop()
4141
}
42-
43-
@Override
44-
boolean hasFrameworkInstrumentation() {
45-
false
46-
}
4742
}

instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/test/groovy/CxfJettyHttpServerTest.groovy

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,4 @@
44
*/
55

66
class CxfJettyHttpServerTest extends JaxRsJettyHttpServerTest {
7-
8-
@Override
9-
boolean hasFrameworkInstrumentation() {
10-
false
11-
}
127
}

instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JerseyInstrumentationModule.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public JerseyInstrumentationModule() {
2121
@Override
2222
public List<TypeInstrumentation> typeInstrumentations() {
2323
return asList(
24-
new JerseyRequestContextInstrumentation(), new JerseyServletContainerInstrumentation());
24+
new JerseyRequestContextInstrumentation(),
25+
new JerseyServletContainerInstrumentation(),
26+
new JerseyResourceMethodDispatcherInstrumentation());
2527
}
2628
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
7+
8+
import static java.util.Collections.singletonMap;
9+
import static net.bytebuddy.matcher.ElementMatchers.named;
10+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
11+
12+
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
13+
import java.util.Map;
14+
import javax.ws.rs.core.Request;
15+
import net.bytebuddy.asm.Advice;
16+
import net.bytebuddy.description.method.MethodDescription;
17+
import net.bytebuddy.description.type.TypeDescription;
18+
import net.bytebuddy.matcher.ElementMatcher;
19+
20+
public class JerseyResourceMethodDispatcherInstrumentation implements TypeInstrumentation {
21+
22+
@Override
23+
public ElementMatcher<TypeDescription> typeMatcher() {
24+
return named("org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher");
25+
}
26+
27+
@Override
28+
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
29+
return singletonMap(
30+
named("dispatch")
31+
.and(
32+
takesArgument(
33+
1,
34+
named("javax.ws.rs.core.Request")
35+
.or(named("org.glassfish.jersey.server.ContainerRequest")))),
36+
JerseyResourceMethodDispatcherInstrumentation.class.getName() + "$DispatchAdvice");
37+
}
38+
39+
public static class DispatchAdvice {
40+
41+
@Advice.OnMethodEnter(suppress = Throwable.class)
42+
public static void onEnter(@Advice.Argument(1) Request request) {
43+
JerseyTracingUtil.updateServerSpanName(request);
44+
}
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
7+
8+
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxRsPathUtil.normalizePath;
9+
10+
import io.opentelemetry.api.trace.Span;
11+
import io.opentelemetry.context.Context;
12+
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
13+
import io.opentelemetry.instrumentation.api.servlet.ServletContextPath;
14+
import io.opentelemetry.instrumentation.api.tracer.ServerSpan;
15+
import io.opentelemetry.javaagent.instrumentation.api.jaxrs.JaxrsContextPath;
16+
import java.util.Optional;
17+
import javax.ws.rs.core.Request;
18+
import javax.ws.rs.core.UriInfo;
19+
import org.glassfish.jersey.server.ContainerRequest;
20+
import org.glassfish.jersey.server.ExtendedUriInfo;
21+
22+
public class JerseyTracingUtil {
23+
24+
public static void updateServerSpanName(Request request) {
25+
Context context = Context.current();
26+
Span serverSpan = ServerSpan.fromContextOrNull(context);
27+
if (serverSpan == null) {
28+
return;
29+
}
30+
31+
ContainerRequest containerRequest = (ContainerRequest) request;
32+
UriInfo uriInfo = containerRequest.getUriInfo();
33+
ExtendedUriInfo extendedUriInfo = (ExtendedUriInfo) uriInfo;
34+
Optional<String> name =
35+
extendedUriInfo.getMatchedTemplates().stream()
36+
.map((uriTemplate) -> normalizePath(uriTemplate.getTemplate()))
37+
.reduce((a, b) -> b + a);
38+
if (!name.isPresent()) {
39+
return;
40+
}
41+
42+
serverSpan.updateName(
43+
ServletContextPath.prepend(context, JaxrsContextPath.prepend(context, name.get())));
44+
// mark span name as updated from controller to avoid JaxRsAnnotationsTracer updating it
45+
ServerSpanNaming.updateSource(context, ServerSpanNaming.Source.CONTROLLER);
46+
}
47+
}

instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/test/groovy/JerseyHttpServerTest.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ class JerseyHttpServerTest extends JaxRsHttpServerTest<Server> {
3838
}
3939

4040
@Override
41-
boolean hasFrameworkInstrumentation() {
41+
boolean testInterfaceMethodWithPath() {
42+
// disables a test that jersey deems invalid
4243
false
4344
}
4445
}

0 commit comments

Comments
 (0)