Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.servlet;
package io.opentelemetry.instrumentation.api.servlet;

import java.util.ArrayList;
import java.util.Collection;
Expand All @@ -12,7 +12,10 @@
import java.util.List;
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 List<WildcardMatcher> wildcardMatchers;
private final boolean hasDefault;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public static Context init(Context context, Source initialSource) {
}

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) {
this.updatedBySource = initialSource;
Expand Down Expand Up @@ -58,15 +61,24 @@ public static void updateServerSpanName(
}
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();
if (name != null) {
if (name != null && (!onlyIfBetterName || serverSpanNaming.isBetterName(name))) {
serverSpan.updateName(name);
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
// migrate to new Instrumenters (see
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/2814#discussion_r617351334
Expand All @@ -82,13 +94,22 @@ public static void updateSource(Context context, Source source) {

public enum Source {
CONTAINER(1),
SERVLET(2),
CONTROLLER(3);
// for servlet filters we try to find the best name which isn't necessarily from the first
// filter that is called
FILTER(2, false),
SERVLET(3),
CONTROLLER(4);

private final int order;
private final boolean useFirst;

Source(int order) {
this(order, true);
}

Source(int order, boolean useFirst) {
this.order = order;
this.useFirst = useFirst;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
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.InstrumentationContext;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletAndFilterAdviceHelper;
import javax.servlet.Filter;
import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
Expand All @@ -36,12 +40,24 @@ public static void onEnter(

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);
if (attachedContext != null) {
// We are inside nested servlet/filter/app-server span, don't create new span
if (tracer().needsRescoping(attachedContext)) {
attachedContext =
tracer().updateContext(attachedContext, servletOrFilter, httpServletRequest);
tracer().updateContext(attachedContext, httpServletRequest, mappingResolver, servlet);
scope = attachedContext.makeCurrent();
return;
}
Expand All @@ -50,7 +66,7 @@ public static void onEnter(
// instrumentation, if needed update span with info from current request.
Context currentContext = Java8BytecodeBridge.currentContext();
Context updatedContext =
tracer().updateContext(currentContext, servletOrFilter, httpServletRequest);
tracer().updateContext(currentContext, httpServletRequest, mappingResolver, servlet);
if (updatedContext != currentContext) {
// runOnceUnderAppServer updated context, need to re-scope
scope = updatedContext.makeCurrent();
Expand All @@ -65,15 +81,15 @@ public static void onEnter(
// In case it was created by app server integration we need to update it with info from
// current request.
Context updatedContext =
tracer().updateContext(currentContext, servletOrFilter, httpServletRequest);
tracer().updateContext(currentContext, httpServletRequest, mappingResolver, servlet);
if (currentContext != updatedContext) {
// updateContext updated context, need to re-scope
scope = updatedContext.makeCurrent();
}
return;
}

context = tracer().startSpan(servletOrFilter, httpServletRequest);
context = tracer().startSpan(httpServletRequest, mappingResolver, servlet);
scope = context.makeCurrent();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ public Servlet3InstrumentationModule() {
public List<TypeInstrumentation> typeInstrumentations() {
return asList(
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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, I hadn't noticed ContextStore.Factory before!

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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import okhttp3.RequestBody
import okhttp3.Response
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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import javax.servlet.http.HttpServletResponse
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.servlet.ServletContextHandler

class JettyServletMappingTest extends AbstractServletMappingTest<Server, ServletContextHandler> {
class JettyServlet3MappingTest extends AbstractServlet3MappingTest<Server, ServletContextHandler> {

@Override
Server startServer(int port) {
Expand Down
Loading