Update jaxrs-common to Instrumenter API (#3796)

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

View File

@ -19,4 +19,7 @@ dependencies {
compileOnly(project(":instrumentation:jaxrs:bootstrap"))
compileOnly("javax.ws.rs:javax.ws.rs-api:2.0")
}
compileOnly("com.google.auto.value:auto-value-annotations")
annotationProcessor("com.google.auto.value:auto-value")
}

View File

@ -0,0 +1,21 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import com.google.auto.value.AutoValue;
import io.opentelemetry.context.Context;
@AutoValue
public abstract class AsyncResponseData {
public static AsyncResponseData create(Context context, HandlerData handlerData) {
return new AutoValue_AsyncResponseData(context, handlerData);
}
public abstract Context getContext();
public abstract HandlerData getHandlerData();
}

View File

@ -5,25 +5,23 @@
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxRsAnnotationsTracer.tracer;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxrsSingletons.instrumenter;
import io.opentelemetry.context.Context;
import java.util.function.BiFunction;
public class CompletionStageFinishCallback<T> implements BiFunction<T, Throwable, T> {
private final Context context;
private final HandlerData handlerData;
public CompletionStageFinishCallback(Context context) {
public CompletionStageFinishCallback(Context context, HandlerData handlerData) {
this.context = context;
this.handlerData = handlerData;
}
@Override
public T apply(T result, Throwable throwable) {
if (throwable == null) {
tracer().end(context);
} else {
tracer().endExceptionally(context, throwable);
}
instrumenter().end(context, handlerData, null, throwable);
return result;
}
}

View File

@ -53,7 +53,7 @@ public class ContainerRequestFilterInstrumentation implements TypeInstrumentatio
public static void setFilterClass(
@Advice.This ContainerRequestFilter filter,
@Advice.Argument(0) ContainerRequestContext context) {
context.setProperty(JaxRsAnnotationsTracer.ABORT_FILTER_CLASS, filter.getClass());
context.setProperty(JaxrsSingletons.ABORT_FILTER_CLASS, filter.getClass());
}
}
}

View File

