Skip to content

Commit

Permalink
Update jaxrs-1.0 to Instrumenter API (#3797)
Browse files Browse the repository at this point in the history
  • Loading branch information
trask authored Aug 10, 2021
1 parent f5be16b commit 19711ca
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,70 +5,38 @@

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;
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) {
return new ConcurrentHashMap<>();
}
};

public Context startSpan(Class<?> target, Method method) {
String pathBasedSpanName = getPathSpanName(target, method);
Context parentContext = Context.current();
Span serverSpan = ServerSpan.fromContextOrNull(parentContext);

// 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);
}
private final Class<?> target;
private final Method method;

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();
}

/**
Expand All @@ -77,8 +45,8 @@ private static void updateServerSpanName(Context context, Span span, String span
*
* @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;
Expand Down Expand Up @@ -168,11 +136,12 @@ private static String buildSpanName(Path classPath, Path methodPath) {
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) {
Expand All @@ -189,9 +158,4 @@ private static String buildSpanName(Path classPath, Path methodPath) {

return spanNameBuilder.toString().trim();
}

@Override
protected String getInstrumentationName() {
return "io.opentelemetry.jaxrs-1.0";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
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;
Expand All @@ -17,16 +17,18 @@

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() {
Expand Down Expand Up @@ -55,7 +57,7 @@ public void transform(TypeTransformer transformer) {
"javax.ws.rs.OPTIONS",
"javax.ws.rs.POST",
"javax.ws.rs.PUT")))),
JaxRsAnnotationsInstrumentation.class.getName() + "$JaxRsAnnotationsAdvice");
JaxrsAnnotationsInstrumentation.class.getName() + "$JaxRsAnnotationsAdvice");
}

@SuppressWarnings("unused")
Expand All @@ -66,32 +68,46 @@ public static void nameSpan(
@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();
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
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");
}

Expand All @@ -29,6 +29,6 @@ public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new JaxRsAnnotationsInstrumentation());
return singletonList(new JaxrsAnnotationsInstrumentation());
}
}
Original file line number Diff line number Diff line change
@@ -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() {}
}
Original file line number Diff line number Diff line change
@@ -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() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 19711ca

Please sign in to comment.