Skip to content

Commit cd80806

Browse files
committed
Library instrumentation for Java Servlet 3.0 Filters.
Includes handling for attaching all the async stuff, but can't support everything the java agent does. I could use help figuring out the right way to shadow all the necessary dependencies. I'm a bit rusty with our shadow usage. Depending on how aggressive we want to be, the dependency could be reversed so that `:servlet-3.0:javaagent` depends on this new module, but that's more aggressive. Perhaps shadow could prune unused classes (with `minimize()`) instead. Once we get the modules figured out, I can work on adding some unit tests. If we like this approach, it could probably be copied and modified pretty easily to support servlet 5, but I'm not familiar with that instrumentation.
1 parent 25471f9 commit cd80806

File tree

5 files changed

+209
-0
lines changed

5 files changed

+209
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
plugins {
2+
id("otel.library-instrumentation")
3+
}
4+
5+
dependencies {
6+
library("javax.servlet:javax.servlet-api:3.0.1")
7+
8+
// FIXME: These dependencies need to be shadowed into the library.
9+
library(project(":instrumentation:servlet:servlet-3.0:javaagent"))
10+
library(project(":instrumentation:servlet:servlet-common:javaagent"))
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package io.opentelemetry.javaagent.instrumentation.servlet.v3_0;
2+
3+
import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper;
4+
5+
import io.opentelemetry.context.Context;
6+
import io.opentelemetry.context.Scope;
7+
import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext;
8+
import java.io.IOException;
9+
import javax.servlet.Filter;
10+
import javax.servlet.FilterChain;
11+
import javax.servlet.FilterConfig;
12+
import javax.servlet.ServletException;
13+
import javax.servlet.ServletRequest;
14+
import javax.servlet.ServletResponse;
15+
import javax.servlet.http.HttpServletRequest;
16+
import javax.servlet.http.HttpServletResponse;
17+
18+
/**
19+
* OpenTelemetry Library instrumentation for Java Servlet based applications that can't use a Java
20+
* Agent. Due to inherit limitations in the servlet filter API, instrumenting at the filter level
21+
* will miss anything that happens earlier in the filter stack or problems handled directly by the
22+
* app server. For this reason, Java Agent instrumentation is preferred when possible.
23+
*/
24+
public class OpenTelemetryServletFilter implements Filter {
25+
26+
@Override
27+
public void init(FilterConfig filterConfig) throws ServletException {}
28+
29+
@Override
30+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
31+
throws IOException, ServletException {
32+
// Only HttpServlets are supported.
33+
if (!(request instanceof HttpServletRequest && response instanceof HttpServletResponse)) {
34+
chain.doFilter(request, response);
35+
return;
36+
}
37+
38+
HttpServletRequest httpRequest = (HttpServletRequest) request;
39+
HttpServletResponse httpResponse = (HttpServletResponse) response;
40+
ServletRequestContext<HttpServletRequest> requestContext =
41+
new ServletRequestContext<>(httpRequest, this);
42+
43+
// Bail if we shouldn't start a new span.
44+
if (!helper().shouldStart(Context.current(), requestContext)) {
45+
chain.doFilter(request, response);
46+
return;
47+
}
48+
49+
Context spanContext = helper().start(Context.current(), requestContext);
50+
helper().setAsyncListenerResponse(spanContext, (HttpServletResponse) response);
51+
52+
// Not using try-with-resources to match the api usage of Servlet3Advice.
53+
// (helper().end is responsible for closing the scope.)
54+
Scope scope = spanContext.makeCurrent();
55+
Throwable throwable = null;
56+
try {
57+
chain.doFilter(
58+
new OtelHttpServletRequest((HttpServletRequest) request), response);
59+
} catch (Throwable e) {
60+
throwable = e;
61+
throw e;
62+
} finally {
63+
helper().end(requestContext, httpRequest, httpResponse, throwable, true, spanContext, scope);
64+
}
65+
}
66+
67+
@Override
68+
public void destroy() {}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package io.opentelemetry.javaagent.instrumentation.servlet.v3_0;
2+
3+
import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper;
4+
5+
import javax.servlet.AsyncContext;
6+
import javax.servlet.AsyncListener;
7+
import javax.servlet.ServletContext;
8+
import javax.servlet.ServletException;
9+
import javax.servlet.ServletRequest;
10+
import javax.servlet.ServletResponse;
11+
12+
/// Delegates all methods except [#start(Runnable) which wraps the [Runnable].
13+
public class OtelAsyncContext implements AsyncContext {
14+
private final AsyncContext delegate;
15+
16+
public OtelAsyncContext(AsyncContext delegate) {
17+
this.delegate = delegate;
18+
}
19+
20+
@Override
21+
public ServletRequest getRequest() {
22+
return delegate.getRequest();
23+
}
24+
25+
@Override
26+
public ServletResponse getResponse() {
27+
return delegate.getResponse();
28+
}
29+
30+
@Override
31+
public boolean hasOriginalRequestAndResponse() {
32+
return delegate.hasOriginalRequestAndResponse();
33+
}
34+
35+
@Override
36+
public void dispatch() {
37+
delegate.dispatch();
38+
}
39+
40+
@Override
41+
public void dispatch(String path) {
42+
delegate.dispatch(path);
43+
}
44+
45+
@Override
46+
public void dispatch(ServletContext context, String path) {
47+
delegate.dispatch(context, path);
48+
}
49+
50+
@Override
51+
public void complete() {
52+
delegate.complete();
53+
}
54+
55+
@Override
56+
public void start(Runnable run) {
57+
delegate.start(helper().wrapAsyncRunnable(run));
58+
}
59+
60+
@Override
61+
public void addListener(AsyncListener listener) {
62+
delegate.addListener(listener);
63+
}
64+
65+
@Override
66+
public void addListener(
67+
AsyncListener listener, ServletRequest servletRequest, ServletResponse servletResponse) {
68+
delegate.addListener(listener, servletRequest, servletResponse);
69+
}
70+
71+
@Override
72+
public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException {
73+
return delegate.createListener(clazz);
74+
}
75+
76+
@Override
77+
public void setTimeout(long timeout) {
78+
delegate.setTimeout(timeout);
79+
}
80+
81+
@Override
82+
public long getTimeout() {
83+
return delegate.getTimeout();
84+
}
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package io.opentelemetry.javaagent.instrumentation.servlet.v3_0;
2+
3+
import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper;
4+
5+
import io.opentelemetry.context.Context;
6+
import javax.servlet.AsyncContext;
7+
import javax.servlet.ServletRequest;
8+
import javax.servlet.ServletResponse;
9+
import javax.servlet.http.HttpServletRequest;
10+
import javax.servlet.http.HttpServletRequestWrapper;
11+
12+
/// Wrapper around [HttpServletRequest] that attaches an async listener if [#startAsync()] is
13+
/// invoked and a wrapper around [#getAsyncContext()] to capture exceptions from async [Runnable]s.
14+
public class OtelHttpServletRequest extends HttpServletRequestWrapper {
15+
16+
public OtelHttpServletRequest(HttpServletRequest request) {
17+
super(request);
18+
}
19+
20+
@Override
21+
public AsyncContext getAsyncContext() {
22+
return new OtelAsyncContext(super.getAsyncContext());
23+
}
24+
25+
@Override
26+
public AsyncContext startAsync() throws IllegalStateException {
27+
try {
28+
return super.startAsync();
29+
} finally {
30+
helper().attachAsyncListener(this, Context.current());
31+
}
32+
}
33+
34+
@Override
35+
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse)
36+
throws IllegalStateException {
37+
try {
38+
return super.startAsync(servletRequest, servletResponse);
39+
} finally {
40+
helper().attachAsyncListener(this, Context.current());
41+
}
42+
}
43+
}

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ include(":instrumentation:scala-fork-join-2.8:javaagent")
590590
include(":instrumentation:servlet:servlet-2.2:javaagent")
591591
include(":instrumentation:servlet:servlet-3.0:javaagent")
592592
include(":instrumentation:servlet:servlet-3.0:javaagent-unit-tests")
593+
include(":instrumentation:servlet:servlet-3.0:library")
593594
include(":instrumentation:servlet:servlet-3.0:testing")
594595
include(":instrumentation:servlet:servlet-5.0:javaagent")
595596
include(":instrumentation:servlet:servlet-5.0:javaagent-unit-tests")

0 commit comments

Comments
 (0)