Skip to content

Commit

Permalink
support jettyclient 12 (#11519)
Browse files Browse the repository at this point in the history
Co-authored-by: Lauri Tulmin <[email protected]>
Co-authored-by: Trask Stalnaker <[email protected]>
  • Loading branch information
3 people committed Jul 9, 2024
1 parent 26591ea commit 7f30056
Show file tree
Hide file tree
Showing 25 changed files with 1,049 additions and 19 deletions.
2 changes: 1 addition & 1 deletion docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ These are the supported libraries and frameworks:
| [Dropwizard Views](https://www.dropwizard.io/en/latest/manual/views.html) | 0.7+ | N/A | Controller Spans [3] |
| [Eclipse Grizzly](https://javaee.github.io/grizzly/httpserverframework.html) | 2.3+ | N/A | [HTTP Server Spans], [HTTP Server Metrics] |
| [Eclipse Jersey](https://eclipse-ee4j.github.io/jersey/) | 2.0+ | N/A | Provides `http.route` [2], Controller Spans [3] |
| [Eclipse Jetty HTTP Client](https://www.eclipse.org/jetty/javadoc/jetty-9/org/eclipse/jetty/client/HttpClient.html) | 9.2+ (not including 10+ yet) | [opentelemetry-jetty-httpclient-9.2](../instrumentation/jetty-httpclient/jetty-httpclient-9.2/library) | [HTTP Client Spans], [HTTP Client Metrics] |
| [Eclipse Jetty HTTP Client](https://www.eclipse.org/jetty/javadoc/jetty-9/org/eclipse/jetty/client/HttpClient.html) | 9.2 - 9.4.x,<br>12.0+ | [opentelemetry-jetty-httpclient-9.2](../instrumentation/jetty-httpclient/jetty-httpclient-9.2/library)<br>[opentelemetry-jetty-httpclient-12.0](../instrumentation/jetty-httpclient/jetty-httpclient-12.0/library) | [HTTP Client Spans], [HTTP Client Metrics] |
| [Eclipse Metro](https://projects.eclipse.org/projects/ee4j.metro) | 2.2+ | N/A | Provides `http.route` [2], Controller Spans [3] |
| [Eclipse Mojarra](https://projects.eclipse.org/projects/ee4j.mojarra) | 1.2+ | N/A | Provides `http.route` [2], Controller Spans [3] |
| [Elasticsearch API Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html) | 7.16 - 7.17.19,<br>8.0 - 8.9.+ [4] | N/A | [Elasticsearch Client Spans] |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("org.eclipse.jetty")
module.set("jetty-client")
versions.set("[12,)")
}
}

otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_17)
}

dependencies {
implementation(project(":instrumentation:jetty-httpclient:jetty-httpclient-12.0:library"))

library("org.eclipse.jetty:jetty-client:12.0.0")

testInstrumentation(project(":instrumentation:jetty-httpclient:jetty-httpclient-9.2:javaagent"))

testImplementation(project(":instrumentation:jetty-httpclient:jetty-httpclient-12.0:testing"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jetty.httpclient.v12_0;

import static io.opentelemetry.javaagent.instrumentation.jetty.httpclient.v12_0.JettyHttpClientSingletons.JETTY_CLIENT_CONTEXT_KEY;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.eclipse.jetty.client.Response;
import org.eclipse.jetty.client.Result;

public class JettyClient12ResponseListenersInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.eclipse.jetty.client.transport.ResponseListeners");
}

@Override
public void transform(TypeTransformer transformer) {
// for response listeners
transformer.applyAdviceToMethod(
isMethod()
.and(
nameContains("notify")
.and(isPublic())
.and(takesArgument(0, named("org.eclipse.jetty.client.Response")))),
JettyClient12ResponseListenersInstrumentation.class.getName()
+ "$JettyHttpClient12RespListenersNotifyAdvice");

// for complete listeners
transformer.applyAdviceToMethod(
isMethod()
.and(
nameContains("notifyComplete")
.and(isPublic())
.and(takesArgument(0, named("org.eclipse.jetty.client.Result")))),
JettyClient12ResponseListenersInstrumentation.class.getName()
+ "$JettyHttpClient12CompleteListenersNotifyAdvice");
}

@SuppressWarnings("unused")
public static class JettyHttpClient12RespListenersNotifyAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnterNotify(
@Advice.Argument(0) Response response,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
context = (Context) response.getRequest().getAttributes().get(JETTY_CLIENT_CONTEXT_KEY);
if (context != null) {
scope = context.makeCurrent();
}
}

@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void onExitNotify(
@Advice.Argument(0) Response response,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (scope == null) {
return;
}

scope.close();
}
}

@SuppressWarnings("unused")
public static class JettyHttpClient12CompleteListenersNotifyAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnterComplete(
@Advice.Argument(0) Result result,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
context = (Context) result.getRequest().getAttributes().get(JETTY_CLIENT_CONTEXT_KEY);
if (context != null) {
scope = context.makeCurrent();
}
}

@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void onExitComplete(
@Advice.Argument(0) Result result,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (scope == null) {
return;
}

scope.close();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jetty.httpclient.v12_0;

import static io.opentelemetry.javaagent.instrumentation.jetty.httpclient.v12_0.JettyHttpClientSingletons.JETTY_CLIENT_CONTEXT_KEY;
import static io.opentelemetry.javaagent.instrumentation.jetty.httpclient.v12_0.JettyHttpClientSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.jetty.httpclient.v12_0.internal.JettyClientTracingListener;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.eclipse.jetty.client.transport.HttpRequest;

public class JettyHttpClient12Instrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.eclipse.jetty.client.transport.HttpRequest");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(named("send"))
.and(takesArgument(0, named("org.eclipse.jetty.client.Response$CompleteListener"))),
JettyHttpClient12Instrumentation.class.getName() + "$JettyHttpClient12SendAdvice");
// For request listeners
transformer.applyAdviceToMethod(
isMethod().and(nameContains("notify")),
JettyHttpClient12Instrumentation.class.getName() + "$JettyHttpClient12NotifyAdvice");
}

@SuppressWarnings("unused")
public static class JettyHttpClient12SendAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnterSend(
@Advice.This HttpRequest request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
// start span
Context parentContext = Context.current();
context = JettyClientTracingListener.handleRequest(parentContext, request, instrumenter());
if (context == null) {
return;
}
// set context for responseListeners
request.attribute(JETTY_CLIENT_CONTEXT_KEY, parentContext);

scope = context.makeCurrent();
}

@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void onExitSend(
@Advice.This HttpRequest request,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (scope == null) {
return;
}

// not ending span here unless error, span ended in the interceptor
scope.close();
if (throwable != null) {
instrumenter().end(context, request, null, throwable);
}
}
}

@SuppressWarnings("unused")
public static class JettyHttpClient12NotifyAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnterNotify(
@Advice.This HttpRequest request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
context = (Context) request.getAttributes().get(JETTY_CLIENT_CONTEXT_KEY);
if (context == null) {
return;
}
scope = context.makeCurrent();
}

@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void onExitNotify(
@Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) {
if (scope == null) {
return;
}

scope.close();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jetty.httpclient.v12_0;

import static java.util.Arrays.asList;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.List;

@AutoService(InstrumentationModule.class)
public class JettyHttpClient12InstrumentationModule extends InstrumentationModule {
public JettyHttpClient12InstrumentationModule() {
super("jetty-httpclient", "jetty-httpclient-12.0");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(
new JettyHttpClient12Instrumentation(),
new JettyClient12ResponseListenersInstrumentation());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jetty.httpclient.v12_0;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.jetty.httpclient.v12_0.internal.JettyHttpClientInstrumenterBuilderFactory;
import io.opentelemetry.javaagent.bootstrap.internal.JavaagentHttpClientInstrumenters;
import org.eclipse.jetty.client.Request;
import org.eclipse.jetty.client.Response;

public final class JettyHttpClientSingletons {

static final String JETTY_CLIENT_CONTEXT_KEY = "otel-jetty-client-context";

private static final Instrumenter<Request, Response> INSTRUMENTER =
JavaagentHttpClientInstrumenters.create(
JettyHttpClientInstrumenterBuilderFactory.create(GlobalOpenTelemetry.get()));

public static Instrumenter<Request, Response> instrumenter() {
return INSTRUMENTER;
}

private JettyHttpClientSingletons() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jetty.httpclient.v12_0;

import io.opentelemetry.instrumentation.jetty.httpclient.v12_0.AbstractJettyClient12Test;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.jupiter.api.extension.RegisterExtension;

class JettyHttpClient12AgentTest extends AbstractJettyClient12Test {

@RegisterExtension
static final InstrumentationExtension testing = HttpClientInstrumentationExtension.forAgent();

@Override
protected HttpClient createStandardClient() {
return new HttpClient();
}

@Override
protected HttpClient createHttpsClient(SslContextFactory.Client sslContextFactory) {
HttpClient httpClient = new HttpClient();
httpClient.setSslContextFactory(sslContextFactory);
return httpClient;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
plugins {
id("otel.library-instrumentation")
}

otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_17)
}

dependencies {
library("org.eclipse.jetty:jetty-client:12.0.0")

testImplementation(project(":instrumentation:jetty-httpclient::jetty-httpclient-12.0:testing"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.jetty.httpclient.v12_0;

import io.opentelemetry.api.OpenTelemetry;
import org.eclipse.jetty.client.HttpClient;

/** Entrypoint for instrumenting Jetty client. */
public final class JettyClientTelemetry {

/** Returns a new {@link JettyClientTelemetry} configured with the given {@link OpenTelemetry}. */
public static JettyClientTelemetry create(OpenTelemetry openTelemetry) {
JettyClientTelemetryBuilder builder = builder(openTelemetry);
return builder.build();
}

/**
* Returns a new {@link JettyClientTelemetryBuilder} configured with the given {@link
* OpenTelemetry}.
*/
public static JettyClientTelemetryBuilder builder(OpenTelemetry openTelemetry) {
return new JettyClientTelemetryBuilder(openTelemetry);
}

private final HttpClient httpClient;

JettyClientTelemetry(HttpClient httpClient) {
this.httpClient = httpClient;
}

public HttpClient getHttpClient() {
return httpClient;
}
}
Loading

0 comments on commit 7f30056

Please sign in to comment.