diff --git a/bom/application/pom.xml b/bom/application/pom.xml index eda86a21d5579..70e4a579a6552 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -27,8 +27,8 @@ 0.2.12 0.1.15 0.1.5 - 1.2.0 - 1.2.0-alpha + 1.3.0 + 1.3.0-alpha 1.4.0 4.1.1 1.0.0.Final diff --git a/extensions/opentelemetry/opentelemetry/deployment/pom.xml b/extensions/opentelemetry/opentelemetry/deployment/pom.xml index 0db48116d1353..5427563d3b711 100644 --- a/extensions/opentelemetry/opentelemetry/deployment/pom.xml +++ b/extensions/opentelemetry/opentelemetry/deployment/pom.xml @@ -78,6 +78,22 @@ + + + io.opentelemetry + opentelemetry-extension-aws + test + + + io.opentelemetry + opentelemetry-sdk-extension-aws + test + + + io.opentelemetry + opentelemetry-sdk-extension-resources + test + diff --git a/extensions/opentelemetry/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java b/extensions/opentelemetry/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java index 0a96cd0f78ef0..c7469f1b83aa2 100644 --- a/extensions/opentelemetry/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java +++ b/extensions/opentelemetry/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java @@ -58,7 +58,9 @@ void registerOpenTelemetryContextStorage( @BuildStep(onlyIf = OpenTelemetryEnabled.class) @Record(ExecutionTime.STATIC_INIT) - void createOpenTelemetry(OpenTelemetryRecorder recorder, Optional tracerProviderBuildItem, + void createOpenTelemetry(OpenTelemetryConfig openTelemetryConfig, + OpenTelemetryRecorder recorder, + Optional tracerProviderBuildItem, LaunchModeBuildItem launchMode) { if (launchMode.getLaunchMode() == LaunchMode.DEVELOPMENT) { recorder.resetGlobalOpenTelemetryForDevMode(); @@ -66,7 +68,7 @@ void createOpenTelemetry(OpenTelemetryRecorder recorder, Optional tracerProvider = tracerProviderBuildItem.map(TracerProviderBuildItem::getTracerProvider) .orElse(null); - recorder.createOpenTelemetry(tracerProvider); + recorder.createOpenTelemetry(tracerProvider, openTelemetryConfig); recorder.eagerlyCreateContextStorage(); } diff --git a/extensions/opentelemetry/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java b/extensions/opentelemetry/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java index d7e743ccd5588..a8a312c205237 100644 --- a/extensions/opentelemetry/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java +++ b/extensions/opentelemetry/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java @@ -14,8 +14,11 @@ import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.IdGenerator; import io.opentelemetry.sdk.trace.SpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; +import io.opentelemetry.sdk.trace.samplers.Sampler; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; @@ -32,10 +35,14 @@ import io.quarkus.opentelemetry.runtime.OpenTelemetryConfig; import io.quarkus.opentelemetry.runtime.tracing.TracerProducer; import io.quarkus.opentelemetry.runtime.tracing.TracerRecorder; +import io.quarkus.opentelemetry.runtime.tracing.TracerRuntimeConfig; import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.vertx.core.deployment.VertxOptionsConsumerBuildItem; public class TracerProcessor { + private static final DotName ID_GENERATOR = DotName.createSimple(IdGenerator.class.getName()); + private static final DotName RESOURCE = DotName.createSimple(Resource.class.getName()); + private static final DotName SAMPLER = DotName.createSimple(Sampler.class.getName()); private static final DotName SPAN_EXPORTER = DotName.createSimple(SpanExporter.class.getName()); private static final DotName SPAN_PROCESSOR = DotName.createSimple(SpanProcessor.class.getName()); @@ -69,6 +76,15 @@ UnremovableBeanBuildItem ensureProducersAreRetained( // Find all known SpanExporters and SpanProcessors Collection knownClasses = new HashSet<>(); + knownClasses.add(ID_GENERATOR.toString()); + index.getAllKnownImplementors(ID_GENERATOR) + .forEach(classInfo -> knownClasses.add(classInfo.name().toString())); + knownClasses.add(RESOURCE.toString()); + index.getAllKnownImplementors(RESOURCE) + .forEach(classInfo -> knownClasses.add(classInfo.name().toString())); + knownClasses.add(SAMPLER.toString()); + index.getAllKnownImplementors(SAMPLER) + .forEach(classInfo -> knownClasses.add(classInfo.name().toString())); knownClasses.add(SPAN_EXPORTER.toString()); index.getAllKnownImplementors(SPAN_EXPORTER) .forEach(classInfo -> knownClasses.add(classInfo.name().toString())); @@ -111,18 +127,23 @@ VertxOptionsConsumerBuildItem vertxTracingOptions(TracerRecorder recorder) { @BuildStep(onlyIf = TracerEnabled.class) @Record(ExecutionTime.STATIC_INIT) - TracerProviderBuildItem createTracerProvider(TracerRecorder recorder, + TracerProviderBuildItem createTracerProvider(OpenTelemetryConfig config, + TracerRecorder recorder, ApplicationInfoBuildItem appInfo, ShutdownContextBuildItem shutdownContext, BeanContainerBuildItem beanContainerBuildItem) { String serviceName = appInfo.getName(); String serviceVersion = appInfo.getVersion(); - return new TracerProviderBuildItem(recorder.createTracerProvider(serviceName, serviceVersion, shutdownContext)); + return new TracerProviderBuildItem( + recorder.createTracerProvider(config.tracer, serviceName, serviceVersion, shutdownContext)); } @BuildStep(onlyIf = TracerEnabled.class) @Record(ExecutionTime.RUNTIME_INIT) - void setupVertxTracer(TracerRecorder recorder) { + void setupTracer(TracerRuntimeConfig runtimeConfig, + TracerRecorder recorder) { + recorder.setupResources(runtimeConfig); + recorder.setupSampler(runtimeConfig); recorder.setupVertxTracer(); } } diff --git a/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryIdGeneratorTest.java b/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryIdGeneratorTest.java new file mode 100644 index 0000000000000..dcabfb24b8366 --- /dev/null +++ b/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryIdGeneratorTest.java @@ -0,0 +1,45 @@ +package io.quarkus.opentelemetry.deployment; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; + +import java.lang.reflect.InvocationTargetException; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.inject.Inject; + +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.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.extension.aws.trace.AwsXrayIdGenerator; +import io.opentelemetry.sdk.trace.IdGenerator; +import io.quarkus.test.QuarkusUnitTest; + +public class OpenTelemetryIdGeneratorTest { + @RegisterExtension + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClass(TestUtil.class)); + + @Inject + OpenTelemetry openTelemetry; + + @Test + void test() throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + IdGenerator idGenerator = TestUtil.getIdGenerator(openTelemetry); + + assertThat(idGenerator, instanceOf(AwsXrayIdGenerator.class)); + } + + @ApplicationScoped + public static class OtelConfiguration { + + @Produces + public IdGenerator idGenerator() { + return AwsXrayIdGenerator.getInstance(); + } + } +} diff --git a/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryPropagatorsTest.java b/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryPropagatorsTest.java new file mode 100644 index 0000000000000..4c27bad5f4808 --- /dev/null +++ b/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryPropagatorsTest.java @@ -0,0 +1,35 @@ +package io.quarkus.opentelemetry.deployment; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.ArrayMatching.arrayContainingInAnyOrder; + +import javax.inject.Inject; + +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.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.extension.aws.AwsXrayPropagator; +import io.quarkus.test.QuarkusUnitTest; + +public class OpenTelemetryPropagatorsTest { + @RegisterExtension + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() + .overrideConfigKey("quarkus.opentelemetry.propagators", "tracecontext,xray") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClass(TestUtil.class)); + + @Inject + OpenTelemetry openTelemetry; + + @Test + void test() throws NoSuchFieldException, IllegalAccessException { + TextMapPropagator[] textMapPropagators = TestUtil.getTextMapPropagators(openTelemetry); + + assertThat(textMapPropagators, arrayContainingInAnyOrder(W3CTraceContextPropagator.getInstance(), + AwsXrayPropagator.getInstance())); + } +} diff --git a/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryResourceTest.java b/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryResourceTest.java new file mode 100644 index 0000000000000..83097376f7f09 --- /dev/null +++ b/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryResourceTest.java @@ -0,0 +1,56 @@ +package io.quarkus.opentelemetry.deployment; + +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.emptyOrNullString; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.reflect.InvocationTargetException; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.inject.Inject; + +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.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.extension.resources.OsResource; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import io.quarkus.test.QuarkusUnitTest; + +public class OpenTelemetryResourceTest { + private static final String RESOURCE_ATTRIBUTES = "quarkus.opentelemetry.tracer.resource-attributes"; + + @RegisterExtension + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() + .overrideConfigKey("quarkus.opentelemetry.tracer.resources", "os") + .setBeforeAllCustomizer(() -> System.setProperty(RESOURCE_ATTRIBUTES, "service.name=authservice")) + .setAfterAllCustomizer(() -> System.getProperties().remove(RESOURCE_ATTRIBUTES)) + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(TestUtil.class) + .addAsResource("resource-config/application.properties")); + + @Inject + OpenTelemetry openTelemetry; + + @Test + void test() throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + Resource resource = TestUtil.getResource(openTelemetry); + + assertEquals("authservice", resource.getAttributes().get(ResourceAttributes.SERVICE_NAME)); + assertThat(resource.getAttributes().get(ResourceAttributes.OS_TYPE), not(emptyOrNullString())); + } + + @ApplicationScoped + public static class OtelConfiguration { + + @Produces + public Resource resource() { + return OsResource.get(); + } + } +} diff --git a/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySamplerBeanTest.java b/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySamplerBeanTest.java new file mode 100644 index 0000000000000..de4bbadbaf071 --- /dev/null +++ b/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySamplerBeanTest.java @@ -0,0 +1,44 @@ +package io.quarkus.opentelemetry.deployment; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.stringContainsInOrder; + +import java.lang.reflect.InvocationTargetException; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.inject.Inject; + +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.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.quarkus.test.QuarkusUnitTest; + +public class OpenTelemetrySamplerBeanTest { + @RegisterExtension + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClass(TestUtil.class)); + + @Inject + OpenTelemetry openTelemetry; + + @Test + void test() throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + Sampler sampler = TestUtil.getSampler(openTelemetry); + + assertThat(sampler.getDescription(), stringContainsInOrder("AlwaysOffSampler")); + } + + @ApplicationScoped + public static class OtelConfiguration { + + @Produces + public Sampler sampler() { + return Sampler.alwaysOff(); + } + } +} diff --git a/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySamplerConfigTest.java b/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySamplerConfigTest.java new file mode 100644 index 0000000000000..1548afe7f1f38 --- /dev/null +++ b/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySamplerConfigTest.java @@ -0,0 +1,35 @@ +package io.quarkus.opentelemetry.deployment; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.reflect.InvocationTargetException; + +import javax.inject.Inject; + +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.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.quarkus.test.QuarkusUnitTest; + +public class OpenTelemetrySamplerConfigTest { + @RegisterExtension + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() + .overrideConfigKey("quarkus.opentelemetry.tracer.sampler", "ratio") + .overrideConfigKey("quarkus.opentelemetry.tracer.sampler.ratio", "0.5") + .overrideConfigKey("quarkus.opentelemetry.tracer.sampler.parent-based", "false") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClass(TestUtil.class)); + + @Inject + OpenTelemetry openTelemetry; + + @Test + void test() throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + Sampler sampler = TestUtil.getSampler(openTelemetry); + + assertEquals(String.format("TraceIdRatioBased{%.6f}", 0.5d), sampler.getDescription()); + } +} diff --git a/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/TestUtil.java b/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/TestUtil.java new file mode 100644 index 0000000000000..401edd9d178f4 --- /dev/null +++ b/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/TestUtil.java @@ -0,0 +1,64 @@ +package io.quarkus.opentelemetry.deployment; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.TracerProvider; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.IdGenerator; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.quarkus.arc.Unremovable; + +@Unremovable +public final class TestUtil { + private TestUtil() { + } + + public static Object getSharedState(OpenTelemetry openTelemetry) + throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + TracerProvider tracerProvider = openTelemetry.getTracerProvider(); + Method unobfuscateMethod = tracerProvider.getClass().getDeclaredMethod("unobfuscate"); + unobfuscateMethod.setAccessible(true); + SdkTracerProvider sdkTracerProvider = (SdkTracerProvider) unobfuscateMethod.invoke(tracerProvider); + Field privateSharedStateField = sdkTracerProvider.getClass().getDeclaredField("sharedState"); + privateSharedStateField.setAccessible(true); + return privateSharedStateField.get(sdkTracerProvider); + } + + public static IdGenerator getIdGenerator(OpenTelemetry openTelemetry) + throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + Object sharedState = getSharedState(openTelemetry); + Field privateIdGeneratorField = sharedState.getClass().getDeclaredField("idGenerator"); + privateIdGeneratorField.setAccessible(true); + return (IdGenerator) privateIdGeneratorField.get(sharedState); + } + + public static Resource getResource(OpenTelemetry openTelemetry) + throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + Object sharedState = getSharedState(openTelemetry); + Field privateResourceField = sharedState.getClass().getDeclaredField("resource"); + privateResourceField.setAccessible(true); + return (Resource) privateResourceField.get(sharedState); + } + + public static Sampler getSampler(OpenTelemetry openTelemetry) + throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + Object sharedState = getSharedState(openTelemetry); + Field privateSamplerField = sharedState.getClass().getDeclaredField("sampler"); + privateSamplerField.setAccessible(true); + return (Sampler) privateSamplerField.get(sharedState); + } + + public static TextMapPropagator[] getTextMapPropagators(OpenTelemetry openTelemetry) + throws NoSuchFieldException, IllegalAccessException { + TextMapPropagator textMapPropagator = openTelemetry.getPropagators().getTextMapPropagator(); + System.out.println(textMapPropagator); + Field privatePropagatorsField = textMapPropagator.getClass().getDeclaredField("textPropagators"); + privatePropagatorsField.setAccessible(true); + return (TextMapPropagator[]) privatePropagatorsField.get(textMapPropagator); + } +} diff --git a/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/VertxOpenTelemetryTest.java b/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/VertxOpenTelemetryTest.java index f1adddbb6d747..93bdb8fca8cac 100644 --- a/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/VertxOpenTelemetryTest.java +++ b/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/VertxOpenTelemetryTest.java @@ -8,10 +8,15 @@ import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_STATUS_CODE; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_TARGET; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_USER_AGENT; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.hamcrest.collection.ArrayMatching.arrayContainingInAnyOrder; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -26,10 +31,16 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.IdGenerator; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SpanExporter; +import io.opentelemetry.sdk.trace.samplers.Sampler; import io.quarkus.runtime.StartupEvent; import io.quarkus.test.QuarkusUnitTest; import io.restassured.RestAssured; @@ -38,19 +49,26 @@ public class VertxOpenTelemetryTest { @RegisterExtension static final QuarkusUnitTest unitTest = new QuarkusUnitTest() - .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClass(TestUtil.class)); @Inject MyExporter myExporter; + @Inject + OpenTelemetry openTelemetry; + @Test - void trace() { + void trace() throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { RestAssured.when().get("/tracer").then() .statusCode(200) .body(is("Hello Tracer!")); List spans = myExporter.getFinishedSpanItems(); + TextMapPropagator[] textMapPropagators = TestUtil.getTextMapPropagators(openTelemetry); + IdGenerator idGenerator = TestUtil.getIdGenerator(openTelemetry); + Sampler sampler = TestUtil.getSampler(openTelemetry); + assertEquals(2, spans.size()); assertEquals("io.quarkus.vertx.opentelemetry", spans.get(0).getName()); assertEquals("hello!", spans.get(0).getAttributes().get(stringKey("test.message"))); @@ -60,6 +78,10 @@ void trace() { assertEquals("http", spans.get(1).getAttributes().get(HTTP_SCHEME)); assertEquals("localhost:8081", spans.get(1).getAttributes().get(HTTP_HOST)); assertEquals("127.0.0.1", spans.get(1).getAttributes().get(HTTP_CLIENT_IP)); + assertThat(textMapPropagators, arrayContainingInAnyOrder(W3CTraceContextPropagator.getInstance(), + W3CBaggagePropagator.getInstance())); + assertThat(idGenerator, instanceOf(IdGenerator.random().getClass())); + assertThat(sampler.getDescription(), stringContainsInOrder("ParentBased", "AlwaysOnSampler")); assertNotNull(spans.get(1).getAttributes().get(HTTP_USER_AGENT)); } diff --git a/extensions/opentelemetry/opentelemetry/deployment/src/test/resources/resource-config/application.properties b/extensions/opentelemetry/opentelemetry/deployment/src/test/resources/resource-config/application.properties new file mode 100644 index 0000000000000..536aa87af3e13 --- /dev/null +++ b/extensions/opentelemetry/opentelemetry/deployment/src/test/resources/resource-config/application.properties @@ -0,0 +1 @@ +quarkus.application.name=resource-test \ No newline at end of file diff --git a/extensions/opentelemetry/opentelemetry/runtime/pom.xml b/extensions/opentelemetry/opentelemetry/runtime/pom.xml index e4fde0e8f6ed1..26f4e7e6e5f5c 100644 --- a/extensions/opentelemetry/opentelemetry/runtime/pom.xml +++ b/extensions/opentelemetry/opentelemetry/runtime/pom.xml @@ -44,6 +44,16 @@ io.opentelemetry opentelemetry-sdk + + io.opentelemetry + opentelemetry-sdk-extension-autoconfigure + + + * + * + + + io.opentelemetry opentelemetry-semconv diff --git a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryConfig.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryConfig.java index 163fafd2f8f7e..16fdf7b4bca9c 100644 --- a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryConfig.java +++ b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryConfig.java @@ -1,5 +1,7 @@ package io.quarkus.opentelemetry.runtime; +import java.util.List; + import io.quarkus.opentelemetry.runtime.tracing.TracerConfig; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigRoot; @@ -15,6 +17,16 @@ public final class OpenTelemetryConfig { @ConfigItem(defaultValue = "true") public boolean enabled; + /** + * Comma separated list of OpenTelemetry propagators which must be supported. + *

+ * Valid values are {@code b3, b3multi, baggage, jaeger, ottrace, tracecontext, xray}. + *

+ * Default value is {@code traceContext,baggage} + */ + @ConfigItem(defaultValue = "tracecontext,baggage") + public List propagators; + /** Build / static runtime config for tracer */ public TracerConfig tracer; } diff --git a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryRecorder.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryRecorder.java index f427ee304d8b1..ff0f126a0e0ad 100644 --- a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryRecorder.java +++ b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryRecorder.java @@ -3,9 +3,7 @@ import java.util.function.Supplier; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.ContextStorage; -import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.OpenTelemetrySdkBuilder; import io.opentelemetry.sdk.trace.SdkTracerProvider; @@ -23,7 +21,7 @@ public void resetGlobalOpenTelemetryForDevMode() { } /* STATIC INIT */ - public void createOpenTelemetry(RuntimeValue tracerProvider) { + public void createOpenTelemetry(RuntimeValue tracerProvider, OpenTelemetryConfig openTelemetryConfig) { OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder(); // Set tracer provider if present @@ -31,8 +29,7 @@ public void createOpenTelemetry(RuntimeValue tracerProvider) builder.setTracerProvider(tracerProvider.getValue()); } - // Add propagators. //TODO Need a way to handle this with config - builder.setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())); + builder.setPropagators(OpenTelemetryUtil.mapPropagators(openTelemetryConfig.propagators)); builder.buildAndRegisterGlobal(); } diff --git a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryUtil.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryUtil.java new file mode 100644 index 0000000000000..31d572715adce --- /dev/null +++ b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryUtil.java @@ -0,0 +1,51 @@ +package io.quarkus.opentelemetry.runtime; + +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider; + +public final class OpenTelemetryUtil { + private OpenTelemetryUtil() { + } + + private static TextMapPropagator getPropagator( + String name, Map spiPropagators) { + if ("tracecontext".equals(name)) { + return W3CTraceContextPropagator.getInstance(); + } + if ("baggage".equals(name)) { + return W3CBaggagePropagator.getInstance(); + } + + TextMapPropagator spiPropagator = spiPropagators.get(name); + if (spiPropagator != null) { + return spiPropagator; + } + throw new IllegalArgumentException( + "Unrecognized value for propagator: " + name + + ". Make sure the artifact including the propagator is on the classpath."); + } + + public static ContextPropagators mapPropagators(List propagators) { + Map spiPropagators = StreamSupport.stream( + ServiceLoader.load(ConfigurablePropagatorProvider.class).spliterator(), false) + .collect( + Collectors.toMap(ConfigurablePropagatorProvider::getName, + ConfigurablePropagatorProvider::getPropagator)); + + Set selectedPropagators = propagators.stream() + .map(propagator -> getPropagator(propagator.trim(), spiPropagators)) + .collect(Collectors.toSet()); + + return ContextPropagators.create(TextMapPropagator.composite(selectedPropagators)); + } +} diff --git a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/graal/OpenTelemetrySdkAutoConfiguration.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/graal/OpenTelemetrySdkAutoConfiguration.java new file mode 100644 index 0000000000000..c12a42bb93d21 --- /dev/null +++ b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/graal/OpenTelemetrySdkAutoConfiguration.java @@ -0,0 +1,20 @@ +package io.quarkus.opentelemetry.runtime.graal; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import io.opentelemetry.sdk.OpenTelemetrySdk; + +// We can remove this substitution once AutoConfigure SPI is a separate artifact +@TargetClass(className = "io.opentelemetry.sdk.autoconfigure.OpenTelemetrySdkAutoConfiguration") +@Substitute +public final class OpenTelemetrySdkAutoConfiguration { + @Substitute + private OpenTelemetrySdkAutoConfiguration() { + } + + @Substitute + public static OpenTelemetrySdk initialize() { + return null; + } +} diff --git a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/DelayedAttributes.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/DelayedAttributes.java new file mode 100644 index 0000000000000..ba62e9275cad0 --- /dev/null +++ b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/DelayedAttributes.java @@ -0,0 +1,96 @@ +package io.quarkus.opentelemetry.runtime.tracing; + +import java.util.Collections; +import java.util.Map; +import java.util.function.BiConsumer; + +import org.jboss.logging.Logger; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; + +/** + * Class enabling Quarkus to instantiate a {@link io.opentelemetry.api.trace.TracerProvider} + * during static initialization and set a {@link Attributes} delegate during runtime initialization. + */ +public class DelayedAttributes implements Attributes { + private static final Logger log = Logger.getLogger(DelayedAttributes.class); + private boolean warningLogged = false; + + private Attributes delegate; + + /** + * Set the actual {@link Attributes} to use as the delegate. + * + * @param delegate Properly constructed {@link Attributes}. + */ + public void setAttributesDelegate(Attributes delegate) { + this.delegate = delegate; + } + + @Override + public T get(AttributeKey attributeKey) { + if (delegate == null) { + logDelegateNotFound(); + return null; + } + return delegate.get(attributeKey); + } + + @Override + public void forEach(BiConsumer, ? super Object> biConsumer) { + if (delegate == null) { + logDelegateNotFound(); + return; + } + delegate.forEach(biConsumer); + } + + @Override + public int size() { + if (delegate == null) { + logDelegateNotFound(); + return 0; + } + return delegate.size(); + } + + @Override + public boolean isEmpty() { + if (delegate == null) { + logDelegateNotFound(); + return true; + } + return delegate.isEmpty(); + } + + @Override + public Map, Object> asMap() { + if (delegate == null) { + logDelegateNotFound(); + return Collections.emptyMap(); + } + return delegate.asMap(); + } + + @Override + public AttributesBuilder toBuilder() { + if (delegate == null) { + logDelegateNotFound(); + return Attributes.builder(); + } + return delegate.toBuilder(); + } + + /** + * If we haven't previously logged an error, + * log an error about a missing {@code delegate} and set {@code warningLogged=true} + */ + private void logDelegateNotFound() { + if (!warningLogged) { + log.warn("No Attributes delegate specified, no action taken."); + warningLogged = true; + } + } +} diff --git a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/LateBoundSampler.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/LateBoundSampler.java new file mode 100644 index 0000000000000..a5068d7c39472 --- /dev/null +++ b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/LateBoundSampler.java @@ -0,0 +1,67 @@ +package io.quarkus.opentelemetry.runtime.tracing; + +import java.util.List; + +import org.jboss.logging.Logger; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.data.LinkData; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.opentelemetry.sdk.trace.samplers.SamplingDecision; +import io.opentelemetry.sdk.trace.samplers.SamplingResult; + +/** + * Class enabling Quarkus to instantiate a {@link io.opentelemetry.api.trace.TracerProvider} + * during static initialization and set a {@link Sampler} delegate during runtime initialization. + */ +public class LateBoundSampler implements Sampler { + private static final Logger log = Logger.getLogger(LateBoundSampler.class); + private boolean warningLogged = false; + + private Sampler delegate; + + /** + * Set the actual {@link Sampler} to use as the delegate. + * + * @param delegate Properly constructed {@link Sampler}. + */ + public void setSamplerDelegate(Sampler delegate) { + this.delegate = delegate; + } + + @Override + public SamplingResult shouldSample(Context parentContext, + String traceId, + String name, + SpanKind spanKind, + Attributes attributes, + List parentLinks) { + if (delegate == null) { + logDelegateNotFound(); + return SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE); + } + return delegate.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks); + } + + @Override + public String getDescription() { + if (delegate == null) { + logDelegateNotFound(); + return ""; + } + return delegate.getDescription(); + } + + /** + * If we haven't previously logged an error, + * log an error about a missing {@code delegate} and set {@code warningLogged=true} + */ + private void logDelegateNotFound() { + if (!warningLogged) { + log.warn("No Sampler delegate specified, no action taken."); + warningLogged = true; + } + } +} diff --git a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerConfig.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerConfig.java index 05f34bb968f46..acf791f2f15bf 100644 --- a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerConfig.java +++ b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerConfig.java @@ -1,5 +1,6 @@ package io.quarkus.opentelemetry.runtime.tracing; +import java.util.List; import java.util.Optional; import io.quarkus.runtime.annotations.ConfigGroup; @@ -19,6 +20,16 @@ public class TracerConfig { /** Build / static runtime config for span exporters */ public SpanExporterConfig exporter; + /** + * Comma separated list of resources that represents the entity that is + * producing telemetry. + *

+ * Valid values are {@code beanstalk, ec2, ecs, eks, host, lambda, os, + * process, processruntime}. + */ + @ConfigItem + public Optional> resources; + /** Build / static runtime config for span exporters */ @ConfigGroup public static class SpanExporterConfig { diff --git a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerProducer.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerProducer.java index a2c8126796462..4b4d0c85d9921 100644 --- a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerProducer.java +++ b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerProducer.java @@ -9,6 +9,18 @@ @Singleton public class TracerProducer { + @Produces + @Singleton + public DelayedAttributes getDelayedAttributes() { + return new DelayedAttributes(); + } + + @Produces + @Singleton + public LateBoundSampler getLateBoundSampler() { + return new LateBoundSampler(); + } + @Produces @Singleton @DefaultBean diff --git a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerRecorder.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerRecorder.java index 673296ab0ffa6..a04809de2f634 100644 --- a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerRecorder.java +++ b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerRecorder.java @@ -1,18 +1,22 @@ package io.quarkus.opentelemetry.runtime.tracing; +import java.util.Optional; import java.util.function.Consumer; import javax.enterprise.inject.Any; import javax.enterprise.inject.Instance; import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.CDI; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.IdGenerator; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; import io.opentelemetry.sdk.trace.SpanProcessor; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; +import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; import io.quarkus.arc.Arc; import io.quarkus.opentelemetry.runtime.tracing.vertx.VertxTracingAdapter; @@ -31,19 +35,40 @@ public Consumer setVertxTracingOptions() { } /* STATIC INIT */ - public RuntimeValue createTracerProvider(String serviceName, String serviceVersion, + public RuntimeValue createTracerProvider(TracerConfig config, + String serviceName, + String serviceVersion, ShutdownContext shutdownContext) { BeanManager beanManager = Arc.container().beanManager(); + + Instance idGenerator = beanManager.createInstance() + .select(IdGenerator.class, Any.Literal.INSTANCE); + SdkTracerProviderBuilder builder = SdkTracerProvider.builder(); + // Define ID Generator if present + if (idGenerator.isResolvable()) { + builder.setIdGenerator(idGenerator.get()); + } + + DelayedAttributes delayedAttributes = beanManager.createInstance() + .select(DelayedAttributes.class, Any.Literal.INSTANCE).get(); + + delayedAttributes.setAttributesDelegate(Resource.getDefault() + .merge(Resource.create( + Attributes.of( + ResourceAttributes.SERVICE_NAME, serviceName, + ResourceAttributes.SERVICE_VERSION, serviceVersion))) + .getAttributes()); + // Define Service Resource - builder.setResource( - Resource.getDefault() - .merge( - Resource.create( - Attributes.of( - ResourceAttributes.SERVICE_NAME, serviceName, - ResourceAttributes.SERVICE_VERSION, serviceVersion)))); + builder.setResource(Resource.create(delayedAttributes)); + + LateBoundSampler lateBoundSampler = beanManager.createInstance() + .select(LateBoundSampler.class, Any.Literal.INSTANCE).get(); + + // Set LateBoundSampler + builder.setSampler(lateBoundSampler); // Find all SpanExporter instances Instance allExporters = beanManager.createInstance() @@ -70,4 +95,49 @@ public RuntimeValue createTracerProvider(String serviceName, public void setupVertxTracer() { vertxTracingAdapter.init(); } + + /* RUNTIME INIT */ + public void setupResources(TracerRuntimeConfig config) { + // Find all Resource instances + Instance allResources = CDI.current() + .getBeanManager() + .createInstance() + .select(Resource.class, Any.Literal.INSTANCE); + + // Merge resource instances with env attributes + Resource resource = allResources.stream() + .reduce(Resource.empty(), Resource::merge) + .merge(config.resourceAttributes + .map(TracerUtil::mapResourceAttributes) + .orElseGet(Resource::empty)); + + // Update Delayed attributes to contain new runtime attributes if necessary + if (resource.getAttributes().size() > 0) { + DelayedAttributes delayedAttributes = CDI.current() + .select(DelayedAttributes.class).get(); + + delayedAttributes.setAttributesDelegate( + delayedAttributes.toBuilder() + .putAll(resource.getAttributes()) + .build()); + } + } + + /* RUNTIME INIT */ + public void setupSampler(TracerRuntimeConfig config) { + LateBoundSampler lateBoundSampler = CDI.current().select(LateBoundSampler.class, Any.Literal.INSTANCE).get(); + Optional samplerBean = CDI.current() + .select(Sampler.class, Any.Literal.INSTANCE) + .stream() + .filter(o -> !(o instanceof LateBoundSampler)) + .findFirst(); + + // Define Sampler using bean if present + if (samplerBean.isPresent()) { + lateBoundSampler.setSamplerDelegate(samplerBean.get()); + } else { + // Define Sampler using config + lateBoundSampler.setSamplerDelegate(TracerUtil.mapSampler(config.sampler)); + } + } } diff --git a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerRuntimeConfig.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerRuntimeConfig.java new file mode 100644 index 0000000000000..17b484688c6e6 --- /dev/null +++ b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerRuntimeConfig.java @@ -0,0 +1,54 @@ +package io.quarkus.opentelemetry.runtime.tracing; + +import java.util.List; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "opentelemetry.tracer", phase = ConfigPhase.RUN_TIME) +public class TracerRuntimeConfig { + + /** + * A comma separated list of name=value resource attributes that + * represents the entity producing telemetry + * (eg. {@code service.name=authservice}). + */ + @ConfigItem + Optional> resourceAttributes; + + /** Config for sampler */ + public SamplerConfig sampler; + + @ConfigGroup + public static class SamplerConfig { + /** + * The sampler to use for tracing + *

+ * Valid values are {@code off, on, ratio}. + *

+ * Defaults to {@code on}. + */ + @ConfigItem(name = ConfigItem.PARENT, defaultValue = "on") + public String samplerName; + + /** + * The sampler ratio to use for tracing + *

+ * Only supported by the {@code ratio} sampler. + */ + public Optional ratio; + + /** + * If the sampler to use for tracing is parent based + *

+ * Valid values are {@code true, false}. + *

+ * Defaults to {@code true}. + */ + @ConfigItem(defaultValue = "true") + public Boolean parentBased; + } +} diff --git a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerUtil.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerUtil.java new file mode 100644 index 0000000000000..a637db71a3fd4 --- /dev/null +++ b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerUtil.java @@ -0,0 +1,53 @@ +package io.quarkus.opentelemetry.runtime.tracing; + +import java.util.AbstractMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.samplers.Sampler; + +public class TracerUtil { + private TracerUtil() { + } + + public static Resource mapResourceAttributes(List resourceAttributes) { + AttributesBuilder attributesBuilder = Attributes.builder(); + + resourceAttributes.stream() + .map(keyValuePair -> keyValuePair.split("=", 2)) + .map(keyValuePair -> new AbstractMap.SimpleImmutableEntry<>(keyValuePair[0].trim(), keyValuePair[1].trim())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (first, next) -> next, LinkedHashMap::new)) + .forEach(attributesBuilder::put); + + return Resource.create(attributesBuilder.build()); + } + + private static Sampler getBaseSampler(String samplerName, Optional ratio) { + switch (samplerName) { + case "on": + return Sampler.alwaysOn(); + case "off": + return Sampler.alwaysOff(); + case "ratio": + return Sampler.traceIdRatioBased(ratio.orElse(1.0d)); + default: + throw new IllegalArgumentException("Unrecognized value for sampler: " + samplerName); + } + } + + public static Sampler mapSampler(TracerRuntimeConfig.SamplerConfig samplerConfig) { + Sampler sampler = getBaseSampler(samplerConfig.samplerName, samplerConfig.ratio); + + if (samplerConfig.parentBased) { + return Sampler.parentBased(sampler); + } + + return sampler; + } +}