From 17d85e1e1b67d30da5d12331f8a25a0827a363f5 Mon Sep 17 00:00:00 2001 From: Taewoong Kim Date: Mon, 9 Nov 2026 00:40:15 +0900 Subject: [PATCH] End span even if ExtendedSpanProcessor.onEnding throws Signed-off-by: Taewoong Kim --- .../io/opentelemetry/sdk/trace/SdkSpan.java | 8 ++++- .../opentelemetry/sdk/trace/SdkSpanTest.java | 30 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) 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 ef3d2254c0b..a075a8927ba 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 @@ -20,6 +20,7 @@ import io.opentelemetry.sdk.common.internal.AttributesMap; import io.opentelemetry.sdk.common.internal.ExceptionAttributeResolver; import io.opentelemetry.sdk.common.internal.InstrumentationScopeUtil; +import io.opentelemetry.sdk.common.internal.ThrowableUtil; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.data.EventData; import io.opentelemetry.sdk.trace.data.ExceptionEventData; @@ -575,7 +576,12 @@ private void endInternal(long endEpochNanos) { if (spanProcessor instanceof ExtendedSpanProcessor) { ExtendedSpanProcessor extendedSpanProcessor = (ExtendedSpanProcessor) spanProcessor; if (extendedSpanProcessor.isOnEndingRequired()) { - extendedSpanProcessor.onEnding(this); + try { + extendedSpanProcessor.onEnding(this); + } catch (Throwable t) { + ThrowableUtil.propagateIfFatal(t); + logger.log(Level.WARNING, "SpanProcessor threw an Exception in onEnding", t); + } } } synchronized (lock) { 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 93d11865044..2756d401d46 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 @@ -23,6 +23,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -40,6 +41,7 @@ import io.opentelemetry.api.trace.TraceId; import io.opentelemetry.api.trace.TraceState; import io.opentelemetry.context.Context; +import io.opentelemetry.internal.testing.slf4j.SuppressLogger; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.common.internal.AttributesMap; import io.opentelemetry.sdk.common.internal.ExceptionAttributeResolver; @@ -256,6 +258,34 @@ void onEnding_latencyPinned() { assertThat(spanLatencyInProcessor.get()).isEqualTo(expectedDuration); } + @Test + @SuppressLogger(SdkSpan.class) + void onEnding_processorThrows() { + SdkSpan span = createTestSpan(SpanKind.INTERNAL); + doThrow(new IllegalStateException("onEnding failed")).when(spanProcessor).onEnding(any()); + + assertThatCode(span::end).doesNotThrowAnyException(); + assertThat(span.hasEnded()).isTrue(); + assertThat(span).extracting("spanEndingThread").isNull(); + verify(spanProcessor).onEnd(same(span)); + } + + @Test + @SuppressLogger(SdkSpan.class) + void onEnding_processorThrowsCheckedException() { + SdkSpan span = createTestSpan(SpanKind.INTERNAL); + doAnswer( + invocation -> { + throw new Exception("onEnding failed"); + }) + .when(spanProcessor) + .onEnding(any()); + + assertThatCode(span::end).doesNotThrowAnyException(); + assertThat(span.hasEnded()).isTrue(); + verify(spanProcessor).onEnd(same(span)); + } + @Test void toSpanData_ActiveSpan() { SdkSpan span = createTestSpan(SpanKind.INTERNAL);