Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .fossa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,9 @@ targets:
- type: gradle
path: ./
target: ':instrumentation:servlet:servlet-3.0:javaagent'
- type: gradle
path: ./
target: ':instrumentation:servlet:servlet-3.0:library'
- type: gradle
path: ./
target: ':instrumentation:servlet:servlet-5.0:javaagent'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

package io.opentelemetry.javaagent.instrumentation.servlet.v3_0;

import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.FILTER_MAPPING_RESOLVER_FACTORY;
import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.FILTER_MAPPING_RESOLVER;

import javax.servlet.Filter;
import javax.servlet.FilterConfig;
Expand All @@ -20,7 +20,6 @@ public static void filterInit(
if (filterConfig == null) {
return;
}
FILTER_MAPPING_RESOLVER_FACTORY.set(
filter, new Servlet3FilterMappingResolverFactory(filterConfig));
FILTER_MAPPING_RESOLVER.set(filter, new Servlet3FilterMappingResolverFactory(filterConfig));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

package io.opentelemetry.javaagent.instrumentation.servlet.v3_0;

import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.SERVLET_MAPPING_RESOLVER_FACTORY;
import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.SERVLET_MAPPING_RESOLVER;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
Expand All @@ -20,7 +20,6 @@ public static void servletInit(
if (servletConfig == null) {
return;
}
SERVLET_MAPPING_RESOLVER_FACTORY.set(
servlet, new Servlet3MappingResolverFactory(servletConfig));
SERVLET_MAPPING_RESOLVER.set(servlet, new Servlet3MappingResolverFactory(servletConfig));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,6 @@
public final class Servlet3Singletons {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.servlet-3.0";

public static final VirtualField<Servlet, MappingResolver.Factory>
SERVLET_MAPPING_RESOLVER_FACTORY =
VirtualField.find(Servlet.class, MappingResolver.Factory.class);

public static final VirtualField<Filter, MappingResolver.Factory>
FILTER_MAPPING_RESOLVER_FACTORY =
VirtualField.find(Filter.class, MappingResolver.Factory.class);
Copy link
Member Author

Choose a reason for hiding this comment

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

This change isn't necessary, but it seems odd that these are duplicated. I combined them, but if it's intentional I can revert this change.


private static final Instrumenter<
ServletRequestContext<HttpServletRequest>, ServletResponseContext<HttpServletResponse>>
INSTRUMENTER =
Expand All @@ -41,9 +33,9 @@ public final class Servlet3Singletons {
private static final ServletHelper<HttpServletRequest, HttpServletResponse> HELPER =
new ServletHelper<>(INSTRUMENTER, Servlet3Accessor.INSTANCE);

private static final VirtualField<Servlet, MappingResolver.Factory> SERVLET_MAPPING_RESOLVER =
public static final VirtualField<Servlet, MappingResolver.Factory> SERVLET_MAPPING_RESOLVER =
VirtualField.find(Servlet.class, MappingResolver.Factory.class);
private static final VirtualField<Filter, MappingResolver.Factory> FILTER_MAPPING_RESOLVER =
public static final VirtualField<Filter, MappingResolver.Factory> FILTER_MAPPING_RESOLVER =
VirtualField.find(Filter.class, MappingResolver.Factory.class);

private static final Instrumenter<ClassAndMethod, Void> RESPONSE_INSTRUMENTER =
Expand Down
21 changes: 21 additions & 0 deletions instrumentation/servlet/servlet-3.0/library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
plugins {
id("otel.library-instrumentation")
}

dependencies {
library("javax.servlet:javax.servlet-api:3.0.1")
library("io.opentelemetry.semconv:opentelemetry-semconv-incubating")

testLibrary("org.eclipse.jetty:jetty-server:8.0.0.v20110901")
testLibrary("org.eclipse.jetty:jetty-servlet:8.0.0.v20110901")
testLibrary("org.apache.tomcat.embed:tomcat-embed-core:8.0.41")
testLibrary("org.apache.tomcat.embed:tomcat-embed-jasper:8.0.41")
}

tasks {
withType<Test>().configureEach {
// required on jdk17+ to allow tomcat to shutdown properly.
jvmArgs("--add-opens=java.base/java.util=ALL-UNNAMED")
jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.servlet.v3_0;

import static io.opentelemetry.instrumentation.servlet.v3_0.copied.Servlet3Singletons.FILTER_MAPPING_RESOLVER;

import io.opentelemetry.instrumentation.servlet.v3_0.copied.CallDepth;
import io.opentelemetry.instrumentation.servlet.v3_0.copied.Servlet3FilterMappingResolverFactory;
import io.opentelemetry.instrumentation.servlet.v3_0.copied.Servlet3RequestAdviceScope;
import java.io.IOException;
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.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* OpenTelemetry Library instrumentation for Java Servlet based applications that can't use a Java
* Agent. Due to inherit limitations in the servlet filter API, instrumenting at the filter level
* will miss anything that happens earlier in the filter stack or problems handled directly by the
* app server. For this reason, Java Agent instrumentation is preferred when possible.
*/
@WebFilter("/*")
public class OpenTelemetryServletFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) {
FILTER_MAPPING_RESOLVER.set(this, new Servlet3FilterMappingResolverFactory(filterConfig));
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// Only HttpServlets are supported.
if (!(request instanceof HttpServletRequest && response instanceof HttpServletResponse)) {
chain.doFilter(request, response);
return;
}

HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;

Throwable throwable = null;
Servlet3RequestAdviceScope adviceScope =
new Servlet3RequestAdviceScope(
CallDepth.forClass(OpenTelemetryServletFilter.class), httpRequest, httpResponse, this);
Copy link
Member Author

Choose a reason for hiding this comment

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

I tried to keep the instrumentation as similar as possible to the javaagent instrumentation.

try {
chain.doFilter(
new OtelHttpServletRequest(httpRequest), new OtelHttpServletResponse(httpResponse));
} catch (Throwable e) {
throwable = e;
throw e;
} finally {
adviceScope.exit(throwable, httpRequest, httpResponse);
}
}

@Override
public void destroy() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.servlet.v3_0;

import static io.opentelemetry.instrumentation.servlet.v3_0.copied.Servlet3Singletons.helper;

import javax.servlet.AsyncContext;
import javax.servlet.AsyncListener;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/// Delegates all methods except [#start(Runnable) which wraps the [Runnable].
public class OtelAsyncContext implements AsyncContext {
private final AsyncContext delegate;

public OtelAsyncContext(AsyncContext delegate) {
this.delegate = delegate;
}

@Override
public ServletRequest getRequest() {
return delegate.getRequest();
}

@Override
public ServletResponse getResponse() {
return delegate.getResponse();
}

@Override
public boolean hasOriginalRequestAndResponse() {
return delegate.hasOriginalRequestAndResponse();
}

@Override
public void dispatch() {
delegate.dispatch();
}

@Override
public void dispatch(String path) {
delegate.dispatch(path);
}

@Override
public void dispatch(ServletContext context, String path) {
delegate.dispatch(context, path);
}

@Override
public void complete() {
delegate.complete();
}

@Override
public void start(Runnable run) {
delegate.start(helper().wrapAsyncRunnable(run));
}

@Override
public void addListener(AsyncListener listener) {
delegate.addListener(listener);
}

@Override
public void addListener(
AsyncListener listener, ServletRequest servletRequest, ServletResponse servletResponse) {
delegate.addListener(listener, servletRequest, servletResponse);
}

@Override
public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException {
return delegate.createListener(clazz);
}

@Override
public void setTimeout(long timeout) {
delegate.setTimeout(timeout);
}

@Override
public long getTimeout() {
return delegate.getTimeout();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.servlet.v3_0;

import static io.opentelemetry.instrumentation.servlet.v3_0.copied.Servlet3Singletons.helper;

import io.opentelemetry.context.Context;
import javax.servlet.AsyncContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/// Wrapper around [HttpServletRequest] that attaches an async listener if [#startAsync()] is
/// invoked and a wrapper around [#getAsyncContext()] to capture exceptions from async [Runnable]s.
public class OtelHttpServletRequest extends HttpServletRequestWrapper {

public OtelHttpServletRequest(HttpServletRequest request) {
super(request);
}

@Override
public AsyncContext getAsyncContext() {
return new OtelAsyncContext(super.getAsyncContext());
}

@Override
public AsyncContext startAsync() {
try {
return new OtelAsyncContext(super.startAsync());
} finally {
helper().attachAsyncListener(this, Context.current());
}
}

@Override
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) {
try {
return new OtelAsyncContext(super.startAsync(servletRequest, servletResponse));
} finally {
helper().attachAsyncListener(this, Context.current());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.servlet.v3_0;

import io.opentelemetry.instrumentation.servlet.v3_0.copied.CallDepth;
import io.opentelemetry.instrumentation.servlet.v3_0.copied.Servlet3ResponseAdviceScope;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

/// Wrapper around [HttpServletResponse].
public class OtelHttpServletResponse extends HttpServletResponseWrapper {

public OtelHttpServletResponse(HttpServletResponse response) {
super(response);
}

@Override
public void sendError(int sc, String msg) throws IOException {
Servlet3ResponseAdviceScope scope =
new Servlet3ResponseAdviceScope(
CallDepth.forClass(HttpServletResponse.class), this.getClass(), "sendError");
Throwable throwable = null;
try {
super.sendError(sc, msg);
} catch (Throwable ex) {
throwable = ex;
throw ex;
} finally {
scope.exit(throwable);
}
}

@Override
public void sendError(int sc) throws IOException {
Servlet3ResponseAdviceScope scope =
new Servlet3ResponseAdviceScope(
CallDepth.forClass(HttpServletResponse.class), this.getClass(), "sendError");
Throwable throwable = null;
try {
super.sendError(sc);
} catch (Throwable ex) {
throwable = ex;
throw ex;
} finally {
scope.exit(throwable);
}
}

@Override
public void sendRedirect(String location) throws IOException {
Servlet3ResponseAdviceScope scope =
new Servlet3ResponseAdviceScope(
CallDepth.forClass(HttpServletResponse.class), this.getClass(), "sendRedirect");
Throwable throwable = null;
try {
super.sendRedirect(location);
} catch (Throwable ex) {
throwable = ex;
throw ex;
} finally {
scope.exit(throwable);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.servlet.v3_0.copied;
Copy link
Member

Choose a reason for hiding this comment

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

i'm wondering about the value / confusion tradeoff with having "copied" in the package name. I understand the motivation, but wondering if it will be obvious to someone stumbling across it. I don't have any better ideas at this time though

Copy link
Member Author

Choose a reason for hiding this comment

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

This is why I originally suggested using shadow/shade to generate the jar and avoid copying all this over. I personally like the distinction between what is copied over vs specific to this library. Open to better ideas.


import io.opentelemetry.instrumentation.api.incubator.config.internal.CommonConfig;

/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public class AgentCommonConfig {
private AgentCommonConfig() {}

private static final CommonConfig instance = new CommonConfig(AgentInstrumentationConfig.get());

public static CommonConfig get() {
return instance;
}
}
Loading
Loading