diff --git a/docs/src/main/asciidoc/opentelemetry.adoc b/docs/src/main/asciidoc/opentelemetry.adoc index f002ed2a8ebf7..c823332ab0338 100644 --- a/docs/src/main/asciidoc/opentelemetry.adoc +++ b/docs/src/main/asciidoc/opentelemetry.adoc @@ -423,6 +423,38 @@ class SpanBean { } ---- +=== Available OpenTelemetry CDI injections + +As per MicroProfile Telemetry Tracing specification, Quarkus supports the CDI injections of the +following classes: + +* `io.opentelemetry.api.OpenTelemetry` +* `io.opentelemetry.api.trace.Tracer` +* `io.opentelemetry.api.trace.Span` +* `io.opentelemetry.api.baggage.Baggage` + +You can inject these classes in any CDI enabled bean. For instance, the `Tracer` is particularly useful to start custom spans: + +[source,java] +---- +@Inject +Tracer tracer; + +... + +public void tracedWork() { + Span span = tracer.spanBuilder("My custom span") + .setAttribute("attr", "attr.value") + .setParent(Context.current().with(Span.current())) + .setSpanKind(SpanKind.INTERNAL) + .startSpan(); + + // traced work + + span.end(); +} +---- + === SmallRye Reactive Messaging - Kafka When using the SmallRye Reactive Messaging extension for Kafka, diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/cdi/TracerProducer.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/cdi/TracerProducer.java index e31f86d4ab84f..c370546986cd9 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/cdi/TracerProducer.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/cdi/TracerProducer.java @@ -2,10 +2,13 @@ import static io.quarkus.opentelemetry.runtime.config.OpenTelemetryConfig.INSTRUMENTATION_NAME; +import javax.enterprise.context.RequestScoped; import javax.enterprise.inject.Produces; import javax.inject.Singleton; import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; import io.quarkus.arc.DefaultBean; import io.quarkus.opentelemetry.runtime.tracing.DelayedAttributes; @@ -31,4 +34,18 @@ public LateBoundSampler getLateBoundSampler() { public Tracer getTracer() { return GlobalOpenTelemetry.getTracer(INSTRUMENTATION_NAME); } + + @Produces + @RequestScoped + @DefaultBean + public Span getSpan() { + return Span.current(); + } + + @Produces + @RequestScoped + @DefaultBean + public Baggage getBaggage() { + return Baggage.current(); + } } diff --git a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryInjectionsTest.java b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryInjectionsTest.java new file mode 100644 index 0000000000000..58d8eaf38430d --- /dev/null +++ b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryInjectionsTest.java @@ -0,0 +1,47 @@ +package io.quarkus.it.opentelemetry; + +import static io.restassured.RestAssured.get; +import static io.restassured.RestAssured.given; +import static java.net.HttpURLConnection.HTTP_OK; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.awaitility.Awaitility.await; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.common.mapper.TypeRef; + +@QuarkusTest +public class OpenTelemetryInjectionsTest { + + @AfterEach + void reset() { + given().get("/reset").then().statusCode(HTTP_OK); + await().atMost(5, SECONDS).until(() -> getSpans().size() == 0); + } + + private List> getSpans() { + return get("/export").body().as(new TypeRef<>() { + }); + } + + @Test + public void testOTelInjections() { + given() + .when().get("/otel/injection") + .then() + .statusCode(200); + } + + @Test + public void testOTelInjectionsAsync() { + given() + .when().get("/otel/injection/async") + .then() + .statusCode(200); + } +} diff --git a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/util/InjectionResource.java b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/util/InjectionResource.java new file mode 100644 index 0000000000000..08b6b91e031ad --- /dev/null +++ b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/util/InjectionResource.java @@ -0,0 +1,58 @@ +package io.quarkus.it.opentelemetry.util; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; + +import org.junit.jupiter.api.Assertions; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.smallrye.mutiny.Uni; + +@Path("/otel/injection") +@RequestScoped +public class InjectionResource { + + @Inject + OpenTelemetry openTelemetry; + + @Inject + Tracer tracer; + + @Inject + Span span; + + @Inject + Baggage baggage; + + @GET + public Response verifyOTelInjections() { + verifyInjections(); + return Response.ok().build(); + } + + @GET + @Path("/async") + public Uni verifyOTelInjectionsAsync() { + verifyInjections(); + return Uni.createFrom().item(Response.ok().build()); + } + + private void verifyInjections() { + Assertions.assertNotNull(openTelemetry, "OpenTelemetry cannot be injected"); + Assertions.assertNotNull(tracer, "Tracer cannot be injected"); + Assertions.assertNotNull(span, "Span cannot be injected"); + Assertions.assertNotNull(openTelemetry, "Baggage cannot be injected"); + + Assertions.assertEquals(GlobalOpenTelemetry.get(), openTelemetry); + Assertions.assertEquals(Span.current().getSpanContext(), span.getSpanContext()); + Assertions.assertEquals(Baggage.current().size(), baggage.size()); + baggage.asMap().forEach((s, baggageEntry) -> Assertions.assertEquals(baggageEntry, baggage.asMap().get(s))); + } +}