Skip to content

Commit

Permalink
Merge pull request #34521 from geoand/#30462
Browse files Browse the repository at this point in the history
Track exceptions thrown during JAX-RS processing in the current span
  • Loading branch information
geoand authored Jul 5, 2023
2 parents 97b71d5 + ea1a2a0 commit 66e1053
Show file tree
Hide file tree
Showing 16 changed files with 312 additions and 11 deletions.
2 changes: 1 addition & 1 deletion extensions/opentelemetry/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-spi-deployment</artifactId>
<artifactId>quarkus-resteasy-reactive-server-spi-deployment</artifactId>
</dependency>

<!-- Test Dependencies -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -125,19 +127,20 @@ void registerResteasyClassicAndOrResteasyReactiveProvider(
}

@BuildStep
void registerResteasyReactiveProvider(
void resteasyReactiveIntegration(
Capabilities capabilities,
BuildProducer<CustomContainerRequestFilterBuildItem> containerRequestFilterBuildItemBuildProducer) {
BuildProducer<CustomContainerRequestFilterBuildItem> containerRequestFilterBuildItemBuildProducer,
BuildProducer<PreExceptionMapperHandlerBuildItem> 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()));
}

}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1098,6 +1101,7 @@ public void setupDeployment(BeanContainerBuildItem beanContainerBuildItem,
HttpBuildTimeConfig vertxConfig,
SetupEndpointsResultBuildItem setupEndpointsResult,
ServerSerialisersBuildItem serverSerialisersBuildItem,
List<PreExceptionMapperHandlerBuildItem> preExceptionMapperHandlerBuildItems,
List<DynamicFeatureBuildItem> dynamicFeatures,
List<JaxrsFeatureBuildItem> features,
Optional<RequestContextFactoryBuildItem> requestContextFactoryBuildItem,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1282,6 +1287,19 @@ public void setupDeployment(BeanContainerBuildItem beanContainerBuildItem,
}
}

private ServerRestHandler determinePreExceptionMapperHandler(
List<PreExceptionMapperHandlerBuildItem> 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()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> 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");
}
}
}
Original file line number Diff line number Diff line change
@@ -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<PreExceptionMapperHandlerBuildItem> {

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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -25,6 +26,8 @@ public class DeploymentInfo {
private Features features;
private DynamicFeatures dynamicFeatures;
private ServerSerialisers serialisers;

private ServerRestHandler preExceptionMapperHandler;
private List<ResourceClass> resourceClasses;
private List<ResourceClass> locatableResourceClasses;
private ParamConverterProviders paramConverterProviders;
Expand Down Expand Up @@ -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<ResourceClass> getResourceClasses() {
return resourceClasses;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ServerRestHandler> abortHandlingChain = new ArrayList<>(3 + (interceptorHandler != null ? 1 : 0));
List<ServerRestHandler> abortHandlingChain = new ArrayList<>(
3 + (interceptorHandler != null ? 1 : 0) + (info.getPreExceptionMapperHandler() != null ? 1 : 0));

List<ServerRestHandler> handlers = new ArrayList<>(HANDLERS_CAPACITY);
// we add null as the first item to make sure that subsequent items are added in the proper positions
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ServerRestHandler> delegates;

public DelegatingServerRestHandler(List<ServerRestHandler> delegates) {
this.delegates = delegates;
}

// for bytecode recording
public DelegatingServerRestHandler() {
}

public List<ServerRestHandler> getDelegates() {
return delegates;
}

public void setDelegates(List<ServerRestHandler> 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);
}
}
}
5 changes: 5 additions & 0 deletions integration-tests/opentelemetry-reactive/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@
<artifactId>wiremock-jre8-standalone</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>

<!-- Minimal test dependencies to *-deployment artifacts for consistent build order -->
<dependency>
Expand Down
Loading

0 comments on commit 66e1053

Please sign in to comment.