@ -5,10 +5,12 @@
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxRsAnnotationsTracer.tracer;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxrsSingletons.instrumenter;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import java.lang.reflect.Method;
import javax.ws.rs.container.ContainerRequestContext;
import net.bytebuddy.asm.Advice;
@ -35,30 +37,55 @@ public class DefaultRequestContextInstrumentation extends AbstractRequestContext
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void createGenericSpan(
@Advice.This ContainerRequestContext requestContext,
@Local("otelHandlerData") HandlerData handlerData,
@Local("otelContext") Context context,
@Local("otelScope") Scope scope) {
if (requestContext.getProperty(JaxRsAnnotationsTracer.ABORT_HANDLED) == null) {
Class<?> filterClass =
(Class<?>) requestContext.getProperty(JaxRsAnnotationsTracer.ABORT_FILTER_CLASS);
Method method = null;
try {
method = filterClass.getMethod("filter", ContainerRequestContext.class);
} catch (NoSuchMethodException e) {
// Unable to find the filter method. This should not be reachable because the context
// can only be aborted inside the filter method
}
context = tracer().startSpan(filterClass, method);
scope = context.makeCurrent();
if (requestContext.getProperty(JaxrsSingletons.ABORT_HANDLED) != null) {
return;
}
Class<?> filterClass =
(Class<?>) requestContext.getProperty(JaxrsSingletons.ABORT_FILTER_CLASS);
Method method = null;
try {
method = filterClass.getMethod("filter", ContainerRequestContext.class);
} catch (NoSuchMethodException e) {
// Unable to find the filter method. This should not be reachable because the context
// can only be aborted inside the filter method
}
if (filterClass == null || method == null) {
return;
}
Context parentContext = Java8BytecodeBridge.currentContext();
handlerData = new HandlerData(filterClass, 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(
@Local("otelHandlerData") HandlerData handlerData,
@Local("otelContext") Context context,
@Local("otelScope") Scope scope,
@Advice.Thrown Throwable throwable) {
RequestContextHelper.closeSpanAndScope(context, scope, throwable);
if (scope == null) {
return;
}
scope.close();
instrumenter().end(context, handlerData, null, throwable);
}
}
}

View File

@ -5,40 +5,17 @@
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static io.opentelemetry.api.trace.SpanKind.INTERNAL;
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;
import io.opentelemetry.instrumentation.api.tracer.SpanNames;
import io.opentelemetry.javaagent.bootstrap.jaxrs.ClassHierarchyIterable;
import io.opentelemetry.javaagent.bootstrap.jaxrs.JaxrsContextPath;
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 java.util.function.Supplier;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
public class JaxRsAnnotationsTracer extends BaseTracer {
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";
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) {
@ -46,61 +23,20 @@ public class JaxRsAnnotationsTracer extends BaseTracer {
}
};
public Context startSpan(Class<?> target, Method method) {
return startSpan(Context.current(), target, method);
private final Class<?> target;
private final Method method;
public HandlerData(Class<?> target, Method method) {
this.target = target;
this.method = method;
}
public Context startSpan(Context parentContext, Class<?> target, Method method) {
// We create span and immediately update its name
// We do that in order to reuse logic inside updateSpanNames method, which is used externally as
// well.
SpanBuilder spanBuilder = spanBuilder(parentContext, "jax-rs.request", INTERNAL);
setCodeAttributes(spanBuilder, target, method);
Span span = spanBuilder.startSpan();
updateSpanNames(
parentContext, span, ServerSpan.fromContextOrNull(parentContext), target, method);
return parentContext.with(span);
public Class<?> codeClass() {
return target;
}
public void updateSpanNames(
Context context, Span span, Span serverSpan, Class<?> target, Method method) {
Supplier<String> spanNameSupplier = getPathSpanNameSupplier(context, target, method);
if (serverSpan == null) {
updateSpanName(span, spanNameSupplier.get());
} else {
ServerSpanNaming.updateServerSpanName(
context, ServerSpanNaming.Source.CONTROLLER, spanNameSupplier);
updateSpanName(span, SpanNames.fromMethod(target, method));
}
}
private static void updateSpanName(Span span, String spanName) {
if (!spanName.isEmpty()) {
span.updateName(spanName);
}
}
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());
}
}
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;
};
public String methodName() {
return method.getName();
}
/**
@ -109,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;
@ -222,9 +158,4 @@ public class JaxRsAnnotationsTracer extends BaseTracer {
return spanNameBuilder.toString().trim();
}
@Override
protected String getInstrumentationName() {
return "io.opentelemetry.jaxrs-2.0-common";
}
}

View File

@ -8,7 +8,7 @@ package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_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.v2_0.JaxRsAnnotationsTracer.tracer;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_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,11 +17,13 @@ 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.ContextStore;
import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import java.lang.reflect.Method;
import java.util.concurrent.CompletionStage;
import javax.ws.rs.Path;
@ -31,7 +33,7 @@ import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.bytecode.assign.Assigner.Typing;
import net.bytebuddy.matcher.ElementMatcher;
public class JaxRsAnnotationsInstrumentation implements TypeInstrumentation {
public class JaxrsAnnotationsInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("javax.ws.rs.Path");
@ -60,7 +62,7 @@ public class JaxRsAnnotationsInstrumentation implements TypeInstrumentation {
"javax.ws.rs.PATCH",
"javax.ws.rs.POST",
"javax.ws.rs.PUT")))),
JaxRsAnnotationsInstrumentation.class.getName() + "$JaxRsAnnotationsAdvice");
JaxrsAnnotationsInstrumentation.class.getName() + "$JaxRsAnnotationsAdvice");
}
@SuppressWarnings("unused")
@ -72,6 +74,7 @@ public class JaxRsAnnotationsInstrumentation implements TypeInstrumentation {
@Advice.Origin Method method,
@Advice.AllArguments Object[] args,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelHandlerData") HandlerData handlerData,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope,
@Advice.Local("otelAsyncResponse") AsyncResponse asyncResponse) {
@ -80,11 +83,11 @@ public class JaxRsAnnotationsInstrumentation implements TypeInstrumentation {
return;
}
ContextStore<AsyncResponse, Context> contextStore = null;
ContextStore<AsyncResponse, AsyncResponseData> contextStore = null;
for (Object arg : args) {
if (arg instanceof AsyncResponse) {
asyncResponse = (AsyncResponse) arg;
contextStore = InstrumentationContext.get(AsyncResponse.class, Context.class);
contextStore = InstrumentationContext.get(AsyncResponse.class, AsyncResponseData.class);
if (contextStore.get(asyncResponse) != null) {
/*
* We are probably in a recursive call and don't want to start a new span because it
@ -98,13 +101,24 @@ public class JaxRsAnnotationsInstrumentation implements TypeInstrumentation {
}
}
context = tracer().startSpan(target.getClass(), method);
Context parentContext = Java8BytecodeBridge.currentContext();
handlerData = new HandlerData(target.getClass(), method);
if (contextStore != null && asyncResponse != null) {
contextStore.put(asyncResponse, context);
ServerSpanNaming.updateServerSpanName(
parentContext,
ServerSpanNaming.Source.CONTROLLER,
JaxrsServerSpanNaming.getServerSpanNameSupplier(parentContext, handlerData));
if (!instrumenter().shouldStart(parentContext, handlerData)) {
return;
}
context = instrumenter().start(parentContext, handlerData);
scope = context.makeCurrent();
if (contextStore != null && asyncResponse != null) {
contextStore.put(asyncResponse, AsyncResponseData.create(context, handlerData));
}
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
@ -112,6 +126,7 @@ public class JaxRsAnnotationsInstrumentation implements TypeInstrumentation {
@Advice.Return(readOnly = false, typing = Typing.DYNAMIC) Object returnValue,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelHandlerData") HandlerData handlerData,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope,
@Advice.Local("otelAsyncResponse") AsyncResponse asyncResponse) {
@ -119,13 +134,14 @@ public class JaxRsAnnotationsInstrumentation implements TypeInstrumentation {
return;
}
if (context == null || scope == null) {
if (scope == null) {
return;
}
scope.close();
if (throwable != null) {
tracer().endExceptionally(context, throwable);
scope.close();
instrumenter().end(context, handlerData, null, throwable);
return;
}
@ -134,18 +150,18 @@ public class JaxRsAnnotationsInstrumentation implements TypeInstrumentation {
if (asyncResponse != null && !asyncResponse.isSuspended()) {
// Clear span from the asyncResponse. Logically this should never happen. Added to be safe.
InstrumentationContext.get(AsyncResponse.class, Context.class).put(asyncResponse, null);
InstrumentationContext.get(AsyncResponse.class, AsyncResponseData.class)
.put(asyncResponse, null);
}
if (asyncReturnValue != null) {
// span finished by CompletionStageFinishCallback
asyncReturnValue = asyncReturnValue.handle(new CompletionStageFinishCallback<>(context));
asyncReturnValue =
asyncReturnValue.handle(new CompletionStageFinishCallback<>(context, handlerData));
}
if ((asyncResponse == null || !asyncResponse.isSuspended()) && asyncReturnValue == null) {
tracer().end(context);
instrumenter().end(context, handlerData, null, null);
}
// else span finished by AsyncResponseAdvice
scope.close();
// else span finished by AsyncResponse*Advice
}
}
}

View File

@ -7,7 +7,7 @@ package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxRsAnnotationsTracer.tracer;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxrsSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
@ -23,7 +23,7 @@ import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
public class JaxRsAsyncResponseInstrumentation implements TypeInstrumentation {
public class JaxrsAsyncResponseInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
@ -39,13 +39,13 @@ public class JaxRsAsyncResponseInstrumentation implements TypeInstrumentation {
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("resume").and(takesArgument(0, Object.class)).and(isPublic()),
JaxRsAsyncResponseInstrumentation.class.getName() + "$AsyncResponseAdvice");
JaxrsAsyncResponseInstrumentation.class.getName() + "$AsyncResponseAdvice");
transformer.applyAdviceToMethod(
named("resume").and(takesArgument(0, Throwable.class)).and(isPublic()),
JaxRsAsyncResponseInstrumentation.class.getName() + "$AsyncResponseThrowableAdvice");
JaxrsAsyncResponseInstrumentation.class.getName() + "$AsyncResponseThrowableAdvice");
transformer.applyAdviceToMethod(
named("cancel"),
JaxRsAsyncResponseInstrumentation.class.getName() + "$AsyncResponseCancelAdvice");
JaxrsAsyncResponseInstrumentation.class.getName() + "$AsyncResponseCancelAdvice");
}
@SuppressWarnings("unused")
@ -54,13 +54,13 @@ public class JaxRsAsyncResponseInstrumentation implements TypeInstrumentation {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void stopSpan(@Advice.This AsyncResponse asyncResponse) {
ContextStore<AsyncResponse, Context> contextStore =
InstrumentationContext.get(AsyncResponse.class, Context.class);
ContextStore<AsyncResponse, AsyncResponseData> contextStore =
InstrumentationContext.get(AsyncResponse.class, AsyncResponseData.class);
Context context = contextStore.get(asyncResponse);
if (context != null) {
AsyncResponseData data = contextStore.get(asyncResponse);
if (data != null) {
contextStore.put(asyncResponse, null);
tracer().end(context);
instrumenter().end(data.getContext(), data.getHandlerData(), null, null);
}
}
}
@ -72,13 +72,13 @@ public class JaxRsAsyncResponseInstrumentation implements TypeInstrumentation {
public static void stopSpan(
@Advice.This AsyncResponse asyncResponse, @Advice.Argument(0) Throwable throwable) {
ContextStore<AsyncResponse, Context> contextStore =
InstrumentationContext.get(AsyncResponse.class, Context.class);
ContextStore<AsyncResponse, AsyncResponseData> contextStore =
InstrumentationContext.get(AsyncResponse.class, AsyncResponseData.class);
Context context = contextStore.get(asyncResponse);
if (context != null) {
AsyncResponseData data = contextStore.get(asyncResponse);
if (data != null) {
contextStore.put(asyncResponse, null);
tracer().endExceptionally(context, throwable);
instrumenter().end(data.getContext(), data.getHandlerData(), null, throwable);
}
}
}
@ -89,16 +89,17 @@ public class JaxRsAsyncResponseInstrumentation implements TypeInstrumentation {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void stopSpan(@Advice.This AsyncResponse asyncResponse) {
ContextStore<AsyncResponse, Context> contextStore =
InstrumentationContext.get(AsyncResponse.class, Context.class);
ContextStore<AsyncResponse, AsyncResponseData> contextStore =
InstrumentationContext.get(AsyncResponse.class, AsyncResponseData.class);
Context context = contextStore.get(asyncResponse);
if (context != null) {
AsyncResponseData data = contextStore.get(asyncResponse);
if (data != null) {
contextStore.put(asyncResponse, null);
Context context = data.getContext();
if (JaxrsConfig.CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
Java8BytecodeBridge.spanFromContext(context).setAttribute("jaxrs.canceled", true);
}
tracer().end(context);
instrumenter().end(context, data.getHandlerData(), null, null);
}
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_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

@ -15,8 +15,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-2.0");
}
@ -31,7 +31,7 @@ public class JaxRsInstrumentationModule extends InstrumentationModule {
return asList(
new ContainerRequestFilterInstrumentation(),
new DefaultRequestContextInstrumentation(),
new JaxRsAnnotationsInstrumentation(),
new JaxRsAsyncResponseInstrumentation());
new JaxrsAnnotationsInstrumentation(),
new JaxrsAsyncResponseInstrumentation());
}
}

View File

@ -5,8 +5,8 @@
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
public final class JaxRsPathUtil {
private JaxRsPathUtil() {}
public final class JaxrsPathUtil {
private JaxrsPathUtil() {}
public static String normalizePath(String path) {
// ensure that non-empty path starts with /

View File

@ -0,0 +1,32 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_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.v2_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-2.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

@ -5,51 +5,44 @@
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxRsAnnotationsTracer.tracer;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxrsSingletons.instrumenter;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
import io.opentelemetry.instrumentation.api.tracer.ServerSpan;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import java.lang.reflect.Method;
import javax.ws.rs.container.ContainerRequestContext;
public final class RequestContextHelper {
public static Context createOrUpdateAbortSpan(
ContainerRequestContext requestContext, Class<?> resourceClass, Method method) {
ContainerRequestContext requestContext, HandlerData handlerData) {
if (method != null && resourceClass != null) {
requestContext.setProperty(JaxRsAnnotationsTracer.ABORT_HANDLED, true);
Context context = Java8BytecodeBridge.currentContext();
Span serverSpan = ServerSpan.fromContextOrNull(context);
Span currentSpan = Java8BytecodeBridge.spanFromContext(context);
// if there's no current span or it's the same as the server (servlet) span we need to start
// a JAX-RS one
// in other case, DefaultRequestContextInstrumentation must have already run so it's enough
// to just update the names
if (currentSpan == null || currentSpan == serverSpan) {
return tracer().startSpan(context, resourceClass, method);
} else {
tracer().updateSpanNames(context, currentSpan, serverSpan, resourceClass, method);
}
}
return null;
}
public static void closeSpanAndScope(Context context, Scope scope, Throwable throwable) {
if (context == null || scope == null) {
return;
if (handlerData == null) {
return null;
}
if (throwable != null) {
tracer().endExceptionally(context, throwable);
} else {
tracer().end(context);
requestContext.setProperty(JaxrsSingletons.ABORT_HANDLED, true);
Context parentContext = Java8BytecodeBridge.currentContext();
Span serverSpan = ServerSpan.fromContextOrNull(parentContext);
Span currentSpan = Java8BytecodeBridge.spanFromContext(parentContext);
ServerSpanNaming.updateServerSpanName(
parentContext,
ServerSpanNaming.Source.CONTROLLER,
JaxrsServerSpanNaming.getServerSpanNameSupplier(parentContext, handlerData));
if (currentSpan != null && currentSpan != serverSpan) {
// there's already an active span, and it's not the same as the server (servlet) span,
// so we don't want to start a JAX-RS one
return null;
}
scope.close();
if (!instrumenter().shouldStart(parentContext, handlerData)) {
return null;
}
return instrumenter().start(parentContext, handlerData);
}
private RequestContextHelper() {}

View File

@ -5,6 +5,7 @@
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxrsSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
@ -58,38 +59,47 @@ public class CxfRequestContextInstrumentation implements TypeInstrumentation {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void decorateAbortSpan(
@Advice.This AbstractRequestContextImpl requestContext,
@Local("otelHandlerData") HandlerData handlerData,
@Local("otelContext") Context context,
@Local("otelScope") Scope scope) {
if (requestContext.getProperty(JaxRsAnnotationsTracer.ABORT_HANDLED) == null
&& requestContext instanceof ContainerRequestContext) {
Message message = requestContext.getMessage();
OperationResourceInfoStack resourceInfoStack =
(OperationResourceInfoStack)
message.get("org.apache.cxf.jaxrs.model.OperationResourceInfoStack");
if (resourceInfoStack == null || resourceInfoStack.isEmpty()) {
return;
}
if (requestContext.getProperty(JaxrsSingletons.ABORT_HANDLED) != null
|| !(requestContext instanceof ContainerRequestContext)) {
return;
}
MethodInvocationInfo invocationInfo = resourceInfoStack.peek();
Method method = invocationInfo.getMethodInfo().getMethodToInvoke();
Class<?> resourceClass = invocationInfo.getRealClass();
Message message = requestContext.getMessage();
OperationResourceInfoStack resourceInfoStack =
(OperationResourceInfoStack)
message.get("org.apache.cxf.jaxrs.model.OperationResourceInfoStack");
if (resourceInfoStack == null || resourceInfoStack.isEmpty()) {
return;
}
context =
RequestContextHelper.createOrUpdateAbortSpan(
(ContainerRequestContext) requestContext, resourceClass, method);
if (context != null) {
scope = context.makeCurrent();
}
MethodInvocationInfo invocationInfo = resourceInfoStack.peek();
Method method = invocationInfo.getMethodInfo().getMethodToInvoke();
Class<?> resourceClass = invocationInfo.getRealClass();
handlerData = new HandlerData(resourceClass, method);
context =
RequestContextHelper.createOrUpdateAbortSpan(
(ContainerRequestContext) requestContext, handlerData);
if (context != null) {
scope = context.makeCurrent();
}
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Local("otelHandlerData") HandlerData handlerData,
@Local("otelContext") Context context,
@Local("otelScope") Scope scope,
@Advice.Thrown Throwable throwable) {
RequestContextHelper.closeSpanAndScope(context, scope, throwable);
if (scope == null) {
return;
}
scope.close();
instrumenter().end(context, handlerData, null, throwable);
}
}
}

View File

@ -5,7 +5,7 @@
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxRsPathUtil.normalizePath;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxrsPathUtil.normalizePath;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;

View File

@ -3,5 +3,5 @@
* SPDX-License-Identifier: Apache-2.0
*/
class CxfAnnotationInstrumentationTest extends JaxRsAnnotationsInstrumentationTest {
class CxfAnnotationInstrumentationTest extends JaxrsAnnotationsInstrumentationTest {
}

View File

@ -5,6 +5,8 @@
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxrsSingletons.instrumenter;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import java.lang.reflect.Method;
@ -35,31 +37,42 @@ public class JerseyRequestContextInstrumentation extends AbstractRequestContextI
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void decorateAbortSpan(
@Advice.This ContainerRequestContext requestContext,
@Local("otelHandlerData") HandlerData handlerData,
@Local("otelContext") Context context,
@Local("otelScope") Scope scope) {
UriInfo uriInfo = requestContext.getUriInfo();
if (requestContext.getProperty(JaxRsAnnotationsTracer.ABORT_HANDLED) == null
&& uriInfo instanceof ResourceInfo) {
if (requestContext.getProperty(JaxrsSingletons.ABORT_HANDLED) != null
|| !(uriInfo instanceof ResourceInfo)) {
return;
}
ResourceInfo resourceInfo = (ResourceInfo) uriInfo;
Method method = resourceInfo.getResourceMethod();
Class<?> resourceClass = resourceInfo.getResourceClass();
ResourceInfo resourceInfo = (ResourceInfo) uriInfo;
Method method = resourceInfo.getResourceMethod();
Class<?> resourceClass = resourceInfo.getResourceClass();
context =
RequestContextHelper.createOrUpdateAbortSpan(requestContext, resourceClass, method);
if (context != null) {
scope = context.makeCurrent();
}
if (resourceClass == null || method == null) {
return;
}
handlerData = new HandlerData(resourceClass, method);
context = RequestContextHelper.createOrUpdateAbortSpan(requestContext, handlerData);
if (context != null) {
scope = context.makeCurrent();
}
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Local("otelHandlerData") HandlerData handlerData,
@Local("otelContext") Context context,
@Local("otelScope") Scope scope,
@Advice.Thrown Throwable throwable) {
RequestContextHelper.closeSpanAndScope(context, scope, throwable);
if (scope == null) {
return;
}
scope.close();
instrumenter().end(context, handlerData, null, throwable);
}
}
}

