Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .fossa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
1 change: 1 addition & 0 deletions docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]&nbsp;[6], [HTTP Client Spans] |
| [Azure Core](https://docs.microsoft.com/en-us/java/api/overview/azure/core-readme) | 1.14+ | N/A | Context propagation |
Expand Down
22 changes: 22 additions & 0 deletions instrumentation/avaje-jex-3.0/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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"))
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
}
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.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());
}
}
Original file line number Diff line number Diff line change
@@ -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}")))));
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading