move ServletContextPath context creation to servlet-common, make servlet2&3 depend on servlet-common so that it would be used in tests that depend on servlet3

This commit is contained in:
Lauri Tulmin 2021-01-29 19:04:49 +02:00
parent a6a13d1c27
commit 8b4065c250
25 changed files with 565 additions and 126 deletions

View File

@ -8,7 +8,6 @@ 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
@ -40,15 +39,7 @@ 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) {
Context context = return ctx.with(AppServerBridge.CONTEXT_KEY, new AppServerBridge(shouldRecordException));
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);

View File

@ -7,7 +7,6 @@ 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
@ -23,12 +22,11 @@ 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<AtomicReference<String>> CONTEXT_KEY = public static final ContextKey<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) {
AtomicReference<String> valueReference = context.get(CONTEXT_KEY); String value = 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;

View File

@ -9,13 +9,11 @@ import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context; import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapPropagator.Getter; import io.opentelemetry.context.propagation.TextMapPropagator.Getter;
import io.opentelemetry.instrumentation.api.servlet.AppServerBridge; import io.opentelemetry.instrumentation.api.servlet.AppServerBridge;
import io.opentelemetry.instrumentation.api.servlet.ServletContextPath;
import io.opentelemetry.instrumentation.api.tracer.HttpServerTracer; import io.opentelemetry.instrumentation.api.tracer.HttpServerTracer;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; 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,16 +25,7 @@ public abstract class ServletHttpServerTracer<RESPONSE>
private static final Logger log = LoggerFactory.getLogger(ServletHttpServerTracer.class); private static final Logger log = LoggerFactory.getLogger(ServletHttpServerTracer.class);
public Context startSpan(HttpServletRequest request) { public Context startSpan(HttpServletRequest request) {
Context context = startSpan(request, request, request, getSpanName(request)); return startSpan(request, request, request, getSpanName(request));
return addContextPathContext(context, request);
}
protected Context addContextPathContext(Context context, HttpServletRequest request) {
String contextPath = request.getContextPath();
if (contextPath != null && !contextPath.isEmpty() && !contextPath.equals("/")) {
context = context.with(ServletContextPath.CONTEXT_KEY, new AtomicReference<>(contextPath));
}
return context;
} }
@Override @Override
@ -158,41 +147,19 @@ 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.
*/ */
private void updateServerSpanName(Context attachedContext, HttpServletRequest request) { public void updateServerSpanNameOnce(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);
} }

View File

@ -30,4 +30,9 @@ class JerseyHttpServerTest extends JaxRsHttpServerTest<Server> {
void stopServer(Server httpServer) { void stopServer(Server httpServer) {
httpServer.stop() httpServer.stop()
} }
@Override
boolean asyncCancelHasSendError() {
true
}
} }

View File

@ -49,10 +49,18 @@ abstract class JaxRsHttpServerTest<S> extends HttpServerTest<S> {
assert response.code() == statusCode assert response.code() == statusCode
assert bodyPredicate(response.body().string()) assert bodyPredicate(response.body().string())
def spanCount = 2
def hasSendError = asyncCancelHasSendError() && action == "cancel"
if (hasSendError) {
spanCount++
}
assertTraces(1) { assertTraces(1) {
trace(0, 2) { trace(0, spanCount) {
asyncServerSpan(it, 0, url, statusCode) asyncServerSpan(it, 0, url, statusCode)
handlerSpan(it, 1, span(0), "asyncOp", isCancelled, isError, errorMessage) handlerSpan(it, 1, span(0), "asyncOp", isCancelled, isError, errorMessage)
if (hasSendError) {
sendErrorSpan(it, 2, span(1))
}
} }
} }
@ -117,6 +125,10 @@ abstract class JaxRsHttpServerTest<S> extends HttpServerTest<S> {
true true
} }
boolean asyncCancelHasSendError() {
false
}
private static boolean shouldTestCompletableStageAsync() { private static boolean shouldTestCompletableStageAsync() {
Boolean.getBoolean("testLatestDeps") Boolean.getBoolean("testLatestDeps")
} }

View File

