Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server span naming for servlet filters #2887

Merged
merged 11 commits into from
May 6, 2021
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,6 +12,9 @@
import java.util.List;
import java.util.Set;

/**
* Helper class for finding a mapping that matches current request from a collection of mappings.
*/
public class MappingResolver {
laurit marked this conversation as resolved.
Show resolved Hide resolved
private final Set<String> exactMatches;
private final List<WildcardMatcher> wildcardMatchers;
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,59 @@
/*
* 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() {
if (filterConfig == null) {
return null;
}
laurit marked this conversation as resolved.
Show resolved Hide resolved

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,41 @@
/*
* 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
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() {
if (servletConfig == null) {
return null;
}

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