From eeefe6436455ed9f4b6b3352581d06ced7d170ba Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 29 Apr 2026 17:32:20 +0200 Subject: [PATCH 1/2] make span stacktrace ignore inferred spans --- span-stacktrace/build.gradle.kts | 4 ++- .../stacktrace/StackTraceSpanProcessor.java | 11 +++++++ .../StackTraceSpanProcessorTest.java | 31 +++++++++++++------ 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/span-stacktrace/build.gradle.kts b/span-stacktrace/build.gradle.kts index 57f299754c..94cd5a36c8 100644 --- a/span-stacktrace/build.gradle.kts +++ b/span-stacktrace/build.gradle.kts @@ -15,7 +15,6 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") - compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") compileOnly("io.opentelemetry:opentelemetry-sdk-extension-incubator") compileOnly("io.opentelemetry.instrumentation:opentelemetry-declarative-config-bridge") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") @@ -30,4 +29,7 @@ dependencies { testCompileOnly("com.google.auto.service:auto-service-annotations") testImplementation("io.opentelemetry:opentelemetry-exporter-logging") + + // allows to test inferred spans that should be filtered-out + testCompileOnly(project(":inferred-spans")) } diff --git a/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java b/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java index 441e074469..474277ed78 100644 --- a/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java +++ b/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java @@ -59,6 +59,17 @@ public void onEnding(ReadWriteSpan span) { if (!filterPredicate.test(span)) { return; } + + // inferred spans are generated from sampling, thus the span processor is not actually called + // on when the span is active, so the stack trace collection should be skipped. + // + // we only get such spans when inferred spans processor executes before this span processor + // which may be considered to be a configuration error. + boolean isInferred = span.getInstrumentationScopeInfo().getName().equals("inferred-spans"); + if (isInferred) { + return; + } + span.setAttribute(CodeAttributes.CODE_STACKTRACE, generateSpanEndStacktrace()); } diff --git a/span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessorTest.java b/span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessorTest.java index e5be320194..e89de83da5 100644 --- a/span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessorTest.java +++ b/span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessorTest.java @@ -12,6 +12,7 @@ import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Scope; +import io.opentelemetry.contrib.inferredspans.InferredSpansProcessor; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder; @@ -49,10 +50,10 @@ void durationAndFiltering() { // over duration threshold checkSpanWithStackTrace("1ms", msToNs(2)); // under duration threshold - checkSpanWithoutStackTrace(YesPredicate.class, "2ms", msToNs(1)); + checkSpanWithoutStackTrace(YesPredicate.class, "2ms", msToNs(1), "test"); // filtering out span - checkSpanWithoutStackTrace(NoPredicate.class, "1ms", msToNs(20)); + checkSpanWithoutStackTrace(NoPredicate.class, "1ms", msToNs(20), "test"); } public static class YesPredicate implements Predicate { @@ -75,12 +76,12 @@ void defaultConfig() { long expectedDefault = msToNs(5); checkSpanWithStackTrace(null, expectedDefault); checkSpanWithStackTrace(null, expectedDefault + 1); - checkSpanWithoutStackTrace(YesPredicate.class, null, expectedDefault - 1); + checkSpanWithoutStackTrace(YesPredicate.class, null, expectedDefault - 1, "test"); } @Test void disabledConfig() { - checkSpanWithoutStackTrace(YesPredicate.class, "-1", 5); + checkSpanWithoutStackTrace(YesPredicate.class, "-1", 5, "test"); } @Test @@ -90,7 +91,13 @@ void spanWithExistingStackTrace() { "1ms", Duration.ofMillis(1).toNanos(), sb -> sb.setAttribute(CodeAttributes.CODE_STACKTRACE, "hello"), - stacktrace -> assertThat(stacktrace).isEqualTo("hello")); + stacktrace -> assertThat(stacktrace).isEqualTo("hello"), + "test"); + } + + @Test + void spanFromInferredSpansIgnored() { + checkSpanWithoutStackTrace(null, "1ms", msToNs(1), InferredSpansProcessor.TRACER_NAME); } private static void checkSpanWithStackTrace(String minDurationString, long spanDurationNanos) { @@ -102,19 +109,22 @@ private static void checkSpanWithStackTrace(String minDurationString, long spanD (stackTrace) -> assertThat(stackTrace) .describedAs("span stack trace should contain caller class name") - .contains(StackTraceSpanProcessorTest.class.getCanonicalName())); + .contains(StackTraceSpanProcessorTest.class.getCanonicalName()), + "test"); } private static void checkSpanWithoutStackTrace( Class> predicateClass, String minDurationString, - long spanDurationNanos) { + long spanDurationNanos, + String scopeName) { checkSpan( predicateClass, minDurationString, spanDurationNanos, Function.identity(), - (stackTrace) -> assertThat(stackTrace).describedAs("no stack trace expected").isNull()); + (stackTrace) -> assertThat(stackTrace).describedAs("no stack trace expected").isNull(), + scopeName); } private static void checkSpan( @@ -122,7 +132,8 @@ private static void checkSpan( String minDurationString, long spanDurationNanos, Function customizeSpanBuilder, - Consumer stackTraceCheck) { + Consumer stackTraceCheck, + String scopeName) { // must be re-created on every test as exporter is shut down on span processor close InMemorySpanExporter spansExporter = InMemorySpanExporter.create(); @@ -153,7 +164,7 @@ private static void checkSpan( try (OpenTelemetrySdk sdk = sdkBuilder.build().getOpenTelemetrySdk()) { - Tracer tracer = sdk.getTracer("test"); + Tracer tracer = sdk.getTracer(scopeName); Instant start = Instant.now(); Instant end = start.plusNanos(spanDurationNanos); From 65858cea959d586e0f429861a0c0f9af6dad9f38 Mon Sep 17 00:00:00 2001 From: SylvainJuge <763082+SylvainJuge@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:09:02 +0200 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../contrib/stacktrace/StackTraceSpanProcessor.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java b/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java index 474277ed78..5a3e64c7c2 100644 --- a/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java +++ b/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java @@ -60,11 +60,11 @@ public void onEnding(ReadWriteSpan span) { return; } - // inferred spans are generated from sampling, thus the span processor is not actually called - // on when the span is active, so the stack trace collection should be skipped. + // Inferred spans are generated from sampling, so this span processor is not actually called + // when the span is active, and stack trace collection should be skipped. // - // we only get such spans when inferred spans processor executes before this span processor - // which may be considered to be a configuration error. + // We only get such spans when the inferred spans processor executes before this span + // processor, which may be considered a configuration error. boolean isInferred = span.getInstrumentationScopeInfo().getName().equals("inferred-spans"); if (isInferred) { return;