-
Notifications
You must be signed in to change notification settings - Fork 364
Add Jetty 12 native WebSocket tracing and fix upgrade span completion #12674
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: master
Are you sure you want to change the base?
Changes from all commits
e25af91
087453f
6e5b2c4
2bc4277
5be991e
b99a233
1f751f6
77b4362
38b819b
5052a0c
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,100 @@ | ||
| package datadog.trace.instrumentation.jetty_client12; | ||
|
|
||
| import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; | ||
| import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; | ||
| import static datadog.trace.instrumentation.jetty_client12.JettyClientDecorator.DECORATE; | ||
| import static java.util.Collections.singletonMap; | ||
| import static net.bytebuddy.matcher.ElementMatchers.takesArgument; | ||
| import static net.bytebuddy.matcher.ElementMatchers.takesArguments; | ||
|
|
||
| import com.google.auto.service.AutoService; | ||
| import datadog.context.ContextScope; | ||
| import datadog.trace.agent.tooling.Instrumenter; | ||
| import datadog.trace.agent.tooling.InstrumenterModule; | ||
| import datadog.trace.bootstrap.InstrumentationContext; | ||
| import datadog.trace.bootstrap.instrumentation.api.AgentSpan; | ||
| import java.util.Map; | ||
| import net.bytebuddy.asm.Advice; | ||
| import org.eclipse.jetty.client.Request; | ||
| import org.eclipse.jetty.client.Response; | ||
| import org.eclipse.jetty.io.EndPoint; | ||
| import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest; | ||
|
|
||
| @AutoService(InstrumenterModule.class) | ||
| public class JettyWebSocketUpgradeInstrumentation extends InstrumenterModule.Tracing | ||
| implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { | ||
| public JettyWebSocketUpgradeInstrumentation() { | ||
| super("jetty-client"); | ||
| } | ||
|
|
||
| @Override | ||
| public String muzzleDirective() { | ||
| return "jetty-websocket-core-client"; | ||
| } | ||
|
|
||
| @Override | ||
| public String instrumentedType() { | ||
| return "org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest"; | ||
| } | ||
|
|
||
| @Override | ||
| public String[] helperClassNames() { | ||
| return new String[] {packageName + ".JettyClientDecorator"}; | ||
| } | ||
|
|
||
| @Override | ||
| public Map<String, String> contextStore() { | ||
| return singletonMap("org.eclipse.jetty.client.Request", AgentSpan.class.getName()); | ||
| } | ||
|
|
||
| @Override | ||
| public void methodAdvice(MethodTransformer transformer) { | ||
| transformer.applyAdvice( | ||
| named("upgrade") | ||
| .and(takesArguments(2)) | ||
| .and(takesArgument(0, named("org.eclipse.jetty.client.Response"))) | ||
| .and(takesArgument(1, named("org.eclipse.jetty.io.EndPoint"))), | ||
| getClass().getName() + "$WebSocketUpgradeAdvice"); | ||
| } | ||
|
|
||
| public static class WebSocketUpgradeAdvice { | ||
| @Advice.OnMethodEnter(suppress = Throwable.class) | ||
| public static ContextScope beforeUpgrade(@Advice.Argument(0) Response response) { | ||
| AgentSpan span = | ||
| InstrumentationContext.get(Request.class, AgentSpan.class).get(response.getRequest()); | ||
| return span == null ? null : activateSpan(span); | ||
| } | ||
|
|
||
| @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) | ||
| public static void afterUpgrade( | ||
| @Advice.Argument(0) Response response, | ||
| @Advice.Enter ContextScope scope, | ||
| @Advice.Thrown Throwable failure) { | ||
| AgentSpan span = | ||
| InstrumentationContext.get(Request.class, AgentSpan.class).get(response.getRequest()); | ||
| try { | ||
| if (span != null && failure == null) { | ||
|
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.
When Useful? React with 👍 / 👎. |
||
| // Successful upgrades bypass the request's response completion listeners. | ||
| DECORATE.onResponse(span, response); | ||
| DECORATE.beforeFinish(span); | ||
| } | ||
| } finally { | ||
| if (scope != null) { | ||
| scope.close(); | ||
| } | ||
| if (span != null && failure == null) { | ||
| span.finish(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Lets Muzzle fail CI if the upgrade method is removed or its signature changes, instead of | ||
| * silently skipping instrumentation. | ||
| */ | ||
| private void muzzleCheck( | ||
| CoreClientUpgradeRequest request, Response response, EndPoint endPoint) { | ||
| request.upgrade(response, endPoint); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import static datadog.trace.agent.test.assertions.SpanMatcher.span; | ||
| import static datadog.trace.agent.test.assertions.TraceMatcher.trace; | ||
| import static java.util.concurrent.TimeUnit.SECONDS; | ||
| import static java.util.regex.Pattern.compile; | ||
| import static java.util.regex.Pattern.quote; | ||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
|
||
| import datadog.trace.agent.test.AbstractInstrumentationTest; | ||
| import datadog.trace.api.DDSpanTypes; | ||
| import datadog.trace.core.DDSpan; | ||
| import java.net.URI; | ||
| import java.util.List; | ||
| import org.eclipse.jetty.server.Server; | ||
| import org.eclipse.jetty.server.ServerConnector; | ||
| import org.eclipse.jetty.server.handler.ContextHandler; | ||
| import org.eclipse.jetty.websocket.api.Session; | ||
| import org.eclipse.jetty.websocket.client.WebSocketClient; | ||
| import org.eclipse.jetty.websocket.server.WebSocketUpgradeHandler; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| class JettyWebSocketUpgradeTest extends AbstractInstrumentationTest { | ||
| @Test | ||
| void httpClientSpanFinishesWhenWebSocketUpgradeSucceeds() throws Exception { | ||
| Server server = new Server(0); | ||
| WebSocketClient client = new WebSocketClient(); | ||
| try { | ||
| ContextHandler context = new ContextHandler("/"); | ||
| server.setHandler(context); | ||
| context.setHandler( | ||
| WebSocketUpgradeHandler.from(server, context) | ||
| .configure( | ||
| container -> | ||
| container.addMapping( | ||
| "/upgrade", (request, response, callback) -> new Endpoint()))); | ||
| server.start(); | ||
| client.start(); | ||
| URI uri = | ||
| URI.create( | ||
| "ws://localhost:" | ||
| + ((ServerConnector) server.getConnectors()[0]).getLocalPort() | ||
| + "/upgrade"); | ||
|
|
||
| Session session = client.connect(new Endpoint(), uri).get(5, SECONDS); | ||
|
|
||
| assertTrue(session.isOpen()); | ||
| // The HTTP handshake must be reported before the WebSocket connection closes. | ||
| assertTraces( | ||
| trace( | ||
| span() | ||
| .operationName(compile(quote("http.request"))) | ||
| .resourceName(compile(quote("GET /upgrade"))) | ||
| .type(DDSpanTypes.HTTP_CLIENT) | ||
| .root() | ||
| .error(false)), | ||
| trace(span().type(DDSpanTypes.HTTP_SERVER).error(false))); | ||
| DDSpan handshake = | ||
| writer.stream() | ||
| .flatMap(List::stream) | ||
| .filter(s -> "client".equals(s.getTag("span.kind"))) | ||
| .findFirst() | ||
| .orElseThrow(() -> new AssertionError("Missing client handshake span")); | ||
| assertEquals("jetty-client", handshake.getTag("component").toString()); | ||
| assertEquals("client", handshake.getTag("span.kind")); | ||
| assertEquals(101, handshake.getTag("http.status_code")); | ||
| } finally { | ||
| try { | ||
| client.stop(); | ||
| } finally { | ||
| server.stop(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public static class Endpoint implements Session.Listener.AutoDemanding {} | ||
| } |
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.
Remove redundant settings. Additionally, I scanned the project for similar redundant settings and cleaned them up in #12689