From d28274610a421a9bc77c718f207857d4e5a45bb2 Mon Sep 17 00:00:00 2001 From: Matheus Cruz Date: Sat, 19 Oct 2024 23:19:53 -0300 Subject: [PATCH 1/5] Allow to drop application uri --- .../tracing/DropApplicationUrisBuildItem.java | 19 +++ .../deployment/tracing/TracerProcessor.java | 118 +++++++++++++++- .../OpenTelemetryTracelessTest.java | 128 ++++++++++++++++++ ...penTelemetryTracelessWithRootPathTest.java | 127 +++++++++++++++++ .../common/traces/TraceMeResource.java | 20 +++ .../traces/TracelessClassLevelResource.java | 36 +++++ .../common/traces/TracelessHelloResource.java | 47 +++++++ .../runtime/tracing/Traceless.java | 16 +++ 8 files changed, 509 insertions(+), 2 deletions(-) create mode 100644 extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/DropApplicationUrisBuildItem.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTracelessTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTracelessWithRootPathTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/traces/TraceMeResource.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/traces/TracelessClassLevelResource.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/traces/TracelessHelloResource.java create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/Traceless.java diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/DropApplicationUrisBuildItem.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/DropApplicationUrisBuildItem.java new file mode 100644 index 0000000000000..7d06ca4f4c66b --- /dev/null +++ b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/DropApplicationUrisBuildItem.java @@ -0,0 +1,19 @@ +package io.quarkus.opentelemetry.deployment.tracing; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * Represents an application uri that must be ignored for tracing. + */ +public final class DropApplicationUrisBuildItem extends MultiBuildItem { + + private final String uri; + + public DropApplicationUrisBuildItem(String uri) { + this.uri = uri; + } + + public String uri() { + return uri; + } +} diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java index 557749f5c104a..73b3cf9f00f16 100644 --- a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java +++ b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java @@ -10,6 +10,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.BooleanSupplier; @@ -17,8 +18,10 @@ import jakarta.enterprise.inject.spi.EventContext; import jakarta.inject.Singleton; +import org.eclipse.microprofile.config.ConfigProvider; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.IndexView; @@ -53,6 +56,7 @@ import io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig; import io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig.SecurityEvents.SecurityEventType; import io.quarkus.opentelemetry.runtime.tracing.DelayedAttributes; +import io.quarkus.opentelemetry.runtime.tracing.Traceless; import io.quarkus.opentelemetry.runtime.tracing.TracerRecorder; import io.quarkus.opentelemetry.runtime.tracing.cdi.TracerProducer; import io.quarkus.opentelemetry.runtime.tracing.security.EndUserSpanProcessor; @@ -69,6 +73,8 @@ public class TracerProcessor { private static final DotName SPAN_EXPORTER = DotName.createSimple(SpanExporter.class.getName()); private static final DotName SPAN_PROCESSOR = DotName.createSimple(SpanProcessor.class.getName()); private static final DotName TEXT_MAP_PROPAGATOR = DotName.createSimple(TextMapPropagator.class.getName()); + private static final DotName TRACELESS = DotName.createSimple(Traceless.class.getName()); + private static final DotName PATH = DotName.createSimple("jakarta.ws.rs.Path"); @BuildStep UnremovableBeanBuildItem ensureProducersAreRetained( @@ -131,15 +137,31 @@ UnremovableBeanBuildItem ensureProducersAreRetained( return new UnremovableBeanBuildItem(new UnremovableBeanBuildItem.BeanClassNamesExclusion(retainProducers)); } + @BuildStep + void dropApplicationUris( + CombinedIndexBuildItem combinedIndexBuildItem, + BuildProducer uris) { + String rootPath = ConfigProvider.getConfig().getOptionalValue("quarkus.http.root-path", String.class).orElse("/"); + IndexView index = combinedIndexBuildItem.getIndex(); + Collection annotations = index.getAnnotations(TRACELESS); + Set tracelessUris = generateTracelessUris(annotations.stream().toList(), rootPath); + for (String uri : tracelessUris) { + uris.produce(new DropApplicationUrisBuildItem(uri)); + } + } + @BuildStep void dropNames( Optional frameworkEndpoints, Optional staticResources, BuildProducer dropNonApplicationUris, - BuildProducer dropStaticResources) { + BuildProducer dropStaticResources, + List applicationUris) { + + List nonApplicationUris = new ArrayList<>( + applicationUris.stream().map(DropApplicationUrisBuildItem::uri).toList()); // Drop framework paths - List nonApplicationUris = new ArrayList<>(); frameworkEndpoints.ifPresent( frameworkEndpointsBuildItem -> { for (String endpoint : frameworkEndpointsBuildItem.getEndpoints()) { @@ -170,6 +192,67 @@ void dropNames( dropStaticResources.produce(new DropStaticResourcesBuildItem(resources)); } + private Set generateTracelessUris(final List annotations, final String rootPath) { + final Set applicationUris = new HashSet<>(); + for (AnnotationInstance annotation : annotations) { + AnnotationTarget.Kind kind = annotation.target().kind(); + + switch (kind) { + case CLASS -> { + AnnotationInstance classAnnotated = annotation.target().asClass().annotations() + .stream().filter(TracerProcessor::isClassAnnotatedWithPath).findFirst().orElse(null); + + if (Objects.isNull(classAnnotated)) { + continue; + } + + String classPath = classAnnotated.value().asString(); + String finalPath = combinePaths(rootPath, classPath); + + if (containsPathExpression(finalPath)) { + applicationUris.add(sanitizeForTraceless(finalPath) + "*"); + continue; + } + + applicationUris.add(finalPath + "*"); + applicationUris.add(finalPath); + } + case METHOD -> { + ClassInfo classInfo = annotation.target().asMethod().declaringClass(); + + AnnotationInstance possibleClassAnnotatedWithPath = classInfo.asClass() + .annotations() + .stream() + .filter(TracerProcessor::isClassAnnotatedWithPath) + .findFirst() + .orElse(null); + + if (Objects.isNull(possibleClassAnnotatedWithPath)) { + continue; + } + + String finalPath; + String classPath = possibleClassAnnotatedWithPath.value().asString(); + AnnotationInstance possibleMethodAnnotatedWithPath = annotation.target().annotation(PATH); + if (possibleMethodAnnotatedWithPath != null) { + String methodValue = possibleMethodAnnotatedWithPath.value().asString(); + finalPath = combinePaths(rootPath, combinePaths(classPath, methodValue)); + } else { + finalPath = combinePaths(rootPath, classPath); + } + + if (containsPathExpression(finalPath)) { + applicationUris.add(sanitizeForTraceless(finalPath) + "*"); + continue; + } + + applicationUris.add(finalPath); + } + } + } + return applicationUris; + } + @BuildStep @Record(ExecutionTime.STATIC_INIT) SyntheticBeanBuildItem setupDelayedAttribute(TracerRecorder recorder, ApplicationInfoBuildItem appInfo) { @@ -256,6 +339,37 @@ private static ObserverConfiguratorBuildItem createEventObserver( })); } + private static boolean containsPathExpression(String value) { + return value.indexOf('{') != -1; + } + + private static String sanitizeForTraceless(final String path) { + int braceIndex = path.indexOf('{'); + if (braceIndex == -1) { + return path; + } + if (braceIndex > 0 && path.charAt(braceIndex - 1) == '/') { + return path.substring(0, braceIndex - 1); + } else { + return path.substring(0, braceIndex); + } + } + + private static boolean isClassAnnotatedWithPath(AnnotationInstance annotation) { + return annotation.target().kind().equals(AnnotationTarget.Kind.CLASS) && + annotation.name().equals(PATH); + } + + private String combinePaths(String basePath, String relativePath) { + if (!basePath.endsWith("/")) { + basePath += "/"; + } + if (relativePath.startsWith("/")) { + relativePath = relativePath.substring(1); + } + return basePath + relativePath; + } + static final class SecurityEventsEnabled implements BooleanSupplier { private final boolean enabled; diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTracelessTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTracelessTest.java new file mode 100644 index 0000000000000..bf97c8be15507 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTracelessTest.java @@ -0,0 +1,128 @@ +package io.quarkus.opentelemetry.deployment; + +import static org.hamcrest.Matchers.is; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryExporter; +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporterProvider; +import io.quarkus.opentelemetry.deployment.common.traces.TraceMeResource; +import io.quarkus.opentelemetry.deployment.common.traces.TracelessClassLevelResource; +import io.quarkus.opentelemetry.deployment.common.traces.TracelessHelloResource; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class OpenTelemetryTracelessTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addPackage(InMemoryExporter.class.getPackage()) + .addAsResource("resource-config/application.properties", "application.properties") + .addAsResource( + "META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider") + .addClasses(TracelessHelloResource.class, TracelessClassLevelResource.class, TraceMeResource.class)); + + @Inject + InMemoryExporter exporter; + + @BeforeEach + void setup() { + exporter.reset(); + } + + @Test + @DisplayName("Should not trace when the method @Path uses @PathParam") + void testingWithPathParam() { + RestAssured.when() + .get("/hello/mask/1").then() + .statusCode(200) + .body(is("mask-1")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + exporter.getSpanExporter().getFinishedSpanItems(1); + + } + + @Test + @DisplayName("Should not trace when the annotation @Traceless is at method level") + void testingTracelessHelloHi() { + + RestAssured.when() + .get("/hello").then() + .statusCode(200) + .body(is("hello")); + + RestAssured.when() + .get("/hello/hi").then() + .statusCode(200) + .body(is("hi")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + // should have only one + exporter.getSpanExporter().getFinishedSpanItems(1); + } + + @Test + @DisplayName("Should not trace when the method @Path is without '/'") + void testingHelloNoSlash() { + RestAssured.when() + .get("/hello/no-slash").then() + .statusCode(200) + .body(is("no-slash")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + // should have only one + exporter.getSpanExporter().getFinishedSpanItems(1); + } + + @Test + @DisplayName("Should not trace when the annotation is at class level") + void testingTracelessAtClassLevel() { + + RestAssured.when() + .get("class-level").then() + .statusCode(200) + .body(is("class-level")); + + RestAssured.when() + .get("/class-level/first-method").then() + .statusCode(200) + .body(is("first-method")); + + RestAssured.when() + .get("/class-level/second-method").then() + .statusCode(200) + .body(is("second-method")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + // should have only one + exporter.getSpanExporter().getFinishedSpanItems(1); + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTracelessWithRootPathTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTracelessWithRootPathTest.java new file mode 100644 index 0000000000000..9443f65f703fe --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTracelessWithRootPathTest.java @@ -0,0 +1,127 @@ +package io.quarkus.opentelemetry.deployment; + +import static org.hamcrest.Matchers.is; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryExporter; +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporterProvider; +import io.quarkus.opentelemetry.deployment.common.traces.TraceMeResource; +import io.quarkus.opentelemetry.deployment.common.traces.TracelessClassLevelResource; +import io.quarkus.opentelemetry.deployment.common.traces.TracelessHelloResource; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class OpenTelemetryTracelessWithRootPathTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addPackage(InMemoryExporter.class.getPackage()) + .addAsResource("resource-config/application.properties", "application.properties") + .addAsResource( + "META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider") + .addClasses(TracelessHelloResource.class, TracelessClassLevelResource.class, TraceMeResource.class)) + .overrideConfigKey("quarkus.http.root-path", "/api"); + + @Inject + InMemoryExporter exporter; + + @BeforeEach + void setup() { + exporter.reset(); + } + + @Test + @DisplayName("Should not trace when the annotation @Traceless is at method level") + void testingTracelessHelloHi() { + + RestAssured.when() + .get("/hello").then() + .statusCode(200) + .body(is("hello")); + + RestAssured.when() + .get("/hello/hi").then() + .statusCode(200) + .body(is("hi")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + exporter.getSpanExporter().getFinishedSpanItems(1); + } + + @Test + @DisplayName("Should not trace when the method @Path uses @PathParam") + void testingWithPathParam() { + RestAssured.when() + .get("/hello/mask/1").then() + .statusCode(200) + .body(is("mask-1")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + exporter.getSpanExporter().getFinishedSpanItems(1); + + } + + @Test + @DisplayName("Should not trace when the method @Path is without '/'") + void testingHelloNoSlash() { + RestAssured.when() + .get("/hello/no-slash").then() + .statusCode(200) + .body(is("no-slash")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + exporter.getSpanExporter().getFinishedSpanItems(1); + } + + @Test + @DisplayName("Should not trace when the annotation is at class level") + void testingTracelessAtClassLevel() { + + RestAssured.when() + .get("class-level").then() + .statusCode(200) + .body(is("class-level")); + + RestAssured.when() + .get("/class-level/first-method").then() + .statusCode(200) + .body(is("first-method")); + + RestAssured.when() + .get("/class-level/second-method").then() + .statusCode(200) + .body(is("second-method")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + exporter.getSpanExporter().getFinishedSpanItems(1); + } + +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/traces/TraceMeResource.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/traces/TraceMeResource.java new file mode 100644 index 0000000000000..856d97666550c --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/traces/TraceMeResource.java @@ -0,0 +1,20 @@ +package io.quarkus.opentelemetry.deployment.common.traces; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import io.opentelemetry.api.metrics.Meter; + +@Path("/trace-me") +public class TraceMeResource { + + @Inject + Meter meter; + + @GET + public String traceMe() { + meter.counterBuilder("trace-me").build().add(1); + return "trace-me"; + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/traces/TracelessClassLevelResource.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/traces/TracelessClassLevelResource.java new file mode 100644 index 0000000000000..b27e2da3851c9 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/traces/TracelessClassLevelResource.java @@ -0,0 +1,36 @@ +package io.quarkus.opentelemetry.deployment.common.traces; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import io.opentelemetry.api.metrics.Meter; +import io.quarkus.opentelemetry.runtime.tracing.Traceless; + +@Path("/class-level") +@Traceless +public class TracelessClassLevelResource { + + @Inject + Meter meter; + + @GET + public String classLevel() { + meter.counterBuilder("class-level").build().add(1); + return "class-level"; + } + + @GET + @Path("/first-method") + public String firstMethod() { + meter.counterBuilder("first-method").build().add(1); + return "first-method"; + } + + @Path("/second-method") + @GET + public String secondMethod() { + meter.counterBuilder("second-method").build().add(1); + return "second-method"; + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/traces/TracelessHelloResource.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/traces/TracelessHelloResource.java new file mode 100644 index 0000000000000..f9824313798b4 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/traces/TracelessHelloResource.java @@ -0,0 +1,47 @@ +package io.quarkus.opentelemetry.deployment.common.traces; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; + +import io.opentelemetry.api.metrics.Meter; +import io.quarkus.opentelemetry.runtime.tracing.Traceless; + +@Path("/hello") +public class TracelessHelloResource { + + @Inject + Meter meter; + + @GET + @Traceless + public String hello() { + meter.counterBuilder("hello").build().add(1); + return "hello"; + } + + @Path("/hi") + @GET + @Traceless + public String hi() { + meter.counterBuilder("hi").build().add(1); + return "hi"; + } + + @Path("no-slash") + @GET + @Traceless + public String noSlash() { + meter.counterBuilder("no-slash").build().add(1); + return "no-slash"; + } + + @GET + @Path("/mask/{number}") + @Traceless + public String mask(@PathParam("number") String number) { + meter.counterBuilder("mask").build().add(Integer.parseInt(number)); + return "mask-" + number; + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/Traceless.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/Traceless.java new file mode 100644 index 0000000000000..254022bb8ee36 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/Traceless.java @@ -0,0 +1,16 @@ +package io.quarkus.opentelemetry.runtime.tracing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Identifies that the current path should not be select for tracing. + *

+ * Used together with {@code jakarta.ws.rs.Path} annotation. + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Traceless { +} From 402ff7a0f086561ee417ba369c13d6c665812476 Mon Sep 17 00:00:00 2001 From: Matheus Cruz Date: Mon, 21 Oct 2024 10:22:42 -0300 Subject: [PATCH 2/5] Add specific assertion --- .../OpenTelemetryTracelessTest.java | 27 ++++++++++++++----- ...penTelemetryTracelessWithRootPathTest.java | 27 ++++++++++++++++--- .../config/runtime/TracesRuntimeConfig.java | 10 +++++++ 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTracelessTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTracelessTest.java index bf97c8be15507..bd61aacf40a31 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTracelessTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTracelessTest.java @@ -1,7 +1,10 @@ package io.quarkus.opentelemetry.deployment; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is; +import java.util.List; + import jakarta.inject.Inject; import org.jboss.shrinkwrap.api.ShrinkWrap; @@ -12,6 +15,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.opentelemetry.sdk.trace.data.SpanData; import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryExporter; import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporterProvider; import io.quarkus.opentelemetry.deployment.common.traces.TraceMeResource; @@ -54,8 +58,11 @@ void testingWithPathParam() { .statusCode(200) .body(is("trace-me")); - exporter.getSpanExporter().getFinishedSpanItems(1); + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); } @Test @@ -77,8 +84,10 @@ void testingTracelessHelloHi() { .statusCode(200) .body(is("trace-me")); - // should have only one - exporter.getSpanExporter().getFinishedSpanItems(1); + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); } @Test @@ -94,8 +103,10 @@ void testingHelloNoSlash() { .statusCode(200) .body(is("trace-me")); - // should have only one - exporter.getSpanExporter().getFinishedSpanItems(1); + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); } @Test @@ -122,7 +133,9 @@ void testingTracelessAtClassLevel() { .statusCode(200) .body(is("trace-me")); - // should have only one - exporter.getSpanExporter().getFinishedSpanItems(1); + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); } } diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTracelessWithRootPathTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTracelessWithRootPathTest.java index 9443f65f703fe..03c442a1e46ed 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTracelessWithRootPathTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTracelessWithRootPathTest.java @@ -1,7 +1,10 @@ package io.quarkus.opentelemetry.deployment; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is; +import java.util.List; + import jakarta.inject.Inject; import org.jboss.shrinkwrap.api.ShrinkWrap; @@ -12,6 +15,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.opentelemetry.sdk.trace.data.SpanData; import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryExporter; import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporterProvider; import io.quarkus.opentelemetry.deployment.common.traces.TraceMeResource; @@ -61,7 +65,11 @@ void testingTracelessHelloHi() { .statusCode(200) .body(is("trace-me")); - exporter.getSpanExporter().getFinishedSpanItems(1); + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); } @Test @@ -77,8 +85,11 @@ void testingWithPathParam() { .statusCode(200) .body(is("trace-me")); - exporter.getSpanExporter().getFinishedSpanItems(1); + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); } @Test @@ -94,7 +105,11 @@ void testingHelloNoSlash() { .statusCode(200) .body(is("trace-me")); - exporter.getSpanExporter().getFinishedSpanItems(1); + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); } @Test @@ -121,7 +136,11 @@ void testingTracelessAtClassLevel() { .statusCode(200) .body(is("trace-me")); - exporter.getSpanExporter().getFinishedSpanItems(1); + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); } } diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/TracesRuntimeConfig.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/TracesRuntimeConfig.java index 07b6abb8eaca5..99c503775425d 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/TracesRuntimeConfig.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/TracesRuntimeConfig.java @@ -1,5 +1,6 @@ package io.quarkus.opentelemetry.runtime.config.runtime; +import java.util.List; import java.util.Optional; import io.quarkus.runtime.annotations.ConfigGroup; @@ -25,6 +26,15 @@ public interface TracesRuntimeConfig { @WithDefault("true") Boolean suppressNonApplicationUris(); + /** + * Comma-separated, suppress application uris from trace collection. + *

+ * This will suppress all uris set by this property. + *

+ */ + @WithName("suppress-application-uris") + Optional> suppressApplicationUris(); + /** * Include static resources from trace collection. *

From 5a125c4ad12c72ff2e6ad8935697685984111a5a Mon Sep 17 00:00:00 2001 From: Matheus Cruz Date: Mon, 21 Oct 2024 21:55:39 -0300 Subject: [PATCH 3/5] Add quarkus.otel.traces.suppress-application-uris --- .../OpenTelemetrySuppressAppUrisTest.java | 87 +++++++++++ .../deployment/common/TracerRouter.java | 2 + .../OpenTelemetrySuppressAppUrisTest.java | 87 +++++++++++ .../traces/OpenTelemetryTracelessTest.java | 141 +++++++++++++++++ ...penTelemetryTracelessWithRootPathTest.java | 146 ++++++++++++++++++ ...uredOpenTelemetrySdkBuilderCustomizer.java | 17 ++ .../config/runtime/TracesRuntimeConfig.java | 2 + 7 files changed, 482 insertions(+) create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySuppressAppUrisTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetrySuppressAppUrisTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryTracelessTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryTracelessWithRootPathTest.java diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySuppressAppUrisTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySuppressAppUrisTest.java new file mode 100644 index 0000000000000..003c21e5b8154 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySuppressAppUrisTest.java @@ -0,0 +1,87 @@ +package io.quarkus.opentelemetry.deployment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.opentelemetry.sdk.trace.data.SpanData; +import io.quarkus.opentelemetry.deployment.common.TracerRouter; +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryExporter; +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporterProvider; +import io.quarkus.opentelemetry.deployment.common.traces.TraceMeResource; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class OpenTelemetrySuppressAppUrisTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addPackage(InMemoryExporter.class.getPackage()) + .addAsResource("resource-config/application.properties", "application.properties") + .addAsResource( + "META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider") + .addClasses(TracerRouter.class, TraceMeResource.class)) + .overrideConfigKey("quarkus.otel.traces.suppress-application-uris", "tracer,/hello/Itachi"); + + @Inject + InMemoryExporter exporter; + + @BeforeEach + void setup() { + exporter.reset(); + } + + @Test + @DisplayName("Should not trace when the using configuration quarkus.otel.traces.suppress-application-uris without slash") + void testingSuppressAppUrisWithoutSlash() { + RestAssured.when() + .get("/tracer").then() + .statusCode(200) + .body(is("Hello Tracer!")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); + } + + @Test + @DisplayName("Should not trace when the using configuration quarkus.otel.traces.suppress-application-uris with slash") + void testingSuppressAppUrisWithSlash() { + RestAssured.when() + .get("/hello/Itachi").then() + .statusCode(200) + .body(is("Amaterasu!")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/TracerRouter.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/TracerRouter.java index 688d1f3a1d976..17007e372da1c 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/TracerRouter.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/TracerRouter.java @@ -27,6 +27,8 @@ public void register(@Observes StartupEvent ev) { String name = rc.pathParam("name"); if (name.equals("Naruto")) { rc.response().end("hello " + name); + } else if (name.equals("Itachi")) { + rc.response().end("Amaterasu!"); } else { rc.response().setStatusCode(404).end(); } diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetrySuppressAppUrisTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetrySuppressAppUrisTest.java new file mode 100644 index 0000000000000..3cf785619047d --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetrySuppressAppUrisTest.java @@ -0,0 +1,87 @@ +package io.quarkus.opentelemetry.deployment.traces; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.opentelemetry.sdk.trace.data.SpanData; +import io.quarkus.opentelemetry.deployment.common.TracerRouter; +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryExporter; +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporterProvider; +import io.quarkus.opentelemetry.deployment.common.traces.TraceMeResource; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class OpenTelemetrySuppressAppUrisTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addPackage(InMemoryExporter.class.getPackage()) + .addAsResource("resource-config/application.properties", "application.properties") + .addAsResource( + "META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider") + .addClasses(TracerRouter.class, TraceMeResource.class)) + .overrideConfigKey("quarkus.otel.traces.suppress-application-uris", "tracer,/hello/Itachi"); + + @Inject + InMemoryExporter exporter; + + @BeforeEach + void setup() { + exporter.reset(); + } + + @Test + @DisplayName("Should not trace when the using configuration quarkus.otel.traces.suppress-application-uris without slash") + void testingSuppressAppUrisWithoutSlash() { + RestAssured.when() + .get("/tracer").then() + .statusCode(200) + .body(is("Hello Tracer!")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); + } + + @Test + @DisplayName("Should not trace when the using configuration quarkus.otel.traces.suppress-application-uris with slash") + void testingSuppressAppUrisWithSlash() { + RestAssured.when() + .get("/hello/Itachi").then() + .statusCode(200) + .body(is("Amaterasu!")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryTracelessTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryTracelessTest.java new file mode 100644 index 0000000000000..42f91ca3c7288 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryTracelessTest.java @@ -0,0 +1,141 @@ +package io.quarkus.opentelemetry.deployment.traces; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.opentelemetry.sdk.trace.data.SpanData; +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryExporter; +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporterProvider; +import io.quarkus.opentelemetry.deployment.common.traces.TraceMeResource; +import io.quarkus.opentelemetry.deployment.common.traces.TracelessClassLevelResource; +import io.quarkus.opentelemetry.deployment.common.traces.TracelessHelloResource; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class OpenTelemetryTracelessTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addPackage(InMemoryExporter.class.getPackage()) + .addAsResource("resource-config/application.properties", "application.properties") + .addAsResource( + "META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider") + .addClasses(TracelessHelloResource.class, TracelessClassLevelResource.class, TraceMeResource.class)); + + @Inject + InMemoryExporter exporter; + + @BeforeEach + void setup() { + exporter.reset(); + } + + @Test + @DisplayName("Should not trace when the method @Path uses @PathParam") + void testingWithPathParam() { + RestAssured.when() + .get("/hello/mask/1").then() + .statusCode(200) + .body(is("mask-1")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); + } + + @Test + @DisplayName("Should not trace when the annotation @Traceless is at method level") + void testingTracelessHelloHi() { + + RestAssured.when() + .get("/hello").then() + .statusCode(200) + .body(is("hello")); + + RestAssured.when() + .get("/hello/hi").then() + .statusCode(200) + .body(is("hi")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); + } + + @Test + @DisplayName("Should not trace when the method @Path is without '/'") + void testingHelloNoSlash() { + RestAssured.when() + .get("/hello/no-slash").then() + .statusCode(200) + .body(is("no-slash")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); + } + + @Test + @DisplayName("Should not trace when the annotation is at class level") + void testingTracelessAtClassLevel() { + + RestAssured.when() + .get("class-level").then() + .statusCode(200) + .body(is("class-level")); + + RestAssured.when() + .get("/class-level/first-method").then() + .statusCode(200) + .body(is("first-method")); + + RestAssured.when() + .get("/class-level/second-method").then() + .statusCode(200) + .body(is("second-method")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryTracelessWithRootPathTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryTracelessWithRootPathTest.java new file mode 100644 index 0000000000000..a36362a7e1760 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryTracelessWithRootPathTest.java @@ -0,0 +1,146 @@ +package io.quarkus.opentelemetry.deployment.traces; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.opentelemetry.sdk.trace.data.SpanData; +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryExporter; +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporterProvider; +import io.quarkus.opentelemetry.deployment.common.traces.TraceMeResource; +import io.quarkus.opentelemetry.deployment.common.traces.TracelessClassLevelResource; +import io.quarkus.opentelemetry.deployment.common.traces.TracelessHelloResource; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class OpenTelemetryTracelessWithRootPathTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addPackage(InMemoryExporter.class.getPackage()) + .addAsResource("resource-config/application.properties", "application.properties") + .addAsResource( + "META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider") + .addClasses(TracelessHelloResource.class, TracelessClassLevelResource.class, TraceMeResource.class)) + .overrideConfigKey("quarkus.http.root-path", "/api"); + + @Inject + InMemoryExporter exporter; + + @BeforeEach + void setup() { + exporter.reset(); + } + + @Test + @DisplayName("Should not trace when the annotation @Traceless is at method level") + void testingTracelessHelloHi() { + + RestAssured.when() + .get("/hello").then() + .statusCode(200) + .body(is("hello")); + + RestAssured.when() + .get("/hello/hi").then() + .statusCode(200) + .body(is("hi")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); + } + + @Test + @DisplayName("Should not trace when the method @Path uses @PathParam") + void testingWithPathParam() { + RestAssured.when() + .get("/hello/mask/1").then() + .statusCode(200) + .body(is("mask-1")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); + } + + @Test + @DisplayName("Should not trace when the method @Path is without '/'") + void testingHelloNoSlash() { + RestAssured.when() + .get("/hello/no-slash").then() + .statusCode(200) + .body(is("no-slash")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); + } + + @Test + @DisplayName("Should not trace when the annotation is at class level") + void testingTracelessAtClassLevel() { + + RestAssured.when() + .get("class-level").then() + .statusCode(200) + .body(is("class-level")); + + RestAssured.when() + .get("/class-level/first-method").then() + .statusCode(200) + .body(is("first-method")); + + RestAssured.when() + .get("/class-level/second-method").then() + .statusCode(200) + .body(is("second-method")); + + RestAssured.when() + .get("/trace-me").then() + .statusCode(200) + .body(is("trace-me")); + + List spans = exporter.getSpanExporter().getFinishedSpanItems(1); + + assertThat(spans) + .hasSize(1) + .satisfiesOnlyOnce(span -> assertThat(span.getName()).containsOnlyOnce("trace-me")); + } + +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/AutoConfiguredOpenTelemetrySdkBuilderCustomizer.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/AutoConfiguredOpenTelemetrySdkBuilderCustomizer.java index e601962c7b01c..a7efb6067608f 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/AutoConfiguredOpenTelemetrySdkBuilderCustomizer.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/AutoConfiguredOpenTelemetrySdkBuilderCustomizer.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; import java.util.function.BiFunction; +import java.util.function.Function; import java.util.function.Predicate; import jakarta.enterprise.inject.Any; @@ -132,6 +133,12 @@ public Sampler apply(Sampler existingSampler, ConfigProperties configProperties) if (!oTelRuntimeConfig.traces().includeStaticResources()) {// default is false dropTargets.addAll(TracerRecorder.dropStaticResourceTargets); } + if (oTelRuntimeConfig.traces().suppressApplicationUris().isPresent()) { + dropTargets.addAll(oTelRuntimeConfig.traces().suppressApplicationUris().get() + .stream().filter(Predicate.not(String::isEmpty)) + .map(addSlashIfNecessary()) + .toList()); + } // make sure dropped targets are not sampled if (!dropTargets.isEmpty()) { @@ -147,6 +154,16 @@ public Sampler apply(Sampler existingSampler, ConfigProperties configProperties) } } + private static Function addSlashIfNecessary() { + return item -> { + if (item.startsWith("/")) { + return item; + } else { + return "/" + item; + } + }; + } + @Singleton final class TracerProviderCustomizer implements AutoConfiguredOpenTelemetrySdkBuilderCustomizer { diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/TracesRuntimeConfig.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/TracesRuntimeConfig.java index 99c503775425d..8f00dbe67b03c 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/TracesRuntimeConfig.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/TracesRuntimeConfig.java @@ -31,6 +31,8 @@ public interface TracesRuntimeConfig { *

* This will suppress all uris set by this property. *

+ * If you are using quarkus.http.root-path, you need to consider it when setting your uris, in + * other words, you need to configure it using the root-path if necessary. */ @WithName("suppress-application-uris") Optional> suppressApplicationUris(); From 0bd7b0c0f50e300404ca20bf3691865069e137b9 Mon Sep 17 00:00:00 2001 From: Matheus Cruz Date: Mon, 21 Oct 2024 22:22:50 -0300 Subject: [PATCH 4/5] Add test to quarkus-integration-test-opentelemetry module --- .../it/opentelemetry/SimpleResource.java | 18 +++++++++ .../src/main/resources/application.properties | 3 ++ .../quarkus/it/opentelemetry/TracingTest.java | 38 ++++++++++++++++--- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/integration-tests/opentelemetry/src/main/java/io/quarkus/it/opentelemetry/SimpleResource.java b/integration-tests/opentelemetry/src/main/java/io/quarkus/it/opentelemetry/SimpleResource.java index 5abc30290a410..dcb38b74ea92a 100644 --- a/integration-tests/opentelemetry/src/main/java/io/quarkus/it/opentelemetry/SimpleResource.java +++ b/integration-tests/opentelemetry/src/main/java/io/quarkus/it/opentelemetry/SimpleResource.java @@ -17,6 +17,7 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.context.Scope; +import io.quarkus.opentelemetry.runtime.tracing.Traceless; @Path("") @Produces(MediaType.APPLICATION_JSON) @@ -150,4 +151,21 @@ public String exception() { LOG.error("Oh no {}", exception.getMessage(), exception); return "Oh no! An exception"; } + + @GET + @Path("/suppress-app-uri") + public TraceData suppressAppUri() { + TraceData traceData = new TraceData(); + traceData.message = "Suppress me!"; + return traceData; + } + + @GET + @Path("/traceless") + @Traceless + public TraceData traceless() { + TraceData traceData = new TraceData(); + traceData.message = "@Traceless"; + return traceData; + } } diff --git a/integration-tests/opentelemetry/src/main/resources/application.properties b/integration-tests/opentelemetry/src/main/resources/application.properties index 3df96e1b77b03..25541a9d29c7c 100644 --- a/integration-tests/opentelemetry/src/main/resources/application.properties +++ b/integration-tests/opentelemetry/src/main/resources/application.properties @@ -20,3 +20,6 @@ quarkus.security.users.embedded.users.scott=reader quarkus.security.users.embedded.plain-text=true quarkus.security.users.embedded.enabled=true quarkus.http.auth.basic=true + + +quarkus.otel.traces.suppress-application-uris=/suppress-app-uri \ No newline at end of file diff --git a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/TracingTest.java b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/TracingTest.java index 43b8b52daa1b4..fdad57689c703 100644 --- a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/TracingTest.java +++ b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/TracingTest.java @@ -12,12 +12,7 @@ import static org.awaitility.Awaitility.await; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; import java.io.IOException; import java.net.URI; @@ -27,6 +22,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; +import org.awaitility.core.ConditionTimeoutException; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -58,6 +54,8 @@ public class TracingTest { URL deepPathUrl; @TestHTTPResource("param") URL pathParamUrl; + @TestHTTPResource + URL suppressAppUrl; @BeforeEach @AfterEach @@ -743,6 +741,34 @@ public void testNoEndUserAttributes() { .findAny().isEmpty()); } + @Test + public void testSuppressAppUri() { + RestAssured.given() + .when().get("/suppress-app-uri") + .then() + .statusCode(200) + .body("message", Matchers.is("Suppress me!")); + + // should throw because there are a configuration quarkus.otel.traces.suppress-app-uris=/suppress-app-uri + assertThrows(ConditionTimeoutException.class, () -> { + await().atMost(5, SECONDS).until(() -> !getSpans().isEmpty()); + }); + } + + @Test + public void testTracelessResource() { + RestAssured.given() + .when().get("/traceless") + .then() + .statusCode(200) + .body("message", Matchers.is("@Traceless")); + + // should throw because there is no span + assertThrows(ConditionTimeoutException.class, () -> { + await().atMost(5, SECONDS).until(() -> !getSpans().isEmpty()); + }); + } + private void verifyResource(Map spanData) { assertEquals("opentelemetry-integration-test", spanData.get("resource_service.name")); assertEquals("999-SNAPSHOT", spanData.get("resource_service.version")); From 58fbee575bba1a23dc4d5b170cd56d94d94b7080 Mon Sep 17 00:00:00 2001 From: Matheus Cruz Date: Tue, 22 Oct 2024 08:00:06 -0300 Subject: [PATCH 5/5] Remove star import --- .../java/io/quarkus/it/opentelemetry/TracingTest.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/TracingTest.java b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/TracingTest.java index fdad57689c703..5dea04160a238 100644 --- a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/TracingTest.java +++ b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/TracingTest.java @@ -12,7 +12,13 @@ import static org.awaitility.Awaitility.await; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.net.URI; @@ -54,8 +60,6 @@ public class TracingTest { URL deepPathUrl; @TestHTTPResource("param") URL pathParamUrl; - @TestHTTPResource - URL suppressAppUrl; @BeforeEach @AfterEach