-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add instrumentation for servlet-5.0 (#404)
- Loading branch information
1 parent
281b70f
commit cbf3c2f
Showing
26 changed files
with
2,943 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
plugins { | ||
`java-library` | ||
id("net.bytebuddy.byte-buddy") | ||
id("io.opentelemetry.instrumentation.auto-instrumentation") | ||
muzzle | ||
} | ||
evaluationDependsOn(":javaagent-tooling") | ||
|
||
muzzle { | ||
pass { | ||
group = "jakarta.servlet" | ||
module = "jakarta.servlet-api" | ||
versions = "[5.0.0,)" | ||
} | ||
} | ||
|
||
afterEvaluate{ | ||
io.opentelemetry.instrumentation.gradle.bytebuddy.ByteBuddyPluginConfigurator(project, | ||
sourceSets.main.get(), | ||
io.opentelemetry.javaagent.tooling.muzzle.generation.MuzzleCodeGenerationPlugin::class.java.name, | ||
files(project(":javaagent-tooling").configurations["instrumentationMuzzle"], configurations.runtimeClasspath) | ||
).configure() | ||
} | ||
|
||
val versions: Map<String, String> by extra | ||
|
||
dependencies { | ||
implementation("io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-servlet-common:${versions["opentelemetry_java_agent"]}") | ||
implementation("io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-servlet-5.0:${versions["opentelemetry_java_agent"]}") // Servlet5Accessor | ||
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-bootstrap:${versions["opentelemetry_java_agent"]}") | ||
compileOnly("jakarta.servlet:jakarta.servlet-api:5.0.0") | ||
muzzleBootstrap("io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-servlet-common-bootstrap:${versions["opentelemetry_java_agent"]}") | ||
|
||
testImplementation(project(":testing-common", "shadow")) | ||
testCompileOnly("com.squareup.okhttp3:okhttp:4.9.0") | ||
testImplementation("org.eclipse.jetty:jetty-server:11.0.0") | ||
testImplementation("org.eclipse.jetty:jetty-servlet:11.0.0") | ||
} |
239 changes: 239 additions & 0 deletions
239
.../javaagent/instrumentation/hypertrace/servlet/v5_0/Servlet50AndFilterInstrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
/* | ||
* Copyright The Hypertrace Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.hypertrace.servlet.v5_0; | ||
|
||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; | ||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType; | ||
import static net.bytebuddy.matcher.ElementMatchers.isPublic; | ||
import static net.bytebuddy.matcher.ElementMatchers.named; | ||
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; | ||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument; | ||
|
||
import io.opentelemetry.api.common.AttributeKey; | ||
import io.opentelemetry.api.trace.Span; | ||
import io.opentelemetry.instrumentation.api.util.VirtualField; | ||
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; | ||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; | ||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; | ||
import jakarta.servlet.ServletInputStream; | ||
import jakarta.servlet.ServletOutputStream; | ||
import jakarta.servlet.ServletRequest; | ||
import jakarta.servlet.ServletResponse; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.io.PrintWriter; | ||
import java.util.Collections; | ||
import java.util.Enumeration; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import net.bytebuddy.asm.Advice; | ||
import net.bytebuddy.description.type.TypeDescription; | ||
import net.bytebuddy.matcher.ElementMatcher; | ||
import org.hypertrace.agent.core.config.InstrumentationConfig; | ||
import org.hypertrace.agent.core.filter.FilterResult; | ||
import org.hypertrace.agent.core.instrumentation.HypertraceCallDepthThreadLocalMap; | ||
import org.hypertrace.agent.core.instrumentation.HypertraceEvaluationException; | ||
import org.hypertrace.agent.core.instrumentation.HypertraceSemanticAttributes; | ||
import org.hypertrace.agent.core.instrumentation.SpanAndObjectPair; | ||
import org.hypertrace.agent.core.instrumentation.buffer.BoundedByteArrayOutputStream; | ||
import org.hypertrace.agent.core.instrumentation.buffer.BoundedCharArrayWriter; | ||
import org.hypertrace.agent.core.instrumentation.buffer.ByteBufferSpanPair; | ||
import org.hypertrace.agent.core.instrumentation.buffer.CharBufferSpanPair; | ||
import org.hypertrace.agent.core.instrumentation.buffer.StringMapSpanPair; | ||
import org.hypertrace.agent.core.instrumentation.utils.ContentTypeUtils; | ||
import org.hypertrace.agent.filter.FilterRegistry; | ||
|
||
public class Servlet50AndFilterInstrumentation implements TypeInstrumentation { | ||
|
||
@Override | ||
public ElementMatcher<ClassLoader> classLoaderOptimization() { | ||
return hasClassesNamed("jakarta.servlet.Filter"); | ||
} | ||
|
||
@Override | ||
public ElementMatcher<TypeDescription> typeMatcher() { | ||
return hasSuperType(namedOneOf("jakarta.servlet.Filter", "jakarta.servlet.Servlet")); | ||
} | ||
|
||
@Override | ||
public void transform(TypeTransformer transformer) { | ||
transformer.applyAdviceToMethod( | ||
namedOneOf("doFilter", "service") | ||
.and(takesArgument(0, named("jakarta.servlet.ServletRequest"))) | ||
.and(takesArgument(1, named("jakarta.servlet.ServletResponse"))) | ||
.and(isPublic()), | ||
Servlet50AndFilterInstrumentation.class.getName() + "$ServletAdvice"); | ||
} | ||
|
||
public static class ServletAdvice { | ||
|
||
@Advice.OnMethodEnter(suppress = Throwable.class, skipOn = Advice.OnNonDefaultValue.class) | ||
public static boolean start( | ||
@Advice.Argument(value = 0) ServletRequest request, | ||
@Advice.Argument(value = 1) ServletResponse response, | ||
@Advice.Local("currentSpan") Span currentSpan) { | ||
|
||
int callDepth = | ||
HypertraceCallDepthThreadLocalMap.incrementCallDepth(Servlet50InstrumentationName.class); | ||
if (callDepth > 0) { | ||
return false; | ||
} | ||
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { | ||
return false; | ||
} | ||
|
||
HttpServletRequest httpRequest = (HttpServletRequest) request; | ||
HttpServletResponse httpResponse = (HttpServletResponse) response; | ||
currentSpan = Java8BytecodeBridge.currentSpan(); | ||
|
||
InstrumentationConfig instrumentationConfig = InstrumentationConfig.ConfigProvider.get(); | ||
|
||
Utils.addSessionId(currentSpan, httpRequest); | ||
|
||
// set request headers | ||
Enumeration<String> headerNames = httpRequest.getHeaderNames(); | ||
Map<String, String> headers = new HashMap<>(); | ||
while (headerNames.hasMoreElements()) { | ||
String headerName = headerNames.nextElement(); | ||
String headerValue = httpRequest.getHeader(headerName); | ||
AttributeKey<String> attributeKey = | ||
HypertraceSemanticAttributes.httpRequestHeader(headerName); | ||
|
||
if (instrumentationConfig.httpHeaders().request()) { | ||
currentSpan.setAttribute(attributeKey, headerValue); | ||
} | ||
headers.put(attributeKey.getKey(), headerValue); | ||
} | ||
|
||
FilterResult filterResult = | ||
FilterRegistry.getFilter().evaluateRequestHeaders(currentSpan, headers); | ||
if (filterResult.shouldBlock()) { | ||
try { | ||
httpResponse.getWriter().write(filterResult.getBlockingMsg()); | ||
} catch (IOException ignored) { | ||
} | ||
httpResponse.setStatus(filterResult.getBlockingStatusCode()); | ||
// skip execution of the user code | ||
return true; | ||
} | ||
|
||
if (instrumentationConfig.httpBody().request() | ||
&& ContentTypeUtils.shouldCapture(httpRequest.getContentType())) { | ||
// The HttpServletRequest instrumentation uses this to | ||
// enable the instrumentation | ||
VirtualField.find(HttpServletRequest.class, SpanAndObjectPair.class) | ||
.set( | ||
httpRequest, | ||
new SpanAndObjectPair(currentSpan, Collections.unmodifiableMap(headers))); | ||
} | ||
return false; | ||
} | ||
|
||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) | ||
public static void exit( | ||
@Advice.Argument(0) ServletRequest request, | ||
@Advice.Argument(1) ServletResponse response, | ||
@Advice.Thrown(readOnly = false) Throwable throwable, | ||
@Advice.Local("currentSpan") Span currentSpan) { | ||
int callDepth = | ||
HypertraceCallDepthThreadLocalMap.decrementCallDepth(Servlet50InstrumentationName.class); | ||
if (callDepth > 0) { | ||
return; | ||
} | ||
// we are in the most outermost level of Servlet instrumentation | ||
|
||
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { | ||
return; | ||
} | ||
|
||
HttpServletResponse httpResponse = (HttpServletResponse) response; | ||
HttpServletRequest httpRequest = (HttpServletRequest) request; | ||
InstrumentationConfig instrumentationConfig = InstrumentationConfig.ConfigProvider.get(); | ||
|
||
try { | ||
// response context to capture body and clear the context | ||
VirtualField<HttpServletResponse, SpanAndObjectPair> responseContextStore = | ||
VirtualField.find(HttpServletResponse.class, SpanAndObjectPair.class); | ||
VirtualField<ServletOutputStream, BoundedByteArrayOutputStream> outputStreamContextStore = | ||
VirtualField.find(ServletOutputStream.class, BoundedByteArrayOutputStream.class); | ||
VirtualField<PrintWriter, BoundedCharArrayWriter> writerContextStore = | ||
VirtualField.find(PrintWriter.class, BoundedCharArrayWriter.class); | ||
|
||
// request context to clear body buffer | ||
VirtualField<HttpServletRequest, SpanAndObjectPair> requestContextStore = | ||
VirtualField.find(HttpServletRequest.class, SpanAndObjectPair.class); | ||
VirtualField<ServletInputStream, ByteBufferSpanPair> inputStreamContextStore = | ||
VirtualField.find(ServletInputStream.class, ByteBufferSpanPair.class); | ||
VirtualField<BufferedReader, CharBufferSpanPair> readerContextStore = | ||
VirtualField.find(BufferedReader.class, CharBufferSpanPair.class); | ||
VirtualField<HttpServletRequest, StringMapSpanPair> urlEncodedMapContextStore = | ||
VirtualField.find(HttpServletRequest.class, StringMapSpanPair.class); | ||
|
||
if (!request.isAsyncStarted()) { | ||
if (instrumentationConfig.httpHeaders().response()) { | ||
for (String headerName : httpResponse.getHeaderNames()) { | ||
String headerValue = httpResponse.getHeader(headerName); | ||
currentSpan.setAttribute( | ||
HypertraceSemanticAttributes.httpResponseHeader(headerName), headerValue); | ||
} | ||
} | ||
|
||
// capture response body | ||
if (instrumentationConfig.httpBody().response() | ||
&& ContentTypeUtils.shouldCapture(httpResponse.getContentType())) { | ||
Utils.captureResponseBody( | ||
currentSpan, | ||
httpResponse, | ||
responseContextStore, | ||
outputStreamContextStore, | ||
writerContextStore); | ||
} | ||
|
||
// remove request body buffers from context stores, otherwise they might get reused | ||
if (instrumentationConfig.httpBody().request() | ||
&& ContentTypeUtils.shouldCapture(httpRequest.getContentType())) { | ||
Utils.resetRequestBodyBuffers( | ||
httpRequest, | ||
requestContextStore, | ||
inputStreamContextStore, | ||
readerContextStore, | ||
urlEncodedMapContextStore); | ||
} | ||
} | ||
} finally { | ||
Throwable tmp = throwable; | ||
while (tmp != null) { // loop in case our exception is nested (eg. springframework) | ||
if (tmp instanceof HypertraceEvaluationException) { | ||
FilterResult filterResult = ((HypertraceEvaluationException) tmp).getFilterResult(); | ||
try { | ||
httpResponse.getWriter().write(filterResult.getBlockingMsg()); | ||
} catch (IOException ignored) { | ||
} | ||
httpResponse.setStatus(filterResult.getBlockingStatusCode()); | ||
// bytebuddy treats the reassignment of this variable to null as an instruction to | ||
// suppress this exception, which is what we want | ||
throwable = null; | ||
break; | ||
} | ||
tmp = tmp.getCause(); | ||
} | ||
} | ||
} | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
...try/javaagent/instrumentation/hypertrace/servlet/v5_0/Servlet50InstrumentationModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
* Copyright The Hypertrace Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.hypertrace.servlet.v5_0; | ||
|
||
import com.google.auto.service.AutoService; | ||
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; | ||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; | ||
import io.opentelemetry.javaagent.instrumentation.hypertrace.servlet.v5_0.async.Servlet50AsyncInstrumentation; | ||
import io.opentelemetry.javaagent.instrumentation.hypertrace.servlet.v5_0.request.ServletInputStreamInstrumentation; | ||
import io.opentelemetry.javaagent.instrumentation.hypertrace.servlet.v5_0.request.ServletRequestInstrumentation; | ||
import io.opentelemetry.javaagent.instrumentation.hypertrace.servlet.v5_0.response.ServletOutputStreamInstrumentation; | ||
import io.opentelemetry.javaagent.instrumentation.hypertrace.servlet.v5_0.response.ServletResponseInstrumentation; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
@AutoService(InstrumentationModule.class) | ||
public class Servlet50InstrumentationModule extends InstrumentationModule { | ||
|
||
public Servlet50InstrumentationModule() { | ||
super(Servlet50InstrumentationName.PRIMARY, Servlet50InstrumentationName.OTHER); | ||
} | ||
|
||
@Override | ||
public int order() { | ||
return 1; | ||
} | ||
|
||
@Override | ||
public List<TypeInstrumentation> typeInstrumentations() { | ||
return Arrays.asList( | ||
new Servlet50AndFilterInstrumentation(), | ||
new ServletRequestInstrumentation(), | ||
new ServletInputStreamInstrumentation(), | ||
new ServletResponseInstrumentation(), | ||
new ServletOutputStreamInstrumentation(), | ||
new Servlet50AsyncInstrumentation()); | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
...metry/javaagent/instrumentation/hypertrace/servlet/v5_0/Servlet50InstrumentationName.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/* | ||
* Copyright The Hypertrace Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.hypertrace.servlet.v5_0; | ||
|
||
public class Servlet50InstrumentationName { | ||
public static final String PRIMARY = "servlet"; | ||
public static final String[] OTHER = {"servlet-5", "ht", "servlet-ht", "servlet-5-ht"}; | ||
} |
Oops, something went wrong.