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 index 50d41be0d81f7..1548afe7f1f38 100644 --- 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 @@ -18,8 +18,9 @@ public class OpenTelemetrySamplerConfigTest { @RegisterExtension static final QuarkusUnitTest unitTest = new QuarkusUnitTest() - .overrideConfigKey("quarkus.opentelemetry.tracer.sampler", "traceidratio") + .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 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 index ee7f313a03e45..ba62e9275cad0 100644 --- 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 @@ -1,46 +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 index cbdd6b0bbf4c7..a5068d7c39472 100644 --- 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 @@ -2,16 +2,31 @@ 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; } @@ -23,11 +38,30 @@ public SamplingResult shouldSample(Context parentContext, 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/TracerRecorder.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerRecorder.java index 0da96cfae71b8..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 @@ -137,7 +137,7 @@ public void setupSampler(TracerRuntimeConfig config) { lateBoundSampler.setSamplerDelegate(samplerBean.get()); } else { // Define Sampler using config - lateBoundSampler.setSamplerDelegate(TracerUtil.mapSampler(config.sampler, config.samplerRatio)); + 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 index 89aa208c7d70c..ec22401d51369 100644 --- 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 @@ -3,6 +3,7 @@ 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; @@ -18,23 +19,36 @@ public class TracerRuntimeConfig { @ConfigItem Optional> resourceAttributes; - /** - * The sampler to use for tracing - *

- * Valid values are {@code always_off, always_on, parentbased_always_off, - * parentbased_always_on, parentbased_traceidratio, traceidratio}. - *

- * Defaults to {@code parentbased_always_on}. - */ - @ConfigItem(defaultValue = "parentbased_always_on") - public String sampler; + /** Config for sampler */ + public SamplerConfig sampler; - /** - * The sampler ratio to use for tracing. - *

- * Only supported by the {@code parentbased_traceidratio, traceidratio} - * samplers. - */ - @ConfigItem(name = "sampler.ratio") - public Optional samplerRatio; + @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.z + *

+ * 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 index 2b39968e4a484..a637db71a3fd4 100644 --- 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 @@ -28,22 +28,26 @@ public static Resource mapResourceAttributes(List resourceAttributes) { return Resource.create(attributesBuilder.build()); } - public static Sampler mapSampler(String sampler, Optional ratio) { - switch (sampler) { - case "always_on": + private static Sampler getBaseSampler(String samplerName, Optional ratio) { + switch (samplerName) { + case "on": return Sampler.alwaysOn(); - case "always_off": + case "off": return Sampler.alwaysOff(); - case "traceidratio": + case "ratio": return Sampler.traceIdRatioBased(ratio.orElse(1.0d)); - case "parentbased_always_on": - return Sampler.parentBased(Sampler.alwaysOn()); - case "parentbased_always_off": - return Sampler.parentBased(Sampler.alwaysOff()); - case "parentbased_traceidratio": - return Sampler.parentBased(Sampler.traceIdRatioBased(ratio.orElse(1.0d))); default: - throw new IllegalArgumentException("Unrecognized value for sampler: " + sampler); + 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; + } }