ServletContextPath.prepend doesn't work when server span is created from app server integration
This commit is contained in:
parent
49206212cf
commit
a6a13d1c27
|
@ -8,6 +8,7 @@ package io.opentelemetry.instrumentation.api.servlet;
|
||||||
import io.opentelemetry.context.Context;
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.context.ContextKey;
|
import io.opentelemetry.context.ContextKey;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper container for Context attributes for transferring certain information between servlet
|
* Helper container for Context attributes for transferring certain information between servlet
|
||||||
|
@ -39,7 +40,15 @@ public class AppServerBridge {
|
||||||
* @return new context with AppServerBridge attached.
|
* @return new context with AppServerBridge attached.
|
||||||
*/
|
*/
|
||||||
public static Context init(Context ctx, boolean shouldRecordException) {
|
public static Context init(Context ctx, boolean shouldRecordException) {
|
||||||
return ctx.with(AppServerBridge.CONTEXT_KEY, new AppServerBridge(shouldRecordException));
|
Context context =
|
||||||
|
ctx.with(AppServerBridge.CONTEXT_KEY, new AppServerBridge(shouldRecordException));
|
||||||
|
// Add context for storing servlet context path. Servlet context path is updated from servlet
|
||||||
|
// integration.
|
||||||
|
if (context.get(ServletContextPath.CONTEXT_KEY) == null) {
|
||||||
|
context = context.with(ServletContextPath.CONTEXT_KEY, new AtomicReference<>(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final AtomicBoolean servletUpdatedServerSpanName = new AtomicBoolean(false);
|
private final AtomicBoolean servletUpdatedServerSpanName = new AtomicBoolean(false);
|
||||||
|
|
|
@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.api.servlet;
|
||||||
|
|
||||||
import io.opentelemetry.context.Context;
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.context.ContextKey;
|
import io.opentelemetry.context.ContextKey;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The context key here is used to propagate the servlet context path throughout the request, so
|
* The context key here is used to propagate the servlet context path throughout the request, so
|
||||||
|
@ -22,11 +23,12 @@ public class ServletContextPath {
|
||||||
|
|
||||||
// Keeps track of the servlet context path that needs to be prepended to the route when updating
|
// Keeps track of the servlet context path that needs to be prepended to the route when updating
|
||||||
// the span name
|
// the span name
|
||||||
public static final ContextKey<String> CONTEXT_KEY =
|
public static final ContextKey<AtomicReference<String>> CONTEXT_KEY =
|
||||||
ContextKey.named("opentelemetry-servlet-context-path-key");
|
ContextKey.named("opentelemetry-servlet-context-path-key");
|
||||||
|
|
||||||
public static String prepend(Context context, String spanName) {
|
public static String prepend(Context context, String spanName) {
|
||||||
String value = context.get(CONTEXT_KEY);
|
AtomicReference<String> valueReference = context.get(CONTEXT_KEY);
|
||||||
|
String value = valueReference != null ? valueReference.get() : null;
|
||||||
// checking isEmpty just to avoid unnecessary string concat / allocation
|
// checking isEmpty just to avoid unnecessary string concat / allocation
|
||||||
if (value != null && !value.isEmpty()) {
|
if (value != null && !value.isEmpty()) {
|
||||||
return value + spanName;
|
return value + spanName;
|
||||||
|
|
|
@ -15,6 +15,7 @@ import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -27,9 +28,13 @@ public abstract class ServletHttpServerTracer<RESPONSE>
|
||||||
|
|
||||||
public Context startSpan(HttpServletRequest request) {
|
public Context startSpan(HttpServletRequest request) {
|
||||||
Context context = startSpan(request, request, request, getSpanName(request));
|
Context context = startSpan(request, request, request, getSpanName(request));
|
||||||
|
return addContextPathContext(context, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Context addContextPathContext(Context context, HttpServletRequest request) {
|
||||||
String contextPath = request.getContextPath();
|
String contextPath = request.getContextPath();
|
||||||
if (contextPath != null && !contextPath.isEmpty() && !contextPath.equals("/")) {
|
if (contextPath != null && !contextPath.isEmpty() && !contextPath.equals("/")) {
|
||||||
context = context.with(ServletContextPath.CONTEXT_KEY, contextPath);
|
context = context.with(ServletContextPath.CONTEXT_KEY, new AtomicReference<>(contextPath));
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
@ -153,19 +158,41 @@ public abstract class ServletHttpServerTracer<RESPONSE>
|
||||||
return contextPath + servletPath;
|
return contextPath + servletPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When server spans are managed by app server instrumentation servlet instrumentation should set
|
||||||
|
* information that was not available from app server instrumentation.
|
||||||
|
*/
|
||||||
|
public void updateServerSpan(Context attachedContext, HttpServletRequest request) {
|
||||||
|
updateServerSpanName(attachedContext, request);
|
||||||
|
updateContextPath(attachedContext, request);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When server spans are managed by app server instrumentation, servlet must update server span
|
* When server spans are managed by app server instrumentation, servlet must update server span
|
||||||
* name only once and only during the first pass through the servlet stack. There are potential
|
* name only once and only during the first pass through the servlet stack. There are potential
|
||||||
* forward and other scenarios, where servlet path may change, but we don't want this to be
|
* forward and other scenarios, where servlet path may change, but we don't want this to be
|
||||||
* reflected in the span name.
|
* reflected in the span name.
|
||||||
*/
|
*/
|
||||||
public void updateServerSpanNameOnce(Context attachedContext, HttpServletRequest request) {
|
private void updateServerSpanName(Context attachedContext, HttpServletRequest request) {
|
||||||
|
// Update name only when server span wasn't created by servlet integration
|
||||||
|
// and has not been already updated
|
||||||
if (AppServerBridge.shouldUpdateServerSpanName(attachedContext)) {
|
if (AppServerBridge.shouldUpdateServerSpanName(attachedContext)) {
|
||||||
updateSpanName(Span.fromContext(attachedContext), request);
|
updateSpanName(Span.fromContext(attachedContext), request);
|
||||||
AppServerBridge.setServletUpdatedServerSpanName(attachedContext, true);
|
AppServerBridge.setServletUpdatedServerSpanName(attachedContext, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateContextPath(Context attachedContext, HttpServletRequest request) {
|
||||||
|
// Update context path if it isn't already set
|
||||||
|
AtomicReference<String> reference = attachedContext.get(ServletContextPath.CONTEXT_KEY);
|
||||||
|
if (reference != null && reference.get() == null) {
|
||||||
|
String contextPath = request.getContextPath();
|
||||||
|
if (contextPath != null && !contextPath.isEmpty() && !contextPath.equals("/")) {
|
||||||
|
reference.compareAndSet(null, contextPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void updateSpanName(HttpServletRequest request) {
|
public void updateSpanName(HttpServletRequest request) {
|
||||||
updateSpanName(getServerSpan(request), request);
|
updateSpanName(getServerSpan(request), request);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
package io.opentelemetry.javaagent.instrumentation.liberty;
|
package io.opentelemetry.javaagent.instrumentation.liberty;
|
||||||
|
|
||||||
import io.opentelemetry.context.Context;
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.instrumentation.api.servlet.ServletContextPath;
|
|
||||||
import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3HttpServerTracer;
|
import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3HttpServerTracer;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
@ -23,11 +22,7 @@ public class LibertyHttpServerTracer extends Servlet3HttpServerTracer {
|
||||||
// span name will be updated a bit later when calling request.getServletPath() works
|
// span name will be updated a bit later when calling request.getServletPath() works
|
||||||
// see LibertyUpdateSpanAdvice
|
// see LibertyUpdateSpanAdvice
|
||||||
Context context = startSpan(request, request, request, "HTTP " + request.getMethod());
|
Context context = startSpan(request, request, request, "HTTP " + request.getMethod());
|
||||||
String contextPath = request.getContextPath();
|
return addContextPathContext(context, request);
|
||||||
if (contextPath != null && !contextPath.isEmpty() && !contextPath.equals("/")) {
|
|
||||||
context = context.with(ServletContextPath.CONTEXT_KEY, contextPath);
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class Servlet2Advice {
|
||||||
|
|
||||||
Context serverContext = tracer().getServerContext(httpServletRequest);
|
Context serverContext = tracer().getServerContext(httpServletRequest);
|
||||||
if (serverContext != null) {
|
if (serverContext != null) {
|
||||||
tracer().updateServerSpanNameOnce(serverContext, httpServletRequest);
|
tracer().updateServerSpan(serverContext, httpServletRequest);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,14 +40,14 @@ public class Servlet3Advice {
|
||||||
scope = attachedContext.makeCurrent();
|
scope = attachedContext.makeCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
tracer().updateServerSpanNameOnce(attachedContext, httpServletRequest);
|
tracer().updateServerSpan(attachedContext, httpServletRequest);
|
||||||
// We are inside nested servlet/filter/app-server span, don't create new span
|
// We are inside nested servlet/filter/app-server span, don't create new span
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Context parentContext = Java8BytecodeBridge.currentContext();
|
Context parentContext = Java8BytecodeBridge.currentContext();
|
||||||
if (parentContext != null && Java8BytecodeBridge.spanFromContext(parentContext).isRecording()) {
|
if (parentContext != null && Java8BytecodeBridge.spanFromContext(parentContext).isRecording()) {
|
||||||
tracer().updateServerSpanNameOnce(parentContext, httpServletRequest);
|
tracer().updateServerSpan(parentContext, httpServletRequest);
|
||||||
// We are inside nested servlet/filter/app-server span, don't create new span
|
// We are inside nested servlet/filter/app-server span, don't create new span
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
|
||||||
import javax.servlet.DispatcherType
|
import javax.servlet.DispatcherType
|
||||||
import org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
|
import org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
|
||||||
import org.eclipse.jetty.server.Server
|
import org.eclipse.jetty.server.Server
|
||||||
|
import org.eclipse.jetty.server.handler.HandlerCollection
|
||||||
import org.eclipse.jetty.servlet.DefaultServlet
|
import org.eclipse.jetty.servlet.DefaultServlet
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler
|
import org.eclipse.jetty.servlet.ServletContextHandler
|
||||||
import org.eclipse.jetty.util.resource.FileResource
|
import org.eclipse.jetty.util.resource.FileResource
|
||||||
|
@ -79,7 +80,11 @@ class Struts2ActionSpanTest extends HttpServerTest<Server> {
|
||||||
context.setContextPath(getContextPath())
|
context.setContextPath(getContextPath())
|
||||||
def resource = new FileResource(getClass().getResource("/"))
|
def resource = new FileResource(getClass().getResource("/"))
|
||||||
context.setBaseResource(resource)
|
context.setBaseResource(resource)
|
||||||
server.setHandler(context)
|
// jetty integration is disabled for some handler classes, using HandlerCollection here
|
||||||
|
// enables jetty integration
|
||||||
|
HandlerCollection handlerCollection = new HandlerCollection()
|
||||||
|
handlerCollection.addHandler(context)
|
||||||
|
server.setHandler(handlerCollection)
|
||||||
|
|
||||||
context.addServlet(DefaultServlet, "/")
|
context.addServlet(DefaultServlet, "/")
|
||||||
context.addFilter(StrutsPrepareAndExecuteFilter, "/*", EnumSet.of(DispatcherType.REQUEST))
|
context.addFilter(StrutsPrepareAndExecuteFilter, "/*", EnumSet.of(DispatcherType.REQUEST))
|
||||||
|
|
Loading…
Reference in New Issue