From e3b403d6da8901fa375df41fff6ebb128f1c0e4f Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Thu, 8 May 2025 14:53:22 +0200 Subject: [PATCH 01/10] Implement span live and ended metrics --- .../sdk/internal/SemConvAttributes.java | 3 + .../sdk/internal/SemConvAttributesTest.java | 2 + sdk/trace/build.gradle.kts | 1 + .../sdk/trace/NonRecordingSpan.java | 133 +++++++++++ .../io/opentelemetry/sdk/trace/SdkSpan.java | 14 +- .../sdk/trace/SdkSpanBuilder.java | 8 +- .../sdk/trace/SdkTracerProvider.java | 26 +- .../sdk/trace/SdkTracerProviderBuilder.java | 31 ++- .../sdk/trace/TracerSharedState.java | 10 +- .../internal/metrics/NoopSpanMetrics.java | 26 ++ .../internal/metrics/SemConvSpanMetrics.java | 120 ++++++++++ .../trace/internal/metrics/SpanMetrics.java | 30 +++ .../sdk/trace/SdkSpanBuilderTest.java | 223 ++++++++++++++++++ .../opentelemetry/sdk/trace/SdkSpanTest.java | 14 +- .../metrics/SemConvSpanMetricsTest.java | 38 +++ 15 files changed, 666 insertions(+), 13 deletions(-) create mode 100644 sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java create mode 100644 sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/NoopSpanMetrics.java create mode 100644 sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanMetrics.java create mode 100644 sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SpanMetrics.java create mode 100644 sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanMetricsTest.java diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/internal/SemConvAttributes.java b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/SemConvAttributes.java index 1075d537604..7813fc819d2 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/internal/SemConvAttributes.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/SemConvAttributes.java @@ -25,6 +25,9 @@ private SemConvAttributes() {} AttributeKey.stringKey("otel.component.name"); public static final AttributeKey ERROR_TYPE = AttributeKey.stringKey("error.type"); + public static final AttributeKey OTEL_SPAN_SAMPLING_RESULT = + AttributeKey.stringKey("otel.span.sampling_result"); + public static final AttributeKey SERVER_ADDRESS = AttributeKey.stringKey("server.address"); public static final AttributeKey SERVER_PORT = AttributeKey.longKey("server.port"); diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/internal/SemConvAttributesTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/internal/SemConvAttributesTest.java index d4a5d5de1f5..1b4df0b84a5 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/internal/SemConvAttributesTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/internal/SemConvAttributesTest.java @@ -22,6 +22,8 @@ void testAttributeKeys() { .isEqualTo(OtelIncubatingAttributes.OTEL_COMPONENT_NAME); assertThat(SemConvAttributes.OTEL_COMPONENT_TYPE) .isEqualTo(OtelIncubatingAttributes.OTEL_COMPONENT_TYPE); + assertThat(SemConvAttributes.OTEL_SPAN_SAMPLING_RESULT) + .isEqualTo(OtelIncubatingAttributes.OTEL_SPAN_SAMPLING_RESULT); assertThat(SemConvAttributes.ERROR_TYPE).isEqualTo(ErrorAttributes.ERROR_TYPE); diff --git a/sdk/trace/build.gradle.kts b/sdk/trace/build.gradle.kts index 35d30c8513b..e644b724e5b 100644 --- a/sdk/trace/build.gradle.kts +++ b/sdk/trace/build.gradle.kts @@ -32,6 +32,7 @@ dependencies { testImplementation(project(":sdk:testing")) testImplementation("com.google.guava:guava") testImplementation("com.google.guava:guava-testlib") + testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") jmh(project(":sdk:metrics")) jmh(project(":sdk:testing")) { diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java new file mode 100644 index 00000000000..7e93741d1a0 --- /dev/null +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java @@ -0,0 +1,133 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.sdk.trace.internal.metrics.SpanMetrics; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * Span implementation used from {@link io.opentelemetry.sdk.trace.SdkTracer} when starting a span + * which is not recording. All operations are noop, except for {@link #end()}, which ensures health + * metrics are still collected. + */ +@Immutable +final class NonRecordingSpan implements Span { + + private final SpanContext spanContext; + private final SpanMetrics.Recording metricRecording; + + NonRecordingSpan(SpanContext spanContext, SpanMetrics.Recording metricRecording) { + this.spanContext = spanContext; + this.metricRecording = metricRecording; + } + + @Override + public Span setAttribute(String key, @Nullable String value) { + return this; + } + + @Override + public Span setAttribute(String key, long value) { + return this; + } + + @Override + public Span setAttribute(String key, double value) { + return this; + } + + @Override + public Span setAttribute(String key, boolean value) { + return this; + } + + @Override + public Span setAttribute(AttributeKey key, @Nullable T value) { + return this; + } + + @Override + public Span setAllAttributes(Attributes attributes) { + return this; + } + + @Override + public Span addEvent(String name) { + return this; + } + + @Override + public Span addEvent(String name, long timestamp, TimeUnit unit) { + return this; + } + + @Override + public Span addEvent(String name, Attributes attributes) { + return this; + } + + @Override + public Span addEvent(String name, Attributes attributes, long timestamp, TimeUnit unit) { + return this; + } + + @Override + public Span setStatus(StatusCode statusCode) { + return this; + } + + @Override + public Span setStatus(StatusCode statusCode, String description) { + return this; + } + + @Override + public Span recordException(Throwable exception) { + return this; + } + + @Override + public Span recordException(Throwable exception, Attributes additionalAttributes) { + return this; + } + + @Override + public Span updateName(String name) { + return this; + } + + @Override + public void end() { + metricRecording.recordSpanEnd(); + } + + @Override + public void end(long timestamp, TimeUnit unit) { + end(); + } + + @Override + public SpanContext getSpanContext() { + return spanContext; + } + + @Override + public boolean isRecording() { + return false; + } + + @Override + public String toString() { + return "NonRecordingSpan{" + spanContext + '}'; + } +} diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java index 37deab7ffc8..27acbe0b789 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java @@ -26,6 +26,7 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.data.StatusData; import io.opentelemetry.sdk.trace.internal.ExtendedSpanProcessor; +import io.opentelemetry.sdk.trace.internal.metrics.SpanMetrics; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -99,6 +100,8 @@ final class SdkSpan implements ReadWriteSpan { @GuardedBy("lock") private long endEpochNanos; + private final SpanMetrics.Recording metricRecording; + private enum EndState { NOT_ENDED, ENDING, @@ -132,7 +135,8 @@ private SdkSpan( @Nullable AttributesMap attributes, @Nullable List links, int totalRecordedLinks, - long startEpochNanos) { + long startEpochNanos, + SpanMetrics.Recording metricRecording) { this.context = context; this.instrumentationScopeInfo = instrumentationScopeInfo; this.parentSpanContext = parentSpanContext; @@ -143,6 +147,7 @@ private SdkSpan( this.spanProcessor = spanProcessor; this.exceptionAttributeResolver = exceptionAttributeResolver; this.resource = resource; + this.metricRecording = metricRecording; this.hasEnded = EndState.NOT_ENDED; this.clock = clock; this.startEpochNanos = startEpochNanos; @@ -180,7 +185,8 @@ static SdkSpan startSpan( @Nullable AttributesMap attributes, @Nullable List links, int totalRecordedLinks, - long userStartEpochNanos) { + long userStartEpochNanos, + SpanMetrics.Recording metricsRecording) { boolean createdAnchoredClock; AnchoredClock clock; if (parentSpan instanceof SdkSpan) { @@ -219,7 +225,8 @@ static SdkSpan startSpan( attributes, links, totalRecordedLinks, - startEpochNanos); + startEpochNanos, + metricsRecording); // Call onStart here instead of calling in the constructor to make sure the span is completely // initialized. if (spanProcessor.isStartRequired()) { @@ -557,6 +564,7 @@ private void endInternal(long endEpochNanos) { spanEndingThread = Thread.currentThread(); hasEnded = EndState.ENDING; } + metricRecording.recordSpanEnd(); if (spanProcessor instanceof ExtendedSpanProcessor) { ExtendedSpanProcessor extendedSpanProcessor = (ExtendedSpanProcessor) spanProcessor; if (extendedSpanProcessor.isOnEndingRequired()) { diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java index c0f872265ec..2a4ff79cc30 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java @@ -24,6 +24,7 @@ import io.opentelemetry.sdk.internal.AttributeUtil; import io.opentelemetry.sdk.internal.AttributesMap; import io.opentelemetry.sdk.trace.data.LinkData; +import io.opentelemetry.sdk.trace.internal.metrics.SpanMetrics; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; import io.opentelemetry.sdk.trace.samplers.SamplingResult; import java.util.ArrayList; @@ -204,8 +205,10 @@ public Span startSpan() { /* remote= */ false, tracerSharedState.isIdGeneratorSafeToSkipIdValidation()); + SpanMetrics.Recording metricsRecording = + tracerSharedState.getSpanMetrics().recordSpanStart(samplingResult); if (!isRecording(samplingDecision)) { - return Span.wrap(spanContext); + return new NonRecordingSpan(spanContext, metricsRecording); } Attributes samplingAttributes = samplingResult.getAttributes(); if (!samplingAttributes.isEmpty()) { @@ -232,7 +235,8 @@ public Span startSpan() { recordedAttributes, currentLinks, totalNumberOfLinksAdded, - startEpochNanos); + startEpochNanos, + metricsRecording); } private AttributesMap attributes() { diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java index f39ce565731..d6754338a22 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java @@ -5,18 +5,22 @@ package io.opentelemetry.sdk.trace; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.TracerBuilder; import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.sdk.common.Clock; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.common.InternalTelemetryVersion; import io.opentelemetry.sdk.internal.ComponentRegistry; import io.opentelemetry.sdk.internal.ExceptionAttributeResolver; import io.opentelemetry.sdk.internal.ScopeConfigurator; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil; import io.opentelemetry.sdk.trace.internal.TracerConfig; +import io.opentelemetry.sdk.trace.internal.metrics.SemConvSpanMetrics; +import io.opentelemetry.sdk.trace.internal.metrics.SpanMetrics; import io.opentelemetry.sdk.trace.samplers.Sampler; import java.io.Closeable; import java.util.List; @@ -54,7 +58,9 @@ public static SdkTracerProviderBuilder builder() { Sampler sampler, List spanProcessors, ScopeConfigurator tracerConfigurator, - ExceptionAttributeResolver exceptionAttributeResolver) { + ExceptionAttributeResolver exceptionAttributeResolver, + InternalTelemetryVersion internalTelemetryVersion, + Supplier meterProviderSupplier) { this.sharedState = new TracerSharedState( clock, @@ -63,7 +69,8 @@ public static SdkTracerProviderBuilder builder() { spanLimitsSupplier, sampler, spanProcessors, - exceptionAttributeResolver); + exceptionAttributeResolver, + createSpanMetrics(internalTelemetryVersion, meterProviderSupplier)); this.tracerSdkComponentRegistry = new ComponentRegistry<>( instrumentationScopeInfo -> @@ -74,6 +81,21 @@ public static SdkTracerProviderBuilder builder() { this.tracerConfigurator = tracerConfigurator; } + private static SpanMetrics createSpanMetrics( + InternalTelemetryVersion internalTelemetryVersion, + Supplier meterProviderSupplier) { + switch (internalTelemetryVersion) { + case LEGACY: + case DISABLED: + return SpanMetrics.noop(); + case V1_33: + case LATEST: + return new SemConvSpanMetrics(meterProviderSupplier); + } + throw new IllegalStateException( + "Unhandled telemetry schema version: " + internalTelemetryVersion); + } + private TracerConfig getTracerConfig(InstrumentationScopeInfo instrumentationScopeInfo) { TracerConfig tracerConfig = tracerConfigurator.apply(instrumentationScopeInfo); return tracerConfig == null ? TracerConfig.defaultConfig() : tracerConfig; diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java index 194aa67bc17..14e27bc1fab 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java @@ -7,11 +7,14 @@ import static java.util.Objects.requireNonNull; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.trace.Span; import io.opentelemetry.sdk.common.Clock; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.internal.DefaultExceptionAttributeResolver; import io.opentelemetry.sdk.internal.ExceptionAttributeResolver; +import io.opentelemetry.sdk.common.InternalTelemetryVersion; import io.opentelemetry.sdk.internal.ScopeConfigurator; import io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder; import io.opentelemetry.sdk.resources.Resource; @@ -35,6 +38,9 @@ public final class SdkTracerProviderBuilder { private Resource resource = Resource.getDefault(); private Supplier spanLimitsSupplier = SpanLimits::getDefault; private Sampler sampler = DEFAULT_SAMPLER; + private Supplier meterProviderSupplier = GlobalOpenTelemetry::getMeterProvider; + private InternalTelemetryVersion internalTelemetryVersion = + InternalTelemetryVersion.DISABLED; private ScopeConfiguratorBuilder tracerConfiguratorBuilder = TracerConfig.configuratorBuilder(); private ExceptionAttributeResolver exceptionAttributeResolver = @@ -177,6 +183,27 @@ public SdkTracerProviderBuilder addSpanProcessorFirst(SpanProcessor spanProcesso return this; } + /** + * Sets the {@link MeterProvider} supplier used to collect self-monitoring metrics. If not set, + * uses {@link GlobalOpenTelemetry#getMeterProvider()}. + */ + public SdkTracerProviderBuilder setMeterProvider(Supplier meterProviderSupplier) { + requireNonNull(meterProviderSupplier, "meterProviderSupplier"); + this.meterProviderSupplier = meterProviderSupplier; + return this; + } + + /** + * Sets the {@link InternalTelemetryVersion} defining which self-monitoring metrics the + * tracers originating from this provider collect. + */ + public SdkTracerProviderBuilder setInternalTelemetry( + InternalTelemetryVersion internalTelemetryVersion) { + requireNonNull(internalTelemetryVersion, "internalTelemetryVersion"); + this.internalTelemetryVersion = internalTelemetryVersion; + return this; + } + /** * Set the tracer configurator, which computes {@link TracerConfig} for each {@link * InstrumentationScopeInfo}. @@ -247,7 +274,9 @@ public SdkTracerProvider build() { sampler, spanProcessors, tracerConfiguratorBuilder.build(), - exceptionAttributeResolver); + exceptionAttributeResolver, + internalTelemetryVersion, + meterProviderSupplier); } SdkTracerProviderBuilder() {} diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java index 74d43076b97..c7c75881ed3 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java @@ -9,6 +9,7 @@ import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.internal.ExceptionAttributeResolver; import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.internal.metrics.SpanMetrics; import io.opentelemetry.sdk.trace.samplers.Sampler; import java.util.List; import java.util.function.Supplier; @@ -28,6 +29,7 @@ final class TracerSharedState { private final Sampler sampler; private final SpanProcessor activeSpanProcessor; private final ExceptionAttributeResolver exceptionAttributeResolver; + private final SpanMetrics spanMetrics; @Nullable private volatile CompletableResultCode shutdownResult = null; @@ -38,7 +40,8 @@ final class TracerSharedState { Supplier spanLimitsSupplier, Sampler sampler, List spanProcessors, - ExceptionAttributeResolver exceptionAttributeResolver) { + ExceptionAttributeResolver exceptionAttributeResolver, + SpanMetrics spanMetrics) { this.clock = clock; this.idGenerator = idGenerator; this.idGeneratorSafeToSkipIdValidation = idGenerator instanceof RandomIdGenerator; @@ -47,6 +50,7 @@ final class TracerSharedState { this.sampler = sampler; this.activeSpanProcessor = SpanProcessor.composite(spanProcessors); this.exceptionAttributeResolver = exceptionAttributeResolver; + this.spanMetrics = spanMetrics; } Clock getClock() { @@ -84,6 +88,10 @@ SpanProcessor getActiveSpanProcessor() { return activeSpanProcessor; } + SpanMetrics getSpanMetrics() { + return spanMetrics; + } + /** * Returns {@code true} if tracing has been shut down. * diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/NoopSpanMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/NoopSpanMetrics.java new file mode 100644 index 00000000000..03e57f5564f --- /dev/null +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/NoopSpanMetrics.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace.internal.metrics; + +import io.opentelemetry.sdk.trace.samplers.SamplingResult; + +class NoopSpanMetrics implements SpanMetrics { + + static final NoopSpanMetrics INSTANCE = new NoopSpanMetrics(); + + @Override + public SpanMetrics.Recording recordSpanStart(SamplingResult samplingResult) { + return NoopRecording.INSTANCE; + } + + private static class NoopRecording implements SpanMetrics.Recording { + + private static final NoopRecording INSTANCE = new NoopRecording(); + + @Override + public void recordSpanEnd() {} + } +} diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanMetrics.java new file mode 100644 index 00000000000..f895547930b --- /dev/null +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanMetrics.java @@ -0,0 +1,120 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace.internal.metrics; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.LongUpDownCounter; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.sdk.internal.SemConvAttributes; +import io.opentelemetry.sdk.trace.samplers.SamplingDecision; +import io.opentelemetry.sdk.trace.samplers.SamplingResult; +import java.util.function.Supplier; +import javax.annotation.Nullable; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class SemConvSpanMetrics implements SpanMetrics { + + private final Supplier meterProviderSupplier; + + private static final Attributes SAMPLING_DROP_ATTRIBUTES = + Attributes.of(SemConvAttributes.OTEL_SPAN_SAMPLING_RESULT, "DROP"); + private static final Attributes SAMPLING_RECORD_ONLY_ATTRIBUTES = + Attributes.of(SemConvAttributes.OTEL_SPAN_SAMPLING_RESULT, "RECORD_ONLY"); + private static final Attributes SAMPLING_RECORD_AND_SAMPLED_ATTRIBUTES = + Attributes.of(SemConvAttributes.OTEL_SPAN_SAMPLING_RESULT, "RECORD_AND_SAMPLE"); + + @Nullable private volatile LongUpDownCounter live = null; + @Nullable private volatile LongCounter ended = null; + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + public SemConvSpanMetrics(Supplier meterProviderSupplier) { + this.meterProviderSupplier = meterProviderSupplier; + } + + private Meter meter() { + MeterProvider meterProvider = meterProviderSupplier.get(); + if (meterProvider == null) { + meterProvider = MeterProvider.noop(); + } + return meterProvider.get("io.opentelemetry.sdk.trace"); + } + + private LongUpDownCounter live() { + LongUpDownCounter live = this.live; + if (live == null) { + live = + meter() + .upDownCounterBuilder("otel.sdk.span.live") + .setUnit("{span}") + .setDescription( + "The number of created spans for which the end operation has not been called yet") + .build(); + this.live = live; + } + return live; + } + + private LongCounter ended() { + LongCounter ended = this.ended; + if (ended == null) { + ended = + meter() + .counterBuilder("otel.sdk.span.ended") + .setUnit("{span}") + .setDescription("The number of created spans for which the end operation was called") + .build(); + this.ended = ended; + } + return ended; + } + + static Attributes getAttributesForSamplingDecisions(SamplingDecision decision) { + switch (decision) { + case DROP: + return SAMPLING_DROP_ATTRIBUTES; + case RECORD_ONLY: + return SAMPLING_RECORD_ONLY_ATTRIBUTES; + case RECORD_AND_SAMPLE: + return SAMPLING_RECORD_AND_SAMPLED_ATTRIBUTES; + } + throw new IllegalStateException("Unhandled SamplingDecision case: " + decision); + } + + @Override + public SpanMetrics.Recording recordSpanStart(SamplingResult samplingResult) { + Attributes attribs = getAttributesForSamplingDecisions(samplingResult.getDecision()); + live().add(1, attribs); + return new Recording(attribs); + } + + private class Recording implements SpanMetrics.Recording { + + private final Attributes attributes; + private boolean endAlreadyReported = false; + + private Recording(Attributes attributes) { + this.attributes = attributes; + } + + @Override + public synchronized void recordSpanEnd() { + if (endAlreadyReported) { + return; + } + endAlreadyReported = true; + live().add(-1, attributes); + ended().add(1, attributes); + } + } +} diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SpanMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SpanMetrics.java new file mode 100644 index 00000000000..810004d31ce --- /dev/null +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SpanMetrics.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace.internal.metrics; + +import io.opentelemetry.sdk.trace.samplers.SamplingResult; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public interface SpanMetrics { + + static SpanMetrics noop() { + return NoopSpanMetrics.INSTANCE; + } + + Recording recordSpanStart(SamplingResult samplingResult); + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + interface Recording { + + void recordSpanEnd(); + } +} diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java index 9c9ecdc7e9c..b54524dd53e 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java @@ -17,9 +17,11 @@ import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.mockito.Mockito.verifyNoInteractions; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanContext; @@ -28,18 +30,27 @@ import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.TraceFlags; import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.sdk.common.InternalTelemetryVersion; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; import io.opentelemetry.sdk.trace.samplers.SamplingResult; +import io.opentelemetry.semconv.incubating.OtelIncubatingAttributes; import java.time.Instant; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import java.util.stream.IntStream; import javax.annotation.Nullable; import org.junit.jupiter.api.BeforeEach; @@ -951,6 +962,218 @@ void parentCurrentSpan_clockIsSame() { } } + @Test + void healthMetricsEnabled() { + AtomicReference currentSamplingDecision = new AtomicReference<>(); + Sampler sampler = + new Sampler() { + @Override + public SamplingResult shouldSample( + Context parentContext, + String traceId, + String name, + SpanKind spanKind, + Attributes attributes, + List parentLinks) { + return SamplingResult.create(currentSamplingDecision.get(), Attributes.empty()); + } + + @Override + public String getDescription() { + return "test"; + } + }; + + InMemoryMetricReader inMemoryMetrics = InMemoryMetricReader.create(); + try (SdkMeterProvider meterProvider = + SdkMeterProvider.builder().registerMetricReader(inMemoryMetrics).build()) { + Tracer tracer = + SdkTracerProvider.builder() + .setSampler(sampler) + .setMeterProvider(() -> meterProvider) + .setInternalTelemetry(InternalTelemetryVersion.LATEST) + .build() + .get("testing"); + + List spansToEnd = new ArrayList<>(); + + currentSamplingDecision.set(SamplingDecision.DROP); + spansToEnd.add(tracer.spanBuilder("dropped").startSpan()); + + currentSamplingDecision.set(SamplingDecision.RECORD_ONLY); + spansToEnd.add(tracer.spanBuilder("record_only1").startSpan()); + spansToEnd.add(tracer.spanBuilder("record_only2").startSpan()); + + currentSamplingDecision.set(SamplingDecision.RECORD_AND_SAMPLE); + spansToEnd.add(tracer.spanBuilder("record_and_sample1").startSpan()); + spansToEnd.add(tracer.spanBuilder("record_and_sample2").startSpan()); + spansToEnd.add(tracer.spanBuilder("record_and_sample3").startSpan()); + + assertThat(inMemoryMetrics.collectAllMetrics()) + .hasSize(1) + .anySatisfy( + metric -> + OpenTelemetryAssertions.assertThat(metric) + .hasName("otel.sdk.span.live") + .hasUnit("{span}") + .hasLongSumSatisfying( + ma -> + ma.hasPointsSatisfying( + pa -> + pa.hasAttributes( + Attributes.of( + OtelIncubatingAttributes + .OTEL_SPAN_SAMPLING_RESULT, + OtelIncubatingAttributes + .OtelSpanSamplingResultIncubatingValues.DROP)) + .hasValue(1), + pa -> + pa.hasAttributes( + Attributes.of( + OtelIncubatingAttributes + .OTEL_SPAN_SAMPLING_RESULT, + OtelIncubatingAttributes + .OtelSpanSamplingResultIncubatingValues + .RECORD_ONLY)) + .hasValue(2), + pa -> + pa.hasAttributes( + Attributes.of( + OtelIncubatingAttributes + .OTEL_SPAN_SAMPLING_RESULT, + OtelIncubatingAttributes + .OtelSpanSamplingResultIncubatingValues + .RECORD_AND_SAMPLE)) + .hasValue(3)))); + + spansToEnd.forEach(Span::end); + Runnable endAssertions = + () -> + assertThat(inMemoryMetrics.collectAllMetrics()) + .hasSize(2) + .anySatisfy( + metric -> + OpenTelemetryAssertions.assertThat(metric) + .hasName("otel.sdk.span.live") + .hasUnit("{span}") + .hasLongSumSatisfying( + ma -> + ma.hasPointsSatisfying( + pa -> + pa.hasAttributes( + Attributes.of( + OtelIncubatingAttributes + .OTEL_SPAN_SAMPLING_RESULT, + OtelIncubatingAttributes + .OtelSpanSamplingResultIncubatingValues + .DROP)) + .hasValue(0), + pa -> + pa.hasAttributes( + Attributes.of( + OtelIncubatingAttributes + .OTEL_SPAN_SAMPLING_RESULT, + OtelIncubatingAttributes + .OtelSpanSamplingResultIncubatingValues + .RECORD_ONLY)) + .hasValue(0), + pa -> + pa.hasAttributes( + Attributes.of( + OtelIncubatingAttributes + .OTEL_SPAN_SAMPLING_RESULT, + OtelIncubatingAttributes + .OtelSpanSamplingResultIncubatingValues + .RECORD_AND_SAMPLE)) + .hasValue(0)))) + .anySatisfy( + metric -> + OpenTelemetryAssertions.assertThat(metric) + .hasName("otel.sdk.span.ended") + .hasUnit("{span}") + .hasLongSumSatisfying( + ma -> + ma.hasPointsSatisfying( + pa -> + pa.hasAttributes( + Attributes.of( + OtelIncubatingAttributes + .OTEL_SPAN_SAMPLING_RESULT, + OtelIncubatingAttributes + .OtelSpanSamplingResultIncubatingValues + .DROP)) + .hasValue(1), + pa -> + pa.hasAttributes( + Attributes.of( + OtelIncubatingAttributes + .OTEL_SPAN_SAMPLING_RESULT, + OtelIncubatingAttributes + .OtelSpanSamplingResultIncubatingValues + .RECORD_ONLY)) + .hasValue(2), + pa -> + pa.hasAttributes( + Attributes.of( + OtelIncubatingAttributes + .OTEL_SPAN_SAMPLING_RESULT, + OtelIncubatingAttributes + .OtelSpanSamplingResultIncubatingValues + .RECORD_AND_SAMPLE)) + .hasValue(3)))); + endAssertions.run(); + + // ensure double ending doesn't have any effect on the metrics + spansToEnd.forEach(Span::end); + endAssertions.run(); + } + } + + @Test + @SuppressWarnings("unchecked") + void healthMetricsDisabled() { + AtomicReference currentSamplingDecision = new AtomicReference<>(); + Sampler sampler = + new Sampler() { + @Override + public SamplingResult shouldSample( + Context parentContext, + String traceId, + String name, + SpanKind spanKind, + Attributes attributes, + List parentLinks) { + return SamplingResult.create(currentSamplingDecision.get(), Attributes.empty()); + } + + @Override + public String getDescription() { + return "test"; + } + }; + + Supplier mockMeterProvider = Mockito.mock(Supplier.class); + + Tracer tracer = + SdkTracerProvider.builder() + .setSampler(sampler) + .setMeterProvider(mockMeterProvider) + .setInternalTelemetry(InternalTelemetryVersion.DISABLED) + .build() + .get("testing"); + + currentSamplingDecision.set(SamplingDecision.DROP); + tracer.spanBuilder("dropped").startSpan().end(); + + currentSamplingDecision.set(SamplingDecision.RECORD_ONLY); + tracer.spanBuilder("record_only").startSpan().end(); + + currentSamplingDecision.set(SamplingDecision.RECORD_AND_SAMPLE); + tracer.spanBuilder("record_and_sample").startSpan().end(); + + verifyNoInteractions(mockMeterProvider); + } + @Test void isSampled() { assertThat(SdkSpanBuilder.isSampled(SamplingDecision.DROP)).isFalse(); diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java index fcfec65d47f..7bec0b0b8d6 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java @@ -49,6 +49,7 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.data.StatusData; import io.opentelemetry.sdk.trace.internal.ExtendedSpanProcessor; +import io.opentelemetry.sdk.trace.internal.metrics.SpanMetrics; import java.io.PrintWriter; import java.io.StringWriter; import java.time.Duration; @@ -104,6 +105,7 @@ class SdkSpanTest { private Attributes expectedAttributes; private final LinkData link = LinkData.create(spanContext); @Mock private ExtendedSpanProcessor spanProcessor; + @Mock private SpanMetrics.Recording metricsRecording; private TestClock testClock; @@ -1042,7 +1044,8 @@ void addLink_FaultIn() { null, null, // exercises the fault-in path 0, - 0); + 0, + metricsRecording); SdkSpan linkedSpan = createTestSpan(SpanKind.INTERNAL); span.addLink(linkedSpan.getSpanContext()); @@ -1388,7 +1391,8 @@ void onStartOnEndNotRequired() { spanLimits.getMaxNumberOfAttributes(), spanLimits.getMaxAttributeValueLength()), Collections.emptyList(), 1, - 0); + 0, + metricsRecording); verify(spanProcessor, never()).onStart(any(), any()); span.end(); @@ -1526,7 +1530,8 @@ private SdkSpan createTestSpan( attributes, linksCopy, linksCopy.size(), - 0); + 0, + metricsRecording); Mockito.verify(spanProcessor, Mockito.times(1)).onStart(Context.root(), span); return span; } @@ -1614,7 +1619,8 @@ void testAsSpanData() { attributesWithCapacity, Collections.singletonList(link1), 1, - 0); + 0, + metricsRecording); long startEpochNanos = clock.now(); clock.advance(Duration.ofMillis(4)); long firstEventEpochNanos = clock.now(); diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanMetricsTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanMetricsTest.java new file mode 100644 index 00000000000..1232471671a --- /dev/null +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanMetricsTest.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace.internal.metrics; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import io.opentelemetry.sdk.trace.samplers.SamplingDecision; +import io.opentelemetry.semconv.incubating.OtelIncubatingAttributes; +import org.junit.jupiter.api.Test; + +class SemConvSpanMetricsTest { + + @Test + void verifyAttributesSemConvCompliant() { + assertThat(SemConvSpanMetrics.getAttributesForSamplingDecisions(SamplingDecision.DROP)) + .hasSize(1) + .containsEntry( + OtelIncubatingAttributes.OTEL_SPAN_SAMPLING_RESULT, + OtelIncubatingAttributes.OtelSpanSamplingResultIncubatingValues.DROP); + + assertThat( + SemConvSpanMetrics.getAttributesForSamplingDecisions( + SamplingDecision.RECORD_AND_SAMPLE)) + .hasSize(1) + .containsEntry( + OtelIncubatingAttributes.OTEL_SPAN_SAMPLING_RESULT, + OtelIncubatingAttributes.OtelSpanSamplingResultIncubatingValues.RECORD_AND_SAMPLE); + + assertThat(SemConvSpanMetrics.getAttributesForSamplingDecisions(SamplingDecision.RECORD_ONLY)) + .hasSize(1) + .containsEntry( + OtelIncubatingAttributes.OTEL_SPAN_SAMPLING_RESULT, + OtelIncubatingAttributes.OtelSpanSamplingResultIncubatingValues.RECORD_ONLY); + } +} From 393786e9a3a39c2a735d89bdc75f3a9b67e4a4de Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 18 Jun 2025 12:33:42 +0200 Subject: [PATCH 02/10] Fix toString internalTelemetrySchemaVersion left-overs --- .../exporter/internal/grpc/GrpcExporterBuilder.java | 2 +- .../exporter/internal/http/HttpExporterBuilder.java | 2 +- .../exporter/zipkin/ZipkinSpanExporterBuilder.java | 2 +- .../opentelemetry/exporter/zipkin/ZipkinSpanExporterTest.java | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/GrpcExporterBuilder.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/GrpcExporterBuilder.java index 6f2ae64b862..9c83677ff28 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/GrpcExporterBuilder.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/GrpcExporterBuilder.java @@ -273,7 +273,7 @@ public String toString(boolean includePrefixAndSuffix) { joiner.add("executorService=" + executorService); } joiner.add("exporterType=" + exporterType.toString()); - joiner.add("internalTelemetrySchemaVersion=" + internalTelemetryVersion); + joiner.add("internalTelemetryVersion=" + internalTelemetryVersion); // Note: omit tlsConfigHelper because we can't log the configuration in any readable way // Note: omit meterProviderSupplier because we can't log the configuration in any readable way return joiner.toString(); diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/http/HttpExporterBuilder.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/http/HttpExporterBuilder.java index 4cd671c32ca..fa453ad16cb 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/http/HttpExporterBuilder.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/http/HttpExporterBuilder.java @@ -270,7 +270,7 @@ public String toString(boolean includePrefixAndSuffix) { joiner.add("executorService=" + executorService); } joiner.add("exporterType=" + exporterType); - joiner.add("internalTelemetrySchemaVersion=" + internalTelemetryVersion); + joiner.add("internalTelemetryVersion=" + internalTelemetryVersion); // Note: omit tlsConfigHelper because we can't log the configuration in any readable way // Note: omit meterProviderSupplier because we can't log the configuration in any readable way return joiner.toString(); diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterBuilder.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterBuilder.java index ec25c9983a4..20db8764299 100644 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterBuilder.java +++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterBuilder.java @@ -208,7 +208,7 @@ String toString(boolean includePrefixAndSuffix) { joiner.add("endpoint=" + endpoint); joiner.add("compressionEnabled=" + compressionEnabled); joiner.add("readTimeoutMillis=" + readTimeoutMillis); - joiner.add("internalTelemetrySchemaVersion=" + internalTelemetryVersion); + joiner.add("internalTelemetryVersion=" + internalTelemetryVersion); // Note: omit sender because we can't log the configuration in any readable way // Note: omit encoder because we can't log the configuration in any readable way // Note: omit localIpAddressSupplier because we can't log the configuration in any readable way diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterTest.java index 759cfae782b..cc48ff0fb8f 100644 --- a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterTest.java +++ b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterTest.java @@ -256,7 +256,7 @@ void stringRepresentation() { try (ZipkinSpanExporter exporter = ZipkinSpanExporter.builder().build()) { assertThat(exporter.toString()) .isEqualTo( - "ZipkinSpanExporter{endpoint=http://localhost:9411/api/v2/spans, compressionEnabled=true, readTimeoutMillis=10000, internalTelemetrySchemaVersion=LEGACY}"); + "ZipkinSpanExporter{endpoint=http://localhost:9411/api/v2/spans, compressionEnabled=true, readTimeoutMillis=10000, internalTelemetryVersion=LEGACY}"); } try (ZipkinSpanExporter exporter = ZipkinSpanExporter.builder() @@ -266,7 +266,7 @@ void stringRepresentation() { .build()) { assertThat(exporter.toString()) .isEqualTo( - "ZipkinSpanExporter{endpoint=http://zipkin:9411/api/v2/spans, compressionEnabled=false, readTimeoutMillis=15000, internalTelemetrySchemaVersion=LEGACY}"); + "ZipkinSpanExporter{endpoint=http://zipkin:9411/api/v2/spans, compressionEnabled=false, readTimeoutMillis=15000, internalTelemetryVersion=LEGACY}"); } } From 8ffc6c73be230b0ea969c01b2b47611abc6fd68a Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 18 Jun 2025 12:44:43 +0200 Subject: [PATCH 03/10] Rebase fixes --- .../current_vs_latest/opentelemetry-sdk-trace.txt | 5 ++++- .../io/opentelemetry/sdk/trace/SdkTracerProvider.java | 2 -- .../sdk/trace/SdkTracerProviderBuilder.java | 9 ++++----- .../io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt index e4b6afee82a..70f0adc6508 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt @@ -1,2 +1,5 @@ Comparing source compatibility of opentelemetry-sdk-trace-1.52.0-SNAPSHOT.jar against opentelemetry-sdk-trace-1.51.0.jar -No changes. \ No newline at end of file +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.SdkTracerProviderBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.SdkTracerProviderBuilder setInternalTelemetry(io.opentelemetry.sdk.common.InternalTelemetryVersion) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.SdkTracerProviderBuilder setMeterProvider(java.util.function.Supplier) diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java index d6754338a22..a07aad8a9e1 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java @@ -86,9 +86,7 @@ private static SpanMetrics createSpanMetrics( Supplier meterProviderSupplier) { switch (internalTelemetryVersion) { case LEGACY: - case DISABLED: return SpanMetrics.noop(); - case V1_33: case LATEST: return new SemConvSpanMetrics(meterProviderSupplier); } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java index 14e27bc1fab..d21e5fd2cfb 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java @@ -12,9 +12,9 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.sdk.common.Clock; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.common.InternalTelemetryVersion; import io.opentelemetry.sdk.internal.DefaultExceptionAttributeResolver; import io.opentelemetry.sdk.internal.ExceptionAttributeResolver; -import io.opentelemetry.sdk.common.InternalTelemetryVersion; import io.opentelemetry.sdk.internal.ScopeConfigurator; import io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder; import io.opentelemetry.sdk.resources.Resource; @@ -39,8 +39,7 @@ public final class SdkTracerProviderBuilder { private Supplier spanLimitsSupplier = SpanLimits::getDefault; private Sampler sampler = DEFAULT_SAMPLER; private Supplier meterProviderSupplier = GlobalOpenTelemetry::getMeterProvider; - private InternalTelemetryVersion internalTelemetryVersion = - InternalTelemetryVersion.DISABLED; + private InternalTelemetryVersion internalTelemetryVersion = InternalTelemetryVersion.LEGACY; private ScopeConfiguratorBuilder tracerConfiguratorBuilder = TracerConfig.configuratorBuilder(); private ExceptionAttributeResolver exceptionAttributeResolver = @@ -194,8 +193,8 @@ public SdkTracerProviderBuilder setMeterProvider(Supplier meterPr } /** - * Sets the {@link InternalTelemetryVersion} defining which self-monitoring metrics the - * tracers originating from this provider collect. + * Sets the {@link InternalTelemetryVersion} defining which self-monitoring metrics the tracers + * originating from this provider collect. */ public SdkTracerProviderBuilder setInternalTelemetry( InternalTelemetryVersion internalTelemetryVersion) { diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java index b54524dd53e..f83bca02104 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java @@ -1131,7 +1131,7 @@ public String getDescription() { @Test @SuppressWarnings("unchecked") - void healthMetricsDisabled() { + void healthMetricsDisabledForLegacy() { AtomicReference currentSamplingDecision = new AtomicReference<>(); Sampler sampler = new Sampler() { @@ -1158,7 +1158,7 @@ public String getDescription() { SdkTracerProvider.builder() .setSampler(sampler) .setMeterProvider(mockMeterProvider) - .setInternalTelemetry(InternalTelemetryVersion.DISABLED) + .setInternalTelemetry(InternalTelemetryVersion.LEGACY) .build() .get("testing"); From 54d80c05f6b7c6a8f1c2315ccd7a7360232a42f7 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 18 Jun 2025 13:50:25 +0200 Subject: [PATCH 04/10] Add test for NonRecordingSpan --- .../sdk/trace/NonRecordingSpanTest.java | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java new file mode 100644 index 00000000000..753fb21ee83 --- /dev/null +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java @@ -0,0 +1,104 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace; + +import static io.opentelemetry.api.common.AttributeKey.booleanArrayKey; +import static io.opentelemetry.api.common.AttributeKey.booleanKey; +import static io.opentelemetry.api.common.AttributeKey.doubleArrayKey; +import static io.opentelemetry.api.common.AttributeKey.longArrayKey; +import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.sdk.trace.internal.metrics.SpanMetrics; +import java.time.Instant; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class NonRecordingSpanTest { + + private static final SpanContext DUMMY_CONTEXT = + SpanContext.create( + "a1a2a3a4a5a6a7a8b1b2b3b4b5b6b7b8", + "c1c2c3c4c5c6c7c8", + TraceFlags.getDefault(), + TraceState.getDefault()); + + @Mock SpanMetrics.Recording mockRecording; + + @Test + void notRecording() { + assertThat(new NonRecordingSpan(DUMMY_CONTEXT, mockRecording).isRecording()).isFalse(); + } + + @Test + void recordingFinishedOnEnd() { + Span span = new NonRecordingSpan(DUMMY_CONTEXT, mockRecording); + span.end(); + verify(mockRecording, times(1)).recordSpanEnd(); + span.end(0, TimeUnit.NANOSECONDS); + verify(mockRecording, times(2)).recordSpanEnd(); + span.end(Instant.EPOCH); + verify(mockRecording, times(3)).recordSpanEnd(); + } + + @Test + void doNotCrash() { + Span span = new NonRecordingSpan(DUMMY_CONTEXT, mockRecording); + span.setAttribute(stringKey("MyStringAttributeKey"), "MyStringAttributeValue"); + span.setAttribute(booleanKey("MyBooleanAttributeKey"), true); + span.setAttribute(longKey("MyLongAttributeKey"), 123L); + span.setAttribute(longKey("MyLongAttributeKey"), 123); + span.setAttribute("NullString", null); + span.setAttribute("EmptyString", ""); + span.setAttribute("long", 1); + span.setAttribute("double", 1.0); + span.setAttribute("boolean", true); + span.setAttribute(stringArrayKey("NullArrayString"), null); + span.setAttribute(booleanArrayKey("NullArrayBoolean"), null); + span.setAttribute(longArrayKey("NullArrayLong"), null); + span.setAttribute(doubleArrayKey("NullArrayDouble"), null); + span.setAttribute((String) null, null); + span.setAllAttributes(null); + span.setAllAttributes(Attributes.empty()); + span.setAllAttributes( + Attributes.of(stringKey("MyStringAttributeKey"), "MyStringAttributeValue")); + span.addEvent("event"); + span.addEvent("event", 0, TimeUnit.NANOSECONDS); + span.addEvent("event", Instant.EPOCH); + span.addEvent("event", Attributes.of(booleanKey("MyBooleanAttributeKey"), true)); + span.addEvent( + "event", Attributes.of(booleanKey("MyBooleanAttributeKey"), true), 0, TimeUnit.NANOSECONDS); + span.setStatus(StatusCode.OK); + span.setStatus(StatusCode.OK, "null"); + span.recordException(new IllegalStateException()); + span.recordException(new IllegalStateException(), Attributes.empty()); + span.updateName("name"); + } + + @Test + void defaultSpan_ToString() { + Span span = new NonRecordingSpan(DUMMY_CONTEXT, mockRecording); + assertThat(span.toString()) + .isEqualTo( + "NonRecordingSpan{ImmutableSpanContext{traceId=a1a2a3a4a5a6a7a8b1b2b3b4b5b6b7b8, " + + "spanId=c1c2c3c4c5c6c7c8, traceFlags=00, " + + "traceState=ArrayBasedTraceState{entries=[]}, remote=false, valid=true}}"); + } +} From be0d7661ae850dff8930b8e0f31a11dc4761447a Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 18 Jun 2025 13:50:59 +0200 Subject: [PATCH 05/10] Rename method after copy pasta --- .../java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java index 753fb21ee83..4901d9ae95f 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java @@ -93,7 +93,7 @@ void doNotCrash() { } @Test - void defaultSpan_ToString() { + void verifyToString() { Span span = new NonRecordingSpan(DUMMY_CONTEXT, mockRecording); assertThat(span.toString()) .isEqualTo( From 74fdbae2b7bc190dca4f8a7bfe619a90a6964e07 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Mon, 30 Jun 2025 10:29:13 +0200 Subject: [PATCH 06/10] Use OpenTelemetry at configuration level instead of MeterProvider --- .../current_vs_latest/opentelemetry-sdk-trace.txt | 2 +- .../sdk/trace/SdkTracerProviderBuilder.java | 12 +++++++----- .../sdk/trace/SdkSpanBuilderTest.java | 15 ++++++++++----- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt index 70f0adc6508..2da26c27ef2 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt @@ -2,4 +2,4 @@ Comparing source compatibility of opentelemetry-sdk-trace-1.52.0-SNAPSHOT.jar ag *** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.SdkTracerProviderBuilder (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.SdkTracerProviderBuilder setInternalTelemetry(io.opentelemetry.sdk.common.InternalTelemetryVersion) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.SdkTracerProviderBuilder setMeterProvider(java.util.function.Supplier) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.SdkTracerProviderBuilder setInternalTelemetryOpenTelemetry(java.util.function.Supplier) diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java index d21e5fd2cfb..382df533c12 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java @@ -8,6 +8,7 @@ import static java.util.Objects.requireNonNull; import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.trace.Span; import io.opentelemetry.sdk.common.Clock; @@ -183,12 +184,13 @@ public SdkTracerProviderBuilder addSpanProcessorFirst(SpanProcessor spanProcesso } /** - * Sets the {@link MeterProvider} supplier used to collect self-monitoring metrics. If not set, - * uses {@link GlobalOpenTelemetry#getMeterProvider()}. + * Sets the {@link OpenTelemetry} supplier used to collect self-monitoring telemetry. If not set, + * uses {@link GlobalOpenTelemetry#get()}. */ - public SdkTracerProviderBuilder setMeterProvider(Supplier meterProviderSupplier) { - requireNonNull(meterProviderSupplier, "meterProviderSupplier"); - this.meterProviderSupplier = meterProviderSupplier; + public SdkTracerProviderBuilder setInternalTelemetryOpenTelemetry( + Supplier openTelemetrySupplier) { + requireNonNull(openTelemetrySupplier, "openTelemetrySupplier"); + this.meterProviderSupplier = () -> openTelemetrySupplier.get().getMeterProvider(); return this; } diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java index f83bca02104..8408a3246b2 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java @@ -19,9 +19,9 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.mockito.Mockito.verifyNoInteractions; +import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanContext; @@ -34,6 +34,7 @@ import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.common.InternalTelemetryVersion; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; @@ -987,10 +988,14 @@ public String getDescription() { InMemoryMetricReader inMemoryMetrics = InMemoryMetricReader.create(); try (SdkMeterProvider meterProvider = SdkMeterProvider.builder().registerMetricReader(inMemoryMetrics).build()) { + + OpenTelemetrySdk telemetrySdk = + OpenTelemetrySdk.builder().setMeterProvider(meterProvider).build(); + Tracer tracer = SdkTracerProvider.builder() .setSampler(sampler) - .setMeterProvider(() -> meterProvider) + .setInternalTelemetryOpenTelemetry(() -> telemetrySdk) .setInternalTelemetry(InternalTelemetryVersion.LATEST) .build() .get("testing"); @@ -1152,12 +1157,12 @@ public String getDescription() { } }; - Supplier mockMeterProvider = Mockito.mock(Supplier.class); + Supplier mockTelemetryProvider = Mockito.mock(Supplier.class); Tracer tracer = SdkTracerProvider.builder() .setSampler(sampler) - .setMeterProvider(mockMeterProvider) + .setInternalTelemetryOpenTelemetry(mockTelemetryProvider) .setInternalTelemetry(InternalTelemetryVersion.LEGACY) .build() .get("testing"); @@ -1171,7 +1176,7 @@ public String getDescription() { currentSamplingDecision.set(SamplingDecision.RECORD_AND_SAMPLE); tracer.spanBuilder("record_and_sample").startSpan().end(); - verifyNoInteractions(mockMeterProvider); + verifyNoInteractions(mockTelemetryProvider); } @Test From 44cccc80ffc86c622c64fe6e499dfa35e9ed8ffe Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Mon, 30 Jun 2025 10:34:12 +0200 Subject: [PATCH 07/10] Fix NonRecordingSpan javadoc --- .../java/io/opentelemetry/sdk/trace/NonRecordingSpan.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java index 7e93741d1a0..d99e9820f00 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java @@ -17,8 +17,8 @@ /** * Span implementation used from {@link io.opentelemetry.sdk.trace.SdkTracer} when starting a span - * which is not recording. All operations are noop, except for {@link #end()}, which ensures health - * metrics are still collected. + * which is not recording. This span implementation behaves exactly like one returned from {@link Span#wrap(SpanContext)} + * with the addition that {@link #end()} collects health metrics. */ @Immutable final class NonRecordingSpan implements Span { From 6f4b0afc055ce53251b40df8ad3114ee265fd726 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Mon, 30 Jun 2025 10:57:56 +0200 Subject: [PATCH 08/10] Change pattern to SpanInstrumentation --- .../sdk/trace/NonRecordingSpan.java | 10 ++-- .../io/opentelemetry/sdk/trace/SdkSpan.java | 8 ++-- .../sdk/trace/SdkSpanBuilder.java | 4 +- .../sdk/trace/SdkTracerProvider.java | 18 +------- .../sdk/trace/TracerSharedState.java | 12 ++--- ...rics.java => NoopSpanInstrumentation.java} | 8 ++-- ...s.java => SemConvSpanInstrumentation.java} | 12 ++--- .../internal/metrics/SpanInstrumentation.java | 46 +++++++++++++++++++ .../trace/internal/metrics/SpanMetrics.java | 30 ------------ .../sdk/trace/NonRecordingSpanTest.java | 4 +- .../opentelemetry/sdk/trace/SdkSpanTest.java | 4 +- ...va => SemConvSpanInstrumentationTest.java} | 10 ++-- 12 files changed, 83 insertions(+), 83 deletions(-) rename sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/{NoopSpanMetrics.java => NoopSpanInstrumentation.java} (55%) rename sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/{SemConvSpanMetrics.java => SemConvSpanInstrumentation.java} (89%) create mode 100644 sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SpanInstrumentation.java delete mode 100644 sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SpanMetrics.java rename sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/metrics/{SemConvSpanMetricsTest.java => SemConvSpanInstrumentationTest.java} (76%) diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java index d99e9820f00..777e646a9a3 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java @@ -10,23 +10,23 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.sdk.trace.internal.metrics.SpanMetrics; +import io.opentelemetry.sdk.trace.internal.metrics.SpanInstrumentation; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; /** * Span implementation used from {@link io.opentelemetry.sdk.trace.SdkTracer} when starting a span - * which is not recording. This span implementation behaves exactly like one returned from {@link Span#wrap(SpanContext)} - * with the addition that {@link #end()} collects health metrics. + * which is not recording. This span implementation behaves exactly like one returned from {@link + * Span#wrap(SpanContext)} with the addition that {@link #end()} collects health metrics. */ @Immutable final class NonRecordingSpan implements Span { private final SpanContext spanContext; - private final SpanMetrics.Recording metricRecording; + private final SpanInstrumentation.Recording metricRecording; - NonRecordingSpan(SpanContext spanContext, SpanMetrics.Recording metricRecording) { + NonRecordingSpan(SpanContext spanContext, SpanInstrumentation.Recording metricRecording) { this.spanContext = spanContext; this.metricRecording = metricRecording; } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java index 27acbe0b789..984a31472d7 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java @@ -26,7 +26,7 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.data.StatusData; import io.opentelemetry.sdk.trace.internal.ExtendedSpanProcessor; -import io.opentelemetry.sdk.trace.internal.metrics.SpanMetrics; +import io.opentelemetry.sdk.trace.internal.metrics.SpanInstrumentation; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -100,7 +100,7 @@ final class SdkSpan implements ReadWriteSpan { @GuardedBy("lock") private long endEpochNanos; - private final SpanMetrics.Recording metricRecording; + private final SpanInstrumentation.Recording metricRecording; private enum EndState { NOT_ENDED, @@ -136,7 +136,7 @@ private SdkSpan( @Nullable List links, int totalRecordedLinks, long startEpochNanos, - SpanMetrics.Recording metricRecording) { + SpanInstrumentation.Recording metricRecording) { this.context = context; this.instrumentationScopeInfo = instrumentationScopeInfo; this.parentSpanContext = parentSpanContext; @@ -186,7 +186,7 @@ static SdkSpan startSpan( @Nullable List links, int totalRecordedLinks, long userStartEpochNanos, - SpanMetrics.Recording metricsRecording) { + SpanInstrumentation.Recording metricsRecording) { boolean createdAnchoredClock; AnchoredClock clock; if (parentSpan instanceof SdkSpan) { diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java index 2a4ff79cc30..60c274be59c 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java @@ -24,7 +24,7 @@ import io.opentelemetry.sdk.internal.AttributeUtil; import io.opentelemetry.sdk.internal.AttributesMap; import io.opentelemetry.sdk.trace.data.LinkData; -import io.opentelemetry.sdk.trace.internal.metrics.SpanMetrics; +import io.opentelemetry.sdk.trace.internal.metrics.SpanInstrumentation; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; import io.opentelemetry.sdk.trace.samplers.SamplingResult; import java.util.ArrayList; @@ -205,7 +205,7 @@ public Span startSpan() { /* remote= */ false, tracerSharedState.isIdGeneratorSafeToSkipIdValidation()); - SpanMetrics.Recording metricsRecording = + SpanInstrumentation.Recording metricsRecording = tracerSharedState.getSpanMetrics().recordSpanStart(samplingResult); if (!isRecording(samplingDecision)) { return new NonRecordingSpan(spanContext, metricsRecording); diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java index a07aad8a9e1..26672e7bd83 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java @@ -19,8 +19,7 @@ import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil; import io.opentelemetry.sdk.trace.internal.TracerConfig; -import io.opentelemetry.sdk.trace.internal.metrics.SemConvSpanMetrics; -import io.opentelemetry.sdk.trace.internal.metrics.SpanMetrics; +import io.opentelemetry.sdk.trace.internal.metrics.SpanInstrumentation; import io.opentelemetry.sdk.trace.samplers.Sampler; import java.io.Closeable; import java.util.List; @@ -70,7 +69,7 @@ public static SdkTracerProviderBuilder builder() { sampler, spanProcessors, exceptionAttributeResolver, - createSpanMetrics(internalTelemetryVersion, meterProviderSupplier)); + SpanInstrumentation.create(internalTelemetryVersion, meterProviderSupplier)); this.tracerSdkComponentRegistry = new ComponentRegistry<>( instrumentationScopeInfo -> @@ -81,19 +80,6 @@ public static SdkTracerProviderBuilder builder() { this.tracerConfigurator = tracerConfigurator; } - private static SpanMetrics createSpanMetrics( - InternalTelemetryVersion internalTelemetryVersion, - Supplier meterProviderSupplier) { - switch (internalTelemetryVersion) { - case LEGACY: - return SpanMetrics.noop(); - case LATEST: - return new SemConvSpanMetrics(meterProviderSupplier); - } - throw new IllegalStateException( - "Unhandled telemetry schema version: " + internalTelemetryVersion); - } - private TracerConfig getTracerConfig(InstrumentationScopeInfo instrumentationScopeInfo) { TracerConfig tracerConfig = tracerConfigurator.apply(instrumentationScopeInfo); return tracerConfig == null ? TracerConfig.defaultConfig() : tracerConfig; diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java index c7c75881ed3..c0a3af2d255 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java @@ -9,7 +9,7 @@ import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.internal.ExceptionAttributeResolver; import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.trace.internal.metrics.SpanMetrics; +import io.opentelemetry.sdk.trace.internal.metrics.SpanInstrumentation; import io.opentelemetry.sdk.trace.samplers.Sampler; import java.util.List; import java.util.function.Supplier; @@ -29,7 +29,7 @@ final class TracerSharedState { private final Sampler sampler; private final SpanProcessor activeSpanProcessor; private final ExceptionAttributeResolver exceptionAttributeResolver; - private final SpanMetrics spanMetrics; + private final SpanInstrumentation spanInstrumentation; @Nullable private volatile CompletableResultCode shutdownResult = null; @@ -41,7 +41,7 @@ final class TracerSharedState { Sampler sampler, List spanProcessors, ExceptionAttributeResolver exceptionAttributeResolver, - SpanMetrics spanMetrics) { + SpanInstrumentation spanInstrumentation) { this.clock = clock; this.idGenerator = idGenerator; this.idGeneratorSafeToSkipIdValidation = idGenerator instanceof RandomIdGenerator; @@ -50,7 +50,7 @@ final class TracerSharedState { this.sampler = sampler; this.activeSpanProcessor = SpanProcessor.composite(spanProcessors); this.exceptionAttributeResolver = exceptionAttributeResolver; - this.spanMetrics = spanMetrics; + this.spanInstrumentation = spanInstrumentation; } Clock getClock() { @@ -88,8 +88,8 @@ SpanProcessor getActiveSpanProcessor() { return activeSpanProcessor; } - SpanMetrics getSpanMetrics() { - return spanMetrics; + SpanInstrumentation getSpanMetrics() { + return spanInstrumentation; } /** diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/NoopSpanMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/NoopSpanInstrumentation.java similarity index 55% rename from sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/NoopSpanMetrics.java rename to sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/NoopSpanInstrumentation.java index 03e57f5564f..003f2b52d42 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/NoopSpanMetrics.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/NoopSpanInstrumentation.java @@ -7,16 +7,16 @@ import io.opentelemetry.sdk.trace.samplers.SamplingResult; -class NoopSpanMetrics implements SpanMetrics { +class NoopSpanInstrumentation implements SpanInstrumentation { - static final NoopSpanMetrics INSTANCE = new NoopSpanMetrics(); + static final NoopSpanInstrumentation INSTANCE = new NoopSpanInstrumentation(); @Override - public SpanMetrics.Recording recordSpanStart(SamplingResult samplingResult) { + public SpanInstrumentation.Recording recordSpanStart(SamplingResult samplingResult) { return NoopRecording.INSTANCE; } - private static class NoopRecording implements SpanMetrics.Recording { + private static class NoopRecording implements SpanInstrumentation.Recording { private static final NoopRecording INSTANCE = new NoopRecording(); diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanInstrumentation.java similarity index 89% rename from sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanMetrics.java rename to sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanInstrumentation.java index f895547930b..9af134ce57e 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanMetrics.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanInstrumentation.java @@ -20,7 +20,7 @@ * This class is internal and is hence not for public use. Its APIs are unstable and can change at * any time. */ -public class SemConvSpanMetrics implements SpanMetrics { +public class SemConvSpanInstrumentation implements SpanInstrumentation { private final Supplier meterProviderSupplier; @@ -34,11 +34,7 @@ public class SemConvSpanMetrics implements SpanMetrics { @Nullable private volatile LongUpDownCounter live = null; @Nullable private volatile LongCounter ended = null; - /** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ - public SemConvSpanMetrics(Supplier meterProviderSupplier) { + public SemConvSpanInstrumentation(Supplier meterProviderSupplier) { this.meterProviderSupplier = meterProviderSupplier; } @@ -92,13 +88,13 @@ static Attributes getAttributesForSamplingDecisions(SamplingDecision decision) { } @Override - public SpanMetrics.Recording recordSpanStart(SamplingResult samplingResult) { + public SpanInstrumentation.Recording recordSpanStart(SamplingResult samplingResult) { Attributes attribs = getAttributesForSamplingDecisions(samplingResult.getDecision()); live().add(1, attribs); return new Recording(attribs); } - private class Recording implements SpanMetrics.Recording { + private class Recording implements SpanInstrumentation.Recording { private final Attributes attributes; private boolean endAlreadyReported = false; diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SpanInstrumentation.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SpanInstrumentation.java new file mode 100644 index 00000000000..2ebd6980479 --- /dev/null +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SpanInstrumentation.java @@ -0,0 +1,46 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace.internal.metrics; + +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.sdk.common.InternalTelemetryVersion; +import io.opentelemetry.sdk.trace.samplers.SamplingResult; +import java.util.function.Supplier; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public interface SpanInstrumentation { + + static SpanInstrumentation noop() { + return NoopSpanInstrumentation.INSTANCE; + } + + static SpanInstrumentation create( + InternalTelemetryVersion internalTelemetryVersion, + Supplier meterProviderSupplier) { + switch (internalTelemetryVersion) { + case LEGACY: + return SpanInstrumentation.noop(); + case LATEST: + return new SemConvSpanInstrumentation(meterProviderSupplier); + } + throw new IllegalStateException( + "Unhandled telemetry schema version: " + internalTelemetryVersion); + } + + Recording recordSpanStart(SamplingResult samplingResult); + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + interface Recording { + + void recordSpanEnd(); + } +} diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SpanMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SpanMetrics.java deleted file mode 100644 index 810004d31ce..00000000000 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SpanMetrics.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.trace.internal.metrics; - -import io.opentelemetry.sdk.trace.samplers.SamplingResult; - -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -public interface SpanMetrics { - - static SpanMetrics noop() { - return NoopSpanMetrics.INSTANCE; - } - - Recording recordSpanStart(SamplingResult samplingResult); - - /** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ - interface Recording { - - void recordSpanEnd(); - } -} diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java index 4901d9ae95f..4d862521260 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java @@ -22,7 +22,7 @@ import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.TraceFlags; import io.opentelemetry.api.trace.TraceState; -import io.opentelemetry.sdk.trace.internal.metrics.SpanMetrics; +import io.opentelemetry.sdk.trace.internal.metrics.SpanInstrumentation; import java.time.Instant; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; @@ -40,7 +40,7 @@ class NonRecordingSpanTest { TraceFlags.getDefault(), TraceState.getDefault()); - @Mock SpanMetrics.Recording mockRecording; + @Mock SpanInstrumentation.Recording mockRecording; @Test void notRecording() { diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java index 7bec0b0b8d6..af57d424da8 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java @@ -49,7 +49,7 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.data.StatusData; import io.opentelemetry.sdk.trace.internal.ExtendedSpanProcessor; -import io.opentelemetry.sdk.trace.internal.metrics.SpanMetrics; +import io.opentelemetry.sdk.trace.internal.metrics.SpanInstrumentation; import java.io.PrintWriter; import java.io.StringWriter; import java.time.Duration; @@ -105,7 +105,7 @@ class SdkSpanTest { private Attributes expectedAttributes; private final LinkData link = LinkData.create(spanContext); @Mock private ExtendedSpanProcessor spanProcessor; - @Mock private SpanMetrics.Recording metricsRecording; + @Mock private SpanInstrumentation.Recording metricsRecording; private TestClock testClock; diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanMetricsTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanInstrumentationTest.java similarity index 76% rename from sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanMetricsTest.java rename to sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanInstrumentationTest.java index 1232471671a..03d75f3388b 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanMetricsTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanInstrumentationTest.java @@ -11,25 +11,27 @@ import io.opentelemetry.semconv.incubating.OtelIncubatingAttributes; import org.junit.jupiter.api.Test; -class SemConvSpanMetricsTest { +class SemConvSpanInstrumentationTest { @Test void verifyAttributesSemConvCompliant() { - assertThat(SemConvSpanMetrics.getAttributesForSamplingDecisions(SamplingDecision.DROP)) + assertThat(SemConvSpanInstrumentation.getAttributesForSamplingDecisions(SamplingDecision.DROP)) .hasSize(1) .containsEntry( OtelIncubatingAttributes.OTEL_SPAN_SAMPLING_RESULT, OtelIncubatingAttributes.OtelSpanSamplingResultIncubatingValues.DROP); assertThat( - SemConvSpanMetrics.getAttributesForSamplingDecisions( + SemConvSpanInstrumentation.getAttributesForSamplingDecisions( SamplingDecision.RECORD_AND_SAMPLE)) .hasSize(1) .containsEntry( OtelIncubatingAttributes.OTEL_SPAN_SAMPLING_RESULT, OtelIncubatingAttributes.OtelSpanSamplingResultIncubatingValues.RECORD_AND_SAMPLE); - assertThat(SemConvSpanMetrics.getAttributesForSamplingDecisions(SamplingDecision.RECORD_ONLY)) + assertThat( + SemConvSpanInstrumentation.getAttributesForSamplingDecisions( + SamplingDecision.RECORD_ONLY)) .hasSize(1) .containsEntry( OtelIncubatingAttributes.OTEL_SPAN_SAMPLING_RESULT, From f42b0ad9d7696779b2d4dea36a68b8502ee9b52d Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Mon, 30 Jun 2025 11:18:20 +0200 Subject: [PATCH 09/10] Remaining review fixes --- .../sdk/trace/NonRecordingSpan.java | 2 +- .../io/opentelemetry/sdk/trace/SdkSpan.java | 2 +- .../sdk/trace/SdkSpanBuilder.java | 2 +- .../sdk/trace/SdkTracerProvider.java | 2 +- .../sdk/trace/TracerSharedState.java | 2 +- .../NoopSpanInstrumentation.java | 2 +- .../SemConvSpanInstrumentation.java | 13 ++-- .../{metrics => }/SpanInstrumentation.java | 8 +-- .../sdk/trace/NonRecordingSpanTest.java | 2 +- .../sdk/trace/SdkSpanBuilderTest.java | 62 +++++-------------- .../opentelemetry/sdk/trace/SdkSpanTest.java | 2 +- .../SemConvSpanInstrumentationTest.java | 2 +- 12 files changed, 32 insertions(+), 69 deletions(-) rename sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/{metrics => }/NoopSpanInstrumentation.java (92%) rename sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/{metrics => }/SemConvSpanInstrumentation.java (91%) rename sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/{metrics => }/SpanInstrumentation.java (85%) rename sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/{metrics => }/SemConvSpanInstrumentationTest.java (96%) diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java index 777e646a9a3..d12f5dd1903 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java @@ -10,7 +10,7 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.sdk.trace.internal.metrics.SpanInstrumentation; +import io.opentelemetry.sdk.trace.internal.SpanInstrumentation; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java index 984a31472d7..4866587ec9a 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java @@ -26,7 +26,7 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.data.StatusData; import io.opentelemetry.sdk.trace.internal.ExtendedSpanProcessor; -import io.opentelemetry.sdk.trace.internal.metrics.SpanInstrumentation; +import io.opentelemetry.sdk.trace.internal.SpanInstrumentation; import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java index 60c274be59c..8eefed58724 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java @@ -24,7 +24,7 @@ import io.opentelemetry.sdk.internal.AttributeUtil; import io.opentelemetry.sdk.internal.AttributesMap; import io.opentelemetry.sdk.trace.data.LinkData; -import io.opentelemetry.sdk.trace.internal.metrics.SpanInstrumentation; +import io.opentelemetry.sdk.trace.internal.SpanInstrumentation; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; import io.opentelemetry.sdk.trace.samplers.SamplingResult; import java.util.ArrayList; diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java index 26672e7bd83..fc2136e79bf 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java @@ -18,8 +18,8 @@ import io.opentelemetry.sdk.internal.ScopeConfigurator; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil; +import io.opentelemetry.sdk.trace.internal.SpanInstrumentation; import io.opentelemetry.sdk.trace.internal.TracerConfig; -import io.opentelemetry.sdk.trace.internal.metrics.SpanInstrumentation; import io.opentelemetry.sdk.trace.samplers.Sampler; import java.io.Closeable; import java.util.List; diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java index c0a3af2d255..5d4834bd3f3 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java @@ -9,7 +9,7 @@ import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.internal.ExceptionAttributeResolver; import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.trace.internal.metrics.SpanInstrumentation; +import io.opentelemetry.sdk.trace.internal.SpanInstrumentation; import io.opentelemetry.sdk.trace.samplers.Sampler; import java.util.List; import java.util.function.Supplier; diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/NoopSpanInstrumentation.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/NoopSpanInstrumentation.java similarity index 92% rename from sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/NoopSpanInstrumentation.java rename to sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/NoopSpanInstrumentation.java index 003f2b52d42..0dea6aa0629 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/NoopSpanInstrumentation.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/NoopSpanInstrumentation.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.trace.internal.metrics; +package io.opentelemetry.sdk.trace.internal; import io.opentelemetry.sdk.trace.samplers.SamplingResult; diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanInstrumentation.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SemConvSpanInstrumentation.java similarity index 91% rename from sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanInstrumentation.java rename to sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SemConvSpanInstrumentation.java index 9af134ce57e..ca4718f42fd 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanInstrumentation.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SemConvSpanInstrumentation.java @@ -3,9 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.trace.internal.metrics; +package io.opentelemetry.sdk.trace.internal; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.internal.GuardedBy; import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.metrics.LongUpDownCounter; import io.opentelemetry.api.metrics.Meter; @@ -16,11 +17,7 @@ import java.util.function.Supplier; import javax.annotation.Nullable; -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -public class SemConvSpanInstrumentation implements SpanInstrumentation { +class SemConvSpanInstrumentation implements SpanInstrumentation { private final Supplier meterProviderSupplier; @@ -34,7 +31,7 @@ public class SemConvSpanInstrumentation implements SpanInstrumentation { @Nullable private volatile LongUpDownCounter live = null; @Nullable private volatile LongCounter ended = null; - public SemConvSpanInstrumentation(Supplier meterProviderSupplier) { + SemConvSpanInstrumentation(Supplier meterProviderSupplier) { this.meterProviderSupplier = meterProviderSupplier; } @@ -97,6 +94,8 @@ public SpanInstrumentation.Recording recordSpanStart(SamplingResult samplingResu private class Recording implements SpanInstrumentation.Recording { private final Attributes attributes; + + @GuardedBy("this") private boolean endAlreadyReported = false; private Recording(Attributes attributes) { diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SpanInstrumentation.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SpanInstrumentation.java similarity index 85% rename from sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SpanInstrumentation.java rename to sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SpanInstrumentation.java index 2ebd6980479..e58037e936a 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/metrics/SpanInstrumentation.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SpanInstrumentation.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.trace.internal.metrics; +package io.opentelemetry.sdk.trace.internal; import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.sdk.common.InternalTelemetryVersion; @@ -16,16 +16,12 @@ */ public interface SpanInstrumentation { - static SpanInstrumentation noop() { - return NoopSpanInstrumentation.INSTANCE; - } - static SpanInstrumentation create( InternalTelemetryVersion internalTelemetryVersion, Supplier meterProviderSupplier) { switch (internalTelemetryVersion) { case LEGACY: - return SpanInstrumentation.noop(); + return NoopSpanInstrumentation.INSTANCE; case LATEST: return new SemConvSpanInstrumentation(meterProviderSupplier); } diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java index 4d862521260..b1c74becac3 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java @@ -22,7 +22,7 @@ import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.TraceFlags; import io.opentelemetry.api.trace.TraceState; -import io.opentelemetry.sdk.trace.internal.metrics.SpanInstrumentation; +import io.opentelemetry.sdk.trace.internal.SpanInstrumentation; import java.time.Instant; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java index 8408a3246b2..88ce93f1768 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java @@ -13,6 +13,10 @@ import static io.opentelemetry.api.common.AttributeKey.longKey; import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.semconv.incubating.OtelIncubatingAttributes.OTEL_SPAN_SAMPLING_RESULT; +import static io.opentelemetry.semconv.incubating.OtelIncubatingAttributes.OtelSpanSamplingResultIncubatingValues.DROP; +import static io.opentelemetry.semconv.incubating.OtelIncubatingAttributes.OtelSpanSamplingResultIncubatingValues.RECORD_AND_SAMPLE; +import static io.opentelemetry.semconv.incubating.OtelIncubatingAttributes.OtelSpanSamplingResultIncubatingValues.RECORD_ONLY; import static java.util.Collections.emptyList; import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.assertThat; @@ -44,7 +48,6 @@ import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; import io.opentelemetry.sdk.trace.samplers.SamplingResult; -import io.opentelemetry.semconv.incubating.OtelIncubatingAttributes; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; @@ -1026,29 +1029,16 @@ public String getDescription() { ma.hasPointsSatisfying( pa -> pa.hasAttributes( - Attributes.of( - OtelIncubatingAttributes - .OTEL_SPAN_SAMPLING_RESULT, - OtelIncubatingAttributes - .OtelSpanSamplingResultIncubatingValues.DROP)) + Attributes.of(OTEL_SPAN_SAMPLING_RESULT, DROP)) .hasValue(1), pa -> pa.hasAttributes( - Attributes.of( - OtelIncubatingAttributes - .OTEL_SPAN_SAMPLING_RESULT, - OtelIncubatingAttributes - .OtelSpanSamplingResultIncubatingValues - .RECORD_ONLY)) + Attributes.of(OTEL_SPAN_SAMPLING_RESULT, RECORD_ONLY)) .hasValue(2), pa -> pa.hasAttributes( Attributes.of( - OtelIncubatingAttributes - .OTEL_SPAN_SAMPLING_RESULT, - OtelIncubatingAttributes - .OtelSpanSamplingResultIncubatingValues - .RECORD_AND_SAMPLE)) + OTEL_SPAN_SAMPLING_RESULT, RECORD_AND_SAMPLE)) .hasValue(3)))); spansToEnd.forEach(Span::end); @@ -1067,29 +1057,18 @@ public String getDescription() { pa -> pa.hasAttributes( Attributes.of( - OtelIncubatingAttributes - .OTEL_SPAN_SAMPLING_RESULT, - OtelIncubatingAttributes - .OtelSpanSamplingResultIncubatingValues - .DROP)) + OTEL_SPAN_SAMPLING_RESULT, DROP)) .hasValue(0), pa -> pa.hasAttributes( Attributes.of( - OtelIncubatingAttributes - .OTEL_SPAN_SAMPLING_RESULT, - OtelIncubatingAttributes - .OtelSpanSamplingResultIncubatingValues - .RECORD_ONLY)) + OTEL_SPAN_SAMPLING_RESULT, RECORD_ONLY)) .hasValue(0), pa -> pa.hasAttributes( Attributes.of( - OtelIncubatingAttributes - .OTEL_SPAN_SAMPLING_RESULT, - OtelIncubatingAttributes - .OtelSpanSamplingResultIncubatingValues - .RECORD_AND_SAMPLE)) + OTEL_SPAN_SAMPLING_RESULT, + RECORD_AND_SAMPLE)) .hasValue(0)))) .anySatisfy( metric -> @@ -1102,29 +1081,18 @@ public String getDescription() { pa -> pa.hasAttributes( Attributes.of( - OtelIncubatingAttributes - .OTEL_SPAN_SAMPLING_RESULT, - OtelIncubatingAttributes - .OtelSpanSamplingResultIncubatingValues - .DROP)) + OTEL_SPAN_SAMPLING_RESULT, DROP)) .hasValue(1), pa -> pa.hasAttributes( Attributes.of( - OtelIncubatingAttributes - .OTEL_SPAN_SAMPLING_RESULT, - OtelIncubatingAttributes - .OtelSpanSamplingResultIncubatingValues - .RECORD_ONLY)) + OTEL_SPAN_SAMPLING_RESULT, RECORD_ONLY)) .hasValue(2), pa -> pa.hasAttributes( Attributes.of( - OtelIncubatingAttributes - .OTEL_SPAN_SAMPLING_RESULT, - OtelIncubatingAttributes - .OtelSpanSamplingResultIncubatingValues - .RECORD_AND_SAMPLE)) + OTEL_SPAN_SAMPLING_RESULT, + RECORD_AND_SAMPLE)) .hasValue(3)))); endAssertions.run(); diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java index af57d424da8..0bf4701e500 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java @@ -49,7 +49,7 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.data.StatusData; import io.opentelemetry.sdk.trace.internal.ExtendedSpanProcessor; -import io.opentelemetry.sdk.trace.internal.metrics.SpanInstrumentation; +import io.opentelemetry.sdk.trace.internal.SpanInstrumentation; import java.io.PrintWriter; import java.io.StringWriter; import java.time.Duration; diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanInstrumentationTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/SemConvSpanInstrumentationTest.java similarity index 96% rename from sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanInstrumentationTest.java rename to sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/SemConvSpanInstrumentationTest.java index 03d75f3388b..5f806c3e6e3 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/metrics/SemConvSpanInstrumentationTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/internal/SemConvSpanInstrumentationTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.trace.internal.metrics; +package io.opentelemetry.sdk.trace.internal; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; From 64207146aeae608aaec944a88a6aff313b2ce9d0 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Thu, 3 Jul 2025 11:20:50 +0200 Subject: [PATCH 10/10] Replace span.ended with span.started, add parent.origin attrib --- .../sdk/internal/SemConvAttributes.java | 4 + .../sdk/trace/NonRecordingSpan.java | 133 ------------------ .../sdk/trace/SdkSpanBuilder.java | 8 +- .../internal/NoopSpanInstrumentation.java | 13 +- .../internal/SemConvSpanInstrumentation.java | 57 ++++++-- .../trace/internal/SpanInstrumentation.java | 5 +- .../sdk/trace/NonRecordingSpanTest.java | 104 -------------- .../sdk/trace/SdkSpanBuilderTest.java | 105 +++++++++----- 8 files changed, 139 insertions(+), 290 deletions(-) delete mode 100644 sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java delete mode 100644 sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/internal/SemConvAttributes.java b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/SemConvAttributes.java index 7813fc819d2..b73d6251cd4 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/internal/SemConvAttributes.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/SemConvAttributes.java @@ -28,6 +28,10 @@ private SemConvAttributes() {} public static final AttributeKey OTEL_SPAN_SAMPLING_RESULT = AttributeKey.stringKey("otel.span.sampling_result"); + // TODO: Add tests verifying correctness when included in the semconv-incubating release + public static final AttributeKey OTEL_SPAN_PARENT_ORIGIN = + AttributeKey.stringKey("otel.span.parent.origin"); + public static final AttributeKey SERVER_ADDRESS = AttributeKey.stringKey("server.address"); public static final AttributeKey SERVER_PORT = AttributeKey.longKey("server.port"); diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java deleted file mode 100644 index d12f5dd1903..00000000000 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/NonRecordingSpan.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.trace; - -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.sdk.trace.internal.SpanInstrumentation; -import java.util.concurrent.TimeUnit; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; - -/** - * Span implementation used from {@link io.opentelemetry.sdk.trace.SdkTracer} when starting a span - * which is not recording. This span implementation behaves exactly like one returned from {@link - * Span#wrap(SpanContext)} with the addition that {@link #end()} collects health metrics. - */ -@Immutable -final class NonRecordingSpan implements Span { - - private final SpanContext spanContext; - private final SpanInstrumentation.Recording metricRecording; - - NonRecordingSpan(SpanContext spanContext, SpanInstrumentation.Recording metricRecording) { - this.spanContext = spanContext; - this.metricRecording = metricRecording; - } - - @Override - public Span setAttribute(String key, @Nullable String value) { - return this; - } - - @Override - public Span setAttribute(String key, long value) { - return this; - } - - @Override - public Span setAttribute(String key, double value) { - return this; - } - - @Override - public Span setAttribute(String key, boolean value) { - return this; - } - - @Override - public Span setAttribute(AttributeKey key, @Nullable T value) { - return this; - } - - @Override - public Span setAllAttributes(Attributes attributes) { - return this; - } - - @Override - public Span addEvent(String name) { - return this; - } - - @Override - public Span addEvent(String name, long timestamp, TimeUnit unit) { - return this; - } - - @Override - public Span addEvent(String name, Attributes attributes) { - return this; - } - - @Override - public Span addEvent(String name, Attributes attributes, long timestamp, TimeUnit unit) { - return this; - } - - @Override - public Span setStatus(StatusCode statusCode) { - return this; - } - - @Override - public Span setStatus(StatusCode statusCode, String description) { - return this; - } - - @Override - public Span recordException(Throwable exception) { - return this; - } - - @Override - public Span recordException(Throwable exception, Attributes additionalAttributes) { - return this; - } - - @Override - public Span updateName(String name) { - return this; - } - - @Override - public void end() { - metricRecording.recordSpanEnd(); - } - - @Override - public void end(long timestamp, TimeUnit unit) { - end(); - } - - @Override - public SpanContext getSpanContext() { - return spanContext; - } - - @Override - public boolean isRecording() { - return false; - } - - @Override - public String toString() { - return "NonRecordingSpan{" + spanContext + '}'; - } -} diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java index 8eefed58724..e3b25ae7ba6 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java @@ -206,9 +206,13 @@ public Span startSpan() { tracerSharedState.isIdGeneratorSafeToSkipIdValidation()); SpanInstrumentation.Recording metricsRecording = - tracerSharedState.getSpanMetrics().recordSpanStart(samplingResult); + tracerSharedState.getSpanMetrics().recordSpanStart(samplingResult, parentSpanContext); if (!isRecording(samplingDecision)) { - return new NonRecordingSpan(spanContext, metricsRecording); + if (!metricsRecording.isNoop()) { + throw new IllegalStateException( + "instrumentation ending is not supported for non-recording spans."); + } + return Span.wrap(spanContext); } Attributes samplingAttributes = samplingResult.getAttributes(); if (!samplingAttributes.isEmpty()) { diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/NoopSpanInstrumentation.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/NoopSpanInstrumentation.java index 0dea6aa0629..f3b3abd22e9 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/NoopSpanInstrumentation.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/NoopSpanInstrumentation.java @@ -5,20 +5,27 @@ package io.opentelemetry.sdk.trace.internal; +import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.sdk.trace.samplers.SamplingResult; class NoopSpanInstrumentation implements SpanInstrumentation { static final NoopSpanInstrumentation INSTANCE = new NoopSpanInstrumentation(); + static final SpanInstrumentation.Recording RECORDING_INSTANCE = new NoopRecording(); + @Override - public SpanInstrumentation.Recording recordSpanStart(SamplingResult samplingResult) { - return NoopRecording.INSTANCE; + public SpanInstrumentation.Recording recordSpanStart( + SamplingResult samplingResult, SpanContext parentContext) { + return RECORDING_INSTANCE; } private static class NoopRecording implements SpanInstrumentation.Recording { - private static final NoopRecording INSTANCE = new NoopRecording(); + @Override + public boolean isNoop() { + return true; + } @Override public void recordSpanEnd() {} diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SemConvSpanInstrumentation.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SemConvSpanInstrumentation.java index ca4718f42fd..d8c21ad75a7 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SemConvSpanInstrumentation.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SemConvSpanInstrumentation.java @@ -11,6 +11,7 @@ import io.opentelemetry.api.metrics.LongUpDownCounter; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.sdk.internal.SemConvAttributes; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; import io.opentelemetry.sdk.trace.samplers.SamplingResult; @@ -29,7 +30,7 @@ class SemConvSpanInstrumentation implements SpanInstrumentation { Attributes.of(SemConvAttributes.OTEL_SPAN_SAMPLING_RESULT, "RECORD_AND_SAMPLE"); @Nullable private volatile LongUpDownCounter live = null; - @Nullable private volatile LongCounter ended = null; + @Nullable private volatile LongCounter started = null; SemConvSpanInstrumentation(Supplier meterProviderSupplier) { this.meterProviderSupplier = meterProviderSupplier; @@ -58,18 +59,18 @@ private LongUpDownCounter live() { return live; } - private LongCounter ended() { - LongCounter ended = this.ended; - if (ended == null) { - ended = + private LongCounter started() { + LongCounter started = this.started; + if (started == null) { + started = meter() - .counterBuilder("otel.sdk.span.ended") + .counterBuilder("otel.sdk.span.started") .setUnit("{span}") - .setDescription("The number of created spans for which the end operation was called") + .setDescription("The number of created spans") .build(); - this.ended = ended; + this.started = started; } - return ended; + return started; } static Attributes getAttributesForSamplingDecisions(SamplingDecision decision) { @@ -84,11 +85,35 @@ static Attributes getAttributesForSamplingDecisions(SamplingDecision decision) { throw new IllegalStateException("Unhandled SamplingDecision case: " + decision); } + // TODO: Add test verifying attribute values when released in semantic conventions + static String getParentOriginAttributeValue(SpanContext parentSpanContext) { + if (!parentSpanContext.isValid()) { + return "none"; + } else if (parentSpanContext.isRemote()) { + return "remote"; + } else { + return "local"; + } + } + @Override - public SpanInstrumentation.Recording recordSpanStart(SamplingResult samplingResult) { - Attributes attribs = getAttributesForSamplingDecisions(samplingResult.getDecision()); - live().add(1, attribs); - return new Recording(attribs); + public SpanInstrumentation.Recording recordSpanStart( + SamplingResult samplingResult, SpanContext parentSpanContext) { + Attributes samplingResultAttribs = + getAttributesForSamplingDecisions(samplingResult.getDecision()); + Attributes startAttributes = + samplingResultAttribs.toBuilder() + .put( + SemConvAttributes.OTEL_SPAN_PARENT_ORIGIN, + getParentOriginAttributeValue(parentSpanContext)) + .build(); + started().add(1L, startAttributes); + if (samplingResult.getDecision() == SamplingDecision.DROP) { + // Per semconv, otel.sdk.span.live is NOT collected for non-recording spans + return NoopSpanInstrumentation.RECORDING_INSTANCE; + } + live().add(1, samplingResultAttribs); + return new Recording(samplingResultAttribs); } private class Recording implements SpanInstrumentation.Recording { @@ -102,6 +127,11 @@ private Recording(Attributes attributes) { this.attributes = attributes; } + @Override + public boolean isNoop() { + return false; + } + @Override public synchronized void recordSpanEnd() { if (endAlreadyReported) { @@ -109,7 +139,6 @@ public synchronized void recordSpanEnd() { } endAlreadyReported = true; live().add(-1, attributes); - ended().add(1, attributes); } } } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SpanInstrumentation.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SpanInstrumentation.java index e58037e936a..e5e2eb77e32 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SpanInstrumentation.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SpanInstrumentation.java @@ -6,6 +6,7 @@ package io.opentelemetry.sdk.trace.internal; import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.sdk.common.InternalTelemetryVersion; import io.opentelemetry.sdk.trace.samplers.SamplingResult; import java.util.function.Supplier; @@ -29,7 +30,7 @@ static SpanInstrumentation create( "Unhandled telemetry schema version: " + internalTelemetryVersion); } - Recording recordSpanStart(SamplingResult samplingResult); + Recording recordSpanStart(SamplingResult samplingResult, SpanContext parentSpanContext); /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -37,6 +38,8 @@ static SpanInstrumentation create( */ interface Recording { + boolean isNoop(); + void recordSpanEnd(); } } diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java deleted file mode 100644 index b1c74becac3..00000000000 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/NonRecordingSpanTest.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.trace; - -import static io.opentelemetry.api.common.AttributeKey.booleanArrayKey; -import static io.opentelemetry.api.common.AttributeKey.booleanKey; -import static io.opentelemetry.api.common.AttributeKey.doubleArrayKey; -import static io.opentelemetry.api.common.AttributeKey.longArrayKey; -import static io.opentelemetry.api.common.AttributeKey.longKey; -import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; -import static io.opentelemetry.api.common.AttributeKey.stringKey; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.TraceFlags; -import io.opentelemetry.api.trace.TraceState; -import io.opentelemetry.sdk.trace.internal.SpanInstrumentation; -import java.time.Instant; -import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class NonRecordingSpanTest { - - private static final SpanContext DUMMY_CONTEXT = - SpanContext.create( - "a1a2a3a4a5a6a7a8b1b2b3b4b5b6b7b8", - "c1c2c3c4c5c6c7c8", - TraceFlags.getDefault(), - TraceState.getDefault()); - - @Mock SpanInstrumentation.Recording mockRecording; - - @Test - void notRecording() { - assertThat(new NonRecordingSpan(DUMMY_CONTEXT, mockRecording).isRecording()).isFalse(); - } - - @Test - void recordingFinishedOnEnd() { - Span span = new NonRecordingSpan(DUMMY_CONTEXT, mockRecording); - span.end(); - verify(mockRecording, times(1)).recordSpanEnd(); - span.end(0, TimeUnit.NANOSECONDS); - verify(mockRecording, times(2)).recordSpanEnd(); - span.end(Instant.EPOCH); - verify(mockRecording, times(3)).recordSpanEnd(); - } - - @Test - void doNotCrash() { - Span span = new NonRecordingSpan(DUMMY_CONTEXT, mockRecording); - span.setAttribute(stringKey("MyStringAttributeKey"), "MyStringAttributeValue"); - span.setAttribute(booleanKey("MyBooleanAttributeKey"), true); - span.setAttribute(longKey("MyLongAttributeKey"), 123L); - span.setAttribute(longKey("MyLongAttributeKey"), 123); - span.setAttribute("NullString", null); - span.setAttribute("EmptyString", ""); - span.setAttribute("long", 1); - span.setAttribute("double", 1.0); - span.setAttribute("boolean", true); - span.setAttribute(stringArrayKey("NullArrayString"), null); - span.setAttribute(booleanArrayKey("NullArrayBoolean"), null); - span.setAttribute(longArrayKey("NullArrayLong"), null); - span.setAttribute(doubleArrayKey("NullArrayDouble"), null); - span.setAttribute((String) null, null); - span.setAllAttributes(null); - span.setAllAttributes(Attributes.empty()); - span.setAllAttributes( - Attributes.of(stringKey("MyStringAttributeKey"), "MyStringAttributeValue")); - span.addEvent("event"); - span.addEvent("event", 0, TimeUnit.NANOSECONDS); - span.addEvent("event", Instant.EPOCH); - span.addEvent("event", Attributes.of(booleanKey("MyBooleanAttributeKey"), true)); - span.addEvent( - "event", Attributes.of(booleanKey("MyBooleanAttributeKey"), true), 0, TimeUnit.NANOSECONDS); - span.setStatus(StatusCode.OK); - span.setStatus(StatusCode.OK, "null"); - span.recordException(new IllegalStateException()); - span.recordException(new IllegalStateException(), Attributes.empty()); - span.updateName("name"); - } - - @Test - void verifyToString() { - Span span = new NonRecordingSpan(DUMMY_CONTEXT, mockRecording); - assertThat(span.toString()) - .isEqualTo( - "NonRecordingSpan{ImmutableSpanContext{traceId=a1a2a3a4a5a6a7a8b1b2b3b4b5b6b7b8, " - + "spanId=c1c2c3c4c5c6c7c8, traceFlags=00, " - + "traceState=ArrayBasedTraceState{entries=[]}, remote=false, valid=true}}"); - } -} diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java index 88ce93f1768..782159826f6 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java @@ -988,6 +988,16 @@ public String getDescription() { } }; + SpanContext remoteSpanContext = + SpanContext.createFromRemoteParent( + "12345678876543211234567887654321", + "8765432112345678", + TraceFlags.getSampled(), + TraceState.getDefault()); + + Context remoteContext = Context.root().with(Span.wrap(remoteSpanContext)); + Context localContext = Context.root().with(Span.wrap(sampledSpanContext)); + InMemoryMetricReader inMemoryMetrics = InMemoryMetricReader.create(); try (SdkMeterProvider meterProvider = SdkMeterProvider.builder().registerMetricReader(inMemoryMetrics).build()) { @@ -1005,41 +1015,66 @@ public String getDescription() { List spansToEnd = new ArrayList<>(); - currentSamplingDecision.set(SamplingDecision.DROP); + currentSamplingDecision.set(SamplingDecision.RECORD_AND_SAMPLE); spansToEnd.add(tracer.spanBuilder("dropped").startSpan()); currentSamplingDecision.set(SamplingDecision.RECORD_ONLY); - spansToEnd.add(tracer.spanBuilder("record_only1").startSpan()); - spansToEnd.add(tracer.spanBuilder("record_only2").startSpan()); + spansToEnd.add(tracer.spanBuilder("record_only1").setParent(remoteContext).startSpan()); + spansToEnd.add(tracer.spanBuilder("record_only2").setParent(remoteContext).startSpan()); - currentSamplingDecision.set(SamplingDecision.RECORD_AND_SAMPLE); - spansToEnd.add(tracer.spanBuilder("record_and_sample1").startSpan()); - spansToEnd.add(tracer.spanBuilder("record_and_sample2").startSpan()); - spansToEnd.add(tracer.spanBuilder("record_and_sample3").startSpan()); + currentSamplingDecision.set(SamplingDecision.DROP); + spansToEnd.add(tracer.spanBuilder("record_and_sample1").setParent(localContext).startSpan()); + spansToEnd.add(tracer.spanBuilder("record_and_sample2").setParent(localContext).startSpan()); + spansToEnd.add(tracer.spanBuilder("record_and_sample3").setParent(localContext).startSpan()); assertThat(inMemoryMetrics.collectAllMetrics()) - .hasSize(1) + .hasSize(2) .anySatisfy( metric -> OpenTelemetryAssertions.assertThat(metric) - .hasName("otel.sdk.span.live") + .hasName("otel.sdk.span.started") .hasUnit("{span}") .hasLongSumSatisfying( ma -> ma.hasPointsSatisfying( pa -> pa.hasAttributes( - Attributes.of(OTEL_SPAN_SAMPLING_RESULT, DROP)) + Attributes.builder() + .put(OTEL_SPAN_SAMPLING_RESULT, RECORD_AND_SAMPLE) + .put("otel.span.parent.origin", "none") + .build()) .hasValue(1), pa -> pa.hasAttributes( - Attributes.of(OTEL_SPAN_SAMPLING_RESULT, RECORD_ONLY)) + Attributes.builder() + .put(OTEL_SPAN_SAMPLING_RESULT, RECORD_ONLY) + .put("otel.span.parent.origin", "remote") + .build()) .hasValue(2), + pa -> + pa.hasAttributes( + Attributes.builder() + .put(OTEL_SPAN_SAMPLING_RESULT, DROP) + .put("otel.span.parent.origin", "local") + .build()) + .hasValue(3)))) + .anySatisfy( + metric -> + OpenTelemetryAssertions.assertThat(metric) + .hasName("otel.sdk.span.live") + .hasUnit("{span}") + .hasLongSumSatisfying( + ma -> + ma.hasPointsSatisfying( pa -> pa.hasAttributes( Attributes.of( OTEL_SPAN_SAMPLING_RESULT, RECORD_AND_SAMPLE)) - .hasValue(3)))); + .hasValue(1), + pa -> + pa.hasAttributes( + Attributes.of(OTEL_SPAN_SAMPLING_RESULT, RECORD_ONLY)) + .hasValue(2)))); spansToEnd.forEach(Span::end); Runnable endAssertions = @@ -1049,31 +1084,40 @@ public String getDescription() { .anySatisfy( metric -> OpenTelemetryAssertions.assertThat(metric) - .hasName("otel.sdk.span.live") + .hasName("otel.sdk.span.started") .hasUnit("{span}") .hasLongSumSatisfying( ma -> ma.hasPointsSatisfying( pa -> pa.hasAttributes( - Attributes.of( - OTEL_SPAN_SAMPLING_RESULT, DROP)) - .hasValue(0), + Attributes.builder() + .put( + OTEL_SPAN_SAMPLING_RESULT, + RECORD_AND_SAMPLE) + .put("otel.span.parent.origin", "none") + .build()) + .hasValue(1), pa -> pa.hasAttributes( - Attributes.of( - OTEL_SPAN_SAMPLING_RESULT, RECORD_ONLY)) - .hasValue(0), + Attributes.builder() + .put( + OTEL_SPAN_SAMPLING_RESULT, + RECORD_ONLY) + .put("otel.span.parent.origin", "remote") + .build()) + .hasValue(2), pa -> pa.hasAttributes( - Attributes.of( - OTEL_SPAN_SAMPLING_RESULT, - RECORD_AND_SAMPLE)) - .hasValue(0)))) + Attributes.builder() + .put(OTEL_SPAN_SAMPLING_RESULT, DROP) + .put("otel.span.parent.origin", "local") + .build()) + .hasValue(3)))) .anySatisfy( metric -> OpenTelemetryAssertions.assertThat(metric) - .hasName("otel.sdk.span.ended") + .hasName("otel.sdk.span.live") .hasUnit("{span}") .hasLongSumSatisfying( ma -> @@ -1081,19 +1125,14 @@ public String getDescription() { pa -> pa.hasAttributes( Attributes.of( - OTEL_SPAN_SAMPLING_RESULT, DROP)) - .hasValue(1), + OTEL_SPAN_SAMPLING_RESULT, + RECORD_AND_SAMPLE)) + .hasValue(0), pa -> pa.hasAttributes( Attributes.of( OTEL_SPAN_SAMPLING_RESULT, RECORD_ONLY)) - .hasValue(2), - pa -> - pa.hasAttributes( - Attributes.of( - OTEL_SPAN_SAMPLING_RESULT, - RECORD_AND_SAMPLE)) - .hasValue(3)))); + .hasValue(0)))); endAssertions.run(); // ensure double ending doesn't have any effect on the metrics