-
Notifications
You must be signed in to change notification settings - Fork 1k
Library instrumentation for Java Servlet 3.0 Filters. #15188
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
base: main
Are you sure you want to change the base?
Changes from 6 commits
cd80806
58773b5
4f0eb99
dea3d1c
a3e548b
2e0d691
4b6cd18
fc4f6b8
a34d722
6ce59fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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") | ||||||||
|
||||||||
| // copied from DbIncubatingAttributes | |
| private static final AttributeKey<String> DB_SYSTEM = AttributeKey.stringKey("db.system"); | |
| private static final AttributeKey<String> DB_STATEMENT = AttributeKey.stringKey("db.statement"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the help. Build passes now!
| 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); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
|
||
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
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.