Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
52 changes: 52 additions & 0 deletions instrumentation/servlet/servlet-3.0/library/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Library Instrumentation for Java Servlet version 3.0 and higher

Provides OpenTelemetry instrumentation for Java Servlets through a servlet filter.

## Quickstart

### Add these dependencies to your project

Replace `OPENTELEMETRY_VERSION` with
the [latest release](https://central.sonatype.com/artifact/io.opentelemetry.instrumentation/opentelemetry-lettuce-5.1).

For Maven, add to your `pom.xml` dependencies:

```xml

<dependencies>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-servlet-3.0</artifactId>
<version>OPENTELEMETRY_VERSION</version>
</dependency>
</dependencies>
```

For Gradle, add to your dependencies:

```kotlin
implementation("io.opentelemetry.instrumentation:opentelemetry-servlet-3.0:OPENTELEMETRY_VERSION")
```

### Usage

Add the filter to your `web.xml`

```xml
<web-app>
<filter>
<filter-name>OpenTelemetryServletFilter</filter-name>
<filter-class>io.opentelemetry.instrumentation.servlet.v3_0.OpenTelemetryServletFilter
</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>OpenTelemetryServletFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
```

Note: GlobalOpenTelemetry must be set before filter initialization. If you are unable to ensure it
is set first, consider creating a subclass of `OpenTelemetryServletFilter` that handles
initialization of GlobalOpenTelemetry in a static initializer or constructor.
37 changes: 37 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,37 @@
plugins {
id("otel.library-instrumentation")
}

dependencies {
compileOnly("javax.servlet:javax.servlet-api:3.0.1")

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")

latestDepTestLibrary("org.eclipse.jetty:jetty-server:10.+") // see servlet-5.0 module
latestDepTestLibrary("org.eclipse.jetty:jetty-servlet:10.+") // see servlet-5.0 module

latestDepTestLibrary("org.apache.tomcat.embed:tomcat-embed-core:9.+") // see servlet-5.0 module
latestDepTestLibrary("org.apache.tomcat.embed:tomcat-embed-jasper:9.+") // see servlet-5.0 module
}

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")
}
}

// Servlet 3.0 in latest Jetty versions requires Java 11
// However, projects that depend on this module are still be using Java 8 in testLatestDeps mode
// Therefore, we need a separate project for servlet 3.0 tests
val latestDepTest = findProperty("testLatestDeps") as Boolean

if (latestDepTest) {
otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_11)
}
}
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());
}
}
}
Loading
Loading