Add Avaje Jex Instrumentation (#13733)
Co-authored-by: Lauri Tulmin <ltulmin@splunk.com>
This commit is contained in:
parent
e79ec1ce68
commit
9c00e71bb6
|
@ -76,6 +76,9 @@ targets:
|
|||
- type: gradle
|
||||
path: ./
|
||||
target: ':instrumentation:apache-shenyu-2.4:javaagent'
|
||||
- type: gradle
|
||||
path: ./
|
||||
target: ':instrumentation:avaje-jex-3.0:javaagent'
|
||||
- type: gradle
|
||||
path: ./
|
||||
target: ':instrumentation:c3p0-0.9:javaagent'
|
||||
|
|
|
@ -46,6 +46,7 @@ These are the supported libraries and frameworks:
|
|||
| [Armeria](https://armeria.dev) | 1.3+ | [opentelemetry-armeria-1.3](../instrumentation/armeria/armeria-1.3/library) | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] |
|
||||
| [Armeria gRPC](https://armeria.dev) | 1.14+ | | [RPC Client Spans], [RPC Client Metrics], [RPC Server Spans], [RPC Server Metrics] |
|
||||
| [AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client) | 1.9+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
|
||||
| [Avaje Jex](https://avaje.io/jex/) | 3.0+ | N/A | Provides `http.route` [2] |
|
||||
| [AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/java-handler.html) | 1.0+ | [opentelemetry-aws-lambda-core-1.0](../instrumentation/aws-lambda/aws-lambda-core-1.0/library),<br>[opentelemetry-aws-lambda-events-2.2](../instrumentation/aws-lambda/aws-lambda-events-2.2/library) | [FaaS Server Spans] |
|
||||
| [AWS SDK](https://aws.amazon.com/sdk-for-java/) | 1.11 - 1.12.583,<br>2.2+ | [opentelemetry-aws-sdk-1.11](../instrumentation/aws-sdk/aws-sdk-1.11/library),<br>[opentelemetry-aws-sdk-1.11-autoconfigure](../instrumentation/aws-sdk/aws-sdk-1.11/library-autoconfigure),<br>[opentelemetry-aws-sdk-2.2](../instrumentation/aws-sdk/aws-sdk-2.2/library),<br>[opentelemetry-aws-sdk-2.2-autoconfigure](../instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure) | [Messaging Spans], [Database Client Spans], [Database Client Metrics] [6], [HTTP Client Spans] |
|
||||
| [Azure Core](https://docs.microsoft.com/en-us/java/api/overview/azure/core-readme) | 1.14+ | N/A | Context propagation |
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
plugins {
|
||||
id("otel.javaagent-instrumentation")
|
||||
}
|
||||
|
||||
muzzle {
|
||||
pass {
|
||||
group.set("io.avaje")
|
||||
module.set("avaje-jex")
|
||||
versions.set("[3.0,)")
|
||||
assertInverse.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
otelJava {
|
||||
minJavaVersionSupported.set(JavaVersion.VERSION_21)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
library("io.avaje:avaje-jex:3.0")
|
||||
testLibrary("org.eclipse.jetty:jetty-http-spi:12.0.19")
|
||||
testInstrumentation(project(":instrumentation:jetty:jetty-12.0:javaagent"))
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.avaje.jex.v3_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.isInterface;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||
|
||||
import io.avaje.jex.http.Context;
|
||||
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute;
|
||||
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource;
|
||||
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;
|
||||
|
||||
public class JexInstrumentation implements TypeInstrumentation {
|
||||
|
||||
@Override
|
||||
public ElementMatcher<ClassLoader> classLoaderOptimization() {
|
||||
return hasClassesNamed("io.avaje.jex.http.ExchangeHandler");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return hasSuperType(named("io.avaje.jex.http.ExchangeHandler")).and(not(isInterface()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(TypeTransformer transformer) {
|
||||
transformer.applyAdviceToMethod(
|
||||
named("handle").and(takesArgument(0, named("io.avaje.jex.http.Context"))),
|
||||
this.getClass().getName() + "$HandlerAdapterAdvice");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class HandlerAdapterAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void onMethodExecute(@Advice.Argument(0) Context ctx) {
|
||||
HttpServerRoute.update(
|
||||
io.opentelemetry.context.Context.current(),
|
||||
HttpServerRouteSource.CONTROLLER,
|
||||
ctx.matchedPath());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.avaje.jex.v3_0;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@AutoService(InstrumentationModule.class)
|
||||
public class JexInstrumentationModule extends InstrumentationModule {
|
||||
|
||||
public JexInstrumentationModule() {
|
||||
super("avaje-jex", "avaje-jex-3.0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TypeInstrumentation> typeInstrumentations() {
|
||||
return singletonList(new JexInstrumentation());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.avaje.jex.v3_0;
|
||||
|
||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
|
||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
|
||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
|
||||
import static io.opentelemetry.semconv.ClientAttributes.CLIENT_ADDRESS;
|
||||
import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE;
|
||||
import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD;
|
||||
import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE;
|
||||
import static io.opentelemetry.semconv.HttpAttributes.HTTP_ROUTE;
|
||||
import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_ADDRESS;
|
||||
import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_PORT;
|
||||
import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_VERSION;
|
||||
import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS;
|
||||
import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT;
|
||||
import static io.opentelemetry.semconv.UrlAttributes.URL_PATH;
|
||||
import static io.opentelemetry.semconv.UrlAttributes.URL_SCHEME;
|
||||
import static io.opentelemetry.semconv.UserAgentAttributes.USER_AGENT_ORIGINAL;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import io.avaje.jex.Jex.Server;
|
||||
import io.opentelemetry.api.trace.SpanKind;
|
||||
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
|
||||
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
|
||||
import io.opentelemetry.testing.internal.armeria.client.WebClient;
|
||||
import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
class JexTest {
|
||||
|
||||
@RegisterExtension
|
||||
private static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
|
||||
|
||||
private static Server app;
|
||||
private static int port;
|
||||
private static WebClient client;
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
app = TestJexJavaApplication.initJex();
|
||||
port = app.port();
|
||||
client = WebClient.of("http://localhost:" + port);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void cleanup() {
|
||||
app.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSpanNameAndHttpRouteSpanWithPathParamResponseSuccessful() {
|
||||
String id = "123";
|
||||
AggregatedHttpResponse response = client.get("/test/param/" + id).aggregate().join();
|
||||
String content = response.contentUtf8();
|
||||
|
||||
assertThat(content).isEqualTo(id);
|
||||
assertThat(response.status().code()).isEqualTo(200);
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span ->
|
||||
span.hasName("GET /test/param/{id}")
|
||||
.hasKind(SpanKind.SERVER)
|
||||
.hasNoParent()
|
||||
.hasAttributesSatisfyingExactly(
|
||||
equalTo(URL_SCHEME, "http"),
|
||||
equalTo(URL_PATH, "/test/param/" + id),
|
||||
equalTo(HTTP_REQUEST_METHOD, "GET"),
|
||||
equalTo(HTTP_RESPONSE_STATUS_CODE, 200),
|
||||
satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)),
|
||||
equalTo(HTTP_ROUTE, "/test/param/{id}"),
|
||||
equalTo(NETWORK_PROTOCOL_VERSION, "1.1"),
|
||||
equalTo(SERVER_ADDRESS, "localhost"),
|
||||
equalTo(SERVER_PORT, port),
|
||||
equalTo(CLIENT_ADDRESS, "127.0.0.1"),
|
||||
equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"),
|
||||
satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSpanNameAndHttpRouteSpanResponseError() {
|
||||
client.get("/test/error").aggregate().join();
|
||||
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span ->
|
||||
span.hasName("GET /test/error")
|
||||
.hasKind(SpanKind.SERVER)
|
||||
.hasNoParent()
|
||||
.hasAttributesSatisfyingExactly(
|
||||
equalTo(URL_SCHEME, "http"),
|
||||
equalTo(URL_PATH, "/test/error"),
|
||||
equalTo(HTTP_REQUEST_METHOD, "GET"),
|
||||
equalTo(HTTP_RESPONSE_STATUS_CODE, 500),
|
||||
satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)),
|
||||
equalTo(HTTP_ROUTE, "/test/error"),
|
||||
equalTo(NETWORK_PROTOCOL_VERSION, "1.1"),
|
||||
equalTo(SERVER_ADDRESS, "localhost"),
|
||||
equalTo(SERVER_PORT, port),
|
||||
equalTo(ERROR_TYPE, "500"),
|
||||
equalTo(CLIENT_ADDRESS, "127.0.0.1"),
|
||||
equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"),
|
||||
satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHttpRouteMetricWithPathParamResponseSuccessful() {
|
||||
String id = "123";
|
||||
AggregatedHttpResponse response = client.get("/test/param/" + id).aggregate().join();
|
||||
String content = response.contentUtf8();
|
||||
String instrumentation = "io.opentelemetry.jetty-12.0";
|
||||
|
||||
assertThat(content).isEqualTo(id);
|
||||
assertThat(response.status().code()).isEqualTo(200);
|
||||
testing.waitAndAssertMetrics(
|
||||
instrumentation,
|
||||
"http.server.request.duration",
|
||||
metrics ->
|
||||
metrics.anySatisfy(
|
||||
metric ->
|
||||
assertThat(metric)
|
||||
.hasHistogramSatisfying(
|
||||
histogram ->
|
||||
histogram.hasPointsSatisfying(
|
||||
point -> point.hasAttribute(HTTP_ROUTE, "/test/param/{id}")))));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.avaje.jex.v3_0;
|
||||
|
||||
import io.avaje.jex.Jex;
|
||||
import io.avaje.jex.Jex.Server;
|
||||
|
||||
public class TestJexJavaApplication {
|
||||
|
||||
private TestJexJavaApplication() {}
|
||||
|
||||
public static Server initJex() {
|
||||
Jex app = Jex.create().contextPath("/test");
|
||||
app.get(
|
||||
"/param/{id}",
|
||||
ctx -> {
|
||||
String paramId = ctx.pathParam("id");
|
||||
ctx.write(paramId);
|
||||
});
|
||||
app.get(
|
||||
"/error",
|
||||
ctx -> {
|
||||
throw new RuntimeException("boom");
|
||||
});
|
||||
return app.port(0).start();
|
||||
}
|
||||
}
|
|
@ -156,6 +156,7 @@ include(":instrumentation:armeria:armeria-1.3:testing")
|
|||
include(":instrumentation:armeria:armeria-grpc-1.14:javaagent")
|
||||
include(":instrumentation:async-http-client:async-http-client-1.9:javaagent")
|
||||
include(":instrumentation:async-http-client:async-http-client-2.0:javaagent")
|
||||
include(":instrumentation:avaje-jex-3.0:javaagent")
|
||||
include(":instrumentation:aws-lambda:aws-lambda-core-1.0:javaagent")
|
||||
include(":instrumentation:aws-lambda:aws-lambda-core-1.0:library")
|
||||
include(":instrumentation:aws-lambda:aws-lambda-core-1.0:testing")
|
||||
|
|
Loading…
Reference in New Issue