Server span naming for servlet filters (#2887)
* Server span naming for servlet filters * wildfly default servlet has empty mappings * jetty11 requires java11 * try a differnt way to disable jetty11 tests on java8 * Update instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TomcatServlet5FilterMappingTest.groovy Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com> * review fix * rework to use InstrumentationContext * remove debugging code * move MappingResolver to avoid ClassCastException on wildfly * Update instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3FilterMappingTest.groovy Co-authored-by: Mateusz Rzeszutek <mrzeszutek@splunk.com> * review fixes Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com> Co-authored-by: Mateusz Rzeszutek <mrzeszutek@splunk.com>
This commit is contained in:
parent
c9a3793bf8
commit
357140c081
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.servlet;
|
package io.opentelemetry.instrumentation.api.servlet;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -12,7 +12,10 @@ import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class MappingResolver {
|
/**
|
||||||
|
* Helper class for finding a mapping that matches current request from a collection of mappings.
|
||||||
|
*/
|
||||||
|
public final class MappingResolver {
|
||||||
private final Set<String> exactMatches;
|
private final Set<String> exactMatches;
|
||||||
private final List<WildcardMatcher> wildcardMatchers;
|
private final List<WildcardMatcher> wildcardMatchers;
|
||||||
private final boolean hasDefault;
|
private final boolean hasDefault;
|
|
@ -28,6 +28,9 @@ public final class ServerSpanNaming {
|
||||||
}
|
}
|
||||||
|
|
||||||
private volatile Source updatedBySource;
|
private volatile Source updatedBySource;
|
||||||
|
// Length of the currently set name. This is used when setting name from a servlet filter
|
||||||
|
// to pick the most descriptive (longest) name.
|
||||||
|
private volatile int nameLength;
|
||||||
|
|
||||||
private ServerSpanNaming(Source initialSource) {
|
private ServerSpanNaming(Source initialSource) {
|
||||||
this.updatedBySource = initialSource;
|
this.updatedBySource = initialSource;
|
||||||
|
@ -58,15 +61,24 @@ public final class ServerSpanNaming {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (source.order > serverSpanNaming.updatedBySource.order) {
|
// special case for servlet filters, even when we have a name from previous filter see whether
|
||||||
|
// the new name is better and if so use it instead
|
||||||
|
boolean onlyIfBetterName =
|
||||||
|
!source.useFirst && source.order == serverSpanNaming.updatedBySource.order;
|
||||||
|
if (source.order > serverSpanNaming.updatedBySource.order || onlyIfBetterName) {
|
||||||
String name = serverSpanName.get();
|
String name = serverSpanName.get();
|
||||||
if (name != null) {
|
if (name != null && (!onlyIfBetterName || serverSpanNaming.isBetterName(name))) {
|
||||||
serverSpan.updateName(name);
|
serverSpan.updateName(name);
|
||||||
serverSpanNaming.updatedBySource = source;
|
serverSpanNaming.updatedBySource = source;
|
||||||
|
serverSpanNaming.nameLength = name.length();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isBetterName(String name) {
|
||||||
|
return name.length() > nameLength;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO (trask) migrate the one usage (ServletHttpServerTracer) to ServerSpanNaming.init() once we
|
// TODO (trask) migrate the one usage (ServletHttpServerTracer) to ServerSpanNaming.init() once we
|
||||||
// migrate to new Instrumenters (see
|
// migrate to new Instrumenters (see
|
||||||
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/2814#discussion_r617351334
|
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/2814#discussion_r617351334
|
||||||
|
@ -82,13 +94,22 @@ public final class ServerSpanNaming {
|
||||||
|
|
||||||
public enum Source {
|
public enum Source {
|
||||||
CONTAINER(1),
|
CONTAINER(1),
|
||||||
SERVLET(2),
|
// for servlet filters we try to find the best name which isn't necessarily from the first
|
||||||
CONTROLLER(3);
|
// filter that is called
|
||||||
|
FILTER(2, false),
|
||||||
|
SERVLET(3),
|
||||||
|
CONTROLLER(4);
|
||||||
|
|
||||||
private final int order;
|
private final int order;
|
||||||
|
private final boolean useFirst;
|
||||||
|
|
||||||
Source(int order) {
|
Source(int order) {
|
||||||
|
this(order, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Source(int order, boolean useFirst) {
|
||||||
this.order = order;
|
this.order = order;
|
||||||
|
this.useFirst = useFirst;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,13 @@ import static io.opentelemetry.instrumentation.servlet.v3_0.Servlet3HttpServerTr
|
||||||
import io.opentelemetry.context.Context;
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.context.Scope;
|
import io.opentelemetry.context.Scope;
|
||||||
import io.opentelemetry.instrumentation.api.servlet.AppServerBridge;
|
import io.opentelemetry.instrumentation.api.servlet.AppServerBridge;
|
||||||
|
import io.opentelemetry.instrumentation.api.servlet.MappingResolver;
|
||||||
import io.opentelemetry.javaagent.instrumentation.api.CallDepthThreadLocalMap;
|
import io.opentelemetry.javaagent.instrumentation.api.CallDepthThreadLocalMap;
|
||||||
|
import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext;
|
||||||
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
|
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
|
||||||
import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletAndFilterAdviceHelper;
|
import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletAndFilterAdviceHelper;
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import javax.servlet.Servlet;
|
||||||
import javax.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
import javax.servlet.ServletResponse;
|
import javax.servlet.ServletResponse;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
@ -36,12 +40,24 @@ public class Servlet3Advice {
|
||||||
|
|
||||||
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||||
|
|
||||||
|
boolean servlet = servletOrFilter instanceof Servlet;
|
||||||
|
MappingResolver mappingResolver;
|
||||||
|
if (servlet) {
|
||||||
|
mappingResolver =
|
||||||
|
InstrumentationContext.get(Servlet.class, MappingResolver.class)
|
||||||
|
.get((Servlet) servletOrFilter);
|
||||||
|
} else {
|
||||||
|
mappingResolver =
|
||||||
|
InstrumentationContext.get(Filter.class, MappingResolver.class)
|
||||||
|
.get((Filter) servletOrFilter);
|
||||||
|
}
|
||||||
|
|
||||||
Context attachedContext = tracer().getServerContext(httpServletRequest);
|
Context attachedContext = tracer().getServerContext(httpServletRequest);
|
||||||
if (attachedContext != null) {
|
if (attachedContext != null) {
|
||||||
// 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
|
||||||
if (tracer().needsRescoping(attachedContext)) {
|
if (tracer().needsRescoping(attachedContext)) {
|
||||||
attachedContext =
|
attachedContext =
|
||||||
tracer().updateContext(attachedContext, servletOrFilter, httpServletRequest);
|
tracer().updateContext(attachedContext, httpServletRequest, mappingResolver, servlet);
|
||||||
scope = attachedContext.makeCurrent();
|
scope = attachedContext.makeCurrent();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +66,7 @@ public class Servlet3Advice {
|
||||||
// instrumentation, if needed update span with info from current request.
|
// instrumentation, if needed update span with info from current request.
|
||||||
Context currentContext = Java8BytecodeBridge.currentContext();
|
Context currentContext = Java8BytecodeBridge.currentContext();
|
||||||
Context updatedContext =
|
Context updatedContext =
|
||||||
tracer().updateContext(currentContext, servletOrFilter, httpServletRequest);
|
tracer().updateContext(currentContext, httpServletRequest, mappingResolver, servlet);
|
||||||
if (updatedContext != currentContext) {
|
if (updatedContext != currentContext) {
|
||||||
// runOnceUnderAppServer updated context, need to re-scope
|
// runOnceUnderAppServer updated context, need to re-scope
|
||||||
scope = updatedContext.makeCurrent();
|
scope = updatedContext.makeCurrent();
|
||||||
|
@ -65,7 +81,7 @@ public class Servlet3Advice {
|
||||||
// In case it was created by app server integration we need to update it with info from
|
// In case it was created by app server integration we need to update it with info from
|
||||||
// current request.
|
// current request.
|
||||||
Context updatedContext =
|
Context updatedContext =
|
||||||
tracer().updateContext(currentContext, servletOrFilter, httpServletRequest);
|
tracer().updateContext(currentContext, httpServletRequest, mappingResolver, servlet);
|
||||||
if (currentContext != updatedContext) {
|
if (currentContext != updatedContext) {
|
||||||
// updateContext updated context, need to re-scope
|
// updateContext updated context, need to re-scope
|
||||||
scope = updatedContext.makeCurrent();
|
scope = updatedContext.makeCurrent();
|
||||||
|
@ -73,7 +89,7 @@ public class Servlet3Advice {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context = tracer().startSpan(servletOrFilter, httpServletRequest);
|
context = tracer().startSpan(httpServletRequest, mappingResolver, servlet);
|
||||||
scope = context.makeCurrent();
|
scope = context.makeCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.servlet.v3_0;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.api.servlet.MappingResolver;
|
||||||
|
import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext;
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import javax.servlet.FilterConfig;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
|
||||||
|
public class Servlet3FilterInitAdvice {
|
||||||
|
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static void filterInit(
|
||||||
|
@Advice.This Filter filter, @Advice.Argument(0) FilterConfig filterConfig) {
|
||||||
|
if (filterConfig == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InstrumentationContext.get(Filter.class, MappingResolver.class)
|
||||||
|
.putIfAbsent(filter, new Servlet3FilterMappingResolverFactory(filterConfig));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.servlet.v3_0;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.api.servlet.MappingResolver;
|
||||||
|
import io.opentelemetry.instrumentation.servlet.naming.ServletFilterMappingResolverFactory;
|
||||||
|
import io.opentelemetry.javaagent.instrumentation.api.ContextStore;
|
||||||
|
import java.util.Collection;
|
||||||
|
import javax.servlet.FilterConfig;
|
||||||
|
import javax.servlet.FilterRegistration;
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletRegistration;
|
||||||
|
|
||||||
|
public class Servlet3FilterMappingResolverFactory
|
||||||
|
extends ServletFilterMappingResolverFactory<FilterRegistration>
|
||||||
|
implements ContextStore.Factory<MappingResolver> {
|
||||||
|
private final FilterConfig filterConfig;
|
||||||
|
|
||||||
|
public Servlet3FilterMappingResolverFactory(FilterConfig filterConfig) {
|
||||||
|
this.filterConfig = filterConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected FilterRegistration getFilterRegistration() {
|
||||||
|
String filterName = filterConfig.getFilterName();
|
||||||
|
ServletContext servletContext = filterConfig.getServletContext();
|
||||||
|
if (filterName == null || servletContext == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return servletContext.getFilterRegistration(filterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Collection<String> getUrlPatternMappings(FilterRegistration filterRegistration) {
|
||||||
|
return filterRegistration.getUrlPatternMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Collection<String> getServletNameMappings(FilterRegistration filterRegistration) {
|
||||||
|
return filterRegistration.getServletNameMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Collection<String> getServletMappings(String servletName) {
|
||||||
|
ServletRegistration servletRegistration =
|
||||||
|
filterConfig.getServletContext().getServletRegistration(servletName);
|
||||||
|
if (servletRegistration == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return servletRegistration.getMappings();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.servlet.v3_0;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.api.servlet.MappingResolver;
|
||||||
|
import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext;
|
||||||
|
import javax.servlet.Servlet;
|
||||||
|
import javax.servlet.ServletConfig;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
|
||||||
|
public class Servlet3InitAdvice {
|
||||||
|
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static void servletInit(
|
||||||
|
@Advice.This Servlet servlet, @Advice.Argument(0) ServletConfig servletConfig) {
|
||||||
|
if (servletConfig == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InstrumentationContext.get(Servlet.class, MappingResolver.class)
|
||||||
|
.putIfAbsent(servlet, new Servlet3MappingResolverFactory(servletConfig));
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,11 @@ public class Servlet3InstrumentationModule extends InstrumentationModule {
|
||||||
public List<TypeInstrumentation> typeInstrumentations() {
|
public List<TypeInstrumentation> typeInstrumentations() {
|
||||||
return asList(
|
return asList(
|
||||||
new AsyncContextInstrumentation(BASE_PACKAGE, adviceClassName(".AsyncDispatchAdvice")),
|
new AsyncContextInstrumentation(BASE_PACKAGE, adviceClassName(".AsyncDispatchAdvice")),
|
||||||
new ServletAndFilterInstrumentation(BASE_PACKAGE, adviceClassName(".Servlet3Advice")));
|
new ServletAndFilterInstrumentation(
|
||||||
|
BASE_PACKAGE,
|
||||||
|
adviceClassName(".Servlet3Advice"),
|
||||||
|
adviceClassName(".Servlet3InitAdvice"),
|
||||||
|
adviceClassName(".Servlet3FilterInitAdvice")));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String adviceClassName(String suffix) {
|
private static String adviceClassName(String suffix) {
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.servlet.v3_0;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.api.servlet.MappingResolver;
|
||||||
|
import io.opentelemetry.instrumentation.servlet.naming.ServletMappingResolverFactory;
|
||||||
|
import io.opentelemetry.javaagent.instrumentation.api.ContextStore;
|
||||||
|
import java.util.Collection;
|
||||||
|
import javax.servlet.ServletConfig;
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletRegistration;
|
||||||
|
|
||||||
|
public class Servlet3MappingResolverFactory extends ServletMappingResolverFactory
|
||||||
|
implements ContextStore.Factory<MappingResolver> {
|
||||||
|
private final ServletConfig servletConfig;
|
||||||
|
|
||||||
|
public Servlet3MappingResolverFactory(ServletConfig servletConfig) {
|
||||||
|
this.servletConfig = servletConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> getMappings() {
|
||||||
|
String servletName = servletConfig.getServletName();
|
||||||
|
ServletContext servletContext = servletConfig.getServletContext();
|
||||||
|
if (servletName == null || servletContext == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServletRegistration servletRegistration = servletContext.getServletRegistration(servletName);
|
||||||
|
if (servletRegistration == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return servletRegistration.getMappings();
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ import okhttp3.RequestBody
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import spock.lang.Unroll
|
import spock.lang.Unroll
|
||||||
|
|
||||||
abstract class AbstractServletMappingTest<SERVER, CONTEXT> extends AgentInstrumentationSpecification implements HttpServerTestTrait<SERVER> {
|
abstract class AbstractServlet3MappingTest<SERVER, CONTEXT> extends AgentInstrumentationSpecification implements HttpServerTestTrait<SERVER> {
|
||||||
|
|
||||||
abstract void addServlet(CONTEXT context, String path, Class<Servlet> servlet)
|
abstract void addServlet(CONTEXT context, String path, Class<Servlet> servlet)
|
||||||
|
|
|
@ -11,7 +11,7 @@ import javax.servlet.http.HttpServletResponse
|
||||||
import org.eclipse.jetty.server.Server
|
import org.eclipse.jetty.server.Server
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler
|
import org.eclipse.jetty.servlet.ServletContextHandler
|
||||||
|
|
||||||
class JettyServletMappingTest extends AbstractServletMappingTest<Server, ServletContextHandler> {
|
class JettyServlet3MappingTest extends AbstractServlet3MappingTest<Server, ServletContextHandler> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Server startServer(int port) {
|
Server startServer(int port) {
|
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import javax.servlet.Filter
|
||||||
|
import javax.servlet.FilterChain
|
||||||
|
import javax.servlet.FilterConfig
|
||||||
|
import javax.servlet.ServletException
|
||||||
|
import javax.servlet.ServletRequest
|
||||||
|
import javax.servlet.ServletResponse
|
||||||
|
import javax.servlet.http.HttpServlet
|
||||||
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
import javax.servlet.http.HttpServletResponse
|
||||||
|
import org.apache.catalina.Context
|
||||||
|
import org.apache.catalina.startup.Tomcat
|
||||||
|
import org.apache.tomcat.util.descriptor.web.FilterDef
|
||||||
|
import org.apache.tomcat.util.descriptor.web.FilterMap
|
||||||
|
|
||||||
|
abstract class TomcatServlet3FilterMappingTest extends TomcatServlet3MappingTest {
|
||||||
|
|
||||||
|
void addFilter(Context servletContext, String path, Class<Filter> filter) {
|
||||||
|
String name = UUID.randomUUID()
|
||||||
|
FilterDef filterDef = new FilterDef()
|
||||||
|
filterDef.setFilter(filter.newInstance())
|
||||||
|
filterDef.setFilterName(name)
|
||||||
|
servletContext.addFilterDef(filterDef)
|
||||||
|
FilterMap filterMap = new FilterMap()
|
||||||
|
filterMap.setFilterName(name)
|
||||||
|
filterMap.addURLPattern(path)
|
||||||
|
servletContext.addFilterMap(filterMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
void addFilterWithServletName(Context servletContext, String servletName, Class<Filter> filter) {
|
||||||
|
String name = UUID.randomUUID()
|
||||||
|
FilterDef filterDef = new FilterDef()
|
||||||
|
filterDef.setFilter(filter.newInstance())
|
||||||
|
filterDef.setFilterName(name)
|
||||||
|
servletContext.addFilterDef(filterDef)
|
||||||
|
FilterMap filterMap = new FilterMap()
|
||||||
|
filterMap.setFilterName(name)
|
||||||
|
filterMap.addServletName(servletName)
|
||||||
|
servletContext.addFilterMap(filterMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TestFilter implements Filter {
|
||||||
|
@Override
|
||||||
|
void init(FilterConfig filterConfig) throws ServletException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||||
|
if (servletRequest.getAttribute("firstFilterCalled") != null) {
|
||||||
|
servletRequest.setAttribute("testFilterCalled", Boolean.TRUE)
|
||||||
|
filterChain.doFilter(servletRequest, servletResponse)
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("First filter should have been called.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void destroy() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class FirstFilter implements Filter {
|
||||||
|
@Override
|
||||||
|
void init(FilterConfig filterConfig) throws ServletException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||||
|
servletRequest.setAttribute("firstFilterCalled", Boolean.TRUE)
|
||||||
|
filterChain.doFilter(servletRequest, servletResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void destroy() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class LastFilter implements Filter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void init(FilterConfig filterConfig) throws ServletException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||||
|
if (servletRequest.getAttribute("testFilterCalled") != null) {
|
||||||
|
HttpServletResponse response = (HttpServletResponse) servletResponse
|
||||||
|
response.getWriter().write("Ok")
|
||||||
|
response.setStatus(HttpServletResponse.SC_OK)
|
||||||
|
} else {
|
||||||
|
filterChain.doFilter(servletRequest, servletResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void destroy() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class DefaultServlet extends HttpServlet {
|
||||||
|
protected void service(HttpServletRequest req, HttpServletResponse resp) {
|
||||||
|
throw new IllegalStateException("Servlet should not have been called, filter should have handled the request.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TomcatServlet3FilterUrlPatternMappingTest extends TomcatServlet3FilterMappingTest {
|
||||||
|
@Override
|
||||||
|
protected void setupServlets(Context context) {
|
||||||
|
addFilter(context, "/*", FirstFilter)
|
||||||
|
addFilter(context, "/prefix/*", TestFilter)
|
||||||
|
addFilter(context, "*.suffix", TestFilter)
|
||||||
|
addFilter(context, "/*", LastFilter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TomcatServlet3FilterServletNameMappingTest extends TomcatServlet3FilterMappingTest {
|
||||||
|
@Override
|
||||||
|
protected void setupServlets(Context context) {
|
||||||
|
Tomcat.addServlet(context, "prefix-servlet", new DefaultServlet())
|
||||||
|
context.addServletMappingDecoded("/prefix/*", "prefix-servlet")
|
||||||
|
Tomcat.addServlet(context, "suffix-servlet", new DefaultServlet())
|
||||||
|
context.addServletMappingDecoded("*.suffix", "suffix-servlet")
|
||||||
|
|
||||||
|
addFilter(context, "/*", FirstFilter)
|
||||||
|
addFilterWithServletName(context, "prefix-servlet", TestFilter)
|
||||||
|
addFilterWithServletName(context, "suffix-servlet", TestFilter)
|
||||||
|
addFilterWithServletName(context, "prefix-servlet", LastFilter)
|
||||||
|
addFilterWithServletName(context, "suffix-servlet", LastFilter)
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ import org.apache.catalina.startup.Tomcat
|
||||||
import org.apache.tomcat.JarScanFilter
|
import org.apache.tomcat.JarScanFilter
|
||||||
import org.apache.tomcat.JarScanType
|
import org.apache.tomcat.JarScanType
|
||||||
|
|
||||||
class TomcatServletMappingTest extends AbstractServletMappingTest<Tomcat, Context> {
|
class TomcatServlet3MappingTest extends AbstractServlet3MappingTest<Tomcat, Context> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Tomcat startServer(int port) {
|
Tomcat startServer(int port) {
|
|
@ -5,22 +5,21 @@
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.servlet.v3_0;
|
package io.opentelemetry.instrumentation.servlet.v3_0;
|
||||||
|
|
||||||
|
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.FILTER;
|
||||||
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.SERVLET;
|
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.SERVLET;
|
||||||
|
|
||||||
import io.opentelemetry.context.Context;
|
import io.opentelemetry.context.Context;
|
||||||
|
import io.opentelemetry.instrumentation.api.servlet.MappingResolver;
|
||||||
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
|
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
|
||||||
import io.opentelemetry.instrumentation.servlet.MappingResolver;
|
|
||||||
import io.opentelemetry.instrumentation.servlet.javax.JavaxServletHttpServerTracer;
|
import io.opentelemetry.instrumentation.servlet.javax.JavaxServletHttpServerTracer;
|
||||||
import java.util.Collection;
|
import io.opentelemetry.instrumentation.servlet.naming.ServletSpanNameProvider;
|
||||||
import javax.servlet.Servlet;
|
|
||||||
import javax.servlet.ServletConfig;
|
|
||||||
import javax.servlet.ServletContext;
|
|
||||||
import javax.servlet.ServletRegistration;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
public class Servlet3HttpServerTracer extends JavaxServletHttpServerTracer<HttpServletResponse> {
|
public class Servlet3HttpServerTracer extends JavaxServletHttpServerTracer<HttpServletResponse> {
|
||||||
private static final Servlet3HttpServerTracer TRACER = new Servlet3HttpServerTracer();
|
private static final Servlet3HttpServerTracer TRACER = new Servlet3HttpServerTracer();
|
||||||
|
private static final ServletSpanNameProvider<HttpServletRequest> SPAN_NAME_PROVIDER =
|
||||||
|
new ServletSpanNameProvider<>(Servlet3Accessor.INSTANCE);
|
||||||
|
|
||||||
protected Servlet3HttpServerTracer() {
|
protected Servlet3HttpServerTracer() {
|
||||||
super(Servlet3Accessor.INSTANCE);
|
super(Servlet3Accessor.INSTANCE);
|
||||||
|
@ -30,89 +29,20 @@ public class Servlet3HttpServerTracer extends JavaxServletHttpServerTracer<HttpS
|
||||||
return TRACER;
|
return TRACER;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Context startSpan(Object servletOrFilter, HttpServletRequest request) {
|
public Context startSpan(
|
||||||
return startSpan(
|
HttpServletRequest request, MappingResolver mappingResolver, boolean servlet) {
|
||||||
request, getSpanName(servletOrFilter, request), servletOrFilter instanceof Servlet);
|
return startSpan(request, SPAN_NAME_PROVIDER.getSpanName(mappingResolver, request), servlet);
|
||||||
}
|
|
||||||
|
|
||||||
private String getSpanName(Object servletOrFilter, HttpServletRequest request) {
|
|
||||||
String spanName = getSpanNameFromPath(servletOrFilter, request);
|
|
||||||
if (spanName == null) {
|
|
||||||
String contextPath = request.getContextPath();
|
|
||||||
if (contextPath == null || contextPath.isEmpty() || contextPath.equals("/")) {
|
|
||||||
return "HTTP " + request.getMethod();
|
|
||||||
}
|
|
||||||
return contextPath;
|
|
||||||
}
|
|
||||||
return spanName;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getSpanNameFromPath(Object servletOrFilter, HttpServletRequest request) {
|
|
||||||
// we are only interested in Servlets
|
|
||||||
if (!(servletOrFilter instanceof Servlet)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Servlet servlet = (Servlet) servletOrFilter;
|
|
||||||
|
|
||||||
String mapping = getMapping(servlet, request.getServletPath(), request.getPathInfo());
|
|
||||||
// mapping was not found
|
|
||||||
if (mapping == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepend context path
|
|
||||||
String contextPath = request.getContextPath();
|
|
||||||
if (contextPath == null || contextPath.isEmpty() || contextPath.equals("/")) {
|
|
||||||
return mapping;
|
|
||||||
}
|
|
||||||
return contextPath + mapping;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getMapping(Servlet servlet, String servletPath, String pathInfo) {
|
|
||||||
ServletConfig servletConfig = servlet.getServletConfig();
|
|
||||||
if (servletConfig == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String servletName = servletConfig.getServletName();
|
|
||||||
ServletContext servletContext = servletConfig.getServletContext();
|
|
||||||
MappingResolver mappingResolver = getMappingResolver(servletContext, servletName);
|
|
||||||
if (mappingResolver == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mappingResolver.resolve(servletPath, pathInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MappingResolver getMappingResolver(
|
|
||||||
ServletContext servletContext, String servletName) {
|
|
||||||
if (servletContext == null || servletName == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String key = MappingResolver.class.getName() + "." + servletName;
|
|
||||||
MappingResolver mappingResolver = (MappingResolver) servletContext.getAttribute(key);
|
|
||||||
if (mappingResolver != null) {
|
|
||||||
return mappingResolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
ServletRegistration servletRegistration = servletContext.getServletRegistration(servletName);
|
|
||||||
if (servletRegistration == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Collection<String> mappings = servletRegistration.getMappings();
|
|
||||||
if (mappings == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
mappingResolver = MappingResolver.build(mappings);
|
|
||||||
servletContext.setAttribute(key, mappingResolver);
|
|
||||||
|
|
||||||
return mappingResolver;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Context updateContext(
|
public Context updateContext(
|
||||||
Context context, Object servletOrFilter, HttpServletRequest request) {
|
Context context,
|
||||||
|
HttpServletRequest request,
|
||||||
|
MappingResolver mappingResolver,
|
||||||
|
boolean servlet) {
|
||||||
ServerSpanNaming.updateServerSpanName(
|
ServerSpanNaming.updateServerSpanName(
|
||||||
context, SERVLET, () -> getSpanNameFromPath(servletOrFilter, request));
|
context,
|
||||||
|
servlet ? SERVLET : FILTER,
|
||||||
|
() -> SPAN_NAME_PROVIDER.getSpanNameOrNull(mappingResolver, request));
|
||||||
return updateContext(context, request);
|
return updateContext(context, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,4 +13,9 @@ dependencies {
|
||||||
api(project(':instrumentation:servlet:servlet-5.0:library'))
|
api(project(':instrumentation:servlet:servlet-5.0:library'))
|
||||||
implementation(project(':instrumentation:servlet:servlet-common:javaagent'))
|
implementation(project(':instrumentation:servlet:servlet-common:javaagent'))
|
||||||
compileOnly group: 'jakarta.servlet', name: 'jakarta.servlet-api', version: '5.0.0'
|
compileOnly group: 'jakarta.servlet', name: 'jakarta.servlet-api', version: '5.0.0'
|
||||||
|
|
||||||
|
testLibrary group: 'org.eclipse.jetty', name: 'jetty-server', version: '11.0.0'
|
||||||
|
testLibrary group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '11.0.0'
|
||||||
|
testLibrary group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '10.0.0'
|
||||||
|
testLibrary group: 'org.apache.tomcat.embed', name: 'tomcat-embed-jasper', version: '10.0.0'
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,10 @@ public class JakartaServletInstrumentationModule extends InstrumentationModule {
|
||||||
new AsyncContextInstrumentation(
|
new AsyncContextInstrumentation(
|
||||||
BASE_PACKAGE, adviceClassName(".async.AsyncDispatchAdvice")),
|
BASE_PACKAGE, adviceClassName(".async.AsyncDispatchAdvice")),
|
||||||
new ServletAndFilterInstrumentation(
|
new ServletAndFilterInstrumentation(
|
||||||
BASE_PACKAGE, adviceClassName(".service.JakartaServletServiceAdvice")),
|
BASE_PACKAGE,
|
||||||
|
adviceClassName(".service.JakartaServletServiceAdvice"),
|
||||||
|
adviceClassName(".service.JakartaServletInitAdvice"),
|
||||||
|
adviceClassName(".service.JakartaServletFilterInitAdvice")),
|
||||||
new HttpServletResponseInstrumentation(
|
new HttpServletResponseInstrumentation(
|
||||||
BASE_PACKAGE, adviceClassName(".response.ResponseSendAdvice")),
|
BASE_PACKAGE, adviceClassName(".response.ResponseSendAdvice")),
|
||||||
new RequestDispatcherInstrumentation(
|
new RequestDispatcherInstrumentation(
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.servlet.v5_0.service;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.api.servlet.MappingResolver;
|
||||||
|
import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext;
|
||||||
|
import jakarta.servlet.Filter;
|
||||||
|
import jakarta.servlet.FilterConfig;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
|
||||||
|
public class JakartaServletFilterInitAdvice {
|
||||||
|
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static void filterInit(
|
||||||
|
@Advice.This Filter filter, @Advice.Argument(0) FilterConfig filterConfig) {
|
||||||
|
if (filterConfig == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InstrumentationContext.get(Filter.class, MappingResolver.class)
|
||||||
|
.putIfAbsent(filter, new JakartaServletFilterMappingResolverFactory(filterConfig));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.servlet.v5_0.service;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.api.servlet.MappingResolver;
|
||||||
|
import io.opentelemetry.instrumentation.servlet.naming.ServletFilterMappingResolverFactory;
|
||||||
|
import io.opentelemetry.javaagent.instrumentation.api.ContextStore;
|
||||||
|
import jakarta.servlet.FilterConfig;
|
||||||
|
import jakarta.servlet.FilterRegistration;
|
||||||
|
import jakarta.servlet.ServletContext;
|
||||||
|
import jakarta.servlet.ServletRegistration;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public class JakartaServletFilterMappingResolverFactory
|
||||||
|
extends ServletFilterMappingResolverFactory<FilterRegistration>
|
||||||
|
implements ContextStore.Factory<MappingResolver> {
|
||||||
|
private final FilterConfig filterConfig;
|
||||||
|
|
||||||
|
public JakartaServletFilterMappingResolverFactory(FilterConfig filterConfig) {
|
||||||
|
this.filterConfig = filterConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected FilterRegistration getFilterRegistration() {
|
||||||
|
String filterName = filterConfig.getFilterName();
|
||||||
|
ServletContext servletContext = filterConfig.getServletContext();
|
||||||
|
if (filterName == null || servletContext == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return servletContext.getFilterRegistration(filterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Collection<String> getUrlPatternMappings(FilterRegistration filterRegistration) {
|
||||||
|
return filterRegistration.getUrlPatternMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Collection<String> getServletNameMappings(FilterRegistration filterRegistration) {
|
||||||
|
return filterRegistration.getServletNameMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Collection<String> getServletMappings(String servletName) {
|
||||||
|
ServletRegistration servletRegistration =
|
||||||
|
filterConfig.getServletContext().getServletRegistration(servletName);
|
||||||
|
if (servletRegistration == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return servletRegistration.getMappings();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.servlet.v5_0.service;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.api.servlet.MappingResolver;
|
||||||
|
import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext;
|
||||||
|
import jakarta.servlet.Servlet;
|
||||||
|
import jakarta.servlet.ServletConfig;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
|
||||||
|
public class JakartaServletInitAdvice {
|
||||||
|
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static void servletInit(
|
||||||
|
@Advice.This Servlet servlet, @Advice.Argument(0) ServletConfig servletConfig) {
|
||||||
|
if (servletConfig == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InstrumentationContext.get(Servlet.class, MappingResolver.class)
|
||||||
|
.putIfAbsent(servlet, new JakartaServletMappingResolverFactory(servletConfig));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.servlet.v5_0.service;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.api.servlet.MappingResolver;
|
||||||
|
import io.opentelemetry.instrumentation.servlet.naming.ServletMappingResolverFactory;
|
||||||
|
import io.opentelemetry.javaagent.instrumentation.api.ContextStore;
|
||||||
|
import jakarta.servlet.ServletConfig;
|
||||||
|
import jakarta.servlet.ServletContext;
|
||||||
|
import jakarta.servlet.ServletRegistration;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public class JakartaServletMappingResolverFactory extends ServletMappingResolverFactory
|
||||||
|
implements ContextStore.Factory<MappingResolver> {
|
||||||
|
private final ServletConfig servletConfig;
|
||||||
|
|
||||||
|
public JakartaServletMappingResolverFactory(ServletConfig servletConfig) {
|
||||||
|
this.servletConfig = servletConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> getMappings() {
|
||||||
|
String servletName = servletConfig.getServletName();
|
||||||
|
ServletContext servletContext = servletConfig.getServletContext();
|
||||||
|
if (servletName == null || servletContext == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServletRegistration servletRegistration = servletContext.getServletRegistration(servletName);
|
||||||
|
if (servletRegistration == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return servletRegistration.getMappings();
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,9 +10,13 @@ import static io.opentelemetry.instrumentation.servlet.jakarta.v5_0.JakartaServl
|
||||||
import io.opentelemetry.context.Context;
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.context.Scope;
|
import io.opentelemetry.context.Scope;
|
||||||
import io.opentelemetry.instrumentation.api.servlet.AppServerBridge;
|
import io.opentelemetry.instrumentation.api.servlet.AppServerBridge;
|
||||||
|
import io.opentelemetry.instrumentation.api.servlet.MappingResolver;
|
||||||
import io.opentelemetry.javaagent.instrumentation.api.CallDepthThreadLocalMap;
|
import io.opentelemetry.javaagent.instrumentation.api.CallDepthThreadLocalMap;
|
||||||
|
import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext;
|
||||||
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
|
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
|
||||||
import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletAndFilterAdviceHelper;
|
import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletAndFilterAdviceHelper;
|
||||||
|
import jakarta.servlet.Filter;
|
||||||
|
import jakarta.servlet.Servlet;
|
||||||
import jakarta.servlet.ServletRequest;
|
import jakarta.servlet.ServletRequest;
|
||||||
import jakarta.servlet.ServletResponse;
|
import jakarta.servlet.ServletResponse;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
@ -37,12 +41,24 @@ public class JakartaServletServiceAdvice {
|
||||||
|
|
||||||
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||||
|
|
||||||
|
boolean servlet = servletOrFilter instanceof Servlet;
|
||||||
|
MappingResolver mappingResolver;
|
||||||
|
if (servlet) {
|
||||||
|
mappingResolver =
|
||||||
|
InstrumentationContext.get(Servlet.class, MappingResolver.class)
|
||||||
|
.get((Servlet) servletOrFilter);
|
||||||
|
} else {
|
||||||
|
mappingResolver =
|
||||||
|
InstrumentationContext.get(Filter.class, MappingResolver.class)
|
||||||
|
.get((Filter) servletOrFilter);
|
||||||
|
}
|
||||||
|
|
||||||
Context attachedContext = tracer().getServerContext(httpServletRequest);
|
Context attachedContext = tracer().getServerContext(httpServletRequest);
|
||||||
if (attachedContext != null) {
|
if (attachedContext != null) {
|
||||||
// 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
|
||||||
if (tracer().needsRescoping(attachedContext)) {
|
if (tracer().needsRescoping(attachedContext)) {
|
||||||
attachedContext =
|
attachedContext =
|
||||||
tracer().updateContext(attachedContext, servletOrFilter, httpServletRequest);
|
tracer().updateContext(attachedContext, httpServletRequest, mappingResolver, servlet);
|
||||||
scope = attachedContext.makeCurrent();
|
scope = attachedContext.makeCurrent();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -51,7 +67,7 @@ public class JakartaServletServiceAdvice {
|
||||||
// instrumentation, if needed update span with info from current request.
|
// instrumentation, if needed update span with info from current request.
|
||||||
Context currentContext = Java8BytecodeBridge.currentContext();
|
Context currentContext = Java8BytecodeBridge.currentContext();
|
||||||
Context updatedContext =
|
Context updatedContext =
|
||||||
tracer().updateContext(currentContext, servletOrFilter, httpServletRequest);
|
tracer().updateContext(currentContext, httpServletRequest, mappingResolver, servlet);
|
||||||
if (updatedContext != currentContext) {
|
if (updatedContext != currentContext) {
|
||||||
// runOnceUnderAppServer updated context, need to re-scope
|
// runOnceUnderAppServer updated context, need to re-scope
|
||||||
scope = updatedContext.makeCurrent();
|
scope = updatedContext.makeCurrent();
|
||||||
|
@ -66,7 +82,7 @@ public class JakartaServletServiceAdvice {
|
||||||
// In case it was created by app server integration we need to update it with info from
|
// In case it was created by app server integration we need to update it with info from
|
||||||
// current request.
|
// current request.
|
||||||
Context updatedContext =
|
Context updatedContext =
|
||||||
tracer().updateContext(currentContext, servletOrFilter, httpServletRequest);
|
tracer().updateContext(currentContext, httpServletRequest, mappingResolver, servlet);
|
||||||
if (currentContext != updatedContext) {
|
if (currentContext != updatedContext) {
|
||||||
// updateContext updated context, need to re-scope
|
// updateContext updated context, need to re-scope
|
||||||
scope = updatedContext.makeCurrent();
|
scope = updatedContext.makeCurrent();
|
||||||
|
@ -74,7 +90,7 @@ public class JakartaServletServiceAdvice {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context = tracer().startSpan(servletOrFilter, httpServletRequest);
|
context = tracer().startSpan(httpServletRequest, mappingResolver, servlet);
|
||||||
scope = context.makeCurrent();
|
scope = context.makeCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import static io.opentelemetry.api.trace.StatusCode.ERROR
|
||||||
|
|
||||||
|
import io.opentelemetry.api.trace.SpanKind
|
||||||
|
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
|
||||||
|
import io.opentelemetry.instrumentation.test.base.HttpServerTestTrait
|
||||||
|
import jakarta.servlet.Servlet
|
||||||
|
import jakarta.servlet.ServletException
|
||||||
|
import jakarta.servlet.http.HttpServlet
|
||||||
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import okhttp3.Response
|
||||||
|
import spock.lang.Unroll
|
||||||
|
|
||||||
|
abstract class AbstractServlet5MappingTest<SERVER, CONTEXT> extends AgentInstrumentationSpecification implements HttpServerTestTrait<SERVER> {
|
||||||
|
|
||||||
|
abstract void addServlet(CONTEXT context, String path, Class<Servlet> servlet)
|
||||||
|
|
||||||
|
protected void setupServlets(CONTEXT context) {
|
||||||
|
addServlet(context, "/prefix/*", TestServlet)
|
||||||
|
addServlet(context, "*.suffix", TestServlet)
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TestServlet extends HttpServlet {
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||||
|
response.getWriter().write("Ok")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Request.Builder request(HttpUrl url, String method, RequestBody body) {
|
||||||
|
return new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.method(method, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unroll
|
||||||
|
def "test path #path"() {
|
||||||
|
setup:
|
||||||
|
def url = HttpUrl.get(address.resolve(path)).newBuilder().build()
|
||||||
|
def request = request(url, "GET", null).build()
|
||||||
|
Response response = client.newCall(request).execute()
|
||||||
|
|
||||||
|
expect:
|
||||||
|
response.code() == success ? 200 : 404
|
||||||
|
|
||||||
|
and:
|
||||||
|
def spanCount = success ? 1 : 2
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, spanCount) {
|
||||||
|
span(0) {
|
||||||
|
name getContextPath() + spanName
|
||||||
|
kind SpanKind.SERVER
|
||||||
|
if (!success) {
|
||||||
|
status ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
span(1) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
where:
|
||||||
|
path | spanName | success
|
||||||
|
'prefix' | '/prefix/*' | true
|
||||||
|
'prefix/' | '/prefix/*' | true
|
||||||
|
'prefix/a' | '/prefix/*' | true
|
||||||
|
'prefixa' | '/*' | false
|
||||||
|
'a.suffix' | '/*.suffix' | true
|
||||||
|
'.suffix' | '/*.suffix' | true
|
||||||
|
'suffix' | '/*' | false
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import jakarta.servlet.Servlet
|
||||||
|
import jakarta.servlet.ServletException
|
||||||
|
import jakarta.servlet.http.HttpServlet
|
||||||
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
|
import org.eclipse.jetty.server.Server
|
||||||
|
import org.eclipse.jetty.servlet.ServletContextHandler
|
||||||
|
import spock.lang.IgnoreIf
|
||||||
|
|
||||||
|
@IgnoreIf({ !jvm.java11Compatible })
|
||||||
|
class JettyServlet5MappingTest extends AbstractServlet5MappingTest<Object, Object> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Object startServer(int port) {
|
||||||
|
Server server = new Server(port)
|
||||||
|
ServletContextHandler handler = new ServletContextHandler(null, contextPath)
|
||||||
|
setupServlets(handler)
|
||||||
|
server.setHandler(handler)
|
||||||
|
server.start()
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void stopServer(Object serverObject) {
|
||||||
|
Server server = (Server) serverObject
|
||||||
|
server.stop()
|
||||||
|
server.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setupServlets(Object handlerObject) {
|
||||||
|
ServletContextHandler handler = (ServletContextHandler) handlerObject
|
||||||
|
super.setupServlets(handler)
|
||||||
|
|
||||||
|
addServlet(handler, "/", DefaultServlet)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void addServlet(Object handlerObject, String path, Class<Servlet> servlet) {
|
||||||
|
ServletContextHandler handler = (ServletContextHandler) handlerObject
|
||||||
|
handler.addServlet(servlet, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
static class DefaultServlet extends HttpServlet {
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||||
|
response.sendError(404)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String getContextPath() {
|
||||||
|
"/jetty-context"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import jakarta.servlet.Filter
|
||||||
|
import jakarta.servlet.FilterChain
|
||||||
|
import jakarta.servlet.FilterConfig
|
||||||
|
import jakarta.servlet.ServletException
|
||||||
|
import jakarta.servlet.ServletRequest
|
||||||
|
import jakarta.servlet.ServletResponse
|
||||||
|
import jakarta.servlet.http.HttpServlet
|
||||||
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
|
import org.apache.catalina.Context
|
||||||
|
import org.apache.catalina.startup.Tomcat
|
||||||
|
import org.apache.tomcat.util.descriptor.web.FilterDef
|
||||||
|
import org.apache.tomcat.util.descriptor.web.FilterMap
|
||||||
|
|
||||||
|
abstract class TomcatServlet5FilterMappingTest extends TomcatServlet5MappingTest {
|
||||||
|
|
||||||
|
void addFilter(Context servletContext, String path, Class<Filter> filter) {
|
||||||
|
String name = UUID.randomUUID()
|
||||||
|
FilterDef filterDef = new FilterDef()
|
||||||
|
filterDef.setFilter(filter.newInstance())
|
||||||
|
filterDef.setFilterName(name)
|
||||||
|
servletContext.addFilterDef(filterDef)
|
||||||
|
FilterMap filterMap = new FilterMap()
|
||||||
|
filterMap.setFilterName(name)
|
||||||
|
filterMap.addURLPattern(path)
|
||||||
|
servletContext.addFilterMap(filterMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
void addFilterWithServletName(Context servletContext, String servletName, Class<Filter> filter) {
|
||||||
|
String name = UUID.randomUUID()
|
||||||
|
FilterDef filterDef = new FilterDef()
|
||||||
|
filterDef.setFilter(filter.newInstance())
|
||||||
|
filterDef.setFilterName(name)
|
||||||
|
servletContext.addFilterDef(filterDef)
|
||||||
|
FilterMap filterMap = new FilterMap()
|
||||||
|
filterMap.setFilterName(name)
|
||||||
|
filterMap.addServletName(servletName)
|
||||||
|
servletContext.addFilterMap(filterMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TestFilter implements Filter {
|
||||||
|
@Override
|
||||||
|
void init(FilterConfig filterConfig) throws ServletException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||||
|
if (servletRequest.getAttribute("firstFilterCalled") != null) {
|
||||||
|
servletRequest.setAttribute("testFilterCalled", Boolean.TRUE)
|
||||||
|
filterChain.doFilter(servletRequest, servletResponse)
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("First filter should have been called.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void destroy() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class FirstFilter implements Filter {
|
||||||
|
@Override
|
||||||
|
void init(FilterConfig filterConfig) throws ServletException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||||
|
servletRequest.setAttribute("firstFilterCalled", Boolean.TRUE)
|
||||||
|
filterChain.doFilter(servletRequest, servletResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void destroy() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class LastFilter implements Filter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void init(FilterConfig filterConfig) throws ServletException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||||
|
if (servletRequest.getAttribute("testFilterCalled") != null) {
|
||||||
|
HttpServletResponse response = (HttpServletResponse) servletResponse
|
||||||
|
response.getWriter().write("Ok")
|
||||||
|
response.setStatus(HttpServletResponse.SC_OK)
|
||||||
|
} else {
|
||||||
|
filterChain.doFilter(servletRequest, servletResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void destroy() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class DefaultServlet extends HttpServlet {
|
||||||
|
protected void service(HttpServletRequest req, HttpServletResponse resp) {
|
||||||
|
throw new IllegalStateException("Servlet should not have been called, filter should have handled the request.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TomcatServlet5FilterUrlPatternMappingTest extends TomcatServlet5FilterMappingTest {
|
||||||
|
@Override
|
||||||
|
protected void setupServlets(Context context) {
|
||||||
|
addFilter(context, "/*", FirstFilter)
|
||||||
|
addFilter(context, "/prefix/*", TestFilter)
|
||||||
|
addFilter(context, "*.suffix", TestFilter)
|
||||||
|
addFilter(context, "/*", LastFilter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TomcatServlet5FilterServletNameMappingTest extends TomcatServlet5FilterMappingTest {
|
||||||
|
@Override
|
||||||
|
protected void setupServlets(Context context) {
|
||||||
|
Tomcat.addServlet(context, "prefix-servlet", DefaultServlet.newInstance())
|
||||||
|
context.addServletMappingDecoded("/prefix/*", "prefix-servlet")
|
||||||
|
Tomcat.addServlet(context, "suffix-servlet", DefaultServlet.newInstance())
|
||||||
|
context.addServletMappingDecoded("*.suffix", "suffix-servlet")
|
||||||
|
|
||||||
|
addFilter(context, "/*", FirstFilter)
|
||||||
|
addFilterWithServletName(context, "prefix-servlet", TestFilter)
|
||||||
|
addFilterWithServletName(context, "suffix-servlet", TestFilter)
|
||||||
|
addFilterWithServletName(context, "prefix-servlet", LastFilter)
|
||||||
|
addFilterWithServletName(context, "suffix-servlet", LastFilter)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import jakarta.servlet.Servlet
|
||||||
|
import java.nio.file.Files
|
||||||
|
import org.apache.catalina.Context
|
||||||
|
import org.apache.catalina.startup.Tomcat
|
||||||
|
import org.apache.tomcat.JarScanFilter
|
||||||
|
import org.apache.tomcat.JarScanType
|
||||||
|
|
||||||
|
class TomcatServlet5MappingTest extends AbstractServlet5MappingTest<Tomcat, Context> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Tomcat startServer(int port) {
|
||||||
|
def tomcatServer = new Tomcat()
|
||||||
|
|
||||||
|
def baseDir = Files.createTempDirectory("tomcat").toFile()
|
||||||
|
baseDir.deleteOnExit()
|
||||||
|
tomcatServer.setBaseDir(baseDir.getAbsolutePath())
|
||||||
|
|
||||||
|
tomcatServer.setPort(port)
|
||||||
|
tomcatServer.getConnector().enableLookups = true // get localhost instead of 127.0.0.1
|
||||||
|
|
||||||
|
File applicationDir = new File(baseDir, "/webapps/ROOT")
|
||||||
|
if (!applicationDir.exists()) {
|
||||||
|
applicationDir.mkdirs()
|
||||||
|
applicationDir.deleteOnExit()
|
||||||
|
}
|
||||||
|
Context servletContext = tomcatServer.addWebapp(contextPath, applicationDir.getAbsolutePath())
|
||||||
|
// Speed up startup by disabling jar scanning:
|
||||||
|
servletContext.getJarScanner().setJarScanFilter(new JarScanFilter() {
|
||||||
|
@Override
|
||||||
|
boolean check(JarScanType jarScanType, String jarName) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
setupServlets(servletContext)
|
||||||
|
|
||||||
|
tomcatServer.start()
|
||||||
|
|
||||||
|
return tomcatServer
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void stopServer(Tomcat server) {
|
||||||
|
server.stop()
|
||||||
|
server.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void addServlet(Context servletContext, String path, Class<Servlet> servlet) {
|
||||||
|
String name = UUID.randomUUID()
|
||||||
|
Tomcat.addServlet(servletContext, name, servlet.newInstance())
|
||||||
|
servletContext.addServletMappingDecoded(path, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String getContextPath() {
|
||||||
|
return "/tomcat-context"
|
||||||
|
}
|
||||||
|
}
|
|
@ -85,6 +85,11 @@ public class JakartaServletAccessor
|
||||||
return request.getServletPath();
|
return request.getServletPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRequestPathInfo(HttpServletRequest request) {
|
||||||
|
return request.getPathInfo();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Principal getRequestUserPrincipal(HttpServletRequest request) {
|
public Principal getRequestUserPrincipal(HttpServletRequest request) {
|
||||||
return request.getUserPrincipal();
|
return request.getUserPrincipal();
|
||||||
|
|
|
@ -5,25 +5,24 @@
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.servlet.jakarta.v5_0;
|
package io.opentelemetry.instrumentation.servlet.jakarta.v5_0;
|
||||||
|
|
||||||
|
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.FILTER;
|
||||||
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.SERVLET;
|
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.SERVLET;
|
||||||
|
|
||||||
import io.opentelemetry.context.Context;
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.context.propagation.TextMapGetter;
|
import io.opentelemetry.context.propagation.TextMapGetter;
|
||||||
|
import io.opentelemetry.instrumentation.api.servlet.MappingResolver;
|
||||||
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
|
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
|
||||||
import io.opentelemetry.instrumentation.servlet.MappingResolver;
|
|
||||||
import io.opentelemetry.instrumentation.servlet.ServletHttpServerTracer;
|
import io.opentelemetry.instrumentation.servlet.ServletHttpServerTracer;
|
||||||
|
import io.opentelemetry.instrumentation.servlet.naming.ServletSpanNameProvider;
|
||||||
import jakarta.servlet.RequestDispatcher;
|
import jakarta.servlet.RequestDispatcher;
|
||||||
import jakarta.servlet.Servlet;
|
|
||||||
import jakarta.servlet.ServletConfig;
|
|
||||||
import jakarta.servlet.ServletContext;
|
|
||||||
import jakarta.servlet.ServletRegistration;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
public class JakartaServletHttpServerTracer
|
public class JakartaServletHttpServerTracer
|
||||||
extends ServletHttpServerTracer<HttpServletRequest, HttpServletResponse> {
|
extends ServletHttpServerTracer<HttpServletRequest, HttpServletResponse> {
|
||||||
private static final JakartaServletHttpServerTracer TRACER = new JakartaServletHttpServerTracer();
|
private static final JakartaServletHttpServerTracer TRACER = new JakartaServletHttpServerTracer();
|
||||||
|
private static final ServletSpanNameProvider<HttpServletRequest> SPAN_NAME_PROVIDER =
|
||||||
|
new ServletSpanNameProvider<>(JakartaServletAccessor.INSTANCE);
|
||||||
|
|
||||||
public JakartaServletHttpServerTracer() {
|
public JakartaServletHttpServerTracer() {
|
||||||
super(JakartaServletAccessor.INSTANCE);
|
super(JakartaServletAccessor.INSTANCE);
|
||||||
|
@ -33,89 +32,20 @@ public class JakartaServletHttpServerTracer
|
||||||
return TRACER;
|
return TRACER;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Context startSpan(Object servletOrFilter, HttpServletRequest request) {
|
public Context startSpan(
|
||||||
return startSpan(
|
HttpServletRequest request, MappingResolver mappingResolver, boolean servlet) {
|
||||||
request, getSpanName(servletOrFilter, request), servletOrFilter instanceof Servlet);
|
return startSpan(request, SPAN_NAME_PROVIDER.getSpanName(mappingResolver, request), servlet);
|
||||||
}
|
|
||||||
|
|
||||||
private String getSpanName(Object servletOrFilter, HttpServletRequest request) {
|
|
||||||
String spanName = getSpanNameFromPath(servletOrFilter, request);
|
|
||||||
if (spanName == null) {
|
|
||||||
String contextPath = request.getContextPath();
|
|
||||||
if (contextPath == null || contextPath.isEmpty() || contextPath.equals("/")) {
|
|
||||||
return "HTTP " + request.getMethod();
|
|
||||||
}
|
|
||||||
return contextPath;
|
|
||||||
}
|
|
||||||
return spanName;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getSpanNameFromPath(Object servletOrFilter, HttpServletRequest request) {
|
|
||||||
// we are only interested in Servlets
|
|
||||||
if (!(servletOrFilter instanceof Servlet)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Servlet servlet = (Servlet) servletOrFilter;
|
|
||||||
|
|
||||||
String mapping = getMapping(servlet, request.getServletPath(), request.getPathInfo());
|
|
||||||
// mapping was not found
|
|
||||||
if (mapping == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepend context path
|
|
||||||
String contextPath = request.getContextPath();
|
|
||||||
if (contextPath == null || contextPath.isEmpty() || contextPath.equals("/")) {
|
|
||||||
return mapping;
|
|
||||||
}
|
|
||||||
return contextPath + mapping;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getMapping(Servlet servlet, String servletPath, String pathInfo) {
|
|
||||||
ServletConfig servletConfig = servlet.getServletConfig();
|
|
||||||
if (servletConfig == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String servletName = servletConfig.getServletName();
|
|
||||||
ServletContext servletContext = servletConfig.getServletContext();
|
|
||||||
MappingResolver mappingResolver = getMappingResolver(servletContext, servletName);
|
|
||||||
if (mappingResolver == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mappingResolver.resolve(servletPath, pathInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MappingResolver getMappingResolver(
|
|
||||||
ServletContext servletContext, String servletName) {
|
|
||||||
if (servletContext == null || servletName == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String key = MappingResolver.class.getName() + "." + servletName;
|
|
||||||
MappingResolver mappingResolver = (MappingResolver) servletContext.getAttribute(key);
|
|
||||||
if (mappingResolver != null) {
|
|
||||||
return mappingResolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
ServletRegistration servletRegistration = servletContext.getServletRegistration(servletName);
|
|
||||||
if (servletRegistration == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Collection<String> mappings = servletRegistration.getMappings();
|
|
||||||
if (mappings == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
mappingResolver = MappingResolver.build(mappings);
|
|
||||||
servletContext.setAttribute(key, mappingResolver);
|
|
||||||
|
|
||||||
return mappingResolver;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Context updateContext(
|
public Context updateContext(
|
||||||
Context context, Object servletOrFilter, HttpServletRequest request) {
|
Context context,
|
||||||
|
HttpServletRequest request,
|
||||||
|
MappingResolver mappingResolver,
|
||||||
|
boolean servlet) {
|
||||||
ServerSpanNaming.updateServerSpanName(
|
ServerSpanNaming.updateServerSpanName(
|
||||||
context, SERVLET, () -> getSpanNameFromPath(servletOrFilter, request));
|
context,
|
||||||
|
servlet ? SERVLET : FILTER,
|
||||||
|
() -> SPAN_NAME_PROVIDER.getSpanNameOrNull(mappingResolver, request));
|
||||||
return updateContext(context, request);
|
return updateContext(context, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,12 @@ package io.opentelemetry.javaagent.instrumentation.servlet.common.service;
|
||||||
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.safeHasSuperType;
|
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.ClassLoaderMatcher.hasClassesNamed;
|
||||||
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf;
|
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf;
|
||||||
import static java.util.Collections.singletonMap;
|
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||||
|
|
||||||
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
|
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import net.bytebuddy.description.method.MethodDescription;
|
import net.bytebuddy.description.method.MethodDescription;
|
||||||
import net.bytebuddy.description.type.TypeDescription;
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
@ -22,10 +22,22 @@ import net.bytebuddy.matcher.ElementMatcher;
|
||||||
public class ServletAndFilterInstrumentation implements TypeInstrumentation {
|
public class ServletAndFilterInstrumentation implements TypeInstrumentation {
|
||||||
private final String basePackageName;
|
private final String basePackageName;
|
||||||
private final String adviceClassName;
|
private final String adviceClassName;
|
||||||
|
private final String servletInitAdviceClassName;
|
||||||
|
private final String filterInitAdviceClassName;
|
||||||
|
|
||||||
public ServletAndFilterInstrumentation(String basePackageName, String adviceClassName) {
|
public ServletAndFilterInstrumentation(
|
||||||
|
String basePackageName,
|
||||||
|
String adviceClassName,
|
||||||
|
String servletInitAdviceClassName,
|
||||||
|
String filterInitAdviceClassName) {
|
||||||
this.basePackageName = basePackageName;
|
this.basePackageName = basePackageName;
|
||||||
this.adviceClassName = adviceClassName;
|
this.adviceClassName = adviceClassName;
|
||||||
|
this.servletInitAdviceClassName = servletInitAdviceClassName;
|
||||||
|
this.filterInitAdviceClassName = filterInitAdviceClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServletAndFilterInstrumentation(String basePackageName, String adviceClassName) {
|
||||||
|
this(basePackageName, adviceClassName, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -40,11 +52,23 @@ public class ServletAndFilterInstrumentation implements TypeInstrumentation {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||||
return singletonMap(
|
Map<ElementMatcher<MethodDescription>, String> transformers = new HashMap<>();
|
||||||
|
transformers.put(
|
||||||
namedOneOf("doFilter", "service")
|
namedOneOf("doFilter", "service")
|
||||||
.and(takesArgument(0, named(basePackageName + ".ServletRequest")))
|
.and(takesArgument(0, named(basePackageName + ".ServletRequest")))
|
||||||
.and(takesArgument(1, named(basePackageName + ".ServletResponse")))
|
.and(takesArgument(1, named(basePackageName + ".ServletResponse")))
|
||||||
.and(isPublic()),
|
.and(isPublic()),
|
||||||
adviceClassName);
|
adviceClassName);
|
||||||
|
if (servletInitAdviceClassName != null) {
|
||||||
|
transformers.put(
|
||||||
|
named("init").and(takesArgument(0, named(basePackageName + ".ServletConfig"))),
|
||||||
|
servletInitAdviceClassName);
|
||||||
|
}
|
||||||
|
if (filterInitAdviceClassName != null) {
|
||||||
|
transformers.put(
|
||||||
|
named("init").and(takesArgument(0, named(basePackageName + ".FilterConfig"))),
|
||||||
|
filterInitAdviceClassName);
|
||||||
|
}
|
||||||
|
return transformers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,11 @@ package io.opentelemetry.instrumentation.servlet;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface is used to access methods of HttpServletRequest and HttpServletResponse classes in
|
* This interface is used to access methods of ServletContext, HttpServletRequest and
|
||||||
* shared code that is used for both jakarta.servlet and javax.servlet versions of those classes. A
|
* HttpServletResponse classes in shared code that is used for both jakarta.servlet and
|
||||||
* wrapper class with extra information attached may be used as well in cases where the class itself
|
* javax.servlet versions of those classes. A wrapper class with extra information attached may be
|
||||||
* does not provide some field (such as response status for Servlet API 2.2).
|
* used as well in cases where the class itself does not provide some field (such as response status
|
||||||
|
* for Servlet API 2.2).
|
||||||
*
|
*
|
||||||
* @param <REQUEST> HttpServletRequest class (or a wrapper)
|
* @param <REQUEST> HttpServletRequest class (or a wrapper)
|
||||||
* @param <RESPONSE> HttpServletResponse class (or a wrapper)
|
* @param <RESPONSE> HttpServletResponse class (or a wrapper)
|
||||||
|
@ -43,6 +44,8 @@ public interface ServletAccessor<REQUEST, RESPONSE> {
|
||||||
|
|
||||||
String getRequestServletPath(REQUEST request);
|
String getRequestServletPath(REQUEST request);
|
||||||
|
|
||||||
|
String getRequestPathInfo(REQUEST request);
|
||||||
|
|
||||||
Principal getRequestUserPrincipal(REQUEST request);
|
Principal getRequestUserPrincipal(REQUEST request);
|
||||||
|
|
||||||
Integer getRequestRemotePort(REQUEST request);
|
Integer getRequestRemotePort(REQUEST request);
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
package io.opentelemetry.instrumentation.servlet;
|
package io.opentelemetry.instrumentation.servlet;
|
||||||
|
|
||||||
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.CONTAINER;
|
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.CONTAINER;
|
||||||
|
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.FILTER;
|
||||||
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.SERVLET;
|
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.SERVLET;
|
||||||
|
|
||||||
import io.opentelemetry.api.trace.Span;
|
import io.opentelemetry.api.trace.Span;
|
||||||
|
@ -48,11 +49,10 @@ public abstract class ServletHttpServerTracer<REQUEST, RESPONSE>
|
||||||
accessor.setRequestAttribute(request, "trace_id", spanContext.getTraceId());
|
accessor.setRequestAttribute(request, "trace_id", spanContext.getTraceId());
|
||||||
accessor.setRequestAttribute(request, "span_id", spanContext.getSpanId());
|
accessor.setRequestAttribute(request, "span_id", spanContext.getSpanId());
|
||||||
|
|
||||||
if (servlet) {
|
// server span name shouldn't be updated when server span was created from a call to Servlet
|
||||||
// server span name shouldn't be updated when server span was created from a call to Servlet
|
// (if created from a call to Filter then name may be updated from updateContext)
|
||||||
// (if created from a call to Filter then name may be updated from updateContext)
|
ServerSpanNaming.updateSource(context, servlet ? SERVLET : FILTER);
|
||||||
ServerSpanNaming.updateSource(context, SERVLET);
|
|
||||||
}
|
|
||||||
return addServletContextPath(context, request);
|
return addServletContextPath(context, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.servlet.naming;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.api.servlet.MappingResolver;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public abstract class ServletFilterMappingResolverFactory<FILTERREGISTRATION> {
|
||||||
|
|
||||||
|
protected abstract FILTERREGISTRATION getFilterRegistration();
|
||||||
|
|
||||||
|
protected abstract Collection<String> getUrlPatternMappings(
|
||||||
|
FILTERREGISTRATION filterRegistration);
|
||||||
|
|
||||||
|
protected abstract Collection<String> getServletNameMappings(
|
||||||
|
FILTERREGISTRATION filterRegistration);
|
||||||
|
|
||||||
|
protected abstract Collection<String> getServletMappings(String servletName);
|
||||||
|
|
||||||
|
private Collection<String> getMappings() {
|
||||||
|
FILTERREGISTRATION filterRegistration = getFilterRegistration();
|
||||||
|
if (filterRegistration == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Set<String> mappings = new HashSet<>();
|
||||||
|
Collection<String> urlPatternMappings = getUrlPatternMappings(filterRegistration);
|
||||||
|
if (urlPatternMappings != null) {
|
||||||
|
mappings.addAll(urlPatternMappings);
|
||||||
|
}
|
||||||
|
Collection<String> servletNameMappings = getServletNameMappings(filterRegistration);
|
||||||
|
if (servletNameMappings != null) {
|
||||||
|
for (String servletName : servletNameMappings) {
|
||||||
|
Collection<String> servletMappings = getServletMappings(servletName);
|
||||||
|
if (servletMappings != null) {
|
||||||
|
mappings.addAll(servletMappings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mappings.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> mappingsList = new ArrayList<>(mappings);
|
||||||
|
// sort longest mapping first
|
||||||
|
Collections.sort(mappingsList, (s1, s2) -> s2.length() - s1.length());
|
||||||
|
|
||||||
|
return mappingsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final MappingResolver create() {
|
||||||
|
Collection<String> mappings = getMappings();
|
||||||
|
if (mappings == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MappingResolver.build(mappings);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.servlet.naming;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.api.servlet.MappingResolver;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public abstract class ServletMappingResolverFactory {
|
||||||
|
|
||||||
|
protected abstract Collection<String> getMappings();
|
||||||
|
|
||||||
|
public final MappingResolver create() {
|
||||||
|
Collection<String> mappings = getMappings();
|
||||||
|
if (mappings == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MappingResolver.build(mappings);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.servlet.naming;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.api.servlet.MappingResolver;
|
||||||
|
import io.opentelemetry.instrumentation.servlet.ServletAccessor;
|
||||||
|
|
||||||
|
/** Helper class for constructing span name for given servlet/filter mapping and request. */
|
||||||
|
public class ServletSpanNameProvider<REQUEST> {
|
||||||
|
private final ServletAccessor<REQUEST, ?> servletAccessor;
|
||||||
|
|
||||||
|
public ServletSpanNameProvider(ServletAccessor<REQUEST, ?> servletAccessor) {
|
||||||
|
this.servletAccessor = servletAccessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSpanName(MappingResolver mappingResolver, REQUEST request) {
|
||||||
|
String spanName = getSpanNameOrNull(mappingResolver, request);
|
||||||
|
if (spanName == null) {
|
||||||
|
String contextPath = servletAccessor.getRequestContextPath(request);
|
||||||
|
if (contextPath == null || contextPath.isEmpty() || contextPath.equals("/")) {
|
||||||
|
return "HTTP " + servletAccessor.getRequestMethod(request);
|
||||||
|
}
|
||||||
|
return contextPath + "/*";
|
||||||
|
}
|
||||||
|
return spanName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSpanNameOrNull(MappingResolver mappingResolver, REQUEST request) {
|
||||||
|
if (mappingResolver == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String servletPath = servletAccessor.getRequestServletPath(request);
|
||||||
|
String pathInfo = servletAccessor.getRequestPathInfo(request);
|
||||||
|
String mapping = mappingResolver.resolve(servletPath, pathInfo);
|
||||||
|
// mapping was not found
|
||||||
|
if (mapping == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepend context path
|
||||||
|
String contextPath = servletAccessor.getRequestContextPath(request);
|
||||||
|
if (contextPath == null || contextPath.isEmpty() || contextPath.equals("/")) {
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
return contextPath + mapping;
|
||||||
|
}
|
||||||
|
}
|
|
@ -76,6 +76,11 @@ public abstract class JavaxServletAccessor<R> implements ServletAccessor<HttpSer
|
||||||
return request.getServletPath();
|
return request.getServletPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRequestPathInfo(HttpServletRequest request) {
|
||||||
|
return request.getPathInfo();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Principal getRequestUserPrincipal(HttpServletRequest request) {
|
public Principal getRequestUserPrincipal(HttpServletRequest request) {
|
||||||
return request.getUserPrincipal();
|
return request.getUserPrincipal();
|
||||||
|
|
|
@ -87,7 +87,7 @@ class SpringBootBasedTest extends HttpServerTest<ConfigurableApplicationContext>
|
||||||
case NOT_FOUND:
|
case NOT_FOUND:
|
||||||
return getContextPath() + "/**"
|
return getContextPath() + "/**"
|
||||||
case LOGIN:
|
case LOGIN:
|
||||||
return "HTTP POST"
|
return getContextPath() + "/*"
|
||||||
default:
|
default:
|
||||||
return super.expectedServerSpanName(endpoint)
|
return super.expectedServerSpanName(endpoint)
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ class Struts2ActionSpanTest extends HttpServerTest<Server> implements AgentTestT
|
||||||
case PATH_PARAM:
|
case PATH_PARAM:
|
||||||
return getContextPath() + "/path/{id}/param"
|
return getContextPath() + "/path/{id}/param"
|
||||||
case NOT_FOUND:
|
case NOT_FOUND:
|
||||||
return "HTTP GET"
|
return getContextPath() + "/*"
|
||||||
default:
|
default:
|
||||||
return endpoint.resolvePath(address).path
|
return endpoint.resolvePath(address).path
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,4 +16,13 @@ class LibertyServletOnlySmokeTest extends LibertySmokeTest {
|
||||||
return ["liberty-servlet.xml": "/config/server.xml"]
|
return ["liberty-servlet.xml": "/config/server.xml"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getSpanName(String path) {
|
||||||
|
switch (path) {
|
||||||
|
case "/app/hello.txt":
|
||||||
|
case "/app/file-that-does-not-exist":
|
||||||
|
return "HTTP GET"
|
||||||
|
}
|
||||||
|
return super.getSpanName(path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,14 +21,4 @@ class LibertySmokeTest extends AppServerTest {
|
||||||
protected TargetWaitStrategy getWaitStrategy() {
|
protected TargetWaitStrategy getWaitStrategy() {
|
||||||
return new TargetWaitStrategy.Log(Duration.ofMinutes(3), ".*server is ready to run a smarter planet.*")
|
return new TargetWaitStrategy.Log(Duration.ofMinutes(3), ".*server is ready to run a smarter planet.*")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getSpanName(String path) {
|
|
||||||
switch (path) {
|
|
||||||
case "/app/hello.txt":
|
|
||||||
case "/app/file-that-does-not-exist":
|
|
||||||
return "HTTP GET"
|
|
||||||
}
|
|
||||||
return super.getSpanName(path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue