Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exclude uri from otel tracing #43885

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,22 @@
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;

import jakarta.enterprise.inject.spi.EventContext;
import jakarta.inject.Singleton;

import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.ConfigValue;
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.Index;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
import org.jboss.logging.Logger;
Expand All @@ -46,13 +51,15 @@
import io.quarkus.deployment.annotations.BuildSteps;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.ApplicationIndexBuildItem;
import io.quarkus.deployment.builditem.ApplicationInfoBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
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;
Expand All @@ -69,6 +76,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(
Expand Down Expand Up @@ -136,10 +145,58 @@ void dropNames(
Optional<FrameworkEndpointsBuildItem> frameworkEndpoints,
Optional<StaticResourcesBuildItem> staticResources,
BuildProducer<DropNonApplicationUrisBuildItem> dropNonApplicationUris,
BuildProducer<DropStaticResourcesBuildItem> dropStaticResources) {
BuildProducer<DropStaticResourcesBuildItem> dropStaticResources,
ApplicationIndexBuildItem appIndex) {

// Drop framework paths
List<String> nonApplicationUris = new ArrayList<>();

Index jandex = appIndex.getIndex();
List<AnnotationInstance> annotations = jandex.getAnnotations(TRACELESS);
for (AnnotationInstance annotation : annotations) {
AnnotationTarget.Kind kind = annotation.target().kind();

switch (kind) {
case CLASS -> {
AnnotationInstance annotationInstance = annotation.target().asClass().annotations()
.stream().filter(TracerProcessor::isPathAnnotationAtClass).findFirst().orElse(null);

if (Objects.isNull(annotationInstance)) {
continue;
}

nonApplicationUris.add(annotationInstance.value().asString() + "*");
}
case METHOD -> {
ClassInfo classInfo = annotation.target().asMethod().declaringClass();

AnnotationInstance annotationInstance = classInfo.asClass()
.annotations()
.stream()
.filter(TracerProcessor::isPathAnnotationAtClass)
.findFirst()
.orElse(null);

if (Objects.isNull(annotationInstance)) {
continue;
}

String finalPath;
String classPath = annotationInstance.value().asString();
AnnotationInstance annotationMethodInstance = annotation.target().annotation(PATH);
if (annotationMethodInstance != null) {
String methodPath = annotationMethodInstance.value().asString();
finalPath = normalizePath(classPath, methodPath);
} else {
finalPath = classPath;
}

nonApplicationUris.add(finalPath);

}
}
}

// Drop framework paths
frameworkEndpoints.ifPresent(
frameworkEndpointsBuildItem -> {
for (String endpoint : frameworkEndpointsBuildItem.getEndpoints()) {
Expand Down Expand Up @@ -170,6 +227,21 @@ void dropNames(
dropStaticResources.produce(new DropStaticResourcesBuildItem(resources));
}

private static boolean isPathAnnotationAtClass(AnnotationInstance ann) {
return ann.target().kind().equals(AnnotationTarget.Kind.CLASS) &&
ann.name().equals(PATH);
}

public String normalizePath(String classPath, String methodPath) {
if (!classPath.endsWith("/")) {
classPath += "/";
}
if (methodPath.startsWith("/")) {
methodPath = methodPath.substring(1);
}
return classPath + methodPath;
}

@BuildStep
@Record(ExecutionTime.STATIC_INIT)
SyntheticBeanBuildItem setupDelayedAttribute(TracerRecorder recorder, ApplicationInfoBuildItem appInfo) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package io.quarkus.opentelemetry.deployment;

import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

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.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.opentelemetry.api.metrics.Meter;
import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryExporter;
import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporterProvider;
import io.quarkus.opentelemetry.runtime.tracing.Traceless;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import io.smallrye.config.SmallRyeConfig;

public class OpenTelemetryExcludedResourceTest {
@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"));
private static final Logger log = LoggerFactory.getLogger(OpenTelemetryExcludedResourceTest.class);

@Inject
SmallRyeConfig config;
@Inject
InMemoryExporter exporter;

@BeforeEach
void setup() {
exporter.reset();
}

@Test
void testingHello() {
RestAssured.when()
.get("/hello").then()
.statusCode(200)
.body(is("hello"));

// should fail, because there is no span
assertThrows(org.awaitility.core.ConditionTimeoutException.class,
() -> exporter.getSpanExporter().getFinishedSpanItems(1));
}

@Test
void testingHi() {
RestAssured.when()
.get("/hello/hi").then()
.statusCode(200)
.body(is("hi"));

// should fail, because there is no span
assertThrows(org.awaitility.core.ConditionTimeoutException.class,
() -> exporter.getSpanExporter().getFinishedSpanItems(1));
}

@Test
void testingNoSlash() {
RestAssured.when()
.get("/hello/no-slash").then()
.statusCode(200)
.body(is("no-slash"));

// should fail, because there is no span
assertThrows(org.awaitility.core.ConditionTimeoutException.class,
() -> exporter.getSpanExporter().getFinishedSpanItems(1));
}

@Test
void testingAtClassLevel() {
RestAssured.when()
.get("/class-level").then()
.statusCode(200)
.body(is("no-slash"));

// should fail, because there is no span
assertThrows(org.awaitility.core.ConditionTimeoutException.class,
() -> exporter.getSpanExporter().getFinishedSpanItems(1));
}

@Path("/class-level")
@Traceless
public static class ClassLevelResource {

@Inject
Meter meter;

@GET
@Path("/first-method")

public String firstMethod() {
meter.counterBuilder("method").build().add(1);
return "method";
}

@Path("/second-method")
@GET
public String secondMethod() {
meter.counterBuilder("method").build().add(1);
return "method";
}
}

@Path("/hello")
public static class HelloResource {
@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";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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 select for
* adding trace headers.
* <p>
* Used together with {@code jakarta.ws.rs.Path} annotation.
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Traceless {
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package io.quarkus.it.opentelemetry;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;

@Path("template/path/{value}")
public class PathTemplateResource {

@GET
public String get(@PathParam("value") String value) {
return "Received: " + value;
Expand Down