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