From 5106802eda4235e1d2dbacded026ca59ecb246db Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 19 Jan 2026 20:54:01 -0800 Subject: [PATCH 1/3] Have user supplied attributes take precedence over exception-derived attributes --- .../sdk/logs/ExtendedSdkLogRecordBuilder.java | 22 +++++++++++++++++- .../sdk/logs/ExtendedLoggerBuilderTest.java | 23 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordBuilder.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordBuilder.java index 41596afa3da..2f8f3cfc7d8 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordBuilder.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordBuilder.java @@ -13,6 +13,7 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.internal.ExceptionAttributeResolver; import io.opentelemetry.sdk.internal.ExtendedAttributesMap; import java.time.Instant; import java.util.concurrent.TimeUnit; @@ -44,7 +45,7 @@ public ExtendedSdkLogRecordBuilder setException(Throwable throwable) { loggerSharedState .getExceptionAttributeResolver() .setExceptionAttributes( - this::setAttribute, + new ExceptionAttributeSetterWithPrecedence(), throwable, loggerSharedState.getLogLimits().getMaxAttributeValueLength()); @@ -154,4 +155,23 @@ public void emit() { body, extendedAttributes)); } + + /** + * AttributeSetter that only sets attributes if they haven't already been set by the user. This + * ensures user-set attributes take precedence over exception-derived attributes. + */ + private class ExceptionAttributeSetterWithPrecedence + implements ExceptionAttributeResolver.AttributeSetter { + + @Override + public void setAttribute(AttributeKey key, @javax.annotation.Nullable T value) { + if (key == null || key.getKey().isEmpty() || value == null) { + return; + } + // Only set the attribute if it hasn't been set already + if (extendedAttributes == null || extendedAttributes.get(key) == null) { + ExtendedSdkLogRecordBuilder.this.setAttribute(key, value); + } + } + } } diff --git a/sdk/logs/src/testIncubating/java/io/opentelemetry/sdk/logs/ExtendedLoggerBuilderTest.java b/sdk/logs/src/testIncubating/java/io/opentelemetry/sdk/logs/ExtendedLoggerBuilderTest.java index 17e1eb0ddff..9a97f302048 100644 --- a/sdk/logs/src/testIncubating/java/io/opentelemetry/sdk/logs/ExtendedLoggerBuilderTest.java +++ b/sdk/logs/src/testIncubating/java/io/opentelemetry/sdk/logs/ExtendedLoggerBuilderTest.java @@ -76,4 +76,27 @@ public void setExceptionAttributes( equalTo(EXCEPTION_TYPE, "type"), equalTo(EXCEPTION_STACKTRACE, "stacktrace"))); } + + @Test + void setException_UserAttributesTakePrecedence() { + Logger logger = loggerProviderBuilder.build().get("logger"); + + ((ExtendedLogRecordBuilder) logger.logRecordBuilder()) + .setException(new Exception("error")) + .setAttribute(EXCEPTION_MESSAGE, "custom message") + .emit(); + + assertThat(exporter.getFinishedLogRecordItems()) + .satisfiesExactly( + logRecord -> + assertThat(logRecord) + .hasAttributesSatisfyingExactly( + equalTo(EXCEPTION_TYPE, "java.lang.Exception"), + equalTo(EXCEPTION_MESSAGE, "custom message"), + satisfies( + EXCEPTION_STACKTRACE, + stacktrace -> + stacktrace.startsWith( + "java.lang.Exception: error" + System.lineSeparator())))); + } } From 56ed6ee6efd56f5cee6236266ce283cadd59bbd7 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 2 Feb 2026 18:31:18 -0800 Subject: [PATCH 2/3] setExceptionAttribute --- .../sdk/logs/ExtendedSdkLogRecordBuilder.java | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordBuilder.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordBuilder.java index 2f8f3cfc7d8..a05ea8d520b 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordBuilder.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordBuilder.java @@ -13,7 +13,6 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.internal.ExceptionAttributeResolver; import io.opentelemetry.sdk.internal.ExtendedAttributesMap; import java.time.Instant; import java.util.concurrent.TimeUnit; @@ -45,7 +44,7 @@ public ExtendedSdkLogRecordBuilder setException(Throwable throwable) { loggerSharedState .getExceptionAttributeResolver() .setExceptionAttributes( - new ExceptionAttributeSetterWithPrecedence(), + this::setExceptionAttribute, throwable, loggerSharedState.getLogLimits().getMaxAttributeValueLength()); @@ -157,21 +156,15 @@ public void emit() { } /** - * AttributeSetter that only sets attributes if they haven't already been set by the user. This + * Sets an exception-derived attribute only if it hasn't already been set by the user. This * ensures user-set attributes take precedence over exception-derived attributes. */ - private class ExceptionAttributeSetterWithPrecedence - implements ExceptionAttributeResolver.AttributeSetter { - - @Override - public void setAttribute(AttributeKey key, @javax.annotation.Nullable T value) { - if (key == null || key.getKey().isEmpty() || value == null) { - return; - } - // Only set the attribute if it hasn't been set already - if (extendedAttributes == null || extendedAttributes.get(key) == null) { - ExtendedSdkLogRecordBuilder.this.setAttribute(key, value); - } + private void setExceptionAttribute(AttributeKey key, @Nullable T value) { + if (key == null || key.getKey().isEmpty() || value == null) { + return; + } + if (extendedAttributes == null || extendedAttributes.get(key) == null) { + setAttribute(key, value); } } } From 13879e17bc18daa3cd35173e85ada83d0585f1e4 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 2 Feb 2026 18:31:49 -0800 Subject: [PATCH 3/3] Re-order test to exercise precedence --- .../io/opentelemetry/sdk/logs/ExtendedLoggerBuilderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/logs/src/testIncubating/java/io/opentelemetry/sdk/logs/ExtendedLoggerBuilderTest.java b/sdk/logs/src/testIncubating/java/io/opentelemetry/sdk/logs/ExtendedLoggerBuilderTest.java index 9a97f302048..b27fd21d641 100644 --- a/sdk/logs/src/testIncubating/java/io/opentelemetry/sdk/logs/ExtendedLoggerBuilderTest.java +++ b/sdk/logs/src/testIncubating/java/io/opentelemetry/sdk/logs/ExtendedLoggerBuilderTest.java @@ -82,8 +82,8 @@ void setException_UserAttributesTakePrecedence() { Logger logger = loggerProviderBuilder.build().get("logger"); ((ExtendedLogRecordBuilder) logger.logRecordBuilder()) - .setException(new Exception("error")) .setAttribute(EXCEPTION_MESSAGE, "custom message") + .setException(new Exception("error")) .emit(); assertThat(exporter.getFinishedLogRecordItems())