make servlet indy-ready (#14200)
This commit is contained in:
parent
298319f7b0
commit
4b184e7569
|
|
@ -5,16 +5,16 @@
|
|||
|
||||
package io.opentelemetry.javaagent.instrumentation.servlet.v2_2;
|
||||
|
||||
import static io.opentelemetry.javaagent.instrumentation.servlet.v2_2.Servlet2Singletons.RESPONSE_STATUS;
|
||||
import static io.opentelemetry.javaagent.instrumentation.servlet.v2_2.Servlet2Singletons.helper;
|
||||
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import io.opentelemetry.instrumentation.api.util.VirtualField;
|
||||
import io.opentelemetry.javaagent.bootstrap.CallDepth;
|
||||
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
|
||||
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder;
|
||||
import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge;
|
||||
import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
|
@ -25,93 +25,109 @@ import net.bytebuddy.implementation.bytecode.assign.Assigner;
|
|||
@SuppressWarnings("unused")
|
||||
public class Servlet2Advice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void onEnter(
|
||||
@Advice.Argument(0) ServletRequest request,
|
||||
@Advice.Argument(value = 1, typing = Assigner.Typing.DYNAMIC) ServletResponse response,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth,
|
||||
@Advice.Local("otelRequest") ServletRequestContext<HttpServletRequest> requestContext,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
public static class AdviceScope {
|
||||
|
||||
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
|
||||
return;
|
||||
}
|
||||
private final CallDepth callDepth;
|
||||
private final ServletRequestContext<HttpServletRequest> requestContext;
|
||||
private final Context context;
|
||||
private final Scope scope;
|
||||
|
||||
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||
public AdviceScope(
|
||||
CallDepth callDepth, HttpServletRequest request, HttpServletResponse response) {
|
||||
this.callDepth = callDepth;
|
||||
callDepth.getAndIncrement();
|
||||
|
||||
callDepth = CallDepth.forClass(AppServerBridge.getCallDepthKey());
|
||||
callDepth.getAndIncrement();
|
||||
|
||||
Context serverContext = helper().getServerContext(httpServletRequest);
|
||||
if (serverContext != null) {
|
||||
Context updatedContext = helper().updateContext(serverContext, httpServletRequest);
|
||||
if (updatedContext != serverContext) {
|
||||
// updateContext updated context, need to re-scope
|
||||
scope = updatedContext.makeCurrent();
|
||||
Context serverContext = helper().getServerContext(request);
|
||||
if (serverContext != null) {
|
||||
Context updatedContext = helper().updateContext(serverContext, request);
|
||||
if (updatedContext != serverContext) {
|
||||
// updateContext updated context, need to re-scope
|
||||
scope = updatedContext.makeCurrent();
|
||||
} else {
|
||||
scope = null;
|
||||
}
|
||||
requestContext = null;
|
||||
context = null;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
|
||||
Context parentContext = Context.current();
|
||||
requestContext = new ServletRequestContext<>(request);
|
||||
|
||||
if (!helper().shouldStart(parentContext, requestContext)) {
|
||||
context = null;
|
||||
scope = null;
|
||||
return;
|
||||
}
|
||||
|
||||
context = helper().start(parentContext, requestContext);
|
||||
scope = context.makeCurrent();
|
||||
// reset response status from previous request
|
||||
// (some servlet containers reuse response objects to reduce memory allocations)
|
||||
RESPONSE_STATUS.set(response, null);
|
||||
|
||||
HttpServerResponseCustomizerHolder.getCustomizer()
|
||||
.customize(context, response, Servlet2Accessor.INSTANCE);
|
||||
}
|
||||
|
||||
Context parentContext = Java8BytecodeBridge.currentContext();
|
||||
requestContext = new ServletRequestContext<>(httpServletRequest);
|
||||
public void exit(
|
||||
@Nullable Throwable throwable, HttpServletRequest request, HttpServletResponse response) {
|
||||
|
||||
if (!helper().shouldStart(parentContext, requestContext)) {
|
||||
return;
|
||||
if (scope != null) {
|
||||
scope.close();
|
||||
}
|
||||
|
||||
boolean topLevel = callDepth.decrementAndGet() == 0;
|
||||
if (context == null && topLevel) {
|
||||
Context currentContext = Context.current();
|
||||
// Something else is managing the context, we're in the outermost level of Servlet
|
||||
// instrumentation and we have an uncaught throwable. Let's add it to the current span.
|
||||
if (throwable != null) {
|
||||
helper().recordException(currentContext, throwable);
|
||||
}
|
||||
// also capture request parameters as servlet attributes
|
||||
helper().captureServletAttributes(currentContext, request);
|
||||
}
|
||||
|
||||
if (scope == null || context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int responseStatusCode = HttpServletResponse.SC_OK;
|
||||
Integer responseStatus = RESPONSE_STATUS.get(response);
|
||||
if (responseStatus != null) {
|
||||
responseStatusCode = responseStatus;
|
||||
}
|
||||
|
||||
helper().end(context, requestContext, response, responseStatusCode, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
context = helper().start(parentContext, requestContext);
|
||||
scope = context.makeCurrent();
|
||||
// reset response status from previous request
|
||||
// (some servlet containers reuse response objects to reduce memory allocations)
|
||||
VirtualField.find(ServletResponse.class, Integer.class).set(response, null);
|
||||
|
||||
HttpServerResponseCustomizerHolder.getCustomizer()
|
||||
.customize(context, (HttpServletResponse) response, Servlet2Accessor.INSTANCE);
|
||||
@Nullable
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static AdviceScope onEnter(
|
||||
@Advice.Argument(0) ServletRequest request,
|
||||
@Advice.Argument(value = 1, typing = Assigner.Typing.DYNAMIC) ServletResponse response) {
|
||||
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
|
||||
return null;
|
||||
}
|
||||
return new AdviceScope(
|
||||
CallDepth.forClass(AppServerBridge.getCallDepthKey()),
|
||||
(HttpServletRequest) request,
|
||||
(HttpServletResponse) response);
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void stopSpan(
|
||||
@Advice.Argument(0) ServletRequest request,
|
||||
@Advice.Argument(1) ServletResponse response,
|
||||
@Advice.Thrown Throwable throwable,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth,
|
||||
@Advice.Local("otelRequest") ServletRequestContext<HttpServletRequest> requestContext,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
|
||||
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
|
||||
@Advice.Thrown @Nullable Throwable throwable,
|
||||
@Advice.Enter @Nullable AdviceScope adviceScope) {
|
||||
if (adviceScope == null
|
||||
|| !(request instanceof HttpServletRequest)
|
||||
|| !(response instanceof HttpServletResponse)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scope != null) {
|
||||
scope.close();
|
||||
}
|
||||
|
||||
boolean topLevel = callDepth.decrementAndGet() == 0;
|
||||
if (context == null && topLevel) {
|
||||
Context currentContext = Java8BytecodeBridge.currentContext();
|
||||
// Something else is managing the context, we're in the outermost level of Servlet
|
||||
// instrumentation and we have an uncaught throwable. Let's add it to the current span.
|
||||
if (throwable != null) {
|
||||
helper().recordException(currentContext, throwable);
|
||||
}
|
||||
// also capture request parameters as servlet attributes
|
||||
helper().captureServletAttributes(currentContext, (HttpServletRequest) request);
|
||||
}
|
||||
|
||||
if (scope == null || context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int responseStatusCode = HttpServletResponse.SC_OK;
|
||||
Integer responseStatus = VirtualField.find(ServletResponse.class, Integer.class).get(response);
|
||||
if (responseStatus != null) {
|
||||
responseStatusCode = responseStatus;
|
||||
}
|
||||
|
||||
helper()
|
||||
.end(
|
||||
context, requestContext, (HttpServletResponse) response, responseStatusCode, throwable);
|
||||
adviceScope.exit(throwable, (HttpServletRequest) request, (HttpServletResponse) response);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v2_2;
|
|||
|
||||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
|
||||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType;
|
||||
import static io.opentelemetry.javaagent.instrumentation.servlet.v2_2.Servlet2Singletons.RESPONSE_STATUS;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
|
||||
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import io.opentelemetry.instrumentation.api.util.VirtualField;
|
||||
import io.opentelemetry.javaagent.bootstrap.CallDepth;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
||||
|
|
@ -63,7 +63,7 @@ public class Servlet2HttpServletResponseInstrumentation implements TypeInstrumen
|
|||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void onEnter(@Advice.This HttpServletResponse response) {
|
||||
VirtualField.find(ServletResponse.class, Integer.class).set(response, 302);
|
||||
RESPONSE_STATUS.set(response, 302);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ public class Servlet2HttpServletResponseInstrumentation implements TypeInstrumen
|
|||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void onEnter(
|
||||
@Advice.This HttpServletResponse response, @Advice.Argument(0) Integer status) {
|
||||
VirtualField.find(ServletResponse.class, Integer.class).set(response, status);
|
||||
RESPONSE_STATUS.set(response, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import static net.bytebuddy.matcher.ElementMatchers.not;
|
|||
import com.google.auto.service.AutoService;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
|
||||
import io.opentelemetry.javaagent.instrumentation.servlet.common.response.HttpServletResponseInstrumentation;
|
||||
import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletAndFilterInstrumentation;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -18,7 +19,8 @@ import java.util.List;
|
|||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
|
||||
@AutoService(InstrumentationModule.class)
|
||||
public class Servlet2InstrumentationModule extends InstrumentationModule {
|
||||
public class Servlet2InstrumentationModule extends InstrumentationModule
|
||||
implements ExperimentalInstrumentationModule {
|
||||
private static final String BASE_PACKAGE = "javax.servlet";
|
||||
|
||||
public Servlet2InstrumentationModule() {
|
||||
|
|
@ -43,4 +45,9 @@ public class Servlet2InstrumentationModule extends InstrumentationModule {
|
|||
private static String adviceClassName(String suffix) {
|
||||
return Servlet2InstrumentationModule.class.getPackage().getName() + suffix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIndyReady() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,47 +12,61 @@ import io.opentelemetry.context.Scope;
|
|||
import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
|
||||
import io.opentelemetry.javaagent.bootstrap.CallDepth;
|
||||
import io.opentelemetry.javaagent.instrumentation.servlet.common.response.HttpServletResponseAdviceHelper;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Servlet2ResponseSendAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void start(
|
||||
@Advice.Origin("#t") Class<?> declaringClass,
|
||||
@Advice.Origin("#m") String methodName,
|
||||
@Advice.Local("otelMethod") ClassAndMethod classAndMethod,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth) {
|
||||
callDepth = CallDepth.forClass(HttpServletResponse.class);
|
||||
if (callDepth.getAndIncrement() > 0) {
|
||||
return;
|
||||
public static class AdviceScope {
|
||||
private final CallDepth callDepth;
|
||||
private final ClassAndMethod classAndMethod;
|
||||
private final Context context;
|
||||
private final Scope scope;
|
||||
|
||||
public AdviceScope(CallDepth callDepth, Class<?> declaringClass, String methodName) {
|
||||
this.callDepth = callDepth;
|
||||
if (callDepth.getAndIncrement() > 0) {
|
||||
this.classAndMethod = null;
|
||||
this.context = null;
|
||||
this.scope = null;
|
||||
return;
|
||||
}
|
||||
|
||||
HttpServletResponseAdviceHelper.StartResult result =
|
||||
HttpServletResponseAdviceHelper.startSpan(
|
||||
responseInstrumenter(), declaringClass, methodName);
|
||||
if (result == null) {
|
||||
this.classAndMethod = null;
|
||||
this.context = null;
|
||||
this.scope = null;
|
||||
return;
|
||||
}
|
||||
this.classAndMethod = result.getClassAndMethod();
|
||||
this.context = result.getContext();
|
||||
this.scope = result.getScope();
|
||||
}
|
||||
|
||||
HttpServletResponseAdviceHelper.StartResult result =
|
||||
HttpServletResponseAdviceHelper.startSpan(
|
||||
responseInstrumenter(), declaringClass, methodName);
|
||||
if (result != null) {
|
||||
classAndMethod = result.getClassAndMethod();
|
||||
context = result.getContext();
|
||||
scope = result.getScope();
|
||||
public void exit(@Nullable Throwable throwable) {
|
||||
if (callDepth.decrementAndGet() > 0) {
|
||||
return;
|
||||
}
|
||||
HttpServletResponseAdviceHelper.stopSpan(
|
||||
responseInstrumenter(), throwable, context, scope, classAndMethod);
|
||||
}
|
||||
}
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static AdviceScope start(
|
||||
@Advice.Origin("#t") Class<?> declaringClass, @Advice.Origin("#m") String methodName) {
|
||||
return new AdviceScope(
|
||||
CallDepth.forClass(HttpServletResponse.class), declaringClass, methodName);
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void stopSpan(
|
||||
@Advice.Thrown Throwable throwable,
|
||||
@Advice.Local("otelMethod") ClassAndMethod classAndMethod,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth) {
|
||||
if (callDepth.decrementAndGet() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
HttpServletResponseAdviceHelper.stopSpan(
|
||||
responseInstrumenter(), throwable, context, scope, classAndMethod);
|
||||
@Advice.Thrown @Nullable Throwable throwable, @Advice.Enter AdviceScope adviceScope) {
|
||||
adviceScope.exit(throwable);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,16 +8,21 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v2_2;
|
|||
import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
|
||||
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
||||
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
|
||||
import io.opentelemetry.instrumentation.api.util.VirtualField;
|
||||
import io.opentelemetry.javaagent.instrumentation.servlet.ServletInstrumenterBuilder;
|
||||
import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext;
|
||||
import io.opentelemetry.javaagent.instrumentation.servlet.ServletResponseContext;
|
||||
import io.opentelemetry.javaagent.instrumentation.servlet.common.response.ResponseInstrumenterFactory;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public final class Servlet2Singletons {
|
||||
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.servlet-2.2";
|
||||
|
||||
public static final VirtualField<ServletResponse, Integer> RESPONSE_STATUS =
|
||||
VirtualField.find(ServletResponse.class, Integer.class);
|
||||
|
||||
private static final Servlet2Helper HELPER;
|
||||
private static final Instrumenter<ClassAndMethod, Void> RESPONSE_INSTRUMENTER;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,13 +19,11 @@ import net.bytebuddy.asm.Advice;
|
|||
public class AsyncDispatchAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void enter(
|
||||
@Advice.This AsyncContext context,
|
||||
@Advice.AllArguments Object[] args,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth) {
|
||||
callDepth = CallDepth.forClass(AsyncContext.class);
|
||||
public static CallDepth enter(
|
||||
@Advice.This AsyncContext context, @Advice.AllArguments Object[] args) {
|
||||
CallDepth callDepth = CallDepth.forClass(AsyncContext.class);
|
||||
if (callDepth.getAndIncrement() > 0) {
|
||||
return;
|
||||
return callDepth;
|
||||
}
|
||||
|
||||
ServletRequest request = context.getRequest();
|
||||
|
|
@ -42,10 +40,11 @@ public class AsyncDispatchAdvice {
|
|||
// processing, and nothing can be done with the request anymore after this
|
||||
request.setAttribute(CONTEXT_ATTRIBUTE, currentContext);
|
||||
}
|
||||
return callDepth;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void exit(@Advice.Local("otelCallDepth") CallDepth callDepth) {
|
||||
public static void exit(@Advice.Enter CallDepth callDepth) {
|
||||
callDepth.decrementAndGet();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,37 +11,101 @@ import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Si
|
|||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import io.opentelemetry.javaagent.bootstrap.CallDepth;
|
||||
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
|
||||
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder;
|
||||
import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge;
|
||||
import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver;
|
||||
import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext;
|
||||
import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Servlet3SnippetInjectingResponseWrapper;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.Servlet;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.asm.Advice.AssignReturned;
|
||||
import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument;
|
||||
import net.bytebuddy.implementation.bytecode.assign.Assigner;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Servlet3Advice {
|
||||
|
||||
public static class AdviceScope {
|
||||
private final CallDepth callDepth;
|
||||
private final ServletRequestContext<HttpServletRequest> requestContext;
|
||||
private final Context context;
|
||||
private final Scope scope;
|
||||
|
||||
public AdviceScope(
|
||||
CallDepth callDepth,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Object servletOrFilter) {
|
||||
this.callDepth = callDepth;
|
||||
this.callDepth.getAndIncrement();
|
||||
|
||||
Context currentContext = Context.current();
|
||||
Context attachedContext = helper().getServerContext(request);
|
||||
Context contextToUpdate;
|
||||
|
||||
requestContext = new ServletRequestContext<>(request, servletOrFilter);
|
||||
if (attachedContext == null && helper().shouldStart(currentContext, requestContext)) {
|
||||
context = helper().start(currentContext, requestContext);
|
||||
helper().setAsyncListenerResponse(context, response);
|
||||
|
||||
contextToUpdate = context;
|
||||
} else if (attachedContext != null
|
||||
&& helper().needsRescoping(currentContext, attachedContext)) {
|
||||
// Given request already has a context associated with it.
|
||||
// see the needsRescoping() javadoc for more explanation
|
||||
contextToUpdate = attachedContext;
|
||||
context = null;
|
||||
} else {
|
||||
// We are inside nested servlet/filter/app-server span, don't create new span
|
||||
contextToUpdate = currentContext;
|
||||
context = null;
|
||||
}
|
||||
|
||||
// Update context with info from current request to ensure that server span gets the best
|
||||
// possible name.
|
||||
// In case server span was created by app server instrumentations calling updateContext
|
||||
// returns a new context that contains servlet context path that is used in other
|
||||
// instrumentations for naming server span.
|
||||
MappingResolver mappingResolver = Servlet3Singletons.getMappingResolver(servletOrFilter);
|
||||
boolean servlet = servletOrFilter instanceof Servlet;
|
||||
contextToUpdate = helper().updateContext(contextToUpdate, request, mappingResolver, servlet);
|
||||
scope = contextToUpdate.makeCurrent();
|
||||
|
||||
if (context != null) {
|
||||
// Only trigger response customizer once, so only if server span was created here
|
||||
HttpServerResponseCustomizerHolder.getCustomizer()
|
||||
.customize(contextToUpdate, response, Servlet3Accessor.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
public void exit(
|
||||
@Nullable Throwable throwable, HttpServletRequest request, HttpServletResponse response) {
|
||||
|
||||
boolean topLevel = callDepth.decrementAndGet() == 0;
|
||||
helper().end(requestContext, request, response, throwable, topLevel, context, scope);
|
||||
}
|
||||
}
|
||||
|
||||
@AssignReturned.ToArguments({
|
||||
@ToArgument(value = 0, index = 1),
|
||||
@ToArgument(value = 1, index = 2)
|
||||
})
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void onEnter(
|
||||
public static Object[] onEnter(
|
||||
@Advice.This(typing = Assigner.Typing.DYNAMIC) Object servletOrFilter,
|
||||
@Advice.Argument(value = 0, readOnly = false) ServletRequest request,
|
||||
@Advice.Argument(value = 1, readOnly = false) ServletResponse response,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth,
|
||||
@Advice.Local("otelRequest") ServletRequestContext<HttpServletRequest> requestContext,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
@Advice.Argument(0) ServletRequest request,
|
||||
@Advice.Argument(1) ServletResponse originalResponse) {
|
||||
|
||||
ServletResponse response = originalResponse;
|
||||
|
||||
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
|
||||
return;
|
||||
return new Object[] {null, request, response};
|
||||
}
|
||||
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||
|
||||
String snippet = getSnippetInjectionHelper().getSnippet();
|
||||
if (!snippet.isEmpty()
|
||||
|
|
@ -50,70 +114,27 @@ public class Servlet3Advice {
|
|||
response =
|
||||
new Servlet3SnippetInjectingResponseWrapper((HttpServletResponse) response, snippet);
|
||||
}
|
||||
callDepth = CallDepth.forClass(AppServerBridge.getCallDepthKey());
|
||||
callDepth.getAndIncrement();
|
||||
|
||||
Context currentContext = Java8BytecodeBridge.currentContext();
|
||||
Context attachedContext = helper().getServerContext(httpServletRequest);
|
||||
Context contextToUpdate;
|
||||
|
||||
requestContext = new ServletRequestContext<>(httpServletRequest, servletOrFilter);
|
||||
if (attachedContext == null && helper().shouldStart(currentContext, requestContext)) {
|
||||
context = helper().start(currentContext, requestContext);
|
||||
helper().setAsyncListenerResponse(context, (HttpServletResponse) response);
|
||||
|
||||
contextToUpdate = context;
|
||||
} else if (attachedContext != null
|
||||
&& helper().needsRescoping(currentContext, attachedContext)) {
|
||||
// Given request already has a context associated with it.
|
||||
// see the needsRescoping() javadoc for more explanation
|
||||
contextToUpdate = attachedContext;
|
||||
} else {
|
||||
// We are inside nested servlet/filter/app-server span, don't create new span
|
||||
contextToUpdate = currentContext;
|
||||
}
|
||||
|
||||
// Update context with info from current request to ensure that server span gets the best
|
||||
// possible name.
|
||||
// In case server span was created by app server instrumentations calling updateContext
|
||||
// returns a new context that contains servlet context path that is used in other
|
||||
// instrumentations for naming server span.
|
||||
MappingResolver mappingResolver = Servlet3Singletons.getMappingResolver(servletOrFilter);
|
||||
boolean servlet = servletOrFilter instanceof Servlet;
|
||||
contextToUpdate =
|
||||
helper().updateContext(contextToUpdate, httpServletRequest, mappingResolver, servlet);
|
||||
scope = contextToUpdate.makeCurrent();
|
||||
|
||||
if (context != null) {
|
||||
// Only trigger response customizer once, so only if server span was created here
|
||||
HttpServerResponseCustomizerHolder.getCustomizer()
|
||||
.customize(contextToUpdate, (HttpServletResponse) response, Servlet3Accessor.INSTANCE);
|
||||
}
|
||||
AdviceScope adviceScope =
|
||||
new AdviceScope(
|
||||
CallDepth.forClass(AppServerBridge.getCallDepthKey()),
|
||||
(HttpServletRequest) request,
|
||||
(HttpServletResponse) response,
|
||||
servletOrFilter);
|
||||
return new Object[] {adviceScope, request, response};
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void stopSpan(
|
||||
@Advice.Argument(0) ServletRequest request,
|
||||
@Advice.Argument(1) ServletResponse response,
|
||||
@Advice.Thrown Throwable throwable,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth,
|
||||
@Advice.Local("otelRequest") ServletRequestContext<HttpServletRequest> requestContext,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
|
||||
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
|
||||
@Advice.Thrown @Nullable Throwable throwable,
|
||||
@Advice.Enter Object[] enterResult) {
|
||||
AdviceScope adviceScope = (AdviceScope) enterResult[0];
|
||||
if (adviceScope == null
|
||||
|| !(request instanceof HttpServletRequest)
|
||||
|| !(response instanceof HttpServletResponse)) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean topLevel = callDepth.decrementAndGet() == 0;
|
||||
helper()
|
||||
.end(
|
||||
requestContext,
|
||||
(HttpServletRequest) request,
|
||||
(HttpServletResponse) response,
|
||||
throwable,
|
||||
topLevel,
|
||||
context,
|
||||
scope);
|
||||
adviceScope.exit(throwable, (HttpServletRequest) request, (HttpServletResponse) response);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,15 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0;
|
|||
import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper;
|
||||
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.asm.Advice.AssignReturned;
|
||||
import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Servlet3AsyncContextStartAdvice {
|
||||
|
||||
@AssignReturned.ToArguments(@ToArgument(0))
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void start(@Advice.Argument(value = 0, readOnly = false) Runnable runnable) {
|
||||
runnable = helper().wrapAsyncRunnable(runnable);
|
||||
public static Runnable start(@Advice.Argument(0) Runnable runnable) {
|
||||
return helper().wrapAsyncRunnable(runnable);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,16 +18,16 @@ import net.bytebuddy.asm.Advice;
|
|||
public class Servlet3AsyncStartAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void startAsyncEnter(@Advice.Local("otelCallDepth") CallDepth callDepth) {
|
||||
public static CallDepth startAsyncEnter() {
|
||||
CallDepth callDepth = CallDepth.forClass(AsyncContext.class);
|
||||
// This allows to detect the outermost invocation of startAsync in method exit
|
||||
callDepth = CallDepth.forClass(AsyncContext.class);
|
||||
callDepth.getAndIncrement();
|
||||
return callDepth;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(suppress = Throwable.class)
|
||||
public static void startAsyncExit(
|
||||
@Advice.This ServletRequest servletRequest,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth) {
|
||||
@Advice.This ServletRequest servletRequest, @Advice.Enter CallDepth callDepth) {
|
||||
|
||||
if (callDepth.decrementAndGet() != 0) {
|
||||
// This is not the outermost invocation, ignore.
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
package io.opentelemetry.javaagent.instrumentation.servlet.v3_0;
|
||||
|
||||
import io.opentelemetry.instrumentation.api.util.VirtualField;
|
||||
import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver;
|
||||
import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.FILTER_MAPPING_RESOLVER_FACTORY;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterConfig;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
|
|
@ -20,7 +20,7 @@ public class Servlet3FilterInitAdvice {
|
|||
if (filterConfig == null) {
|
||||
return;
|
||||
}
|
||||
VirtualField.find(Filter.class, MappingResolver.Factory.class)
|
||||
.set(filter, new Servlet3FilterMappingResolverFactory(filterConfig));
|
||||
FILTER_MAPPING_RESOLVER_FACTORY.set(
|
||||
filter, new Servlet3FilterMappingResolverFactory(filterConfig));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
package io.opentelemetry.javaagent.instrumentation.servlet.v3_0;
|
||||
|
||||
import io.opentelemetry.instrumentation.api.util.VirtualField;
|
||||
import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver;
|
||||
import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.SERVLET_MAPPING_RESOLVER_FACTORY;
|
||||
|
||||
import javax.servlet.Servlet;
|
||||
import javax.servlet.ServletConfig;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
|
|
@ -20,7 +20,7 @@ public class Servlet3InitAdvice {
|
|||
if (servletConfig == null) {
|
||||
return;
|
||||
}
|
||||
VirtualField.find(Servlet.class, MappingResolver.Factory.class)
|
||||
.set(servlet, new Servlet3MappingResolverFactory(servletConfig));
|
||||
SERVLET_MAPPING_RESOLVER_FACTORY.set(
|
||||
servlet, new Servlet3MappingResolverFactory(servletConfig));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,4 +65,9 @@ public class Servlet3InstrumentationModule extends InstrumentationModule
|
|||
private static String adviceClassName(String suffix) {
|
||||
return Servlet3InstrumentationModule.class.getPackage().getName() + suffix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIndyReady() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,47 +12,60 @@ import io.opentelemetry.context.Scope;
|
|||
import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
|
||||
import io.opentelemetry.javaagent.bootstrap.CallDepth;
|
||||
import io.opentelemetry.javaagent.instrumentation.servlet.common.response.HttpServletResponseAdviceHelper;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Servlet3ResponseSendAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void start(
|
||||
@Advice.Origin("#t") Class<?> declaringClass,
|
||||
@Advice.Origin("#m") String methodName,
|
||||
@Advice.Local("otelMethod") ClassAndMethod classAndMethod,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth) {
|
||||
callDepth = CallDepth.forClass(HttpServletResponse.class);
|
||||
if (callDepth.getAndIncrement() > 0) {
|
||||
return;
|
||||
public static class AdviceScope {
|
||||
private final CallDepth callDepth;
|
||||
private final ClassAndMethod classAndMethod;
|
||||
private final Context context;
|
||||
private final Scope scope;
|
||||
|
||||
public AdviceScope(CallDepth callDepth, Class<?> declaringClass, String methodName) {
|
||||
this.callDepth = callDepth;
|
||||
if (callDepth.getAndIncrement() > 0) {
|
||||
this.classAndMethod = null;
|
||||
this.context = null;
|
||||
this.scope = null;
|
||||
return;
|
||||
}
|
||||
HttpServletResponseAdviceHelper.StartResult result =
|
||||
HttpServletResponseAdviceHelper.startSpan(
|
||||
responseInstrumenter(), declaringClass, methodName);
|
||||
if (result != null) {
|
||||
classAndMethod = result.getClassAndMethod();
|
||||
context = result.getContext();
|
||||
scope = result.getScope();
|
||||
} else {
|
||||
classAndMethod = null;
|
||||
context = null;
|
||||
scope = null;
|
||||
}
|
||||
}
|
||||
|
||||
HttpServletResponseAdviceHelper.StartResult result =
|
||||
HttpServletResponseAdviceHelper.startSpan(
|
||||
responseInstrumenter(), declaringClass, methodName);
|
||||
if (result != null) {
|
||||
classAndMethod = result.getClassAndMethod();
|
||||
context = result.getContext();
|
||||
scope = result.getScope();
|
||||
public void exit(@Nullable Throwable throwable) {
|
||||
if (callDepth.decrementAndGet() > 0) {
|
||||
return;
|
||||
}
|
||||
HttpServletResponseAdviceHelper.stopSpan(
|
||||
responseInstrumenter(), throwable, context, scope, classAndMethod);
|
||||
}
|
||||
}
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static AdviceScope start(
|
||||
@Advice.Origin("#t") Class<?> declaringClass, @Advice.Origin("#m") String methodName) {
|
||||
return new AdviceScope(
|
||||
CallDepth.forClass(HttpServletResponse.class), declaringClass, methodName);
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void stopSpan(
|
||||
@Advice.Thrown Throwable throwable,
|
||||
@Advice.Local("otelMethod") ClassAndMethod classAndMethod,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth) {
|
||||
if (callDepth.decrementAndGet() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
HttpServletResponseAdviceHelper.stopSpan(
|
||||
responseInstrumenter(), throwable, context, scope, classAndMethod);
|
||||
@Advice.Thrown Throwable throwable, @Advice.Enter AdviceScope adviceScope) {
|
||||
adviceScope.exit(throwable);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,14 @@ import javax.servlet.http.HttpServletResponse;
|
|||
public final class Servlet3Singletons {
|
||||
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.servlet-3.0";
|
||||
|
||||
public static final VirtualField<Servlet, MappingResolver.Factory>
|
||||
SERVLET_MAPPING_RESOLVER_FACTORY =
|
||||
VirtualField.find(Servlet.class, MappingResolver.Factory.class);
|
||||
|
||||
public static final VirtualField<Filter, MappingResolver.Factory>
|
||||
FILTER_MAPPING_RESOLVER_FACTORY =
|
||||
VirtualField.find(Filter.class, MappingResolver.Factory.class);
|
||||
|
||||
private static final Instrumenter<
|
||||
ServletRequestContext<HttpServletRequest>, ServletResponseContext<HttpServletResponse>>
|
||||
INSTRUMENTER =
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v5_0;
|
|||
import com.google.auto.service.AutoService;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
|
||||
import io.opentelemetry.javaagent.instrumentation.servlet.common.async.AsyncContextInstrumentation;
|
||||
import io.opentelemetry.javaagent.instrumentation.servlet.common.async.AsyncContextStartInstrumentation;
|
||||
import io.opentelemetry.javaagent.instrumentation.servlet.common.async.AsyncStartInstrumentation;
|
||||
|
|
@ -18,7 +19,8 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
|
||||
@AutoService(InstrumentationModule.class)
|
||||
public class JakartaServletInstrumentationModule extends InstrumentationModule {
|
||||
public class JakartaServletInstrumentationModule extends InstrumentationModule
|
||||
implements ExperimentalInstrumentationModule {
|
||||
private static final String BASE_PACKAGE = "jakarta.servlet";
|
||||
|
||||
public JakartaServletInstrumentationModule() {
|
||||
|
|
@ -50,4 +52,9 @@ public class JakartaServletInstrumentationModule extends InstrumentationModule {
|
|||
private static String adviceClassName(String suffix) {
|
||||
return JakartaServletInstrumentationModule.class.getPackage().getName() + suffix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIndyReady() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,9 +34,9 @@ public final class Servlet5Singletons {
|
|||
private static final ServletHelper<HttpServletRequest, HttpServletResponse> HELPER =
|
||||
new ServletHelper<>(INSTRUMENTER, Servlet5Accessor.INSTANCE);
|
||||
|
||||
private static final VirtualField<Servlet, MappingResolver.Factory> SERVLET_MAPPING_RESOLVER =
|
||||
public static final VirtualField<Servlet, MappingResolver.Factory> SERVLET_MAPPING_RESOLVER =
|
||||
VirtualField.find(Servlet.class, MappingResolver.Factory.class);
|
||||
private static final VirtualField<Filter, MappingResolver.Factory> FILTER_MAPPING_RESOLVER =
|
||||
public static final VirtualField<Filter, MappingResolver.Factory> FILTER_MAPPING_RESOLVER =
|
||||
VirtualField.find(Filter.class, MappingResolver.Factory.class);
|
||||
|
||||
private static final Instrumenter<ClassAndMethod, Void> RESPONSE_INSTRUMENTER =
|
||||
|
|
|
|||
|
|
@ -8,12 +8,15 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v5_0.async;
|
|||
import static io.opentelemetry.javaagent.instrumentation.servlet.v5_0.Servlet5Singletons.helper;
|
||||
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.asm.Advice.AssignReturned;
|
||||
import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class AsyncContextStartAdvice {
|
||||
|
||||
@AssignReturned.ToArguments(@ToArgument(0))
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void start(@Advice.Argument(value = 0, readOnly = false) Runnable runnable) {
|
||||
runnable = helper().wrapAsyncRunnable(runnable);
|
||||
public static Runnable start(@Advice.Argument(0) Runnable runnable) {
|
||||
return helper().wrapAsyncRunnable(runnable);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,13 +19,11 @@ import net.bytebuddy.asm.Advice;
|
|||
public class AsyncDispatchAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void enter(
|
||||
@Advice.This AsyncContext context,
|
||||
@Advice.AllArguments Object[] args,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth) {
|
||||
callDepth = CallDepth.forClass(AsyncContext.class);
|
||||
public static CallDepth enter(
|
||||
@Advice.This AsyncContext context, @Advice.AllArguments Object[] args) {
|
||||
CallDepth callDepth = CallDepth.forClass(AsyncContext.class);
|
||||
if (callDepth.getAndIncrement() > 0) {
|
||||
return;
|
||||
return callDepth;
|
||||
}
|
||||
|
||||
ServletRequest request = context.getRequest();
|
||||
|
|
@ -42,10 +40,11 @@ public class AsyncDispatchAdvice {
|
|||
// processing, and nothing can be done with the request anymore after this
|
||||
request.setAttribute(CONTEXT_ATTRIBUTE, currentContext);
|
||||
}
|
||||
return callDepth;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void exit(@Advice.Local("otelCallDepth") CallDepth callDepth) {
|
||||
public static void exit(@Advice.Enter CallDepth callDepth) {
|
||||
callDepth.decrementAndGet();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,16 +18,17 @@ import net.bytebuddy.implementation.bytecode.assign.Assigner;
|
|||
public class AsyncStartAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void startAsyncEnter(@Advice.Local("otelCallDepth") CallDepth callDepth) {
|
||||
public static CallDepth startAsyncEnter() {
|
||||
// This allows to detect the outermost invocation of startAsync in method exit
|
||||
callDepth = CallDepth.forClass(AsyncContext.class);
|
||||
CallDepth callDepth = CallDepth.forClass(AsyncContext.class);
|
||||
callDepth.getAndIncrement();
|
||||
return callDepth;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(suppress = Throwable.class)
|
||||
public static void startAsyncExit(
|
||||
@Advice.This(typing = Assigner.Typing.DYNAMIC) HttpServletRequest request,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth) {
|
||||
@Advice.Enter CallDepth callDepth) {
|
||||
|
||||
if (callDepth.decrementAndGet() != 0) {
|
||||
// This is not the outermost invocation, ignore.
|
||||
|
|
|
|||
|
|
@ -13,47 +13,71 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMetho
|
|||
import io.opentelemetry.javaagent.bootstrap.CallDepth;
|
||||
import io.opentelemetry.javaagent.instrumentation.servlet.common.response.HttpServletResponseAdviceHelper;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import javax.annotation.Nullable;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ResponseSendAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void start(
|
||||
@Advice.This Object response,
|
||||
@Advice.Origin("#t") Class<?> declaringClass,
|
||||
@Advice.Origin("#m") String methodName,
|
||||
@Advice.Local("otelMethod") ClassAndMethod classAndMethod,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth) {
|
||||
callDepth = CallDepth.forClass(HttpServletResponse.class);
|
||||
if (callDepth.getAndIncrement() > 0) {
|
||||
return;
|
||||
public static class AdviceScope {
|
||||
private final ClassAndMethod classAndMethod;
|
||||
private final CallDepth callDepth;
|
||||
private final Context context;
|
||||
private final Scope scope;
|
||||
|
||||
public AdviceScope(CallDepth callDepth, Class<?> declaringClass, String methodName) {
|
||||
this.callDepth = callDepth;
|
||||
if (callDepth.getAndIncrement() > 0) {
|
||||
this.classAndMethod = null;
|
||||
this.context = null;
|
||||
this.scope = null;
|
||||
return;
|
||||
}
|
||||
HttpServletResponseAdviceHelper.StartResult result =
|
||||
HttpServletResponseAdviceHelper.startSpan(
|
||||
responseInstrumenter(), declaringClass, methodName);
|
||||
if (result == null) {
|
||||
this.classAndMethod = null;
|
||||
this.context = null;
|
||||
this.scope = null;
|
||||
return;
|
||||
}
|
||||
this.classAndMethod = result.getClassAndMethod();
|
||||
this.context = result.getContext();
|
||||
this.scope = result.getScope();
|
||||
}
|
||||
|
||||
HttpServletResponseAdviceHelper.StartResult result =
|
||||
HttpServletResponseAdviceHelper.startSpan(
|
||||
responseInstrumenter(), declaringClass, methodName);
|
||||
if (result != null) {
|
||||
classAndMethod = result.getClassAndMethod();
|
||||
context = result.getContext();
|
||||
scope = result.getScope();
|
||||
public AdviceScope(
|
||||
CallDepth callDepth, HttpServletResponseAdviceHelper.StartResult startResult) {
|
||||
this.callDepth = callDepth;
|
||||
this.classAndMethod = startResult.getClassAndMethod();
|
||||
this.context = startResult.getContext();
|
||||
this.scope = startResult.getScope();
|
||||
}
|
||||
|
||||
public void exit(@Nullable Throwable throwable) {
|
||||
if (callDepth.decrementAndGet() > 0) {
|
||||
return;
|
||||
}
|
||||
HttpServletResponseAdviceHelper.stopSpan(
|
||||
responseInstrumenter(), throwable, context, scope, classAndMethod);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static AdviceScope start(
|
||||
@Advice.This Object response,
|
||||
@Advice.Origin("#t") Class<?> declaringClass,
|
||||
@Advice.Origin("#m") String methodName) {
|
||||
|
||||
CallDepth callDepth = CallDepth.forClass(HttpServletResponse.class);
|
||||
return new AdviceScope(callDepth, declaringClass, methodName);
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void stopSpan(
|
||||
@Advice.Thrown Throwable throwable,
|
||||
@Advice.Local("otelMethod") ClassAndMethod classAndMethod,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth) {
|
||||
if (callDepth.decrementAndGet() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
HttpServletResponseAdviceHelper.stopSpan(
|
||||
responseInstrumenter(), throwable, context, scope, classAndMethod);
|
||||
@Advice.Thrown @Nullable Throwable throwable, @Advice.Enter AdviceScope adviceScope) {
|
||||
adviceScope.exit(throwable);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
package io.opentelemetry.javaagent.instrumentation.servlet.v5_0.service;
|
||||
|
||||
import io.opentelemetry.instrumentation.api.util.VirtualField;
|
||||
import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver;
|
||||
import static io.opentelemetry.javaagent.instrumentation.servlet.v5_0.Servlet5Singletons.FILTER_MAPPING_RESOLVER;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterConfig;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
|
|
@ -20,7 +20,7 @@ public class JakartaServletFilterInitAdvice {
|
|||
if (filterConfig == null) {
|
||||
return;
|
||||
}
|
||||
VirtualField.find(Filter.class, MappingResolver.Factory.class)
|
||||
.set(filter, new JakartaServletFilterMappingResolverFactory(filterConfig));
|
||||
FILTER_MAPPING_RESOLVER.set(
|
||||
filter, new JakartaServletFilterMappingResolverFactory(filterConfig));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
package io.opentelemetry.javaagent.instrumentation.servlet.v5_0.service;
|
||||
|
||||
import io.opentelemetry.instrumentation.api.util.VirtualField;
|
||||
import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver;
|
||||
import static io.opentelemetry.javaagent.instrumentation.servlet.v5_0.Servlet5Singletons.SERVLET_MAPPING_RESOLVER;
|
||||
|
||||
import jakarta.servlet.Servlet;
|
||||
import jakarta.servlet.ServletConfig;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
|
|
@ -20,7 +20,6 @@ public class JakartaServletInitAdvice {
|
|||
if (servletConfig == null) {
|
||||
return;
|
||||
}
|
||||
VirtualField.find(Servlet.class, MappingResolver.Factory.class)
|
||||
.set(servlet, new JakartaServletMappingResolverFactory(servletConfig));
|
||||
SERVLET_MAPPING_RESOLVER.set(servlet, new JakartaServletMappingResolverFactory(servletConfig));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import static io.opentelemetry.javaagent.instrumentation.servlet.v5_0.Servlet5Si
|
|||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import io.opentelemetry.javaagent.bootstrap.CallDepth;
|
||||
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
|
||||
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder;
|
||||
import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge;
|
||||
import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver;
|
||||
|
|
@ -24,24 +23,89 @@ import jakarta.servlet.ServletRequest;
|
|||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import javax.annotation.Nullable;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.asm.Advice.AssignReturned;
|
||||
import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument;
|
||||
import net.bytebuddy.implementation.bytecode.assign.Assigner;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class JakartaServletServiceAdvice {
|
||||
|
||||
public static class AdviceScope {
|
||||
private final CallDepth callDepth;
|
||||
private final ServletRequestContext<HttpServletRequest> requestContext;
|
||||
private final Context context;
|
||||
private final Scope scope;
|
||||
|
||||
public AdviceScope(
|
||||
CallDepth callDepth,
|
||||
Object servletOrFilter,
|
||||
HttpServletRequest request,
|
||||
ServletResponse response) {
|
||||
this.callDepth = callDepth;
|
||||
this.callDepth.getAndIncrement();
|
||||
|
||||
Context currentContext = Context.current();
|
||||
Context attachedContext = helper().getServerContext(request);
|
||||
Context contextToUpdate;
|
||||
|
||||
requestContext = new ServletRequestContext<>(request, servletOrFilter);
|
||||
if (attachedContext == null && helper().shouldStart(currentContext, requestContext)) {
|
||||
context = helper().start(currentContext, requestContext);
|
||||
helper().setAsyncListenerResponse(context, (HttpServletResponse) response);
|
||||
|
||||
contextToUpdate = context;
|
||||
} else if (attachedContext != null
|
||||
&& helper().needsRescoping(currentContext, attachedContext)) {
|
||||
// Given request already has a context associated with it.
|
||||
// see the needsRescoping() javadoc for more explanation
|
||||
contextToUpdate = attachedContext;
|
||||
context = null;
|
||||
} else {
|
||||
// We are inside nested servlet/filter/app-server span, don't create new span
|
||||
contextToUpdate = currentContext;
|
||||
context = null;
|
||||
}
|
||||
|
||||
// Update context with info from current request to ensure that server span gets the best
|
||||
// possible name.
|
||||
// In case server span was created by app server instrumentations calling updateContext
|
||||
// returns a new context that contains servlet context path that is used in other
|
||||
// instrumentations for naming server span.
|
||||
MappingResolver mappingResolver = Servlet5Singletons.getMappingResolver(servletOrFilter);
|
||||
boolean servlet = servletOrFilter instanceof Servlet;
|
||||
contextToUpdate = helper().updateContext(contextToUpdate, request, mappingResolver, servlet);
|
||||
scope = contextToUpdate.makeCurrent();
|
||||
|
||||
if (context != null) {
|
||||
// Only trigger response customizer once, so only if server span was created here
|
||||
HttpServerResponseCustomizerHolder.getCustomizer()
|
||||
.customize(contextToUpdate, (HttpServletResponse) response, Servlet5Accessor.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
public void exit(
|
||||
HttpServletRequest request, HttpServletResponse response, @Nullable Throwable throwable) {
|
||||
boolean topLevel = callDepth.decrementAndGet() == 0;
|
||||
helper().end(requestContext, request, response, throwable, topLevel, context, scope);
|
||||
}
|
||||
}
|
||||
|
||||
@AssignReturned.ToArguments({
|
||||
@ToArgument(value = 0, index = 1),
|
||||
@ToArgument(value = 1, index = 2)
|
||||
})
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void onEnter(
|
||||
public static Object[] onEnter(
|
||||
@Advice.This(typing = Assigner.Typing.DYNAMIC) Object servletOrFilter,
|
||||
@Advice.Argument(value = 0, readOnly = false) ServletRequest request,
|
||||
@Advice.Argument(value = 1, readOnly = false) ServletResponse response,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth,
|
||||
@Advice.Local("otelRequest") ServletRequestContext<HttpServletRequest> requestContext,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
@Advice.Argument(0) ServletRequest request,
|
||||
@Advice.Argument(1) ServletResponse originalResponse) {
|
||||
|
||||
ServletResponse response = originalResponse;
|
||||
|
||||
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
|
||||
return;
|
||||
return new Object[] {null, request, response};
|
||||
}
|
||||
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||
|
||||
|
|
@ -53,45 +117,14 @@ public class JakartaServletServiceAdvice {
|
|||
new Servlet5SnippetInjectingResponseWrapper((HttpServletResponse) response, snippet);
|
||||
}
|
||||
|
||||
callDepth = CallDepth.forClass(AppServerBridge.getCallDepthKey());
|
||||
callDepth.getAndIncrement();
|
||||
AdviceScope adviceScope =
|
||||
new AdviceScope(
|
||||
CallDepth.forClass(AppServerBridge.getCallDepthKey()),
|
||||
servletOrFilter,
|
||||
(HttpServletRequest) request,
|
||||
response);
|
||||
|
||||
Context currentContext = Java8BytecodeBridge.currentContext();
|
||||
Context attachedContext = helper().getServerContext(httpServletRequest);
|
||||
Context contextToUpdate;
|
||||
|
||||
requestContext = new ServletRequestContext<>(httpServletRequest, servletOrFilter);
|
||||
if (attachedContext == null && helper().shouldStart(currentContext, requestContext)) {
|
||||
context = helper().start(currentContext, requestContext);
|
||||
helper().setAsyncListenerResponse(context, (HttpServletResponse) response);
|
||||
|
||||
contextToUpdate = context;
|
||||
} else if (attachedContext != null
|
||||
&& helper().needsRescoping(currentContext, attachedContext)) {
|
||||
// Given request already has a context associated with it.
|
||||
// see the needsRescoping() javadoc for more explanation
|
||||
contextToUpdate = attachedContext;
|
||||
} else {
|
||||
// We are inside nested servlet/filter/app-server span, don't create new span
|
||||
contextToUpdate = currentContext;
|
||||
}
|
||||
|
||||
// Update context with info from current request to ensure that server span gets the best
|
||||
// possible name.
|
||||
// In case server span was created by app server instrumentations calling updateContext
|
||||
// returns a new context that contains servlet context path that is used in other
|
||||
// instrumentations for naming server span.
|
||||
MappingResolver mappingResolver = Servlet5Singletons.getMappingResolver(servletOrFilter);
|
||||
boolean servlet = servletOrFilter instanceof Servlet;
|
||||
contextToUpdate =
|
||||
helper().updateContext(contextToUpdate, httpServletRequest, mappingResolver, servlet);
|
||||
scope = contextToUpdate.makeCurrent();
|
||||
|
||||
if (context != null) {
|
||||
// Only trigger response customizer once, so only if server span was created here
|
||||
HttpServerResponseCustomizerHolder.getCustomizer()
|
||||
.customize(contextToUpdate, (HttpServletResponse) response, Servlet5Accessor.INSTANCE);
|
||||
}
|
||||
return new Object[] {adviceScope, request, response};
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
|
|
@ -99,25 +132,16 @@ public class JakartaServletServiceAdvice {
|
|||
@Advice.Argument(0) ServletRequest request,
|
||||
@Advice.Argument(1) ServletResponse response,
|
||||
@Advice.Thrown Throwable throwable,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth,
|
||||
@Advice.Local("otelRequest") ServletRequestContext<HttpServletRequest> requestContext,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
@Advice.Enter Object[] enterResult) {
|
||||
|
||||
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
|
||||
AdviceScope adviceScope = (AdviceScope) enterResult[0];
|
||||
|
||||
if (adviceScope == null
|
||||
|| !(request instanceof HttpServletRequest)
|
||||
|| !(response instanceof HttpServletResponse)) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean topLevel = callDepth.decrementAndGet() == 0;
|
||||
|
||||
helper()
|
||||
.end(
|
||||
requestContext,
|
||||
(HttpServletRequest) request,
|
||||
(HttpServletResponse) response,
|
||||
throwable,
|
||||
topLevel,
|
||||
context,
|
||||
scope);
|
||||
adviceScope.exit((HttpServletRequest) request, (HttpServletResponse) response, throwable);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue