Skip to content

Commit 250021c

Browse files
authored
Stabilize LogRecordBuilder setException (#8089)
1 parent 2fd26c9 commit 250021c

File tree

10 files changed

+115
-26
lines changed

10 files changed

+115
-26
lines changed

api/all/src/main/java/io/opentelemetry/api/logs/LogRecordBuilder.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,14 @@ default LogRecordBuilder setEventName(String eventName) {
210210
return this;
211211
}
212212

213+
/**
214+
* Set {@code exception.type}, {@code exception.message}, and {@code exception.stacktrace}
215+
* attributes based on the {@code throwable}.
216+
*/
217+
default LogRecordBuilder setException(Throwable throwable) {
218+
return this;
219+
}
220+
213221
/** Emit the log record. */
214222
void emit();
215223
}

api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedLogRecordBuilder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ default ExtendedLogRecordBuilder setAllAttributes(ExtendedAttributes attributes)
133133
*/
134134
<T> ExtendedLogRecordBuilder setAttribute(ExtendedAttributeKey<T> key, T value);
135135

136-
/** Set standard {@code exception.*} attributes based on the {@code throwable}. */
136+
/** {@inheritDoc} */
137+
@Override
137138
ExtendedLogRecordBuilder setException(Throwable throwable);
138139
}

api/testing-internal/src/main/java/io/opentelemetry/api/testing/internal/AbstractDefaultLoggerTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ void buildAndEmit() {
6363
.setBody(Value.of("body"))
6464
.setAttribute(AttributeKey.stringKey("key1"), "value1")
6565
.setAllAttributes(Attributes.builder().put("key2", "value2").build())
66+
.setException(new RuntimeException("error"))
6667
.emit())
6768
.doesNotThrowAnyException();
6869
}

docs/apidiffs/current_vs_latest/opentelemetry-api.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
Comparing source compatibility of opentelemetry-api-1.60.0-SNAPSHOT.jar against opentelemetry-api-1.59.0.jar
2+
*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.logs.LogRecordBuilder (not serializable)
3+
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
4+
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.logs.LogRecordBuilder setException(java.lang.Throwable)
25
*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.trace.TraceFlags (not serializable)
36
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
47
+++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.trace.TraceFlagsBuilder builder()
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
Comparing source compatibility of opentelemetry-sdk-testing-1.60.0-SNAPSHOT.jar against opentelemetry-sdk-testing-1.59.0.jar
2-
No changes.
2+
*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.testing.assertj.LogRecordDataAssert (not serializable)
3+
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
4+
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.LogRecordDataAssert hasException(java.lang.Throwable)

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordBuilder.java

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,7 @@ public ExtendedSdkLogRecordBuilder setEventName(String eventName) {
3939

4040
@Override
4141
public ExtendedSdkLogRecordBuilder setException(Throwable throwable) {
42-
if (throwable == null) {
43-
return this;
44-
}
45-
46-
loggerSharedState
47-
.getExceptionAttributeResolver()
48-
.setExceptionAttributes(
49-
this::setExceptionAttribute,
50-
throwable,
51-
loggerSharedState.getLogLimits().getMaxAttributeValueLength());
52-
42+
super.setException(throwable);
5343
return this;
5444
}
5545

@@ -145,11 +135,8 @@ protected ReadWriteLogRecord createLogRecord(Context context, long observedTimes
145135
extendedAttributes);
146136
}
147137

148-
/**
149-
* Sets an exception-derived attribute only if it hasn't already been set by the user. This
150-
* ensures user-set attributes take precedence over exception-derived attributes.
151-
*/
152-
private <T> void setExceptionAttribute(AttributeKey<T> key, @Nullable T value) {
138+
@Override
139+
protected <T> void setExceptionAttribute(AttributeKey<T> key, @Nullable T value) {
153140
if (key == null || key.getKey().isEmpty() || value == null) {
154141
return;
155142
}

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,22 @@ public SdkLogRecordBuilder setBody(Value<?> value) {
105105
return this;
106106
}
107107

108+
@Override
109+
public SdkLogRecordBuilder setException(Throwable throwable) {
110+
if (throwable == null) {
111+
return this;
112+
}
113+
114+
loggerSharedState
115+
.getExceptionAttributeResolver()
116+
.setExceptionAttributes(
117+
this::setExceptionAttribute,
118+
throwable,
119+
loggerSharedState.getLogLimits().getMaxAttributeValueLength());
120+
121+
return this;
122+
}
123+
108124
@Override
109125
public <T> SdkLogRecordBuilder setAttribute(AttributeKey<T> key, @Nullable T value) {
110126
if (key == null || key.getKey().isEmpty() || value == null) {
@@ -139,6 +155,19 @@ public void emit() {
139155
.onEmit(context, createLogRecord(context, observedTimestampEpochNanos));
140156
}
141157

158+
/**
159+
* Sets an exception-derived attribute only if it hasn't already been set by the user. This
160+
* ensures user-set attributes take precedence over exception-derived attributes.
161+
*/
162+
protected <T> void setExceptionAttribute(AttributeKey<T> key, @Nullable T value) {
163+
if (key == null || key.getKey().isEmpty() || value == null) {
164+
return;
165+
}
166+
if (attributes == null || attributes.get(key) == null) {
167+
setAttribute(key, value);
168+
}
169+
}
170+
142171
protected ReadWriteLogRecord createLogRecord(Context context, long observedTimestampEpochNanos) {
143172
return SdkReadWriteLogRecord.create(
144173
loggerSharedState.getLogLimits(),

sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilderTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import io.opentelemetry.context.Context;
2929
import io.opentelemetry.sdk.common.Clock;
3030
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
31+
import io.opentelemetry.sdk.common.internal.ExceptionAttributeResolver;
3132
import io.opentelemetry.sdk.logs.internal.LoggerConfig;
3233
import io.opentelemetry.sdk.resources.Resource;
3334
import java.time.Instant;
@@ -63,6 +64,8 @@ void setup() {
6364
when(loggerSharedState.getClock()).thenReturn(clock);
6465
when(loggerSharedState.getLoggerInstrumentation())
6566
.thenReturn(new SdkLoggerInstrumentation(MeterProvider::noop));
67+
when(loggerSharedState.getExceptionAttributeResolver())
68+
.thenReturn(ExceptionAttributeResolver.getDefault());
6669

6770
SdkLogger logger = new SdkLogger(loggerSharedState, SCOPE_INFO, LoggerConfig.enabled());
6871
builder = new SdkLogRecordBuilder(loggerSharedState, SCOPE_INFO, logger);
@@ -166,4 +169,31 @@ void testConvenienceAttributeMethods() {
166169
equalTo(booleanKey("bk"), true),
167170
equalTo(longKey("ik"), 13L));
168171
}
172+
173+
@Test
174+
void setException() {
175+
Exception exception = new Exception("error");
176+
177+
builder.setException(exception).emit();
178+
179+
assertThat(emittedLog.get().toLogRecordData()).hasException(exception);
180+
}
181+
182+
@Test
183+
void setException_UserAttributesTakePrecedence() {
184+
builder
185+
.setAttribute(stringKey("exception.message"), "custom message")
186+
.setException(new Exception("error"))
187+
.emit();
188+
189+
assertThat(emittedLog.get().toLogRecordData())
190+
.hasAttributesSatisfying(
191+
attributes -> {
192+
assertThat(attributes.get(stringKey("exception.type")))
193+
.isEqualTo("java.lang.Exception");
194+
assertThat(attributes.get(stringKey("exception.message")))
195+
.isEqualTo("custom message");
196+
assertThat(attributes.get(stringKey("exception.stacktrace"))).isNotNull();
197+
});
198+
}
169199
}

sdk/logs/src/testIncubating/java/io/opentelemetry/sdk/logs/ExtendedLoggerBuilderTest.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE;
1313
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE;
1414

15-
import io.opentelemetry.api.incubator.logs.ExtendedLogRecordBuilder;
1615
import io.opentelemetry.api.logs.Logger;
1716
import io.opentelemetry.sdk.common.internal.ExceptionAttributeResolver;
1817
import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor;
@@ -30,9 +29,7 @@ class ExtendedLoggerBuilderTest {
3029
void setException_DefaultResolver() {
3130
Logger logger = loggerProviderBuilder.build().get("logger");
3231

33-
((ExtendedLogRecordBuilder) logger.logRecordBuilder())
34-
.setException(new Exception("error"))
35-
.emit();
32+
logger.logRecordBuilder().setException(new Exception("error")).emit();
3633

3734
assertThat(exporter.getFinishedLogRecordItems())
3835
.satisfiesExactly(
@@ -64,9 +61,7 @@ public void setExceptionAttributes(
6461

6562
Logger logger = loggerProviderBuilder.build().get("logger");
6663

67-
((ExtendedLogRecordBuilder) logger.logRecordBuilder())
68-
.setException(new Exception("error"))
69-
.emit();
64+
logger.logRecordBuilder().setException(new Exception("error")).emit();
7065

7166
assertThat(exporter.getFinishedLogRecordItems())
7267
.satisfiesExactly(
@@ -81,7 +76,8 @@ public void setExceptionAttributes(
8176
void setException_UserAttributesTakePrecedence() {
8277
Logger logger = loggerProviderBuilder.build().get("logger");
8378

84-
((ExtendedLogRecordBuilder) logger.logRecordBuilder())
79+
logger
80+
.logRecordBuilder()
8581
.setAttribute(EXCEPTION_MESSAGE, "custom message")
8682
.setException(new Exception("error"))
8783
.emit();

sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LogRecordDataAssert.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@
3737
*/
3838
public final class LogRecordDataAssert extends AbstractAssert<LogRecordDataAssert, LogRecordData> {
3939

40+
private static final AttributeKey<String> EXCEPTION_TYPE =
41+
AttributeKey.stringKey("exception.type");
42+
private static final AttributeKey<String> EXCEPTION_MESSAGE =
43+
AttributeKey.stringKey("exception.message");
44+
private static final AttributeKey<String> EXCEPTION_STACKTRACE =
45+
AttributeKey.stringKey("exception.stacktrace");
46+
4047
LogRecordDataAssert(@Nullable LogRecordData actual) {
4148
super(actual, LogRecordDataAssert.class);
4249
}
@@ -350,6 +357,31 @@ public <T> LogRecordDataAssert hasBodyField(AttributeKey<T> key, T value) {
350357
return this;
351358
}
352359

360+
/**
361+
* Asserts the log has exception attributes for the given {@link Throwable}. The stack trace is
362+
* not matched against.
363+
*/
364+
@SuppressWarnings("NullAway")
365+
public LogRecordDataAssert hasException(Throwable exception) {
366+
isNotNull();
367+
368+
assertThat(actual.getAttributes())
369+
.as("exception.type")
370+
.containsEntry(EXCEPTION_TYPE, exception.getClass().getCanonicalName());
371+
if (exception.getMessage() != null) {
372+
assertThat(actual.getAttributes())
373+
.as("exception.message")
374+
.containsEntry(EXCEPTION_MESSAGE, exception.getMessage());
375+
}
376+
377+
// Exceptions used in assertions always have a different stack trace, just confirm it was
378+
// recorded.
379+
String stackTrace = actual.getAttributes().get(EXCEPTION_STACKTRACE);
380+
assertThat(stackTrace).as("exception.stacktrace").isNotNull();
381+
382+
return this;
383+
}
384+
353385
/** Asserts the log has the given attributes. */
354386
public LogRecordDataAssert hasAttributes(Attributes attributes) {
355387
isNotNull();

0 commit comments

Comments
 (0)