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/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index f86b63e796e8d..a7670795d7dd0 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -94,6 +94,7 @@ import org.jboss.resteasy.reactive.server.core.ServerSerialisers; import org.jboss.resteasy.reactive.server.handlers.RestInitialHandler; import org.jboss.resteasy.reactive.server.model.ContextResolvers; +import org.jboss.resteasy.reactive.server.model.DelegatingServerRestHandler; import org.jboss.resteasy.reactive.server.model.DynamicFeatures; import org.jboss.resteasy.reactive.server.model.Features; import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer; @@ -109,6 +110,7 @@ import org.jboss.resteasy.reactive.server.processor.scanning.ResponseStatusMethodScanner; import org.jboss.resteasy.reactive.server.processor.util.ResteasyReactiveServerDotNames; import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration; +import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; import org.jboss.resteasy.reactive.server.vertx.serializers.ServerMutinyAsyncFileMessageBodyWriter; import org.jboss.resteasy.reactive.server.vertx.serializers.ServerMutinyBufferMessageBodyWriter; import org.jboss.resteasy.reactive.server.vertx.serializers.ServerVertxAsyncFileMessageBodyWriter; @@ -180,6 +182,7 @@ import io.quarkus.resteasy.reactive.server.spi.HandlerConfigurationProviderBuildItem; import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem; import io.quarkus.resteasy.reactive.server.spi.NonBlockingReturnTypeBuildItem; +import io.quarkus.resteasy.reactive.server.spi.PreExceptionMapperHandlerBuildItem; import io.quarkus.resteasy.reactive.server.spi.ResumeOn404BuildItem; import io.quarkus.resteasy.reactive.spi.CustomExceptionMapperBuildItem; import io.quarkus.resteasy.reactive.spi.DynamicFeatureBuildItem; @@ -1098,6 +1101,7 @@ public void setupDeployment(BeanContainerBuildItem beanContainerBuildItem, HttpBuildTimeConfig vertxConfig, SetupEndpointsResultBuildItem setupEndpointsResult, ServerSerialisersBuildItem serverSerialisersBuildItem, + List preExceptionMapperHandlerBuildItems, List dynamicFeatures, List features, Optional requestContextFactoryBuildItem, @@ -1212,6 +1216,7 @@ public void setupDeployment(BeanContainerBuildItem beanContainerBuildItem, .setFactoryCreator(recorder.factoryCreator(beanContainerBuildItem.getValue())) .setDynamicFeatures(dynamicFeats) .setSerialisers(serialisers) + .setPreExceptionMapperHandler(determinePreExceptionMapperHandler(preExceptionMapperHandlerBuildItems)) .setApplicationPath(applicationPath) .setGlobalHandlerCustomizers(Collections.singletonList(new SecurityContextOverrideHandler.Customizer())) //TODO: should be pluggable .setResourceClasses(resourceClasses) @@ -1282,6 +1287,19 @@ public void setupDeployment(BeanContainerBuildItem beanContainerBuildItem, } } + private ServerRestHandler determinePreExceptionMapperHandler( + List preExceptionMapperHandlerBuildItems) { + if ((preExceptionMapperHandlerBuildItems == null) || preExceptionMapperHandlerBuildItems.isEmpty()) { + return null; + } + if (preExceptionMapperHandlerBuildItems.size() == 1) { + return preExceptionMapperHandlerBuildItems.get(0).getHandler(); + } + Collections.sort(preExceptionMapperHandlerBuildItems); + return new DelegatingServerRestHandler(preExceptionMapperHandlerBuildItems.stream() + .map(PreExceptionMapperHandlerBuildItem::getHandler).collect(toList())); + } + private static boolean notFoundCustomExMapper(String builtInExSignature, String builtInMapperSignature, ExceptionMapping exceptionMapping) { for (var entry : exceptionMapping.getMappers().entrySet()) { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/PreExceptionMapperHandlerTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/PreExceptionMapperHandlerTest.java new file mode 100644 index 0000000000000..b55931a66c327 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/PreExceptionMapperHandlerTest.java @@ -0,0 +1,99 @@ +package io.quarkus.resteasy.reactive.server.test; + +import static io.restassured.RestAssured.get; +import static org.assertj.core.api.Assertions.*; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.server.ServerExceptionMapper; +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; +import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveContainerRequestContext; +import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.builder.BuildChainBuilder; +import io.quarkus.builder.BuildContext; +import io.quarkus.builder.BuildStep; +import io.quarkus.resteasy.reactive.server.spi.PreExceptionMapperHandlerBuildItem; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.mutiny.Uni; + +public class PreExceptionMapperHandlerTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class, Mappers.class, DummyPreExceptionMapperHandler.class); + } + }).addBuildChainCustomizer(new Consumer<>() { + @Override + public void accept(BuildChainBuilder buildChainBuilder) { + buildChainBuilder.addBuildStep(new BuildStep() { + @Override + public void execute(BuildContext context) { + context.produce( + new PreExceptionMapperHandlerBuildItem(new DummyPreExceptionMapperHandler())); + } + }).produces(PreExceptionMapperHandlerBuildItem.class).build(); + } + }); + + @Test + public void test() { + get("/test") + .then() + .statusCode(999) + .header("foo", "bar"); + + get("/test/uni") + .then() + .statusCode(999) + .header("foo", "bar"); + } + + @Path("test") + public static class Resource { + + @GET + public String get() { + throw new RuntimeException("dummy"); + } + + @Path("uni") + @GET + public Uni uniGet() { + return Uni.createFrom().item(() -> { + throw new RuntimeException("dummy"); + }); + } + } + + public static class Mappers { + + @ServerExceptionMapper(RuntimeException.class) + Response handle(ResteasyReactiveContainerRequestContext requestContext) { + return Response.status(999).header("foo", requestContext.getProperty("foo")).build(); + } + + } + + public static class DummyPreExceptionMapperHandler implements ServerRestHandler { + + @Override + public void handle(ResteasyReactiveRequestContext requestContext) throws Exception { + assertThat(requestContext.getThrowable()).isInstanceOf(RuntimeException.class); + requestContext.setProperty("foo", "bar"); + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/spi-deployment/src/main/java/io/quarkus/resteasy/reactive/server/spi/PreExceptionMapperHandlerBuildItem.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/spi-deployment/src/main/java/io/quarkus/resteasy/reactive/server/spi/PreExceptionMapperHandlerBuildItem.java new file mode 100644 index 0000000000000..ecb6b37bc9699 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/spi-deployment/src/main/java/io/quarkus/resteasy/reactive/server/spi/PreExceptionMapperHandlerBuildItem.java @@ -0,0 +1,42 @@ +package io.quarkus.resteasy.reactive.server.spi; + +import jakarta.ws.rs.Priorities; + +import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * A build item that allows extension to define a {@link ServerRestHandler} that runs write before + * RESTEasy Reactive attempt to do exception mapping according to the JAX-RS spec. + * This is only meant to be used in very advanced use cases. + */ +public final class PreExceptionMapperHandlerBuildItem extends MultiBuildItem + implements Comparable { + + private final ServerRestHandler handler; + private final int priority; + + public PreExceptionMapperHandlerBuildItem(ServerRestHandler handler, int priority) { + this.handler = handler; + this.priority = priority; + } + + public PreExceptionMapperHandlerBuildItem(ServerRestHandler handler) { + this.handler = handler; + this.priority = Priorities.USER; + } + + @Override + public int compareTo(PreExceptionMapperHandlerBuildItem o) { + return Integer.compare(priority, o.priority); + } + + public ServerRestHandler getHandler() { + return handler; + } + + public int getPriority() { + return priority; + } +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/DeploymentInfo.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/DeploymentInfo.java index 043ba32cad7c2..1f7c97d13b485 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/DeploymentInfo.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/DeploymentInfo.java @@ -15,6 +15,7 @@ import org.jboss.resteasy.reactive.server.model.Features; import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer; import org.jboss.resteasy.reactive.server.model.ParamConverterProviders; +import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; import org.jboss.resteasy.reactive.spi.BeanFactory; public class DeploymentInfo { @@ -25,6 +26,8 @@ public class DeploymentInfo { private Features features; private DynamicFeatures dynamicFeatures; private ServerSerialisers serialisers; + + private ServerRestHandler preExceptionMapperHandler; private List resourceClasses; private List locatableResourceClasses; private ParamConverterProviders paramConverterProviders; @@ -91,6 +94,15 @@ public DeploymentInfo setSerialisers(ServerSerialisers serialisers) { return this; } + public ServerRestHandler getPreExceptionMapperHandler() { + return preExceptionMapperHandler; + } + + public DeploymentInfo setPreExceptionMapperHandler(ServerRestHandler preExceptionMapperHandler) { + this.preExceptionMapperHandler = preExceptionMapperHandler; + return this; + } + public List getResourceClasses() { return resourceClasses; } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeDeploymentManager.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeDeploymentManager.java index fb3e64b16110d..6b254608a29a2 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeDeploymentManager.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeDeploymentManager.java @@ -198,6 +198,9 @@ public BeanFactory.BeanInstance apply(Class aClass) { if (interceptorDeployment.getGlobalInterceptorHandler() != null) { abortHandlingChain.add(interceptorDeployment.getGlobalInterceptorHandler()); } + if (info.getPreExceptionMapperHandler() != null) { + abortHandlingChain.add(info.getPreExceptionMapperHandler()); + } abortHandlingChain.add(new ExceptionHandler()); abortHandlingChain.add(ResponseHandler.NO_CUSTOMIZER_INSTANCE); if (!interceptors.getContainerResponseFilters().getGlobalResourceInterceptors().isEmpty()) { diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java index a03c161fa1106..f3ef562e331b5 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java @@ -197,7 +197,8 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, //setup reader and writer interceptors first ServerRestHandler interceptorHandler = interceptorDeployment.setupInterceptorHandler(); //we want interceptors in the abort handler chain - List abortHandlingChain = new ArrayList<>(3 + (interceptorHandler != null ? 1 : 0)); + List abortHandlingChain = new ArrayList<>( + 3 + (interceptorHandler != null ? 1 : 0) + (info.getPreExceptionMapperHandler() != null ? 1 : 0)); List handlers = new ArrayList<>(HANDLERS_CAPACITY); // we add null as the first item to make sure that subsequent items are added in the proper positions @@ -487,6 +488,9 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, // so we can invoke it abortHandlingChain.add(instanceHandler); } + if (info.getPreExceptionMapperHandler() != null) { + abortHandlingChain.add(info.getPreExceptionMapperHandler()); + } abortHandlingChain.add(ExceptionHandler.INSTANCE); abortHandlingChain.add(ResponseHandler.NO_CUSTOMIZER_INSTANCE); abortHandlingChain.addAll(responseFilterHandlers); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/DelegatingServerRestHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/DelegatingServerRestHandler.java new file mode 100644 index 0000000000000..a074f126513f1 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/DelegatingServerRestHandler.java @@ -0,0 +1,34 @@ +package org.jboss.resteasy.reactive.server.model; + +import java.util.List; + +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; +import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; + +public class DelegatingServerRestHandler implements ServerRestHandler { + + private List delegates; + + public DelegatingServerRestHandler(List delegates) { + this.delegates = delegates; + } + + // for bytecode recording + public DelegatingServerRestHandler() { + } + + public List getDelegates() { + return delegates; + } + + public void setDelegates(List delegates) { + this.delegates = delegates; + } + + @Override + public void handle(ResteasyReactiveRequestContext requestContext) throws Exception { + for (int i = 0; i < delegates.size(); i++) { + delegates.get(0).handle(requestContext); + } + } +} 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);