|
| 1 | +package datadog.trace.instrumentation.jaxrs; |
| 2 | + |
| 3 | +import static net.bytebuddy.matcher.ElementMatchers.declaresMethod; |
| 4 | +import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; |
| 5 | +import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith; |
| 6 | +import static net.bytebuddy.matcher.ElementMatchers.named; |
| 7 | + |
| 8 | +import com.google.auto.service.AutoService; |
| 9 | +import datadog.trace.agent.tooling.DDAdvice; |
| 10 | +import datadog.trace.agent.tooling.Instrumenter; |
| 11 | +import datadog.trace.api.DDTags; |
| 12 | +import io.opentracing.Scope; |
| 13 | +import io.opentracing.tag.Tags; |
| 14 | +import io.opentracing.util.GlobalTracer; |
| 15 | +import java.lang.annotation.Annotation; |
| 16 | +import java.lang.reflect.Method; |
| 17 | +import java.util.LinkedList; |
| 18 | +import javax.ws.rs.HttpMethod; |
| 19 | +import javax.ws.rs.Path; |
| 20 | +import net.bytebuddy.agent.builder.AgentBuilder; |
| 21 | +import net.bytebuddy.asm.Advice; |
| 22 | + |
| 23 | +@AutoService(Instrumenter.class) |
| 24 | +public final class JaxRsInstrumentation extends Instrumenter.Configurable { |
| 25 | + |
| 26 | + public JaxRsInstrumentation() { |
| 27 | + super("jax-rs", "jaxrs"); |
| 28 | + } |
| 29 | + |
| 30 | + @Override |
| 31 | + protected boolean defaultEnabled() { |
| 32 | + return false; |
| 33 | + } |
| 34 | + |
| 35 | + @Override |
| 36 | + protected AgentBuilder apply(final AgentBuilder agentBuilder) { |
| 37 | + return agentBuilder |
| 38 | + .type( |
| 39 | + hasSuperType( |
| 40 | + isAnnotatedWith(named("javax.ws.rs.Path")) |
| 41 | + .or(hasSuperType(declaresMethod(isAnnotatedWith(named("javax.ws.rs.Path"))))))) |
| 42 | + .transform( |
| 43 | + DDAdvice.create() |
| 44 | + .advice( |
| 45 | + isAnnotatedWith( |
| 46 | + named("javax.ws.rs.Path") |
| 47 | + .or(named("javax.ws.rs.DELETE")) |
| 48 | + .or(named("javax.ws.rs.GET")) |
| 49 | + .or(named("javax.ws.rs.HEAD")) |
| 50 | + .or(named("javax.ws.rs.OPTIONS")) |
| 51 | + .or(named("javax.ws.rs.POST")) |
| 52 | + .or(named("javax.ws.rs.PUT"))), |
| 53 | + JaxRsAdvice.class.getName())) |
| 54 | + .asDecorator(); |
| 55 | + } |
| 56 | + |
| 57 | + public static class JaxRsAdvice { |
| 58 | + |
| 59 | + @Advice.OnMethodEnter(suppress = Throwable.class) |
| 60 | + public static void nameSpan(@Advice.This final Object obj, @Advice.Origin final Method method) { |
| 61 | + // TODO: do we need caching for this? |
| 62 | + |
| 63 | + final LinkedList<Path> classPaths = new LinkedList<>(); |
| 64 | + Class<?> target = obj.getClass(); |
| 65 | + while (target != Object.class) { |
| 66 | + final Path annotation = target.getAnnotation(Path.class); |
| 67 | + if (annotation != null) { |
| 68 | + classPaths.push(annotation); |
| 69 | + } |
| 70 | + target = target.getSuperclass(); |
| 71 | + } |
| 72 | + final Path methodPath = method.getAnnotation(Path.class); |
| 73 | + String httpMethod = null; |
| 74 | + for (final Annotation ann : method.getDeclaredAnnotations()) { |
| 75 | + if (ann.annotationType().getAnnotation(HttpMethod.class) != null) { |
| 76 | + httpMethod = ann.annotationType().getSimpleName(); |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + final StringBuilder resourceNameBuilder = new StringBuilder(); |
| 81 | + if (httpMethod != null) { |
| 82 | + resourceNameBuilder.append(httpMethod); |
| 83 | + resourceNameBuilder.append(" "); |
| 84 | + } |
| 85 | + for (final Path classPath : classPaths) { |
| 86 | + resourceNameBuilder.append(classPath.value()); |
| 87 | + } |
| 88 | + if (methodPath != null) { |
| 89 | + resourceNameBuilder.append(methodPath.value()); |
| 90 | + } |
| 91 | + final String resourceName = resourceNameBuilder.toString().trim(); |
| 92 | + |
| 93 | + final Scope scope = GlobalTracer.get().scopeManager().active(); |
| 94 | + if (scope != null && !resourceName.isEmpty()) { |
| 95 | + scope.span().setTag(DDTags.RESOURCE_NAME, resourceName); |
| 96 | + Tags.COMPONENT.set(scope.span(), "jax-rs"); |
| 97 | + } |
| 98 | + } |
| 99 | + } |
| 100 | +} |
0 commit comments