View File

@ -5,7 +5,7 @@
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxRsPathUtil.normalizePath;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxrsPathUtil.normalizePath;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;

View File

@ -3,5 +3,5 @@
* SPDX-License-Identifier: Apache-2.0
*/
class JerseyAnnotationInstrumentationTest extends JaxRsAnnotationsInstrumentationTest {
class JerseyAnnotationInstrumentationTest extends JaxrsAnnotationsInstrumentationTest {
}

View File

@ -5,6 +5,8 @@
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxrsSingletons.instrumenter;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import java.lang.reflect.Method;
@ -36,30 +38,37 @@ public class Resteasy30RequestContextInstrumentation extends AbstractRequestCont
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void decorateAbortSpan(
@Advice.This ContainerRequestContext requestContext,
@Local("otelHandlerData") HandlerData handlerData,
@Local("otelContext") Context context,
@Local("otelScope") Scope scope) {
if (requestContext.getProperty(JaxRsAnnotationsTracer.ABORT_HANDLED) == null
&& requestContext instanceof PostMatchContainerRequestContext) {
if (requestContext.getProperty(JaxrsSingletons.ABORT_HANDLED) != null
|| !(requestContext instanceof PostMatchContainerRequestContext)) {
return;
}
ResourceMethodInvoker resourceMethodInvoker =
((PostMatchContainerRequestContext) requestContext).getResourceMethod();
Method method = resourceMethodInvoker.getMethod();
Class<?> resourceClass = resourceMethodInvoker.getResourceClass();
ResourceMethodInvoker resourceMethodInvoker =
((PostMatchContainerRequestContext) requestContext).getResourceMethod();
Method method = resourceMethodInvoker.getMethod();
Class<?> resourceClass = resourceMethodInvoker.getResourceClass();
context =
RequestContextHelper.createOrUpdateAbortSpan(requestContext, resourceClass, method);
if (context != null) {
scope = context.makeCurrent();
}
handlerData = new HandlerData(resourceClass, method);
context = RequestContextHelper.createOrUpdateAbortSpan(requestContext, handlerData);
if (context != null) {
scope = context.makeCurrent();
}
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Local("otelHandlerData") HandlerData handlerData,
@Local("otelContext") Context context,
@Local("otelScope") Scope scope,
@Advice.Thrown Throwable throwable) {
RequestContextHelper.closeSpanAndScope(context, scope, throwable);
if (scope == null) {
return;
}
scope.close();
instrumenter().end(context, handlerData, null, throwable);
}
}
}

