diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java index 39d54355bcda5..1233d54d3567d 100644 --- a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java +++ b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java @@ -49,6 +49,7 @@ import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.util.ServiceUtil; import io.quarkus.maven.dependency.ArtifactKey; +import io.quarkus.opentelemetry.runtime.AutoConfiguredOpenTelemetrySdkBuilderCustomizer; import io.quarkus.opentelemetry.runtime.OpenTelemetryProducer; import io.quarkus.opentelemetry.runtime.OpenTelemetryRecorder; import io.quarkus.opentelemetry.runtime.QuarkusContextStorage; @@ -72,7 +73,10 @@ public class OpenTelemetryProcessor { AdditionalBeanBuildItem ensureProducerIsRetained() { return AdditionalBeanBuildItem.builder() .setUnremovable() - .addBeanClass(OpenTelemetryProducer.class) + .addBeanClasses(OpenTelemetryProducer.class, + AutoConfiguredOpenTelemetrySdkBuilderCustomizer.ResourceCustomizer.class, + AutoConfiguredOpenTelemetrySdkBuilderCustomizer.SamplerCustomizer.class, + AutoConfiguredOpenTelemetrySdkBuilderCustomizer.TracerProviderCustomizer.class) .build(); } 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 new file mode 100644 index 0000000000000..87c0f35ffff9f --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/AutoConfiguredOpenTelemetrySdkBuilderCustomizer.java @@ -0,0 +1,167 @@ +package io.quarkus.opentelemetry.runtime; + +import static java.lang.Boolean.TRUE; +import static java.util.Collections.emptyList; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +import jakarta.enterprise.inject.Any; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Singleton; + +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.IdGenerator; +import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; +import io.opentelemetry.sdk.trace.SpanProcessor; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.quarkus.arc.All; +import io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig; +import io.quarkus.opentelemetry.runtime.config.runtime.OTelRuntimeConfig; +import io.quarkus.opentelemetry.runtime.exporter.otlp.RemoveableLateBoundBatchSpanProcessor; +import io.quarkus.opentelemetry.runtime.tracing.DelayedAttributes; +import io.quarkus.opentelemetry.runtime.tracing.DropTargetsSampler; +import io.quarkus.opentelemetry.runtime.tracing.TracerRecorder; +import io.quarkus.opentelemetry.runtime.tracing.TracerUtil; +import io.quarkus.runtime.ApplicationConfig; + +public interface AutoConfiguredOpenTelemetrySdkBuilderCustomizer { + + void customize(AutoConfiguredOpenTelemetrySdkBuilder builder); + + @Singleton + final class ResourceCustomizer implements AutoConfiguredOpenTelemetrySdkBuilderCustomizer { + + private final ApplicationConfig appConfig; + private final OTelBuildConfig oTelBuildConfig; + private final OTelRuntimeConfig oTelRuntimeConfig; + private final Instance delayedAttributes; + private final List resources; + + public ResourceCustomizer(ApplicationConfig appConfig, + OTelBuildConfig oTelBuildConfig, + OTelRuntimeConfig oTelRuntimeConfig, + @Any Instance delayedAttributes, + @All List resources) { + this.appConfig = appConfig; + this.oTelBuildConfig = oTelBuildConfig; + this.oTelRuntimeConfig = oTelRuntimeConfig; + this.delayedAttributes = delayedAttributes; + this.resources = resources; + } + + @Override + public void customize(AutoConfiguredOpenTelemetrySdkBuilder builder) { + builder.addResourceCustomizer(new BiFunction<>() { + @Override + public Resource apply(Resource existingResource, ConfigProperties configProperties) { + if (oTelBuildConfig.traces().enabled().orElse(TRUE)) { + Resource consolidatedResource = existingResource.merge( + Resource.create(delayedAttributes.get())); + + // if user explicitly set 'otel.service.name', make sure we don't override it with defaults + // inside resource customizer + String serviceName = oTelRuntimeConfig + .serviceName() + .filter(sn -> !sn.equals(appConfig.name.orElse("unset"))) + .orElse(null); + + // Merge resource instances with env attributes + Resource resource = resources.stream() + .reduce(Resource.empty(), Resource::merge) + .merge(TracerUtil.mapResourceAttributes( + oTelRuntimeConfig.resourceAttributes().orElse(emptyList()), + serviceName)); // from properties + return consolidatedResource.merge(resource); + } else { + return Resource.builder().build(); + } + } + }); + } + } + + @Singleton + final class SamplerCustomizer implements AutoConfiguredOpenTelemetrySdkBuilderCustomizer { + + private final OTelBuildConfig oTelBuildConfig; + private final OTelRuntimeConfig oTelRuntimeConfig; + private final List sampler; + + public SamplerCustomizer(OTelBuildConfig oTelBuildConfig, + OTelRuntimeConfig oTelRuntimeConfig, + @All List sampler) { + this.oTelBuildConfig = oTelBuildConfig; + this.oTelRuntimeConfig = oTelRuntimeConfig; + this.sampler = sampler; + } + + @Override + public void customize(AutoConfiguredOpenTelemetrySdkBuilder builder) { + builder.addSamplerCustomizer(new BiFunction<>() { + @Override + public Sampler apply(Sampler existingSampler, ConfigProperties configProperties) { + if (oTelBuildConfig.traces().enabled().orElse(TRUE)) { + final Sampler effectiveSampler = sampler.stream().findFirst() + .map(Sampler.class::cast)// use CDI if it exists + .orElse(existingSampler); + + //collect default filtering targets (Needed for all samplers) + List dropTargets = new ArrayList<>(); + if (oTelRuntimeConfig.traces().suppressNonApplicationUris()) {//default is true + dropTargets.addAll(TracerRecorder.dropNonApplicationUriTargets); + } + if (!oTelRuntimeConfig.traces().includeStaticResources()) {// default is false + dropTargets.addAll(TracerRecorder.dropStaticResourceTargets); + } + + // make sure dropped targets are not sampled + if (!dropTargets.isEmpty()) { + return new DropTargetsSampler(effectiveSampler, dropTargets); + } else { + return effectiveSampler; + } + } else { + return Sampler.alwaysOff(); + } + } + }); + } + } + + @Singleton + final class TracerProviderCustomizer implements AutoConfiguredOpenTelemetrySdkBuilderCustomizer { + + private final OTelBuildConfig oTelBuildConfig; + private final List idGenerator; + private final List spanProcessors; + + public TracerProviderCustomizer(OTelBuildConfig oTelBuildConfig, + @All List idGenerator, + @All List spanProcessors) { + this.oTelBuildConfig = oTelBuildConfig; + this.idGenerator = idGenerator; + this.spanProcessors = spanProcessors; + } + + @Override + public void customize(AutoConfiguredOpenTelemetrySdkBuilder builder) { + builder.addTracerProviderCustomizer( + new BiFunction<>() { + @Override + public SdkTracerProviderBuilder apply(SdkTracerProviderBuilder builder, + ConfigProperties configProperties) { + if (oTelBuildConfig.traces().enabled().orElse(TRUE)) { + idGenerator.stream().findFirst().ifPresent(builder::setIdGenerator); // from cdi + spanProcessors.stream().filter(sp -> !(sp instanceof RemoveableLateBoundBatchSpanProcessor)) + .forEach(builder::addSpanProcessor); + } + return builder; + } + }); + } + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryProducer.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryProducer.java index ba1ca3cd57436..254df7db04955 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryProducer.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryProducer.java @@ -1,19 +1,11 @@ package io.quarkus.opentelemetry.runtime; -import static java.lang.Boolean.TRUE; -import static java.util.Collections.emptyList; - -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.BiFunction; -import jakarta.enterprise.inject.Any; import jakarta.enterprise.inject.Disposes; -import jakarta.enterprise.inject.Instance; import jakarta.enterprise.inject.Produces; -import jakarta.inject.Inject; import jakarta.inject.Singleton; import org.eclipse.microprofile.config.ConfigProvider; @@ -21,21 +13,9 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.trace.IdGenerator; -import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; -import io.opentelemetry.sdk.trace.SpanProcessor; -import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.quarkus.arc.All; import io.quarkus.arc.DefaultBean; -import io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig; import io.quarkus.opentelemetry.runtime.config.runtime.OTelRuntimeConfig; -import io.quarkus.opentelemetry.runtime.exporter.otlp.RemoveableLateBoundBatchSpanProcessor; -import io.quarkus.opentelemetry.runtime.tracing.DelayedAttributes; -import io.quarkus.opentelemetry.runtime.tracing.DropTargetsSampler; -import io.quarkus.opentelemetry.runtime.tracing.TracerRecorder; -import io.quarkus.opentelemetry.runtime.tracing.TracerUtil; -import io.quarkus.runtime.ApplicationConfig; import io.smallrye.config.ConfigValue; import io.smallrye.config.NameIterator; import io.smallrye.config.SmallRyeConfig; @@ -43,28 +23,6 @@ @Singleton public class OpenTelemetryProducer { - @Inject - Instance idGenerator; - @Inject - @Any - Instance resources; - @Inject - @Any - Instance delayedAttributes; - @Inject - @Any - Instance sampler; - @Inject - @Any - Instance spanProcessors; - @Inject - OTelBuildConfig oTelBuildConfig; - @Inject - OTelRuntimeConfig oTelRuntimeConfig; - - @Inject - ApplicationConfig appConfig; - public void disposeOfOpenTelemetry(@Disposes OpenTelemetry openTelemetry) { if (openTelemetry instanceof OpenTelemetrySdk) { var openTelemetrySdk = ((OpenTelemetrySdk) openTelemetry); @@ -76,7 +34,8 @@ public void disposeOfOpenTelemetry(@Disposes OpenTelemetry openTelemetry) { @Produces @Singleton @DefaultBean - public OpenTelemetry getOpenTelemetry() { + public OpenTelemetry getOpenTelemetry(OTelRuntimeConfig oTelRuntimeConfig, + @All List builderCustomizers) { final Map oTelConfigs = getOtelConfigs(); if (oTelRuntimeConfig.sdkDisabled()) { @@ -88,81 +47,16 @@ public OpenTelemetry getOpenTelemetry() { .getOpenTelemetrySdk(); } - final AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk = AutoConfiguredOpenTelemetrySdk.builder() + var builder = AutoConfiguredOpenTelemetrySdk.builder() .setResultAsGlobal(true) .registerShutdownHook(false) .addPropertiesSupplier(() -> oTelConfigs) - .setServiceClassLoader(Thread.currentThread().getContextClassLoader()) - // no customization needed for spanExporter. Loads SPI from CDI - .addResourceCustomizer(new BiFunction() { - @Override - public Resource apply(Resource existingResource, ConfigProperties configProperties) { - if (oTelBuildConfig.traces().enabled().orElse(TRUE)) { - Resource consolidatedResource = existingResource.merge( - Resource.create(delayedAttributes.get())); // from cdi - - // if user explicitly set 'otel.service.name', make sure we don't override it with defaults - // inside resource customizer - String serviceName = oTelRuntimeConfig - .serviceName() - .filter(sn -> !sn.equals(appConfig.name.orElse("unset"))) - .orElse(null); - - // Merge resource instances with env attributes - Resource resource = resources.stream() - .reduce(Resource.empty(), Resource::merge) - .merge(TracerUtil.mapResourceAttributes( - oTelRuntimeConfig.resourceAttributes().orElse(emptyList()), - serviceName)); // from properties - return consolidatedResource.merge(resource); - } else { - return Resource.builder().build(); - } - } - }) - .addSamplerCustomizer(new BiFunction() { - @Override - public Sampler apply(Sampler existingSampler, ConfigProperties configProperties) { - if (oTelBuildConfig.traces().enabled().orElse(TRUE)) { - final Sampler effectiveSampler = sampler.stream().findFirst() - .map(Sampler.class::cast)// use CDI if it exists - .orElse(existingSampler); - - //collect default filtering targets (Needed for all samplers) - List dropTargets = new ArrayList<>(); - if (oTelRuntimeConfig.traces().suppressNonApplicationUris()) {//default is true - dropTargets.addAll(TracerRecorder.dropNonApplicationUriTargets); - } - if (!oTelRuntimeConfig.traces().includeStaticResources()) {// default is false - dropTargets.addAll(TracerRecorder.dropStaticResourceTargets); - } + .setServiceClassLoader(Thread.currentThread().getContextClassLoader()); + for (var customizer : builderCustomizers) { + customizer.customize(builder); + } - // make sure dropped targets are not sampled - if (!dropTargets.isEmpty()) { - return new DropTargetsSampler(effectiveSampler, dropTargets); - } else { - return effectiveSampler; - } - } else { - return Sampler.alwaysOff(); - } - } - }) - .addTracerProviderCustomizer( - new BiFunction() { - @Override - public SdkTracerProviderBuilder apply(SdkTracerProviderBuilder builder, - ConfigProperties configProperties) { - if (oTelBuildConfig.traces().enabled().orElse(TRUE)) { - idGenerator.stream().findFirst().ifPresent(builder::setIdGenerator); // from cdi - spanProcessors.stream().filter(sp -> !(sp instanceof RemoveableLateBoundBatchSpanProcessor)) - .forEach(builder::addSpanProcessor); - } - return builder; - } - }) - .build(); - return autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk(); + return builder.build().getOpenTelemetrySdk(); } private Map getOtelConfigs() {