Skip to content

Commit 4e243f2

Browse files
authored
Add ExtendedLogRecordBuilder#setException (#7182)
1 parent d7fb208 commit 4e243f2

File tree

12 files changed

+149
-68
lines changed

12 files changed

+149
-68
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public ExtendedLogRecordBuilder setEventName(String eventName) {
4040
return this;
4141
}
4242

43+
@Override
44+
public ExtendedLogRecordBuilder setException(Throwable throwable) {
45+
return this;
46+
}
47+
4348
@Override
4449
public LogRecordBuilder setTimestamp(long timestamp, TimeUnit unit) {
4550
return this;

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,7 @@ public interface ExtendedLogRecordBuilder extends LogRecordBuilder {
1919
* record with a non-empty event name is an Event.
2020
*/
2121
ExtendedLogRecordBuilder setEventName(String eventName);
22+
23+
/** Set standard {@code exception.*} attributes based on the {@code throwable}. */
24+
ExtendedLogRecordBuilder setException(Throwable throwable);
2225
}

sdk/common/src/main/java/io/opentelemetry/sdk/internal/AttributeUtil.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
import io.opentelemetry.api.common.AttributeKey;
99
import io.opentelemetry.api.common.Attributes;
1010
import io.opentelemetry.api.common.AttributesBuilder;
11+
import java.io.PrintWriter;
12+
import java.io.StringWriter;
1113
import java.util.ArrayList;
1214
import java.util.List;
1315
import java.util.Map;
16+
import java.util.function.BiConsumer;
1417
import java.util.function.Predicate;
1518

1619
/**
@@ -19,6 +22,13 @@
1922
*/
2023
public final class AttributeUtil {
2124

25+
private static final AttributeKey<String> EXCEPTION_TYPE =
26+
AttributeKey.stringKey("exception.type");
27+
private static final AttributeKey<String> EXCEPTION_MESSAGE =
28+
AttributeKey.stringKey("exception.message");
29+
private static final AttributeKey<String> EXCEPTION_STACKTRACE =
30+
AttributeKey.stringKey("exception.stacktrace");
31+
2232
private AttributeUtil() {}
2333

2434
/**
@@ -95,4 +105,26 @@ public static Object applyAttributeLengthLimit(Object value, int lengthLimit) {
95105
}
96106
return value;
97107
}
108+
109+
public static void addExceptionAttributes(
110+
Throwable exception, BiConsumer<AttributeKey<String>, String> attributeConsumer) {
111+
String exceptionType = exception.getClass().getCanonicalName();
112+
if (exceptionType != null) {
113+
attributeConsumer.accept(EXCEPTION_TYPE, exceptionType);
114+
}
115+
116+
String exceptionMessage = exception.getMessage();
117+
if (exceptionMessage != null) {
118+
attributeConsumer.accept(EXCEPTION_MESSAGE, exceptionMessage);
119+
}
120+
121+
StringWriter stringWriter = new StringWriter();
122+
try (PrintWriter printWriter = new PrintWriter(stringWriter)) {
123+
exception.printStackTrace(printWriter);
124+
}
125+
String stackTrace = stringWriter.toString();
126+
if (stackTrace != null) {
127+
attributeConsumer.accept(EXCEPTION_STACKTRACE, stackTrace);
128+
}
129+
}
98130
}

sdk/logs/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ testing {
2828
dependencies {
2929
implementation(project(":sdk:testing"))
3030
implementation(project(":api:incubator"))
31+
implementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating")
3132
implementation("com.google.guava:guava")
3233
}
3334
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ public ExtendedSdkLogRecordBuilder setEventName(String eventName) {
2929
return this;
3030
}
3131

32+
@Override
33+
public ExtendedSdkLogRecordBuilder setException(Throwable throwable) {
34+
super.setException(throwable);
35+
return this;
36+
}
37+
3238
@Override
3339
public ExtendedSdkLogRecordBuilder setTimestamp(long timestamp, TimeUnit unit) {
3440
super.setTimestamp(timestamp, unit);

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import io.opentelemetry.api.trace.Span;
1313
import io.opentelemetry.context.Context;
1414
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
15+
import io.opentelemetry.sdk.internal.AttributeUtil;
1516
import io.opentelemetry.sdk.internal.AttributesMap;
1617
import java.time.Instant;
1718
import java.util.concurrent.TimeUnit;
@@ -46,6 +47,17 @@ SdkLogRecordBuilder setEventName(String eventName) {
4647
return this;
4748
}
4849

50+
// accessible via ExtendedSdkLogRecordBuilder
51+
SdkLogRecordBuilder setException(Throwable throwable) {
52+
if (throwable == null) {
53+
return this;
54+
}
55+
56+
AttributeUtil.addExceptionAttributes(throwable, this::setAttribute);
57+
58+
return this;
59+
}
60+
4961
@Override
5062
public SdkLogRecordBuilder setTimestamp(long timestamp, TimeUnit unit) {
5163
this.timestampEpochNanos = unit.toNanos(timestamp);
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.sdk.logs;
7+
8+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
9+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
10+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
11+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE;
12+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE;
13+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE;
14+
15+
import io.opentelemetry.api.incubator.logs.ExtendedLogRecordBuilder;
16+
import io.opentelemetry.api.logs.Logger;
17+
import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor;
18+
import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter;
19+
import org.junit.jupiter.api.Test;
20+
21+
class ExtendedLoggerBuilderTest {
22+
23+
private final InMemoryLogRecordExporter exporter = InMemoryLogRecordExporter.create();
24+
private final SdkLoggerProviderBuilder loggerProviderBuilder =
25+
SdkLoggerProvider.builder().addLogRecordProcessor(SimpleLogRecordProcessor.create(exporter));
26+
27+
@Test
28+
void setException() {
29+
Logger logger = loggerProviderBuilder.build().get("logger");
30+
31+
((ExtendedLogRecordBuilder) logger.logRecordBuilder())
32+
.setException(new Exception("error"))
33+
.emit();
34+
35+
assertThat(exporter.getFinishedLogRecordItems())
36+
.satisfiesExactly(
37+
logRecord ->
38+
assertThat(logRecord)
39+
.hasAttributesSatisfyingExactly(
40+
equalTo(EXCEPTION_TYPE, "java.lang.Exception"),
41+
equalTo(EXCEPTION_MESSAGE, "error"),
42+
satisfies(
43+
EXCEPTION_STACKTRACE,
44+
stacktrace ->
45+
stacktrace.startsWith(
46+
"java.lang.Exception: error" + System.lineSeparator()))));
47+
}
48+
}

sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
import io.opentelemetry.sdk.trace.data.SpanData;
2626
import io.opentelemetry.sdk.trace.data.StatusData;
2727
import io.opentelemetry.sdk.trace.internal.ExtendedSpanProcessor;
28-
import java.io.PrintWriter;
29-
import java.io.StringWriter;
3028
import java.util.ArrayList;
3129
import java.util.Collections;
3230
import java.util.List;
@@ -117,13 +115,6 @@ private enum EndState {
117115
@Nullable
118116
private Thread spanEndingThread;
119117

120-
private static final AttributeKey<String> EXCEPTION_TYPE =
121-
AttributeKey.stringKey("exception.type");
122-
private static final AttributeKey<String> EXCEPTION_MESSAGE =
123-
AttributeKey.stringKey("exception.message");
124-
private static final AttributeKey<String> EXCEPTION_STACKTRACE =
125-
AttributeKey.stringKey("exception.stacktrace");
126-
127118
private SdkSpan(
128119
SpanContext context,
129120
String name,
@@ -466,7 +457,6 @@ public ReadWriteSpan recordException(Throwable exception) {
466457
}
467458

468459
@Override
469-
@SuppressWarnings("unchecked")
470460
public ReadWriteSpan recordException(Throwable exception, Attributes additionalAttributes) {
471461
if (exception == null) {
472462
return this;
@@ -478,23 +468,8 @@ public ReadWriteSpan recordException(Throwable exception, Attributes additionalA
478468
AttributesMap attributes =
479469
AttributesMap.create(
480470
spanLimits.getMaxNumberOfAttributes(), spanLimits.getMaxAttributeValueLength());
481-
String exceptionName = exception.getClass().getCanonicalName();
482-
String exceptionMessage = exception.getMessage();
483-
StringWriter stringWriter = new StringWriter();
484-
try (PrintWriter printWriter = new PrintWriter(stringWriter)) {
485-
exception.printStackTrace(printWriter);
486-
}
487-
String stackTrace = stringWriter.toString();
488471

489-
if (exceptionName != null) {
490-
attributes.put(EXCEPTION_TYPE, exceptionName);
491-
}
492-
if (exceptionMessage != null) {
493-
attributes.put(EXCEPTION_MESSAGE, exceptionMessage);
494-
}
495-
if (stackTrace != null) {
496-
attributes.put(EXCEPTION_STACKTRACE, stackTrace);
497-
}
472+
AttributeUtil.addExceptionAttributes(exception, attributes::put);
498473

499474
additionalAttributes.forEach(attributes::put);
500475

sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import static java.util.Objects.requireNonNull;
99

10+
import io.opentelemetry.api.trace.Span;
1011
import io.opentelemetry.sdk.common.Clock;
1112
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
1213
import io.opentelemetry.sdk.internal.ScopeConfigurator;
@@ -36,8 +37,8 @@ public final class SdkTracerProviderBuilder {
3637
TracerConfig.configuratorBuilder();
3738

3839
/**
39-
* Assign a {@link Clock}. {@link Clock} will be used each time a {@link
40-
* io.opentelemetry.api.trace.Span} is started, ended or any event is recorded.
40+
* Assign a {@link Clock}. {@link Clock} will be used each time a {@link Span} is started, ended
41+
* or any event is recorded.
4142
*
4243
* <p>The {@code clock} must be thread-safe and return immediately (no remote calls, as contention
4344
* free as possible).
@@ -52,8 +53,8 @@ public SdkTracerProviderBuilder setClock(Clock clock) {
5253
}
5354

5455
/**
55-
* Assign an {@link IdGenerator}. {@link IdGenerator} will be used each time a {@link
56-
* io.opentelemetry.api.trace.Span} is started.
56+
* Assign an {@link IdGenerator}. {@link IdGenerator} will be used each time a {@link Span} is
57+
* started.
5758
*
5859
* <p>The {@code idGenerator} must be thread-safe and return immediately (no remote calls, as
5960
* contention free as possible).
@@ -97,8 +98,7 @@ public SdkTracerProviderBuilder addResource(Resource resource) {
9798
* <p>This method is equivalent to calling {@link #setSpanLimits(Supplier)} like this {@code
9899
* #setSpanLimits(() -> spanLimits)}.
99100
*
100-
* @param spanLimits the limits that will be used for every {@link
101-
* io.opentelemetry.api.trace.Span}.
101+
* @param spanLimits the limits that will be used for every {@link Span}.
102102
* @return this
103103
*/
104104
public SdkTracerProviderBuilder setSpanLimits(SpanLimits spanLimits) {
@@ -109,13 +109,13 @@ public SdkTracerProviderBuilder setSpanLimits(SpanLimits spanLimits) {
109109

110110
/**
111111
* Assign a {@link Supplier} of {@link SpanLimits}. {@link SpanLimits} will be retrieved each time
112-
* a {@link io.opentelemetry.api.trace.Span} is started.
112+
* a {@link Span} is started.
113113
*
114114
* <p>The {@code spanLimitsSupplier} must be thread-safe and return immediately (no remote calls,
115115
* as contention free as possible).
116116
*
117117
* @param spanLimitsSupplier the supplier that will be used to retrieve the {@link SpanLimits} for
118-
* every {@link io.opentelemetry.api.trace.Span}.
118+
* every {@link Span}.
119119
* @return this
120120
*/
121121
public SdkTracerProviderBuilder setSpanLimits(Supplier<SpanLimits> spanLimitsSupplier) {
@@ -126,7 +126,7 @@ public SdkTracerProviderBuilder setSpanLimits(Supplier<SpanLimits> spanLimitsSup
126126

127127
/**
128128
* Assign a {@link Sampler} to use for sampling traces. {@link Sampler} will be called each time a
129-
* {@link io.opentelemetry.api.trace.Span} is started.
129+
* {@link Span} is started.
130130
*
131131
* <p>The {@code sampler} must be thread-safe and return immediately (no remote calls, as
132132
* contention free as possible).
@@ -142,7 +142,7 @@ public SdkTracerProviderBuilder setSampler(Sampler sampler) {
142142

143143
/**
144144
* Add a SpanProcessor to the span pipeline that will be built. {@link SpanProcessor} will be
145-
* called each time a {@link io.opentelemetry.api.trace.Span} is started or ended.
145+
* called each time a {@link Span} is started or ended.
146146
*
147147
* <p>The {@code spanProcessor} must be thread-safe and return immediately (no remote calls, as
148148
* contention free as possible).

sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ final class TracerSharedState {
4242
this.resource = resource;
4343
this.spanLimitsSupplier = spanLimitsSupplier;
4444
this.sampler = sampler;
45-
activeSpanProcessor = SpanProcessor.composite(spanProcessors);
45+
this.activeSpanProcessor = SpanProcessor.composite(spanProcessors);
4646
}
4747

4848
Clock getClock() {

sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
import static io.opentelemetry.api.common.AttributeKey.longKey;
1414
import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
1515
import static io.opentelemetry.api.common.AttributeKey.stringKey;
16+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
1617
import static java.util.stream.Collectors.joining;
17-
import static org.assertj.core.api.Assertions.assertThat;
1818
import static org.assertj.core.api.Assertions.assertThatCode;
1919
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2020
import static org.mockito.ArgumentMatchers.any;
@@ -1534,9 +1534,7 @@ void testAsSpanData() {
15341534
Resource resource = this.resource;
15351535
Attributes attributes = TestUtils.generateRandomAttributes();
15361536
AttributesMap attributesWithCapacity = AttributesMap.create(32, Integer.MAX_VALUE);
1537-
attributes.forEach(
1538-
(attributeKey, object) ->
1539-
attributesWithCapacity.put((AttributeKey<Object>) attributeKey, object));
1537+
attributes.forEach(attributesWithCapacity::put);
15401538
Attributes event1Attributes = TestUtils.generateRandomAttributes();
15411539
Attributes event2Attributes = TestUtils.generateRandomAttributes();
15421540
SpanContext context =

0 commit comments

Comments
 (0)