Update jaxrs-1.0 to Instrumenter API (#3797)

This commit is contained in:
Trask Stalnaker 2021-08-09 21:59:39 -07:00 committed by GitHub
parent f5be16bc7c
commit 19711ca76b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 151 additions and 89 deletions

View File

@ -5,16 +5,7 @@
package io.opentelemetry.javaagent.instrumentation.jaxrs.v1_0;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.servlet.ServletContextPath;
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
import io.opentelemetry.instrumentation.api.tracer.ServerSpan;
import io.opentelemetry.instrumentation.api.tracer.SpanNames;
import io.opentelemetry.javaagent.bootstrap.jaxrs.ClassHierarchyIterable;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Map;
@ -22,15 +13,9 @@ import java.util.concurrent.ConcurrentHashMap;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
public class JaxRsAnnotationsTracer extends BaseTracer {
public class HandlerData {
private static final JaxRsAnnotationsTracer TRACER = new JaxRsAnnotationsTracer();
public static JaxRsAnnotationsTracer tracer() {
return TRACER;
}
private final ClassValue<Map<Method, String>> spanNames =
private static final ClassValue<Map<Method, String>> serverSpanNames =
new ClassValue<Map<Method, String>>() {
@Override
protected Map<Method, String> computeValue(Class<?> type) {
@ -38,37 +23,20 @@ public class JaxRsAnnotationsTracer extends BaseTracer {
}
};
public Context startSpan(Class<?> target, Method method) {
String pathBasedSpanName = getPathSpanName(target, method);
Context parentContext = Context.current();
Span serverSpan = ServerSpan.fromContextOrNull(parentContext);
private final Class<?> target;
private final Method method;
// When jax-rs is the root, we want to name using the path, otherwise use the class/method.
String spanName;
if (serverSpan == null) {
spanName = pathBasedSpanName;
} else {
spanName = SpanNames.fromMethod(target, method);
updateServerSpanName(parentContext, serverSpan, pathBasedSpanName);
}
SpanBuilder spanBuilder = spanBuilder(parentContext, spanName, SpanKind.INTERNAL);
setCodeAttributes(spanBuilder, target, method);
Span span = spanBuilder.startSpan();
return parentContext.with(span);
public HandlerData(Class<?> target, Method method) {
this.target = target;
this.method = method;
}
private static void setCodeAttributes(SpanBuilder spanBuilder, Class<?> target, Method method) {
spanBuilder.setAttribute(SemanticAttributes.CODE_NAMESPACE, target.getName());
if (method != null) {
spanBuilder.setAttribute(SemanticAttributes.CODE_FUNCTION, method.getName());
}
public Class<?> codeClass() {
return target;
}
private static void updateServerSpanName(Context context, Span span, String spanName) {
if (!spanName.isEmpty()) {
span.updateName(ServletContextPath.prepend(context, spanName));
}
public String methodName() {
return method.getName();
}
/**
@ -77,8 +45,8 @@ public class JaxRsAnnotationsTracer extends BaseTracer {
*
* @return The result can be an empty string but will never be {@code null}.
*/
private String getPathSpanName(Class<?> target, Method method) {
Map<Method, String> classMap = spanNames.get(target);
String getServerSpanName() {
Map<Method, String> classMap = serverSpanNames.get(target);
String spanName = classMap.get(method);
if (spanName == null) {
String httpMethod = null;
@ -168,11 +136,12 @@ public class JaxRsAnnotationsTracer extends BaseTracer {
StringBuilder spanNameBuilder = new StringBuilder();
boolean skipSlash = false;
if (classPath != null) {
if (!classPath.value().startsWith("/")) {
String classPathValue = classPath.value();
if (!classPathValue.startsWith("/")) {
spanNameBuilder.append("/");
}
spanNameBuilder.append(classPath.value());
skipSlash = classPath.value().endsWith("/");
spanNameBuilder.append(classPathValue);
skipSlash = classPathValue.endsWith("/") || classPathValue.isEmpty();
}
if (methodPath != null) {
@ -189,9 +158,4 @@ public class JaxRsAnnotationsTracer extends BaseTracer {
return spanNameBuilder.toString().trim();
}
@Override
protected String getInstrumentationName() {
return "io.opentelemetry.jaxrs-1.0";
}
}

View File

@ -8,7 +8,7 @@ package io.opentelemetry.javaagent.instrumentation.jaxrs.v1_0;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperMethod;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v1_0.JaxRsAnnotationsTracer.tracer;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v1_0.JaxrsSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
@ -17,16 +17,18 @@ import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.CallDepth;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import java.lang.reflect.Method;
import javax.ws.rs.Path;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
public class JaxRsAnnotationsInstrumentation implements TypeInstrumentation {
public class JaxrsAnnotationsInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
@ -55,7 +57,7 @@ public class JaxRsAnnotationsInstrumentation implements TypeInstrumentation {
"javax.ws.rs.OPTIONS",
"javax.ws.rs.POST",
"javax.ws.rs.PUT")))),
JaxRsAnnotationsInstrumentation.class.getName() + "$JaxRsAnnotationsAdvice");
JaxrsAnnotationsInstrumentation.class.getName() + "$JaxRsAnnotationsAdvice");
}
@SuppressWarnings("unused")
@ -66,13 +68,27 @@ public class JaxRsAnnotationsInstrumentation implements TypeInstrumentation {
@Advice.This Object target,
@Advice.Origin Method method,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelHandlerData") HandlerData handlerData,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
callDepth = CallDepth.forClass(Path.class);
if (callDepth.getAndIncrement() > 0) {
return;
}
context = tracer().startSpan(target.getClass(), method);
Context parentContext = Java8BytecodeBridge.currentContext();
handlerData = new HandlerData(target.getClass(), method);
ServerSpanNaming.updateServerSpanName(
parentContext,
ServerSpanNaming.Source.CONTROLLER,
JaxrsServerSpanNaming.getServerSpanNameSupplier(parentContext, handlerData));
if (!instrumenter().shouldStart(parentContext, handlerData)) {
return;
}
context = instrumenter().start(parentContext, handlerData);
scope = context.makeCurrent();
}
@ -80,18 +96,18 @@ public class JaxRsAnnotationsInstrumentation implements TypeInstrumentation {
public static void stopSpan(
@Advice.Thrown Throwable throwable,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelHandlerData") HandlerData handlerData,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (callDepth.decrementAndGet() > 0) {
return;
}
scope.close();
if (throwable == null) {
tracer().end(context);
} else {
tracer().endExceptionally(context, throwable);
if (scope == null) {
return;
}
scope.close();
instrumenter().end(context, handlerData, null, throwable);
}
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxrs.v1_0;
import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesExtractor;
import org.checkerframework.checker.nullness.qual.Nullable;
public class JaxrsCodeAttributesExtractor extends CodeAttributesExtractor<HandlerData, Void> {
@Override
protected @Nullable Class<?> codeClass(HandlerData handlerData) {
return handlerData.codeClass();
}
@Override
protected @Nullable String methodName(HandlerData handlerData) {
return handlerData.methodName();
}
@Override
protected @Nullable String filePath(HandlerData handlerData) {
return null;
}
@Override
protected @Nullable Long lineNumber(HandlerData handlerData) {
return null;
}
}

View File

@ -16,8 +16,8 @@ import java.util.List;
import net.bytebuddy.matcher.ElementMatcher;
@AutoService(InstrumentationModule.class)
public class JaxRsInstrumentationModule extends InstrumentationModule {
public JaxRsInstrumentationModule() {
public class JaxrsInstrumentationModule extends InstrumentationModule {
public JaxrsInstrumentationModule() {
super("jaxrs", "jaxrs-1.0");
}
@ -29,6 +29,6 @@ public class JaxRsInstrumentationModule extends InstrumentationModule {
@Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new JaxRsAnnotationsInstrumentation());
return singletonList(new JaxrsAnnotationsInstrumentation());
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxrs.v1_0;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.servlet.ServletContextPath;
import io.opentelemetry.javaagent.bootstrap.jaxrs.JaxrsContextPath;
import java.util.function.Supplier;
public class JaxrsServerSpanNaming {
public static Supplier<String> getServerSpanNameSupplier(
Context context, HandlerData handlerData) {
return () -> {
String pathBasedSpanName = handlerData.getServerSpanName();
// 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;
};
}
private JaxrsServerSpanNaming() {}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxrs.v1_0;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.code.CodeSpanNameExtractor;
public final class JaxrsSingletons {
public static final String ABORT_FILTER_CLASS =
"io.opentelemetry.javaagent.instrumentation.jaxrs2.filter.abort.class";
public static final String ABORT_HANDLED =
"io.opentelemetry.javaagent.instrumentation.jaxrs2.filter.abort.handled";
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jaxrs-1.0-common";
private static final Instrumenter<HandlerData, Void> INSTRUMENTER;
static {
CodeAttributesExtractor<HandlerData, Void> codeAttributesExtractor =
new JaxrsCodeAttributesExtractor();
SpanNameExtractor<HandlerData> spanNameExtractor =
CodeSpanNameExtractor.create(codeAttributesExtractor);
INSTRUMENTER =
Instrumenter.<HandlerData, Void>newBuilder(
GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, spanNameExtractor)
.addAttributesExtractor(codeAttributesExtractor)
.newInstrumenter();
}
public static Instrumenter<HandlerData, Void> instrumenter() {
return INSTRUMENTER;
}
private JaxrsSingletons() {}
}

View File

@ -20,30 +20,6 @@ import spock.lang.Unroll
class JaxRsAnnotations1InstrumentationTest extends AgentInstrumentationSpecification {
def "instrumentation can be used as root span and resource is set to METHOD PATH"() {
setup:
def jax = new Jax() {
@POST
@Path("/a")
void call() {
}
}
jax.call()
expect:
assertTraces(1) {
trace(0, 1) {
span(0) {
name "/a"
attributes {
"${SemanticAttributes.CODE_NAMESPACE.key}" jax.getClass().getName()
"${SemanticAttributes.CODE_FUNCTION.key}" "call"
}
}
}
}
}
@Unroll
def "span named '#paramName' from annotations on class '#className' when is not root span"() {
setup: