Skip to content

Commit ae636a3

Browse files
author
Phil Denis
committed
Add instrumentation for jetty 11
1 parent bc99b50 commit ae636a3

File tree

7 files changed

+379
-0
lines changed

7 files changed

+379
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
muzzle {
2+
pass {
3+
group = "org.eclipse.jetty"
4+
module = 'jetty-server'
5+
versions = "[11,12)"
6+
assertInverse = true
7+
}
8+
}
9+
10+
apply from: "$rootDir/gradle/java.gradle"
11+
12+
apply plugin: 'org.unbroken-dome.test-sets'
13+
14+
testSets {
15+
latestDepTest {
16+
dirName = 'test'
17+
}
18+
}
19+
20+
dependencies {
21+
compileOnly group: 'org.eclipse.jetty', name: 'jetty-server', version: '11.0.0'
22+
23+
// Don't want to conflict with jetty from the test server.
24+
testImplementation(project(':dd-java-agent:testing')) {
25+
exclude group: 'org.eclipse.jetty', module: 'jetty-server'
26+
}
27+
28+
latestDepTestImplementation group: 'org.eclipse.jetty', name: 'jetty-server', version: '11.+'
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package datadog.trace.instrumentation.jetty11;
2+
3+
import datadog.trace.api.Config;
4+
import datadog.trace.api.DDTags;
5+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
6+
import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter;
7+
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
8+
import datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator;
9+
import jakarta.servlet.ServletException;
10+
import org.eclipse.jetty.server.HttpChannel;
11+
import org.eclipse.jetty.server.Request;
12+
import org.eclipse.jetty.server.Response;
13+
14+
public class JettyDecorator extends HttpServerDecorator<Request, Request, Response> {
15+
public static final CharSequence SERVLET_REQUEST = UTF8BytesString.create("servlet.request");
16+
public static final CharSequence JETTY_SERVER = UTF8BytesString.create("jetty-server");
17+
public static final JettyDecorator DECORATE = new JettyDecorator();
18+
public static final String DD_CONTEXT_PATH_ATTRIBUTE = "datadog.context.path";
19+
public static final String DD_SERVLET_PATH_ATTRIBUTE = "datadog.servlet.path";
20+
21+
@Override
22+
protected String[] instrumentationNames() {
23+
return new String[] {"jetty"};
24+
}
25+
26+
@Override
27+
protected CharSequence component() {
28+
return JETTY_SERVER;
29+
}
30+
31+
@Override
32+
protected String method(final Request request) {
33+
return request.getMethod();
34+
}
35+
36+
@Override
37+
protected URIDataAdapter url(final Request request) {
38+
return new RequestURIDataAdapter(request);
39+
}
40+
41+
@Override
42+
protected String peerHostIP(final Request request) {
43+
return request.getRemoteAddr();
44+
}
45+
46+
@Override
47+
protected int peerPort(final Request request) {
48+
return request.getRemotePort();
49+
}
50+
51+
@Override
52+
protected int status(final Response response) {
53+
return response.getStatus();
54+
}
55+
56+
public AgentSpan onResponse(AgentSpan span, HttpChannel channel) {
57+
Request request = channel.getRequest();
58+
Response response = channel.getResponse();
59+
if (Config.get().isServletPrincipalEnabled() && request.getUserPrincipal() != null) {
60+
span.setTag(DDTags.USER_NAME, request.getUserPrincipal().getName());
61+
}
62+
Object ex = request.getAttribute("jakarta.servlet.error.exception");
63+
if (ex instanceof Throwable) {
64+
Throwable throwable = (Throwable) ex;
65+
if (throwable instanceof ServletException) {
66+
throwable = ((ServletException) throwable).getRootCause();
67+
}
68+
onError(span, throwable);
69+
}
70+
return super.onResponse(span, response);
71+
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package datadog.trace.instrumentation.jetty11;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf;
5+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
6+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.propagate;
7+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
8+
import static datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator.DD_SPAN_ATTRIBUTE;
9+
import static datadog.trace.bootstrap.instrumentation.java.concurrent.ExcludeFilter.ExcludeType.RUNNABLE;
10+
import static datadog.trace.instrumentation.jetty11.JettyDecorator.DECORATE;
11+
import static datadog.trace.instrumentation.jetty11.JettyDecorator.SERVLET_REQUEST;
12+
import static datadog.trace.instrumentation.jetty11.RequestExtractAdapter.GETTER;
13+
import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
14+
import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments;
15+
16+
import com.google.auto.service.AutoService;
17+
import datadog.trace.agent.tooling.ExcludeFilterProvider;
18+
import datadog.trace.agent.tooling.Instrumenter;
19+
import datadog.trace.api.CorrelationIdentifier;
20+
import datadog.trace.api.GlobalTracer;
21+
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
22+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
23+
import datadog.trace.bootstrap.instrumentation.java.concurrent.ExcludeFilter;
24+
import java.util.Arrays;
25+
import java.util.Collection;
26+
import java.util.Collections;
27+
import java.util.Map;
28+
import net.bytebuddy.asm.Advice;
29+
import net.bytebuddy.description.method.MethodDescription;
30+
import net.bytebuddy.description.type.TypeDescription;
31+
import net.bytebuddy.matcher.ElementMatcher;
32+
import org.eclipse.jetty.server.HttpChannel;
33+
import org.eclipse.jetty.server.Request;
34+
35+
@AutoService(Instrumenter.class)
36+
public final class JettyServerInstrumentation extends Instrumenter.Tracing
37+
implements ExcludeFilterProvider {
38+
39+
public JettyServerInstrumentation() {
40+
super("jetty");
41+
}
42+
43+
@Override
44+
public ElementMatcher<TypeDescription> typeMatcher() {
45+
return named("org.eclipse.jetty.server.HttpChannel");
46+
}
47+
48+
@Override
49+
public String[] helperClassNames() {
50+
return new String[] {
51+
packageName + ".JettyDecorator",
52+
packageName + ".RequestExtractAdapter",
53+
packageName + ".RequestURIDataAdapter",
54+
};
55+
}
56+
57+
@Override
58+
public void adviceTransformations(AdviceTransformation transformation) {
59+
transformation.applyAdvice(
60+
takesNoArguments()
61+
.and(
62+
named("handle")
63+
.or(
64+
// In 9.0.3 the handle logic was extracted out to "handle"
65+
// but we still want to instrument run in case handle is missing
66+
// (without the risk of double instrumenting).
67+
named("run")
68+
.and(
69+
new ElementMatcher.Junction.AbstractBase<MethodDescription>() {
70+
@Override
71+
public boolean matches(MethodDescription target) {
72+
// TODO this could probably be made into a nicer matcher.
73+
return !declaresMethod(named("handle"))
74+
.matches(target.getDeclaringType().asErasure());
75+
}
76+
}))),
77+
JettyServerInstrumentation.class.getName() + "$HandleAdvice");
78+
transformation.applyAdvice(
79+
// name changed to recycle in 9.3.0
80+
namedOneOf("reset", "recycle").and(takesNoArguments()),
81+
JettyServerInstrumentation.class.getName() + "$ResetAdvice");
82+
}
83+
84+
@Override
85+
public Map<ExcludeFilter.ExcludeType, ? extends Collection<String>> excludedClasses() {
86+
return Collections.singletonMap(
87+
RUNNABLE,
88+
Arrays.asList(
89+
"org.eclipse.jetty.util.thread.strategy.ProduceConsume",
90+
"org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume",
91+
"org.eclipse.jetty.io.ManagedSelector",
92+
"org.eclipse.jetty.util.thread.TimerScheduler",
93+
"org.eclipse.jetty.util.thread.TimerScheduler$SimpleTask"));
94+
}
95+
96+
public static class HandleAdvice {
97+
98+
@Advice.OnMethodEnter(suppress = Throwable.class)
99+
public static AgentScope onEnter(@Advice.This final HttpChannel channel) {
100+
Request req = channel.getRequest();
101+
102+
Object existingSpan = req.getAttribute(DD_SPAN_ATTRIBUTE);
103+
if (existingSpan instanceof AgentSpan) {
104+
// Request already gone through initial processing, so just activate the span.
105+
return activateSpan((AgentSpan) existingSpan);
106+
}
107+
108+
final AgentSpan.Context.Extracted extractedContext = propagate().extract(req, GETTER);
109+
110+
final AgentSpan span = startSpan(SERVLET_REQUEST, extractedContext).setMeasured(true);
111+
DECORATE.afterStart(span);
112+
DECORATE.onRequest(span, req, req, extractedContext);
113+
114+
final AgentScope scope = activateSpan(span);
115+
scope.setAsyncPropagation(true);
116+
req.setAttribute(DD_SPAN_ATTRIBUTE, span);
117+
req.setAttribute(CorrelationIdentifier.getTraceIdKey(), GlobalTracer.get().getTraceId());
118+
req.setAttribute(CorrelationIdentifier.getSpanIdKey(), GlobalTracer.get().getSpanId());
119+
return scope;
120+
}
121+
122+
@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
123+
public static void closeScope(@Advice.Enter final AgentScope scope) {
124+
scope.close();
125+
}
126+
}
127+
128+
/**
129+
* Jetty ensures that connections are reset immediately after the response is sent. This provides
130+
* a reliable point to finish the server span at the last possible moment.
131+
*/
132+
public static class ResetAdvice {
133+
@Advice.OnMethodEnter(suppress = Throwable.class)
134+
public static void stopSpan(@Advice.This final HttpChannel channel) {
135+
Request req = channel.getRequest();
136+
Object spanObj = req.getAttribute(DD_SPAN_ATTRIBUTE);
137+
if (spanObj instanceof AgentSpan) {
138+
final AgentSpan span = (AgentSpan) spanObj;
139+
DECORATE.onResponse(span, channel);
140+
DECORATE.beforeFinish(span);
141+
span.finish();
142+
}
143+
}
144+
145+
private void muzzleCheck(HttpChannel connection) {
146+
connection.run();
147+
}
148+
}
149+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package datadog.trace.instrumentation.jetty11;
2+
3+
import datadog.trace.bootstrap.instrumentation.api.AgentPropagation;
4+
import org.eclipse.jetty.http.HttpField;
5+
import org.eclipse.jetty.http.HttpFields;
6+
import org.eclipse.jetty.server.Request;
7+
8+
public class RequestExtractAdapter implements AgentPropagation.ContextVisitor<Request> {
9+
10+
public static final RequestExtractAdapter GETTER = new RequestExtractAdapter();
11+
12+
@Override
13+
public void forEachKey(Request carrier, AgentPropagation.KeyClassifier classifier) {
14+
HttpFields headers = carrier.getHttpFields();
15+
for (int i = 0; i < headers.size(); ++i) {
16+
HttpField field = headers.getField(i);
17+
if (!classifier.accept(field.getName(), field.getValue())) {
18+
return;
19+
}
20+
}
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package datadog.trace.instrumentation.jetty11;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.SERVLET_CONTEXT;
5+
import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.SERVLET_PATH;
6+
import static datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator.DD_DISPATCH_SPAN_ATTRIBUTE;
7+
import static datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator.DD_SPAN_ATTRIBUTE;
8+
import static datadog.trace.instrumentation.jetty11.JettyDecorator.DD_CONTEXT_PATH_ATTRIBUTE;
9+
import static datadog.trace.instrumentation.jetty11.JettyDecorator.DD_SERVLET_PATH_ATTRIBUTE;
10+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
11+
12+
import com.google.auto.service.AutoService;
13+
import datadog.trace.agent.tooling.Instrumenter;
14+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
15+
import jakarta.servlet.http.HttpServletRequest;
16+
import net.bytebuddy.asm.Advice;
17+
import net.bytebuddy.description.type.TypeDescription;
18+
import net.bytebuddy.matcher.ElementMatcher;
19+
import org.eclipse.jetty.http.HttpFields;
20+
import org.eclipse.jetty.server.HttpChannel;
21+
import org.eclipse.jetty.server.Request;
22+
import org.eclipse.jetty.server.handler.ContextHandler;
23+
24+
@AutoService(Instrumenter.class)
25+
public final class RequestInstrumentation extends Instrumenter.Tracing {
26+
27+
public RequestInstrumentation() {
28+
super("jetty");
29+
}
30+
31+
@Override
32+
public ElementMatcher<TypeDescription> typeMatcher() {
33+
return named("org.eclipse.jetty.server.Request");
34+
}
35+
36+
@Override
37+
public void adviceTransformations(AdviceTransformation transformation) {
38+
transformation.applyAdvice(
39+
named("setContext")
40+
.and(takesArgument(0, named("org.eclipse.jetty.server.handler.ContextHandler$Context")))
41+
.and(takesArgument(1, String.class)),
42+
RequestInstrumentation.class.getName() + "$SetContextPathAdvice");
43+
}
44+
45+
/**
46+
* Because we are processing the initial request before the contextPath is set, we must update it
47+
* when it is actually set.
48+
*/
49+
public static class SetContextPathAdvice {
50+
@Advice.OnMethodEnter(suppress = Throwable.class)
51+
public static void updateContextPath(
52+
@Advice.This final Request req, @Advice.Argument(0) final ContextHandler.Context context, @Advice.Argument(1) final String contextPath) {
53+
if (contextPath != null) {
54+
Object span = req.getAttribute(DD_SPAN_ATTRIBUTE);
55+
// Don't want to update while being dispatched to new servlet
56+
if (span instanceof AgentSpan && req.getAttribute(DD_DISPATCH_SPAN_ATTRIBUTE) == null) {
57+
((AgentSpan) span).setTag(SERVLET_CONTEXT, contextPath);
58+
req.setAttribute(DD_CONTEXT_PATH_ATTRIBUTE, contextPath);
59+
}
60+
}
61+
}
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package datadog.trace.instrumentation.jetty11;
2+
3+
import datadog.trace.bootstrap.instrumentation.api.URIRawDataAdapter;
4+
import jakarta.servlet.http.HttpServletRequest;
5+
6+
final class RequestURIDataAdapter extends URIRawDataAdapter {
7+
8+
private final HttpServletRequest request;
9+
10+
RequestURIDataAdapter(HttpServletRequest request) {
11+
this.request = request;
12+
}
13+
14+
@Override
15+
public String scheme() {
16+
return request.getScheme();
17+
}
18+
19+
@Override
20+
public String host() {
21+
return request.getServerName();
22+
}
23+
24+
@Override
25+
public int port() {
26+
return request.getServerPort();
27+
}
28+
29+
@Override
30+
protected String innerRawPath() {
31+
return request.getRequestURI();
32+
}
33+
34+
@Override
35+
public String fragment() {
36+
return null;
37+
}
38+
39+
@Override
40+
protected String innerRawQuery() {
41+
return request.getQueryString();
42+
}
43+
}

settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ include ':dd-java-agent:instrumentation:jedis-3.0'
170170
include ':dd-java-agent:instrumentation:jetty-7.0'
171171
include ':dd-java-agent:instrumentation:jetty-7.6'
172172
include ':dd-java-agent:instrumentation:jetty-9'
173+
include ':dd-java-agent:instrumentation:jetty-11'
173174
include ':dd-java-agent:instrumentation:jetty-client-9.1'
174175
include ':dd-java-agent:instrumentation:jetty-util'
175176
include ':dd-java-agent:instrumentation:jms'

0 commit comments

Comments
 (0)