View File

@ -3,5 +3,5 @@
* SPDX-License-Identifier: Apache-2.0
*/
class ResteasyAnnotationInstrumentationTest extends JaxRsAnnotationsInstrumentationTest {
class ResteasyAnnotationInstrumentationTest extends JaxrsAnnotationsInstrumentationTest {
}

View File

@ -5,6 +5,8 @@
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxrsSingletons.instrumenter;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import java.lang.reflect.Method;
@ -36,30 +38,37 @@ public class Resteasy31RequestContextInstrumentation extends AbstractRequestCont
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void decorateAbortSpan(
@Advice.This ContainerRequestContext requestContext,
@Local("otelHandlerData") HandlerData handlerData,
@Local("otelContext") Context context,
@Local("otelScope") Scope scope) {
if (requestContext.getProperty(JaxRsAnnotationsTracer.ABORT_HANDLED) == null
&& requestContext instanceof PostMatchContainerRequestContext) {
if (requestContext.getProperty(JaxrsSingletons.ABORT_HANDLED) != null
|| !(requestContext instanceof PostMatchContainerRequestContext)) {
return;
}
ResourceMethodInvoker resourceMethodInvoker =
((PostMatchContainerRequestContext) requestContext).getResourceMethod();
Method method = resourceMethodInvoker.getMethod();
Class<?> resourceClass = resourceMethodInvoker.getResourceClass();
ResourceMethodInvoker resourceMethodInvoker =
((PostMatchContainerRequestContext) requestContext).getResourceMethod();
Method method = resourceMethodInvoker.getMethod();
Class<?> resourceClass = resourceMethodInvoker.getResourceClass();
context =
RequestContextHelper.createOrUpdateAbortSpan(requestContext, resourceClass, method);
if (context != null) {
scope = context.makeCurrent();
}
handlerData = new HandlerData(resourceClass, method);
context = RequestContextHelper.createOrUpdateAbortSpan(requestContext, handlerData);
if (context != null) {
scope = context.makeCurrent();
}
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Local("otelHandlerData") HandlerData handlerData,
@Local("otelContext") Context context,
@Local("otelScope") Scope scope,
@Advice.Thrown Throwable throwable) {
RequestContextHelper.closeSpanAndScope(context, scope, throwable);
if (scope == null) {
return;
}
scope.close();
instrumenter().end(context, handlerData, null, throwable);
}
}
}

