From ea1a2a09b397d0286d88ae4355d66dbb16a1d831 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 5 Jul 2023 09:30:40 +0300 Subject: [PATCH] Track exceptions thrown during JAX-RS processing in current span Fixes: #30462 Fixes: #27384 --- extensions/opentelemetry/deployment/pom.xml | 2 +- .../InstrumentationProcessor.java | 15 +++++---- .../resteasy/AttachExceptionHandler.java | 16 ++++++++++ .../opentelemetry-reactive/pom.xml | 5 +++ .../reactive/ExporterResource.java | 17 +++++++++- .../reactive/ReactiveResource.java | 14 +++++++++ .../src/main/resources/application.properties | 4 +-- .../reactive/OpenTelemetryReactiveTest.java | 31 +++++++++++++++++++ .../it/opentelemetry/reactive/Utils.java | 5 +++ 9 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/resteasy/AttachExceptionHandler.java diff --git a/extensions/opentelemetry/deployment/pom.xml b/extensions/opentelemetry/deployment/pom.xml index 88a08f63390cb..bb030a1ca3769 100644 --- a/extensions/opentelemetry/deployment/pom.xml +++ b/extensions/opentelemetry/deployment/pom.xml @@ -51,7 +51,7 @@ io.quarkus - quarkus-resteasy-reactive-spi-deployment + quarkus-resteasy-reactive-server-spi-deployment diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/instrumentation/InstrumentationProcessor.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/instrumentation/InstrumentationProcessor.java index b73e6ab0cc1fc..1bac28f2b0fe3 100644 --- a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/instrumentation/InstrumentationProcessor.java +++ b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/instrumentation/InstrumentationProcessor.java @@ -25,9 +25,11 @@ import io.quarkus.opentelemetry.runtime.tracing.intrumentation.grpc.GrpcTracingServerInterceptor; import io.quarkus.opentelemetry.runtime.tracing.intrumentation.reactivemessaging.ReactiveMessagingTracingDecorator; import io.quarkus.opentelemetry.runtime.tracing.intrumentation.restclient.OpenTelemetryClientFilter; +import io.quarkus.opentelemetry.runtime.tracing.intrumentation.resteasy.AttachExceptionHandler; import io.quarkus.opentelemetry.runtime.tracing.intrumentation.resteasy.OpenTelemetryClassicServerFilter; import io.quarkus.opentelemetry.runtime.tracing.intrumentation.resteasy.OpenTelemetryReactiveServerFilter; import io.quarkus.resteasy.common.spi.ResteasyJaxrsProviderBuildItem; +import io.quarkus.resteasy.reactive.server.spi.PreExceptionMapperHandlerBuildItem; import io.quarkus.resteasy.reactive.spi.CustomContainerRequestFilterBuildItem; import io.quarkus.vertx.core.deployment.VertxOptionsConsumerBuildItem; import io.vertx.core.VertxOptions; @@ -125,19 +127,20 @@ void registerResteasyClassicAndOrResteasyReactiveProvider( } @BuildStep - void registerResteasyReactiveProvider( + void resteasyReactiveIntegration( Capabilities capabilities, - BuildProducer containerRequestFilterBuildItemBuildProducer) { + BuildProducer containerRequestFilterBuildItemBuildProducer, + BuildProducer preExceptionMapperHandlerBuildItemBuildProducer) { - boolean isResteasyReactiveAvailable = capabilities.isPresent(Capability.RESTEASY_REACTIVE); - - if (!isResteasyReactiveAvailable) { - // if RestEasy is not available then no need to continue + if (!capabilities.isPresent(Capability.RESTEASY_REACTIVE)) { + // if RESTEasy Reactive is not available then no need to continue return; } containerRequestFilterBuildItemBuildProducer .produce(new CustomContainerRequestFilterBuildItem(OpenTelemetryReactiveServerFilter.class.getName())); + preExceptionMapperHandlerBuildItemBuildProducer + .produce(new PreExceptionMapperHandlerBuildItem(new AttachExceptionHandler())); } } diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/resteasy/AttachExceptionHandler.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/resteasy/AttachExceptionHandler.java new file mode 100644 index 0000000000000..ea6173d3aa131 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/resteasy/AttachExceptionHandler.java @@ -0,0 +1,16 @@ +package io.quarkus.opentelemetry.runtime.tracing.intrumentation.resteasy; + +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; +import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; + +import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan; + +public class AttachExceptionHandler implements ServerRestHandler { + @Override + public void handle(ResteasyReactiveRequestContext requestContext) throws Exception { + Throwable throwable = requestContext.getThrowable(); + if (throwable != null) { // should always be true + LocalRootSpan.current().recordException(throwable); + } + } +} diff --git a/integration-tests/opentelemetry-reactive/pom.xml b/integration-tests/opentelemetry-reactive/pom.xml index 2ebb98c0aee06..c213fc02432ba 100644 --- a/integration-tests/opentelemetry-reactive/pom.xml +++ b/integration-tests/opentelemetry-reactive/pom.xml @@ -62,6 +62,11 @@ wiremock-jre8-standalone test + + org.assertj + assertj-core + test + diff --git a/integration-tests/opentelemetry-reactive/src/main/java/io/quarkus/it/opentelemetry/reactive/ExporterResource.java b/integration-tests/opentelemetry-reactive/src/main/java/io/quarkus/it/opentelemetry/reactive/ExporterResource.java index 1a6743c956d6a..0d8f6cd3c4956 100644 --- a/integration-tests/opentelemetry-reactive/src/main/java/io/quarkus/it/opentelemetry/reactive/ExporterResource.java +++ b/integration-tests/opentelemetry-reactive/src/main/java/io/quarkus/it/opentelemetry/reactive/ExporterResource.java @@ -4,15 +4,16 @@ import java.util.stream.Collectors; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Produces; import jakarta.inject.Inject; import jakarta.inject.Singleton; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.Response; import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.internal.data.ExceptionEventData; @Path("") public class ExporterResource { @@ -35,6 +36,20 @@ public List export() { .collect(Collectors.toList()); } + @GET + @Path("/exportExceptionMessages") + public List exportExceptionMessages() { + return inMemorySpanExporter.getFinishedSpanItems() + .stream() + .filter(sd -> !sd.getName().contains("export") && !sd.getName().contains("reset")) + .filter(sd -> !sd.getEvents().isEmpty()) + .flatMap(sd -> sd.getEvents().stream()) + .filter(e -> e instanceof ExceptionEventData) + .map(e -> (ExceptionEventData) e) + .map(e -> e.getException().getMessage()) + .collect(Collectors.toList()); + } + @ApplicationScoped static class InMemorySpanExporterProducer { @Produces diff --git a/integration-tests/opentelemetry-reactive/src/main/java/io/quarkus/it/opentelemetry/reactive/ReactiveResource.java b/integration-tests/opentelemetry-reactive/src/main/java/io/quarkus/it/opentelemetry/reactive/ReactiveResource.java index a08c9f3fbaffc..02f94b5493f59 100644 --- a/integration-tests/opentelemetry-reactive/src/main/java/io/quarkus/it/opentelemetry/reactive/ReactiveResource.java +++ b/integration-tests/opentelemetry-reactive/src/main/java/io/quarkus/it/opentelemetry/reactive/ReactiveResource.java @@ -51,4 +51,18 @@ public Uni helloPost(String body) { return Uni.createFrom().item("Hello " + body).onItem().delayIt().by(Duration.ofSeconds(2)) .eventually((Runnable) span::end); } + + @Path("blockingException") + @GET + public String blockingException() { + throw new RuntimeException("dummy"); + } + + @Path("reactiveException") + @GET + public Uni reactiveException() { + return Uni.createFrom().item(() -> { + throw new RuntimeException("dummy2"); + }); + } } diff --git a/integration-tests/opentelemetry-reactive/src/main/resources/application.properties b/integration-tests/opentelemetry-reactive/src/main/resources/application.properties index f0d7b887e6dae..7485ece03853e 100644 --- a/integration-tests/opentelemetry-reactive/src/main/resources/application.properties +++ b/integration-tests/opentelemetry-reactive/src/main/resources/application.properties @@ -1,4 +1,4 @@ -quarkus.rest-client.client.url=${test.url} +quarkus.rest-client.client.url=${test.url quarkus.otel.bsp.schedule.delay=100 -quarkus.otel.bsp.export.timeout=5s \ No newline at end of file +quarkus.otel.bsp.export.timeout=5s diff --git a/integration-tests/opentelemetry-reactive/src/test/java/io/quarkus/it/opentelemetry/reactive/OpenTelemetryReactiveTest.java b/integration-tests/opentelemetry-reactive/src/test/java/io/quarkus/it/opentelemetry/reactive/OpenTelemetryReactiveTest.java index 0f899d27bc4b8..26bf417e99ca0 100644 --- a/integration-tests/opentelemetry-reactive/src/test/java/io/quarkus/it/opentelemetry/reactive/OpenTelemetryReactiveTest.java +++ b/integration-tests/opentelemetry-reactive/src/test/java/io/quarkus/it/opentelemetry/reactive/OpenTelemetryReactiveTest.java @@ -5,12 +5,14 @@ import static io.opentelemetry.api.trace.SpanKind.SERVER; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_TARGET; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_URL; +import static io.quarkus.it.opentelemetry.reactive.Utils.getExceptionEventData; import static io.quarkus.it.opentelemetry.reactive.Utils.getSpanByKindAndParentId; import static io.quarkus.it.opentelemetry.reactive.Utils.getSpans; import static io.quarkus.it.opentelemetry.reactive.Utils.getSpansByKindAndParentId; import static io.restassured.RestAssured.given; import static java.net.HttpURLConnection.HTTP_OK; import static java.util.stream.Collectors.toSet; +import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -53,6 +55,35 @@ void get() { assertEquals(spans.get(0).get("traceId"), spans.get(1).get("traceId")); } + @Test + void blockingException() { + given() + .when() + .get("/reactive/blockingException") + .then() + .statusCode(500); + + assertExceptionRecorded(); + } + + @Test + void reactiveException() { + given() + .when() + .get("/reactive/reactiveException") + .then() + .statusCode(500); + + assertExceptionRecorded(); + } + + private static void assertExceptionRecorded() { + await().atMost(5, TimeUnit.SECONDS).until(() -> getExceptionEventData().size() == 1); + assertThat(getExceptionEventData()).singleElement().satisfies(s -> { + assertThat(s).contains("dummy"); + }); + } + @Test void post() { given() diff --git a/integration-tests/opentelemetry-reactive/src/test/java/io/quarkus/it/opentelemetry/reactive/Utils.java b/integration-tests/opentelemetry-reactive/src/test/java/io/quarkus/it/opentelemetry/reactive/Utils.java index f05a3d0fce3b6..56ef345d5fabf 100644 --- a/integration-tests/opentelemetry-reactive/src/test/java/io/quarkus/it/opentelemetry/reactive/Utils.java +++ b/integration-tests/opentelemetry-reactive/src/test/java/io/quarkus/it/opentelemetry/reactive/Utils.java @@ -21,6 +21,11 @@ public static List> getSpans() { }); } + public static List getExceptionEventData() { + return when().get("/exportExceptionMessages").body().as(new TypeRef<>() { + }); + } + public static Map getSpanByKindAndParentId(List> spans, SpanKind kind, Object parentSpanId) { List> span = getSpansByKindAndParentId(spans, kind, parentSpanId);