Skip to content

Commit

Permalink
Resteasy server span naming (#2900)
Browse files Browse the repository at this point in the history
* Resteasy server span naming
  • Loading branch information
laurit authored May 6, 2021
1 parent 357140c commit 1fba628
Show file tree
Hide file tree
Showing 19 changed files with 445 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public static void updateServerSpanName(
ServerSpanNaming serverSpanNaming = context.get(CONTEXT_KEY);
if (serverSpanNaming == null) {
String name = serverSpanName.get();
if (name != null) {
if (name != null && !name.isEmpty()) {
serverSpan.updateName(name);
}
return;
Expand All @@ -67,7 +67,9 @@ public static void updateServerSpanName(
!source.useFirst && source.order == serverSpanNaming.updatedBySource.order;
if (source.order > serverSpanNaming.updatedBySource.order || onlyIfBetterName) {
String name = serverSpanName.get();
if (name != null && (!onlyIfBetterName || serverSpanNaming.isBetterName(name))) {
if (name != null
&& !name.isEmpty()
&& (!onlyIfBetterName || serverSpanNaming.isBetterName(name))) {
serverSpan.updateName(name);
serverSpanNaming.updatedBySource = source;
serverSpanNaming.nameLength = name.length();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
import io.opentelemetry.instrumentation.api.servlet.ServletContextPath;
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
import io.opentelemetry.instrumentation.api.tracer.ServerSpan;
Expand All @@ -20,6 +21,7 @@
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;

Expand Down Expand Up @@ -61,19 +63,12 @@ public Context startSpan(Context parentContext, Class<?> target, Method method)

public void updateSpanNames(
Context context, Span span, Span serverSpan, Class<?> target, Method method) {
String pathBasedSpanName = getPathSpanName(target, method);
// If path based name is empty skip prepending context path so that path based name would
// remain as an empty string for which we skip updating span name. Path base span name is
// empty when method and class don't have a jax-rs path annotation, this can happen when
// creating an "abort" span, see RequestContextHelper.
if (!pathBasedSpanName.isEmpty()) {
pathBasedSpanName = JaxrsContextPath.prepend(context, pathBasedSpanName);
pathBasedSpanName = ServletContextPath.prepend(context, pathBasedSpanName);
}
Supplier<String> spanNameSupplier = getPathSpanNameSupplier(context, target, method);
if (serverSpan == null) {
updateSpanName(span, pathBasedSpanName);
updateSpanName(span, spanNameSupplier.get());
} else {
updateSpanName(serverSpan, pathBasedSpanName);
ServerSpanNaming.updateServerSpanName(
context, ServerSpanNaming.Source.CONTROLLER, spanNameSupplier);
updateSpanName(span, spanNameForMethod(target, method));
}
}
Expand All @@ -91,6 +86,22 @@ private void setCodeAttributes(SpanBuilder spanBuilder, Class<?> target, Method
}
}

private Supplier<String> getPathSpanNameSupplier(
Context context, Class<?> target, Method method) {
return () -> {
String pathBasedSpanName = getPathSpanName(target, method);
// If path based name is empty skip prepending context path so that path based name would
// remain as an empty string for which we skip updating span name. Path base span name is
// empty when method and class don't have a jax-rs path annotation, this can happen when
// creating an "abort" span, see RequestContextHelper.
if (!pathBasedSpanName.isEmpty()) {
pathBasedSpanName = JaxrsContextPath.prepend(context, pathBasedSpanName);
pathBasedSpanName = ServletContextPath.prepend(context, pathBasedSpanName);
}
return pathBasedSpanName;
};
}

/**
* Returns the span name given a JaxRS annotated method. Results are cached so this method can be
* called multiple times without significantly impacting performance.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,9 @@ class CxfHttpServerTest extends JaxRsHttpServerTest<Server> {
void stopServer(Server httpServer) {
httpServer.stop()
}

@Override
boolean hasFrameworkInstrumentation() {
false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@

class CxfJettyHttpServerTest extends JaxRsJettyHttpServerTest {

@Override
boolean hasFrameworkInstrumentation() {
false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,9 @@ class JerseyHttpServerTest extends JaxRsHttpServerTest<Server> {
boolean asyncCancelHasSendError() {
true
}

@Override
boolean hasFrameworkInstrumentation() {
false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ class JerseyJettyHttpServerTest extends JaxRsJettyHttpServerTest {
boolean asyncCancelHasSendError() {
true
}

@Override
boolean hasFrameworkInstrumentation() {
false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies {
library group: 'org.jboss.resteasy', name: 'resteasy-jaxrs', version: '3.0.0.Final'

implementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-common:javaagent')
implementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-resteasy-common:javaagent')

testInstrumentation project(':instrumentation:servlet:servlet-3.0:javaagent')
testInstrumentation project(':instrumentation:servlet:servlet-javax-common:javaagent')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public Resteasy30InstrumentationModule() {
public List<TypeInstrumentation> typeInstrumentations() {
return asList(
new Resteasy30RequestContextInstrumentation(),
new Resteasy30ServletContainerDispatcherInstrumentation());
new ResteasyServletContainerDispatcherInstrumentation(),
new ResteasyRootNodeTypeInstrumentation(),
new ResteasyResourceMethodInvokerInstrumentation(),
new ResteasyResourceLocatorInvokerInstrumentation());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies {
library group: 'org.jboss.resteasy', name: 'resteasy-jaxrs', version: '3.1.0.Final'

implementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-common:javaagent')
implementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-resteasy-common:javaagent')

testInstrumentation project(':instrumentation:servlet:servlet-3.0:javaagent')
testInstrumentation project(':instrumentation:servlet:servlet-javax-common:javaagent')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public Resteasy31InstrumentationModule() {
public List<TypeInstrumentation> typeInstrumentations() {
return asList(
new Resteasy31RequestContextInstrumentation(),
new Resteasy31ServletContainerDispatcherInstrumentation());
new ResteasyServletContainerDispatcherInstrumentation(),
new ResteasyRootNodeTypeInstrumentation(),
new ResteasyResourceMethodInvokerInstrumentation(),
new ResteasyResourceLocatorInvokerInstrumentation());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apply from: "$rootDir/gradle/instrumentation.gradle"

dependencies {
compileOnly group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: '2.0'
compileOnly group: 'org.jboss.resteasy', name: 'resteasy-jaxrs', version: '3.1.0.Final'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;

import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import io.opentelemetry.javaagent.instrumentation.api.jaxrs.JaxrsContextPath;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.jboss.resteasy.core.ResourceLocatorInvoker;

public class ResteasyResourceLocatorInvokerInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.jboss.resteasy.core.ResourceLocatorInvoker");
}

@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
named("invokeOnTargetObject")
.and(takesArgument(0, named("org.jboss.resteasy.spi.HttpRequest")))
.and(takesArgument(1, named("org.jboss.resteasy.spi.HttpResponse")))
.and(takesArgument(2, Object.class)),
ResteasyResourceLocatorInvokerInstrumentation.class.getName()
+ "$InvokeOnTargetObjectAdvice");
}

public static class InvokeOnTargetObjectAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.This ResourceLocatorInvoker resourceInvoker,
@Advice.Local("otelScope") Scope scope) {

Context currentContext = Java8BytecodeBridge.currentContext();

String name =
InstrumentationContext.get(ResourceLocatorInvoker.class, String.class)
.get(resourceInvoker);
ResteasyTracingUtil.updateServerSpanName(currentContext, name);

// subresource locator returns a resources class that may have @Path annotations
// append current path to jax-rs context path so that it would be present in the final path
Context context =
JaxrsContextPath.init(currentContext, JaxrsContextPath.prepend(currentContext, name));
if (context != null) {
scope = context.makeCurrent();
}
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(@Advice.Local("otelScope") Scope scope) {
if (scope != null) {
scope.close();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;

import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.jboss.resteasy.core.ResourceMethodInvoker;

public class ResteasyResourceMethodInvokerInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.jboss.resteasy.core.ResourceMethodInvoker");
}

@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
named("invokeOnTarget")
.and(takesArgument(0, named("org.jboss.resteasy.spi.HttpRequest")))
.and(takesArgument(1, named("org.jboss.resteasy.spi.HttpResponse")))
.and(takesArgument(2, Object.class)),
ResteasyResourceMethodInvokerInstrumentation.class.getName() + "$InvokeOnTargetAdvice");
}

public static class InvokeOnTargetAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(@Advice.This ResourceMethodInvoker resourceInvoker) {

String name =
InstrumentationContext.get(ResourceMethodInvoker.class, String.class)
.get(resourceInvoker);
ResteasyTracingUtil.updateServerSpanName(Java8BytecodeBridge.currentContext(), name);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;

import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.HashMap;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.matcher.ElementMatcher;
import org.jboss.resteasy.core.ResourceLocatorInvoker;
import org.jboss.resteasy.core.ResourceMethodInvoker;

public class ResteasyRootNodeTypeInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.jboss.resteasy.core.registry.RootNode");
}

@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
Map<ElementMatcher<MethodDescription>, String> transformers = new HashMap<>();
transformers.put(
named("addInvoker")
.and(takesArgument(0, String.class))
// package of ResourceInvoker was changed in reasteasy 4
.and(
takesArgument(
1,
named("org.jboss.resteasy.core.ResourceInvoker")
.or(named("org.jboss.resteasy.spi.ResourceInvoker")))),
ResteasyRootNodeTypeInstrumentation.class.getName() + "$AddInvokerAdvice");

return transformers;
}

public static class AddInvokerAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void addInvoker(
@Advice.Argument(0) String path,
@Advice.Argument(value = 1, typing = Assigner.Typing.DYNAMIC) Object invoker) {
String normalizedPath = ResteasyTracingUtil.normalizePath(path);
if (invoker instanceof ResourceLocatorInvoker) {
ResourceLocatorInvoker resourceLocatorInvoker = (ResourceLocatorInvoker) invoker;
InstrumentationContext.get(ResourceLocatorInvoker.class, String.class)
.put(resourceLocatorInvoker, normalizedPath);
} else if (invoker instanceof ResourceMethodInvoker) {
ResourceMethodInvoker resourceMethodInvoker = (ResourceMethodInvoker) invoker;
InstrumentationContext.get(ResourceMethodInvoker.class, String.class)
.put(resourceMethodInvoker, normalizedPath);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

public class Resteasy31ServletContainerDispatcherInstrumentation implements TypeInstrumentation {
public class ResteasyServletContainerDispatcherInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
Expand All @@ -31,7 +31,7 @@ public ElementMatcher<TypeDescription> typeMatcher() {
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return Collections.singletonMap(
isMethod().and(named("service")),
Resteasy31ServletContainerDispatcherInstrumentation.class.getName() + "$ServiceAdvice");
ResteasyServletContainerDispatcherInstrumentation.class.getName() + "$ServiceAdvice");
}

public static class ServiceAdvice {
Expand Down
Loading

0 comments on commit 1fba628

Please sign in to comment.