@ -10,6 +10,7 @@ import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEn
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.test.base.HttpServerTest import io.opentelemetry.instrumentation.test.base.HttpServerTest
import javax.servlet.DispatcherType import javax.servlet.DispatcherType
import javax.servlet.ServletException import javax.servlet.ServletException
@ -62,6 +63,23 @@ class JettyHandlerTest extends HttpServerTest<Server> {
false false
} }
@Override
boolean hasResponseSpan(ServerEndpoint endpoint) {
endpoint == REDIRECT || endpoint == ERROR
}
@Override
void responseSpan(TraceAssert trace, int index, Object parent, String method, ServerEndpoint endpoint) {
switch (endpoint) {
case REDIRECT:
redirectSpan(trace, index, parent)
break
case ERROR:
sendErrorSpan(trace, index, parent)
break
}
}
static void handleRequest(Request request, HttpServletResponse response) { static void handleRequest(Request request, HttpServletResponse response) {
ServerEndpoint endpoint = ServerEndpoint.forPath(request.requestURI) ServerEndpoint endpoint = ServerEndpoint.forPath(request.requestURI)
controller(endpoint) { controller(endpoint) {

View File

@ -332,7 +332,7 @@ class JspInstrumentationBasicTests extends AgentInstrumentationSpecification {
then: then:
assertTraces(1) { assertTraces(1) {
trace(0, 3) { trace(0, 4) {
span(0) { span(0) {
hasNoParent() hasNoParent()
name "/$jspWebappContext/includes/includeHtml.jsp" name "/$jspWebappContext/includes/includeHtml.jsp"
@ -366,6 +366,11 @@ class JspInstrumentationBasicTests extends AgentInstrumentationSpecification {
"jsp.requestURL" reqUrl "jsp.requestURL" reqUrl
} }
} }
span(3) {
childOf span(2)
name "ApplicationDispatcher.include"
errored false
}
} }
} }
res.code() == 200 res.code() == 200
@ -384,7 +389,7 @@ class JspInstrumentationBasicTests extends AgentInstrumentationSpecification {
then: then:
assertTraces(1) { assertTraces(1) {
trace(0, 7) { trace(0, 9) {
span(0) { span(0) {
hasNoParent() hasNoParent()
name "/$jspWebappContext/includes/includeMulti.jsp" name "/$jspWebappContext/includes/includeMulti.jsp"
@ -420,6 +425,11 @@ class JspInstrumentationBasicTests extends AgentInstrumentationSpecification {
} }
span(3) { span(3) {
childOf span(2) childOf span(2)
name "ApplicationDispatcher.include"
errored false
}
span(4) {
childOf span(3)
name "Compile /common/javaLoopH2.jsp" name "Compile /common/javaLoopH2.jsp"
errored false errored false
attributes { attributes {
@ -427,16 +437,21 @@ class JspInstrumentationBasicTests extends AgentInstrumentationSpecification {
"jsp.compiler" "org.apache.jasper.compiler.JDTCompiler" "jsp.compiler" "org.apache.jasper.compiler.JDTCompiler"
} }
} }
span(4) { span(5) {
childOf span(2) childOf span(3)
name "Render /common/javaLoopH2.jsp" name "Render /common/javaLoopH2.jsp"
errored false errored false
attributes { attributes {
"jsp.requestURL" reqUrl "jsp.requestURL" reqUrl
} }
} }
span(5) { span(6) {
childOf span(2) childOf span(2)
name "ApplicationDispatcher.include"
errored false
}
span(7) {
childOf span(6)
name "Compile /common/javaLoopH2.jsp" name "Compile /common/javaLoopH2.jsp"
errored false errored false
attributes { attributes {
@ -444,8 +459,8 @@ class JspInstrumentationBasicTests extends AgentInstrumentationSpecification {
"jsp.compiler" "org.apache.jasper.compiler.JDTCompiler" "jsp.compiler" "org.apache.jasper.compiler.JDTCompiler"
} }
} }
span(6) { span(8) {
childOf span(2) childOf span(6)
name "Render /common/javaLoopH2.jsp" name "Render /common/javaLoopH2.jsp"
errored false errored false
attributes { attributes {

View File

@ -80,7 +80,7 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
then: then:
assertTraces(1) { assertTraces(1) {
trace(0, 5) { trace(0, 6) {
span(0) { span(0) {
hasNoParent() hasNoParent()
name "/$jspWebappContext/$forwardFromFileName" name "/$jspWebappContext/$forwardFromFileName"
@ -116,6 +116,11 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
} }
span(3) { span(3) {
childOf span(2) childOf span(2)
name "ApplicationDispatcher.forward"
errored false
}
span(4) {
childOf span(3)
name "Compile /$forwardDestFileName" name "Compile /$forwardDestFileName"
errored false errored false
attributes { attributes {
@ -123,8 +128,8 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
"jsp.compiler" "org.apache.jasper.compiler.JDTCompiler" "jsp.compiler" "org.apache.jasper.compiler.JDTCompiler"
} }
} }
span(4) { span(5) {
childOf span(2) childOf span(3)
name "Render /$forwardDestFileName" name "Render /$forwardDestFileName"
errored false errored false
attributes { attributes {
@ -155,7 +160,7 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
then: then:
assertTraces(1) { assertTraces(1) {
trace(0, 3) { trace(0, 4) {
span(0) { span(0) {
hasNoParent() hasNoParent()
name "/$jspWebappContext/forwards/forwardToHtml.jsp" name "/$jspWebappContext/forwards/forwardToHtml.jsp"
@ -189,6 +194,11 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
"jsp.requestURL" reqUrl "jsp.requestURL" reqUrl
} }
} }
span(3) {
childOf span(2)
name "ApplicationDispatcher.forward"
errored false
}
} }
} }
res.code() == 200 res.code() == 200
@ -207,7 +217,7 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
then: then:
assertTraces(1) { assertTraces(1) {
trace(0, 9) { trace(0, 12) {
span(0) { span(0) {
hasNoParent() hasNoParent()
name "/$jspWebappContext/forwards/forwardToIncludeMulti.jsp" name "/$jspWebappContext/forwards/forwardToIncludeMulti.jsp"
@ -243,6 +253,11 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
} }
span(3) { span(3) {
childOf span(2) childOf span(2)
name "ApplicationDispatcher.forward"
errored false
}
span(4) {
childOf span(3)
name "Compile /includes/includeMulti.jsp" name "Compile /includes/includeMulti.jsp"
errored false errored false
attributes { attributes {
@ -250,8 +265,8 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
"jsp.compiler" "org.apache.jasper.compiler.JDTCompiler" "jsp.compiler" "org.apache.jasper.compiler.JDTCompiler"
} }
} }
span(4) { span(5) {
childOf span(2) childOf span(3)
name "Render /includes/includeMulti.jsp" name "Render /includes/includeMulti.jsp"
errored false errored false
attributes { attributes {
@ -259,26 +274,13 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
"jsp.requestURL" baseUrl + "/includes/includeMulti.jsp" "jsp.requestURL" baseUrl + "/includes/includeMulti.jsp"
} }
} }
span(5) {
childOf span(4)
name "Compile /common/javaLoopH2.jsp"
errored false
attributes {
"jsp.classFQCN" "org.apache.jsp.common.javaLoopH2_jsp"
"jsp.compiler" "org.apache.jasper.compiler.JDTCompiler"
}
}
span(6) { span(6) {
childOf span(4) childOf span(5)
name "Render /common/javaLoopH2.jsp" name "ApplicationDispatcher.include"
errored false errored false
attributes {
"jsp.forwardOrigin" "/forwards/forwardToIncludeMulti.jsp"
"jsp.requestURL" baseUrl + "/includes/includeMulti.jsp"
}
} }
span(7) { span(7) {
childOf span(4) childOf span(6)
name "Compile /common/javaLoopH2.jsp" name "Compile /common/javaLoopH2.jsp"
errored false errored false
attributes { attributes {
@ -287,7 +289,30 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
} }
} }
span(8) { span(8) {
childOf span(4) childOf span(6)
name "Render /common/javaLoopH2.jsp"
errored false
attributes {
"jsp.forwardOrigin" "/forwards/forwardToIncludeMulti.jsp"
"jsp.requestURL" baseUrl + "/includes/includeMulti.jsp"
}
}
span(9) {
childOf span(5)
name "ApplicationDispatcher.include"
errored false
}
span(10) {
childOf span(9)
name "Compile /common/javaLoopH2.jsp"
errored false
attributes {
"jsp.classFQCN" "org.apache.jsp.common.javaLoopH2_jsp"
"jsp.compiler" "org.apache.jasper.compiler.JDTCompiler"
}
}
span(11) {
childOf span(9)
name "Render /common/javaLoopH2.jsp" name "Render /common/javaLoopH2.jsp"
errored false errored false
attributes { attributes {
@ -313,7 +338,7 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
then: then:
assertTraces(1) { assertTraces(1) {
trace(0, 7) { trace(0, 9) {
span(0) { span(0) {
hasNoParent() hasNoParent()
name "/$jspWebappContext/forwards/forwardToJspForward.jsp" name "/$jspWebappContext/forwards/forwardToJspForward.jsp"
@ -349,6 +374,11 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
} }
span(3) { span(3) {
childOf span(2) childOf span(2)
name "ApplicationDispatcher.forward"
errored false
}
span(4) {
childOf span(3)
name "Compile /forwards/forwardToSimpleJava.jsp" name "Compile /forwards/forwardToSimpleJava.jsp"
errored false errored false
attributes { attributes {
@ -356,8 +386,8 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
"jsp.compiler" "org.apache.jasper.compiler.JDTCompiler" "jsp.compiler" "org.apache.jasper.compiler.JDTCompiler"
} }
} }
span(4) { span(5) {
childOf span(2) childOf span(3)
name "Render /forwards/forwardToSimpleJava.jsp" name "Render /forwards/forwardToSimpleJava.jsp"
errored false errored false
attributes { attributes {
@ -365,8 +395,13 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
"jsp.requestURL" baseUrl + "/forwards/forwardToSimpleJava.jsp" "jsp.requestURL" baseUrl + "/forwards/forwardToSimpleJava.jsp"
} }
} }
span(5) { span(6) {
childOf span(4) childOf span(5)
name "ApplicationDispatcher.forward"
errored false
}
span(7) {
childOf span(6)
name "Compile /common/loop.jsp" name "Compile /common/loop.jsp"
errored false errored false
attributes { attributes {
@ -374,8 +409,8 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
"jsp.compiler" "org.apache.jasper.compiler.JDTCompiler" "jsp.compiler" "org.apache.jasper.compiler.JDTCompiler"
} }
} }
span(6) { span(8) {
childOf span(4) childOf span(6)
name "Render /common/loop.jsp" name "Render /common/loop.jsp"
errored false errored false
attributes { attributes {
@ -401,7 +436,7 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
then: then:
assertTraces(1) { assertTraces(1) {
trace(0, 4) { trace(0, 5) {
span(0) { span(0) {
hasNoParent() hasNoParent()
name "/$jspWebappContext/forwards/forwardToCompileError.jsp" name "/$jspWebappContext/forwards/forwardToCompileError.jsp"
@ -439,6 +474,12 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
} }
span(3) { span(3) {
childOf span(2) childOf span(2)
name "ApplicationDispatcher.forward"
errored true
errorEvent(JasperException, String)
}
span(4) {
childOf span(3)
name "Compile /compileError.jsp" name "Compile /compileError.jsp"
errored true errored true
errorEvent(JasperException, String) errorEvent(JasperException, String)
@ -465,7 +506,7 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
then: then:
assertTraces(1) { assertTraces(1) {
trace(0, 3) { trace(0, 5) {
span(0) { span(0) {
hasNoParent() hasNoParent()
name "/$jspWebappContext/forwards/forwardToNonExistent.jsp" name "/$jspWebappContext/forwards/forwardToNonExistent.jsp"
@ -499,6 +540,14 @@ class JspInstrumentationForwardTests extends AgentInstrumentationSpecification {
"jsp.requestURL" reqUrl "jsp.requestURL" reqUrl
} }
} }
span(3) {
childOf span(2)
name "ApplicationDispatcher.forward"
}
span(4) {
childOf span(3)
name "ResponseFacade.sendError"
}
} }
} }
res.code() == 404 res.code() == 404

View File

@ -21,8 +21,7 @@ public class LibertyHttpServerTracer extends Servlet3HttpServerTracer {
// using request method as span name as server isn't ready for calling request.getServletPath() // using request method as span name as server isn't ready for calling request.getServletPath()
// 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()); return startSpan(request, request, request, "HTTP " + request.getMethod());
return addContextPathContext(context, request);
} }
@Override @Override

View File

@ -3,6 +3,11 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.test.base.HttpServerTest import io.opentelemetry.instrumentation.test.base.HttpServerTest
import org.glassfish.embeddable.BootstrapProperties import org.glassfish.embeddable.BootstrapProperties
import org.glassfish.embeddable.Deployer import org.glassfish.embeddable.Deployer
@ -63,4 +68,22 @@ class GlassFishServerTest extends HttpServerTest<GlassFish> {
boolean redirectHasBody() { boolean redirectHasBody() {
true true
} }
@Override
boolean hasResponseSpan(ServerEndpoint endpoint) {
endpoint == REDIRECT || endpoint == ERROR || endpoint == NOT_FOUND
}
@Override
void responseSpan(TraceAssert trace, int index, Object parent, String method, ServerEndpoint endpoint) {
switch (endpoint) {
case REDIRECT:
redirectSpan(trace, index, parent)
break
case ERROR:
case NOT_FOUND:
sendErrorSpan(trace, index, parent)
break
}
}
} }

View File

@ -18,6 +18,7 @@ muzzle {
dependencies { dependencies {
compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.2' compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.2'
api(project(':instrumentation-core:servlet-2.2')) api(project(':instrumentation-core:servlet-2.2'))
api(project(':instrumentation:servlet:servlet-common:javaagent'))
testImplementation(project(':testing-common')) { testImplementation(project(':testing-common')) {
exclude group: 'org.eclipse.jetty', module: 'jetty-server' exclude group: 'org.eclipse.jetty', module: 'jetty-server'

View File

@ -37,7 +37,7 @@ public class Servlet2Advice {
Context serverContext = tracer().getServerContext(httpServletRequest); Context serverContext = tracer().getServerContext(httpServletRequest);
if (serverContext != null) { if (serverContext != null) {
tracer().updateServerSpan(serverContext, httpServletRequest); tracer().updateServerSpanNameOnce(serverContext, httpServletRequest);
return; return;
} }

View File

@ -70,10 +70,15 @@ class JettyServlet2Test extends HttpServerTest<Server> {
false false
} }
@Override
boolean hasResponseSpan(ServerEndpoint endpoint) {
endpoint == REDIRECT || endpoint == ERROR
}
@Override @Override
void responseSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) { void responseSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
trace.span(index) { trace.span(index) {
name endpoint == REDIRECT ? "HttpServletResponse.sendRedirect" : "HttpServletResponse.sendError" name endpoint == REDIRECT ? "Response.sendRedirect" : "Response.sendError"
kind INTERNAL kind INTERNAL
errored false errored false
childOf((SpanData) parent) childOf((SpanData) parent)

View File

@ -17,6 +17,7 @@ muzzle {
dependencies { dependencies {
compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.0.1' compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.0.1'
api(project(':instrumentation-core:servlet-2.2')) api(project(':instrumentation-core:servlet-2.2'))
api(project(':instrumentation:servlet:servlet-common:javaagent'))
testInstrumentation project(':instrumentation:jetty-8.0:javaagent') testInstrumentation project(':instrumentation:jetty-8.0:javaagent')

View File

@ -40,14 +40,14 @@ public class Servlet3Advice {
scope = attachedContext.makeCurrent(); scope = attachedContext.makeCurrent();
} }
tracer().updateServerSpan(attachedContext, httpServletRequest); tracer().updateServerSpanNameOnce(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().updateServerSpan(parentContext, httpServletRequest); tracer().updateServerSpanNameOnce(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;
} }

View File

@ -10,6 +10,7 @@ import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEn
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.test.base.HttpServerTest import io.opentelemetry.instrumentation.test.base.HttpServerTest
import javax.servlet.Servlet import javax.servlet.Servlet
import okhttp3.Request import okhttp3.Request
@ -49,4 +50,25 @@ abstract class AbstractServlet3Test<SERVER, CONTEXT> extends HttpServerTest<SERV
lastRequest = uri lastRequest = uri
super.request(uri, method, body) super.request(uri, method, body)
} }
boolean errorEndpointUsesSendError() {
true
}
@Override
boolean hasResponseSpan(ServerEndpoint endpoint) {
endpoint == REDIRECT || (endpoint == ERROR && errorEndpointUsesSendError())
}
@Override
void responseSpan(TraceAssert trace, int index, Object parent, String method, ServerEndpoint endpoint) {
switch (endpoint) {
case REDIRECT:
redirectSpan(trace, index, parent)
break
case ERROR:
sendErrorSpan(trace, index, parent)
break
}
}
} }

View File

@ -108,6 +108,11 @@ class JettyServlet3TestAsync extends JettyServlet3Test {
TestServlet3.Async TestServlet3.Async
} }
@Override
boolean errorEndpointUsesSendError() {
false
}
@Override @Override
boolean testException() { boolean testException() {
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/807 // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/807
@ -135,6 +140,11 @@ class JettyServlet3TestForward extends JettyDispatchTest {
TestServlet3.Sync // dispatch to sync servlet TestServlet3.Sync // dispatch to sync servlet
} }
@Override
boolean hasForwardSpan() {
true
}
@Override @Override
protected void setupServlets(ServletContextHandler context) { protected void setupServlets(ServletContextHandler context) {
super.setupServlets(context) super.setupServlets(context)
@ -164,6 +174,11 @@ class JettyServlet3TestInclude extends JettyDispatchTest {
false false
} }
@Override
boolean hasIncludeSpan() {
true
}
@Override @Override
protected void setupServlets(ServletContextHandler context) { protected void setupServlets(ServletContextHandler context) {
super.setupServlets(context) super.setupServlets(context)
@ -223,6 +238,11 @@ class JettyServlet3TestDispatchAsync extends JettyDispatchTest {
addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive) addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive)
} }
@Override
boolean errorEndpointUsesSendError() {
false
}
@Override @Override
boolean testException() { boolean testException() {
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/807 // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/807

View File

@ -6,11 +6,13 @@
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.AUTH_REQUIRED import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.AUTH_REQUIRED
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS
import static org.junit.Assume.assumeTrue import static org.junit.Assume.assumeTrue
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import java.nio.file.Files import java.nio.file.Files
import javax.servlet.Servlet import javax.servlet.Servlet
import javax.servlet.ServletException import javax.servlet.ServletException
@ -40,6 +42,21 @@ abstract class TomcatServlet3Test extends AbstractServlet3Test<Tomcat, Context>
ServletException ServletException
} }
@Override
boolean hasResponseSpan(ServerEndpoint endpoint) {
endpoint == NOT_FOUND || super.hasResponseSpan(endpoint)
}
@Override
void responseSpan(TraceAssert trace, int index, Object parent, String method, ServerEndpoint endpoint) {
switch (endpoint) {
case NOT_FOUND:
sendErrorSpan(trace, index, parent)
break
}
super.responseSpan(trace, index, parent, method, endpoint)
}
@Shared @Shared
def accessLogValue = new TestAccessLogValve() def accessLogValue = new TestAccessLogValve()
@ -122,10 +139,26 @@ abstract class TomcatServlet3Test extends AbstractServlet3Test<Tomcat, Context>
def loggedTraces = accessLogValue.loggedIds*.first def loggedTraces = accessLogValue.loggedIds*.first
def loggedSpans = accessLogValue.loggedIds*.second def loggedSpans = accessLogValue.loggedIds*.second
def expectedCount = 2
if (hasIncludeSpan()) {
expectedCount++
}
if (hasForwardSpan()) {
expectedCount++
}
(0..count - 1).each { (0..count - 1).each {
trace(it, 2) { trace(it, expectedCount) {
serverSpan(it, 0, null, null, "GET", SUCCESS.body.length()) serverSpan(it, 0, null, null, "GET", SUCCESS.body.length())
controllerSpan(it, 1, span(0)) def controllerIndex = 1
if (hasIncludeSpan()) {
includeSpan(it, 1, span(0))
controllerIndex++
}
if (hasForwardSpan()) {
forwardSpan(it, 1, span(0))
controllerIndex++
}
controllerSpan(it, controllerIndex, span(controllerIndex - 1))
} }
assert loggedTraces.contains(traces[it][0].traceId) assert loggedTraces.contains(traces[it][0].traceId)
@ -150,10 +183,27 @@ abstract class TomcatServlet3Test extends AbstractServlet3Test<Tomcat, Context>
response.body().string() == ERROR.body response.body().string() == ERROR.body
and: and:
def spanCount = 2
if (errorEndpointUsesSendError()) {
spanCount++
}
if (hasForwardSpan()) {
spanCount++
}
assertTraces(1) { assertTraces(1) {
trace(0, 2) { trace(0, spanCount) {
serverSpan(it, 0, null, null, method, response.body().contentLength(), ERROR) serverSpan(it, 0, null, null, method, response.body().contentLength(), ERROR)
controllerSpan(it, 1, span(0)) def spanIndex = 1
if (hasForwardSpan()) {
forwardSpan(it, spanIndex, span(spanIndex - 1))
spanIndex++
}
controllerSpan(it, spanIndex, span(spanIndex - 1))
spanIndex++
if (errorEndpointUsesSendError()) {
sendErrorSpan(it, spanIndex, span(spanIndex - 1))
spanIndex++
}
} }
def (String traceId, String spanId) = accessLogValue.loggedIds[0] def (String traceId, String spanId) = accessLogValue.loggedIds[0]
@ -256,6 +306,11 @@ class TomcatServlet3TestAsync extends TomcatServlet3Test {
TestServlet3.Async TestServlet3.Async
} }
@Override
boolean errorEndpointUsesSendError() {
false
}
@Override @Override
boolean testException() { boolean testException() {
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/807 // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/807
@ -288,6 +343,11 @@ class TomcatServlet3TestForward extends TomcatDispatchTest {
false false
} }
@Override
boolean hasForwardSpan() {
true
}
@Override @Override
protected void setupServlets(Context context) { protected void setupServlets(Context context) {
super.setupServlets(context) super.setupServlets(context)
@ -322,6 +382,11 @@ class TomcatServlet3TestInclude extends TomcatDispatchTest {
false false
} }
@Override
boolean hasIncludeSpan() {
true
}
@Override @Override
protected void setupServlets(Context context) { protected void setupServlets(Context context) {
super.setupServlets(context) super.setupServlets(context)
@ -379,6 +444,11 @@ class TomcatServlet3TestDispatchAsync extends TomcatDispatchTest {
addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive) addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive)
} }
@Override
boolean errorEndpointUsesSendError() {
false
}
@Override @Override
boolean testException() { boolean testException() {
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/807 // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/807

View File

@ -0,0 +1,100 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.servlet.contextpath;
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.safeHasSuperType;
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.ClassLoaderMatcher.hasClassesNamed;
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.servlet.ServletContextPath;
import io.opentelemetry.javaagent.instrumentation.api.CallDepthThreadLocalMap;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import io.opentelemetry.javaagent.tooling.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@AutoService(InstrumentationModule.class)
public class ServletContextPathInstrumentationModule extends InstrumentationModule {
public ServletContextPathInstrumentationModule() {
super("servlet", "servlet-context-path");
}
@Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new ServletContextPathInstrumentation());
}
public static class ServletContextPathInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("javax.servlet.Filter");
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return safeHasSuperType(namedOneOf("javax.servlet.Filter", "javax.servlet.Servlet"));
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
namedOneOf("doFilter", "service")
.and(takesArgument(0, named("javax.servlet.ServletRequest")))
.and(takesArgument(1, named("javax.servlet.ServletResponse")))
.and(isPublic()),
ServletContextPathAdvice.class.getName());
}
}
public static class ServletContextPathAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) ServletRequest request, @Advice.Local("otelScope") Scope scope) {
if (!(request instanceof HttpServletRequest)) {
return;
}
int callDepth = CallDepthThreadLocalMap.incrementCallDepth(ServletContextPath.class);
if (callDepth > 0) {
return;
}
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String contextPath = httpServletRequest.getContextPath();
if (contextPath != null && !contextPath.isEmpty() && !contextPath.equals("/")) {
Context context =
Java8BytecodeBridge.currentContext().with(ServletContextPath.CONTEXT_KEY, contextPath);
scope = context.makeCurrent();
}
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stop(@Advice.Local("otelScope") Scope scope) {
if (scope != null) {
CallDepthThreadLocalMap.reset(ServletContextPath.class);
scope.close();
}
}
}
}

View File

@ -64,10 +64,10 @@ public class HandlerAdapterInstrumentation implements TypeInstrumentation {
if (serverSpan != null) { if (serverSpan != null) {
// Name the parent span based on the matching pattern // Name the parent span based on the matching pattern
tracer().onRequest(context, serverSpan, request); tracer().onRequest(context, serverSpan, request);
// Now create a span for handler/controller execution.
span = tracer().startHandlerSpan(handler);
scope = context.with(span).makeCurrent();
} }
// Now create a span for handler/controller execution.
span = tracer().startHandlerSpan(handler);
scope = context.with(span).makeCurrent();
} }
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)

View File

@ -58,6 +58,11 @@ class SpringBootBasedTest extends HttpServerTest<ConfigurableApplicationContext>
endpoint == REDIRECT endpoint == REDIRECT
} }
@Override
boolean hasResponseSpan(ServerEndpoint endpoint) {
endpoint == REDIRECT
}
@Override @Override
boolean testNotFound() { boolean testNotFound() {
// FIXME: the instrumentation adds an extra controller span which is not consistent. // FIXME: the instrumentation adds an extra controller span which is not consistent.
@ -84,9 +89,11 @@ class SpringBootBasedTest extends HttpServerTest<ConfigurableApplicationContext>
and: and:
assertTraces(1) { assertTraces(1) {
trace(0, 2) { trace(0, 4) {
serverSpan(it, 0, null, null, "GET", null, AUTH_ERROR) serverSpan(it, 0, null, null, "GET", null, AUTH_ERROR)
errorPageSpans(it, 1, null) sendErrorSpan(it, 1, span(0))
forwardSpan(it, 2, span(0))
errorPageSpans(it, 3, null)
} }
} }
} }
@ -111,8 +118,9 @@ class SpringBootBasedTest extends HttpServerTest<ConfigurableApplicationContext>
and: and:
assertTraces(1) { assertTraces(1) {
trace(0, 1) { trace(0, 2) {
serverSpan(it, 0, null, null, "POST", response.body()?.contentLength(), LOGIN) serverSpan(it, 0, null, null, "POST", response.body()?.contentLength(), LOGIN)
redirectSpan(it, 1, span(0))
} }
} }
@ -134,7 +142,7 @@ class SpringBootBasedTest extends HttpServerTest<ConfigurableApplicationContext>
@Override @Override
void responseSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) { void responseSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
trace.span(index) { trace.span(index) {
name "HttpServletResponse.sendRedirect" name "OnCommittedResponseWrapper.sendRedirect"
kind INTERNAL kind INTERNAL
errored false errored false
attributes { attributes {

View File

@ -3,8 +3,10 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import io.opentelemetry.api.trace.SpanKind import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.instrumentation.test.asserts.TraceAssert import io.opentelemetry.instrumentation.test.asserts.TraceAssert
@ -46,6 +48,24 @@ class Struts2ActionSpanTest extends HttpServerTest<Server> {
return true return true
} }
@Override
boolean hasResponseSpan(ServerEndpoint endpoint) {
endpoint == REDIRECT || endpoint == ERROR || endpoint == EXCEPTION
}
@Override
void responseSpan(TraceAssert trace, int index, Object controllerSpan, Object handlerSpan, String method, ServerEndpoint endpoint) {
switch (endpoint) {
case REDIRECT:
redirectSpan(trace, index, handlerSpan)
break
case ERROR:
case EXCEPTION:
sendErrorSpan(trace, index, handlerSpan)
break
}
}
String expectedServerSpanName(ServerEndpoint endpoint) { String expectedServerSpanName(ServerEndpoint endpoint) {
return endpoint == PATH_PARAM ? getContextPath() + "/path/{id}/param" : endpoint.resolvePath(address).path return endpoint == PATH_PARAM ? getContextPath() + "/path/{id}/param" : endpoint.resolvePath(address).path
} }

View File

@ -19,21 +19,22 @@ public class TestServlet extends HttpServlet {
HttpServerTest.ServerEndpoint serverEndpoint = HttpServerTest.ServerEndpoint.forPath(path); HttpServerTest.ServerEndpoint serverEndpoint = HttpServerTest.ServerEndpoint.forPath(path);
if (serverEndpoint != null) { if (serverEndpoint != null) {
if (serverEndpoint == HttpServerTest.ServerEndpoint.EXCEPTION) { HttpServerTest.controller(
HttpServerTest.controller( serverEndpoint,
serverEndpoint, () -> {
() -> { if (serverEndpoint == HttpServerTest.ServerEndpoint.EXCEPTION) {
throw new Exception(serverEndpoint.getBody()); throw new Exception(serverEndpoint.getBody());
}); }
} else { resp.getWriter().print(serverEndpoint.getBody());
resp.getWriter().print(HttpServerTest.controller(serverEndpoint, serverEndpoint::getBody)); if (serverEndpoint == HttpServerTest.ServerEndpoint.REDIRECT) {
} resp.sendRedirect(serverEndpoint.getBody());
} else if (serverEndpoint == HttpServerTest.ServerEndpoint.ERROR) {
if (serverEndpoint == HttpServerTest.ServerEndpoint.REDIRECT) { resp.sendError(serverEndpoint.getStatus());
resp.sendRedirect(serverEndpoint.getBody()); } else {
} else { resp.setStatus(serverEndpoint.getStatus());
resp.setStatus(serverEndpoint.getStatus()); }
} return null;
});
} else if ("/errorPage".equals(path)) { } else if ("/errorPage".equals(path)) {
resp.getWriter().print(HttpServerTest.ServerEndpoint.EXCEPTION.getBody()); resp.getWriter().print(HttpServerTest.ServerEndpoint.EXCEPTION.getBody());
resp.setStatus(500); resp.setStatus(500);

View File

@ -5,7 +5,15 @@
package io.opentelemetry.javaagent.instrumentation.tomcat7 package io.opentelemetry.javaagent.instrumentation.tomcat7
import static io.opentelemetry.api.trace.Span.Kind.INTERNAL
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.test.base.HttpServerTest import io.opentelemetry.instrumentation.test.base.HttpServerTest
import io.opentelemetry.sdk.trace.data.SpanData
import org.apache.catalina.Context import org.apache.catalina.Context
import org.apache.catalina.startup.Tomcat import org.apache.catalina.startup.Tomcat
import org.apache.tomcat.util.descriptor.web.ErrorPage import org.apache.tomcat.util.descriptor.web.ErrorPage
@ -53,4 +61,42 @@ class TomcatHandlerTest extends HttpServerTest<Tomcat> {
tomcat.getServer().stop() tomcat.getServer().stop()
} }
@Override
boolean testExceptionBody() {
false
}
@Override
boolean hasErrorPageSpans(ServerEndpoint endpoint) {
endpoint == ERROR || endpoint == EXCEPTION
}
@Override
void errorPageSpans(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {
name "ApplicationDispatcher.forward"
kind INTERNAL
errored false
childOf((SpanData) parent)
attributes {
}
}
}
@Override
boolean hasResponseSpan(ServerEndpoint endpoint) {
endpoint == REDIRECT || endpoint == ERROR
}
@Override
void responseSpan(TraceAssert trace, int index, Object parent, String method, ServerEndpoint endpoint) {
switch (endpoint) {
case REDIRECT:
redirectSpan(trace, index, parent)
break
case ERROR:
sendErrorSpan(trace, index, parent)
break
}
}
} }

View File

@ -51,6 +51,14 @@ abstract class HttpServerTest<SERVER> extends AgentInstrumentationSpecification
false false
} }
boolean hasForwardSpan() {
false
}
boolean hasIncludeSpan() {
false
}
boolean hasErrorPageSpans(ServerEndpoint endpoint) { boolean hasErrorPageSpans(ServerEndpoint endpoint) {
false false
} }
@ -341,12 +349,18 @@ abstract class HttpServerTest<SERVER> extends AgentInstrumentationSpecification
if (hasHandlerSpan()) { if (hasHandlerSpan()) {
spanCount++ spanCount++
} }
if (hasResponseSpan(endpoint)) {
spanCount++
}
if (endpoint != NOT_FOUND) { if (endpoint != NOT_FOUND) {
spanCount++ // controller span spanCount++ // controller span
if (hasRenderSpan(endpoint)) { if (hasRenderSpan(endpoint)) {
spanCount++ spanCount++
} }
if (hasResponseSpan(endpoint)) { if (hasForwardSpan()) {
spanCount++
}
if (hasIncludeSpan()) {
spanCount++ spanCount++
} }
if (hasErrorPageSpans(endpoint)) { if (hasErrorPageSpans(endpoint)) {
@ -362,21 +376,31 @@ abstract class HttpServerTest<SERVER> extends AgentInstrumentationSpecification
handlerSpan(it, spanIndex++, span(0), method, endpoint) handlerSpan(it, spanIndex++, span(0), method, endpoint)
} }
if (endpoint != NOT_FOUND) { if (endpoint != NOT_FOUND) {
def controllerSpanIndex = 0
if (hasHandlerSpan()) { if (hasHandlerSpan()) {
controllerSpan(it, spanIndex++, span(1), errorMessage, expectedExceptionClass()) controllerSpanIndex++
} else {
controllerSpan(it, spanIndex++, span(0), errorMessage, expectedExceptionClass())
} }
if (hasForwardSpan()) {
forwardSpan(it, spanIndex++, span(0), errorMessage, expectedExceptionClass())
controllerSpanIndex++
}
if (hasIncludeSpan()) {
includeSpan(it, spanIndex++, span(0), errorMessage, expectedExceptionClass())
controllerSpanIndex++
}
controllerSpan(it, spanIndex++, span(controllerSpanIndex), errorMessage, expectedExceptionClass())
if (hasRenderSpan(endpoint)) { if (hasRenderSpan(endpoint)) {
renderSpan(it, spanIndex++, span(0), method, endpoint) renderSpan(it, spanIndex++, span(0), method, endpoint)
} }
if (hasResponseSpan(endpoint)) { if (hasResponseSpan(endpoint)) {
responseSpan(it, spanIndex, span(spanIndex - 1), method, endpoint) responseSpan(it, spanIndex, span(spanIndex - 1), span(0), method, endpoint)
spanIndex++ spanIndex++
} }
if (hasErrorPageSpans(endpoint)) { if (hasErrorPageSpans(endpoint)) {
errorPageSpans(it, spanIndex, span(0), method, endpoint) errorPageSpans(it, spanIndex, span(0), method, endpoint)
} }
} else if (hasResponseSpan(endpoint)) {
responseSpan(it, 1, span(0), span(0), method, endpoint)
} }
} }
} }
@ -402,6 +426,10 @@ abstract class HttpServerTest<SERVER> extends AgentInstrumentationSpecification
throw new UnsupportedOperationException("renderSpan not implemented in " + getClass().name) throw new UnsupportedOperationException("renderSpan not implemented in " + getClass().name)
} }
void responseSpan(TraceAssert trace, int index, Object controllerSpan, Object handlerSpan, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
responseSpan(trace, index, controllerSpan, method, endpoint)
}
void responseSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) { void responseSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
throw new UnsupportedOperationException("responseSpan not implemented in " + getClass().name) throw new UnsupportedOperationException("responseSpan not implemented in " + getClass().name)
} }
@ -410,6 +438,46 @@ abstract class HttpServerTest<SERVER> extends AgentInstrumentationSpecification
throw new UnsupportedOperationException("errorPageSpans not implemented in " + getClass().name) throw new UnsupportedOperationException("errorPageSpans not implemented in " + getClass().name)
} }
void redirectSpan(TraceAssert trace, int index, Object parent) {
trace.span(index) {
name ~/\.sendRedirect$/
kind Span.Kind.INTERNAL
childOf((SpanData) parent)
}
}
void sendErrorSpan(TraceAssert trace, int index, Object parent) {
trace.span(index) {
name ~/\.sendError$/
kind Span.Kind.INTERNAL
childOf((SpanData) parent)
}
}
void forwardSpan(TraceAssert trace, int index, Object parent, String errorMessage = null, Class exceptionClass = Exception) {
trace.span(index) {
name ~/\.forward$/
kind Span.Kind.INTERNAL
errored errorMessage != null
if (errorMessage) {
errorEvent(exceptionClass, errorMessage)
}
childOf((SpanData) parent)
}
}
void includeSpan(TraceAssert trace, int index, Object parent, String errorMessage = null, Class exceptionClass = Exception) {
trace.span(index) {
name ~/\.include$/
kind Span.Kind.INTERNAL
errored errorMessage != null
if (errorMessage) {
errorEvent(exceptionClass, errorMessage)
}
childOf((SpanData) parent)
}
}
// parent span must be cast otherwise it breaks debugging classloading (junit loads it early) // parent span must be cast otherwise it breaks debugging classloading (junit loads it early)
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", Long responseContentLength = null, ServerEndpoint endpoint = SUCCESS) { void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", Long responseContentLength = null, ServerEndpoint endpoint = SUCCESS) {
trace.span(index) { trace.span(index) {