Skip to content

Commit

Permalink
Track exceptions thrown during JAX-RS processing in current span
Browse files Browse the repository at this point in the history
  • Loading branch information
geoand committed Jul 5, 2023
1 parent 13b66e1 commit ea1a2a0
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 10 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);
}
}
}
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
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -35,6 +36,20 @@ public List<SpanData> export() {
.collect(Collectors.toList());
}

@GET
@Path("/exportExceptionMessages")
public List<String> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,18 @@ public Uni<String> 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<String> reactiveException() {
return Uni.createFrom().item(() -> {
throw new RuntimeException("dummy2");
});
}
}
Original file line number Diff line number Diff line change
@@ -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
quarkus.otel.bsp.export.timeout=5s
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public static List<Map<String, Object>> getSpans() {
});
}

public static List<String> getExceptionEventData() {
return when().get("/exportExceptionMessages").body().as(new TypeRef<>() {
});
}

public static Map<String, Object> getSpanByKindAndParentId(List<Map<String, Object>> spans, SpanKind kind,
Object parentSpanId) {
List<Map<String, Object>> span = getSpansByKindAndParentId(spans, kind, parentSpanId);
Expand Down

0 comments on commit ea1a2a0

Please sign in to comment.