View File

@ -3,5 +3,5 @@
* SPDX-License-Identifier: Apache-2.0
*/
class ResteasyAnnotationInstrumentationTest extends JaxRsAnnotationsInstrumentationTest {
class ResteasyAnnotationInstrumentationTest extends JaxrsAnnotationsInstrumentationTest {
}

View File

@ -47,7 +47,7 @@ public class ResteasyRootNodeTypeInstrumentation implements TypeInstrumentation
public static void addInvoker(
@Advice.Argument(0) String path,
@Advice.Argument(value = 1, typing = Assigner.Typing.DYNAMIC) Object invoker) {
String normalizedPath = JaxRsPathUtil.normalizePath(path);
String normalizedPath = JaxrsPathUtil.normalizePath(path);
if (invoker instanceof ResourceLocatorInvoker) {
ResourceLocatorInvoker resourceLocatorInvoker = (ResourceLocatorInvoker) invoker;
InstrumentationContext.get(ResourceLocatorInvoker.class, String.class)

View File

@ -18,31 +18,7 @@ import javax.ws.rs.PUT
import javax.ws.rs.Path
import spock.lang.Unroll
abstract class JaxRsAnnotationsInstrumentationTest 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"
}
}
}
}
}
abstract class JaxrsAnnotationsInstrumentationTest extends AgentInstrumentationSpecification {
@Unroll
def "span named '#paramName' from annotations on class '#className' when is not root span"() {