Skip to content

Commit b11316b

Browse files
authored
Configurable exception.* attribute resolution (#7266)
1 parent a756317 commit b11316b

File tree

20 files changed

+364
-58
lines changed

20 files changed

+364
-58
lines changed

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

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,9 @@
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;
1311
import java.util.ArrayList;
1412
import java.util.List;
1513
import java.util.Map;
16-
import java.util.function.BiConsumer;
1714
import java.util.function.Predicate;
1815

1916
/**
@@ -22,13 +19,6 @@
2219
*/
2320
public final class AttributeUtil {
2421

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-
3222
private AttributeUtil() {}
3323

3424
/**
@@ -105,26 +95,4 @@ public static Object applyAttributeLengthLimit(Object value, int lengthLimit) {
10595
}
10696
return value;
10797
}
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-
}
13098
}

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
* <p>WARNING: In order to reduce memory allocation, this class extends {@link HashMap} when it
2222
* would be more appropriate to delegate. The problem with extending is that we don't enforce that
2323
* all {@link HashMap} methods for reading / writing data conform to the configured attribute
24-
* limits. Therefore, it's easy to accidentally call something like {@link Map#putAll(Map)} or
25-
* {@link Map#put(Object, Object)} and bypass the restrictions (see <a
24+
* limits. Therefore, it's easy to accidentally call something like {@link Map#putAll(Map)} and
25+
* bypass the restrictions (see <a
2626
* href="https://github.com/open-telemetry/opentelemetry-java/issues/7135">#7135</a>). Callers MUST
2727
* take care to only call methods from {@link AttributesMap}, and not {@link HashMap}.
2828
*
@@ -58,14 +58,22 @@ public static AttributesMap create(long capacity, int lengthLimit) {
5858
*/
5959
@Override
6060
@Nullable
61-
public Object put(AttributeKey<?> key, Object value) {
61+
public Object put(AttributeKey<?> key, @Nullable Object value) {
62+
if (value == null) {
63+
return null;
64+
}
6265
totalAddedValues++;
6366
if (size() >= capacity && !containsKey(key)) {
6467
return null;
6568
}
6669
return super.put(key, AttributeUtil.applyAttributeLengthLimit(value, lengthLimit));
6770
}
6871

72+
/** Generic overload of {@link #put(AttributeKey, Object)}. */
73+
public <T> void putIfCapacity(AttributeKey<T> key, @Nullable T value) {
74+
put(key, value);
75+
}
76+
6977
/** Get the total number of attributes added, including those dropped for capacity limits. */
7078
public int getTotalAddedValues() {
7179
return totalAddedValues;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.sdk.internal;
7+
8+
import java.io.PrintWriter;
9+
import java.io.StringWriter;
10+
11+
/**
12+
* This class is internal and experimental. Its APIs are unstable and can change at any time. Its
13+
* APIs (or a version of them) may be promoted to the public stable API in the future, but no
14+
* guarantees are made.
15+
*/
16+
public final class DefaultExceptionAttributeResolver implements ExceptionAttributeResolver {
17+
18+
private static final DefaultExceptionAttributeResolver INSTANCE =
19+
new DefaultExceptionAttributeResolver();
20+
21+
private DefaultExceptionAttributeResolver() {}
22+
23+
public static ExceptionAttributeResolver getInstance() {
24+
return INSTANCE;
25+
}
26+
27+
@Override
28+
public void setExceptionAttributes(
29+
AttributeSetter attributeSetter, Throwable throwable, int maxAttributeLength) {
30+
String exceptionType = throwable.getClass().getCanonicalName();
31+
if (exceptionType != null) {
32+
attributeSetter.setAttribute(ExceptionAttributeResolver.EXCEPTION_TYPE, exceptionType);
33+
}
34+
35+
String exceptionMessage = throwable.getMessage();
36+
if (exceptionMessage != null) {
37+
attributeSetter.setAttribute(ExceptionAttributeResolver.EXCEPTION_MESSAGE, exceptionMessage);
38+
}
39+
40+
StringWriter stringWriter = new StringWriter();
41+
try (PrintWriter printWriter = new PrintWriter(stringWriter)) {
42+
throwable.printStackTrace(printWriter);
43+
}
44+
String exceptionStacktrace = stringWriter.toString();
45+
if (exceptionStacktrace != null) {
46+
attributeSetter.setAttribute(
47+
ExceptionAttributeResolver.EXCEPTION_STACKTRACE, exceptionStacktrace);
48+
}
49+
}
50+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.sdk.internal;
7+
8+
import io.opentelemetry.api.common.AttributeKey;
9+
import javax.annotation.Nullable;
10+
11+
/**
12+
* Implementations resolve {@code exception.*} attributes attached to span events, logs, etc.
13+
*
14+
* <p>This class is internal and experimental. Its APIs are unstable and can change at any time. Its
15+
* APIs (or a version of them) may be promoted to the public stable API in the future, but no
16+
* guarantees are made.
17+
*/
18+
public interface ExceptionAttributeResolver {
19+
20+
AttributeKey<String> EXCEPTION_TYPE = AttributeKey.stringKey("exception.type");
21+
AttributeKey<String> EXCEPTION_MESSAGE = AttributeKey.stringKey("exception.message");
22+
AttributeKey<String> EXCEPTION_STACKTRACE = AttributeKey.stringKey("exception.stacktrace");
23+
24+
void setExceptionAttributes(
25+
AttributeSetter attributeSetter, Throwable throwable, int maxAttributeLength);
26+
27+
/**
28+
* This class is internal and experimental. Its APIs are unstable and can change at any time. Its
29+
* APIs (or a version of them) may be promoted to the public stable API in the future, but no
30+
* guarantees are made.
31+
*/
32+
// TODO(jack-berg): Consider promoting to opentelemetry and extending with Span, LogRecordBuilder,
33+
// AttributeBuilder, AttributesMap etc.
34+
interface AttributeSetter {
35+
<T> void setAttribute(AttributeKey<T> key, @Nullable T value);
36+
}
37+
}

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,23 @@ public static ExtendedAttributesMap create(long capacity, int lengthLimit) {
4949
}
5050

5151
/** Add the attribute key value pair, applying capacity and length limits. */
52-
public <T> void put(ExtendedAttributeKey<T> key, T value) {
52+
@Override
53+
@Nullable
54+
public Object put(ExtendedAttributeKey<?> key, @Nullable Object value) {
55+
if (value == null) {
56+
return null;
57+
}
5358
totalAddedValues++;
54-
// TODO(jack-berg): apply capcity to nested entries
59+
// TODO(jack-berg): apply capacity to nested entries
5560
if (size() >= capacity && !containsKey(key)) {
56-
return;
61+
return null;
5762
}
5863
// TODO(jack-berg): apply limits to nested entries
59-
super.put(key, AttributeUtil.applyAttributeLengthLimit(value, lengthLimit));
64+
return super.put(key, AttributeUtil.applyAttributeLengthLimit(value, lengthLimit));
65+
}
66+
67+
public <T> void putIfCapacity(ExtendedAttributeKey<T> key, @Nullable T value) {
68+
put(key, value);
6069
}
6170

6271
/**

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import io.opentelemetry.api.trace.Span;
1414
import io.opentelemetry.context.Context;
1515
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
16-
import io.opentelemetry.sdk.internal.AttributeUtil;
1716
import io.opentelemetry.sdk.internal.ExtendedAttributesMap;
1817
import java.time.Instant;
1918
import java.util.concurrent.TimeUnit;
@@ -42,7 +41,12 @@ public ExtendedSdkLogRecordBuilder setException(Throwable throwable) {
4241
return this;
4342
}
4443

45-
AttributeUtil.addExceptionAttributes(throwable, this::setAttribute);
44+
loggerSharedState
45+
.getExceptionAttributeResolver()
46+
.setExceptionAttributes(
47+
this::setAttribute,
48+
throwable,
49+
loggerSharedState.getLogLimits().getMaxAttributeValueLength());
4650

4751
return this;
4852
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import io.opentelemetry.sdk.common.Clock;
99
import io.opentelemetry.sdk.common.CompletableResultCode;
10+
import io.opentelemetry.sdk.internal.ExceptionAttributeResolver;
1011
import io.opentelemetry.sdk.resources.Resource;
1112
import java.util.function.Supplier;
1213
import javax.annotation.Nullable;
@@ -21,17 +22,20 @@ final class LoggerSharedState {
2122
private final Supplier<LogLimits> logLimitsSupplier;
2223
private final LogRecordProcessor logRecordProcessor;
2324
private final Clock clock;
25+
private final ExceptionAttributeResolver exceptionAttributeResolver;
2426
@Nullable private volatile CompletableResultCode shutdownResult = null;
2527

2628
LoggerSharedState(
2729
Resource resource,
2830
Supplier<LogLimits> logLimitsSupplier,
2931
LogRecordProcessor logRecordProcessor,
30-
Clock clock) {
32+
Clock clock,
33+
ExceptionAttributeResolver exceptionAttributeResolver) {
3134
this.resource = resource;
3235
this.logLimitsSupplier = logLimitsSupplier;
3336
this.logRecordProcessor = logRecordProcessor;
3437
this.clock = clock;
38+
this.exceptionAttributeResolver = exceptionAttributeResolver;
3539
}
3640

3741
Resource getResource() {
@@ -50,6 +54,10 @@ Clock getClock() {
5054
return clock;
5155
}
5256

57+
ExceptionAttributeResolver getExceptionAttributeResolver() {
58+
return exceptionAttributeResolver;
59+
}
60+
5361
boolean hasBeenShutdown() {
5462
return shutdownResult != null;
5563
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io.opentelemetry.sdk.common.CompletableResultCode;
1414
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
1515
import io.opentelemetry.sdk.internal.ComponentRegistry;
16+
import io.opentelemetry.sdk.internal.ExceptionAttributeResolver;
1617
import io.opentelemetry.sdk.internal.ScopeConfigurator;
1718
import io.opentelemetry.sdk.logs.internal.LoggerConfig;
1819
import io.opentelemetry.sdk.resources.Resource;
@@ -56,10 +57,12 @@ public static SdkLoggerProviderBuilder builder() {
5657
Supplier<LogLimits> logLimitsSupplier,
5758
List<LogRecordProcessor> processors,
5859
Clock clock,
59-
ScopeConfigurator<LoggerConfig> loggerConfigurator) {
60+
ScopeConfigurator<LoggerConfig> loggerConfigurator,
61+
ExceptionAttributeResolver exceptionAttributeResolver) {
6062
LogRecordProcessor logRecordProcessor = LogRecordProcessor.composite(processors);
6163
this.sharedState =
62-
new LoggerSharedState(resource, logLimitsSupplier, logRecordProcessor, clock);
64+
new LoggerSharedState(
65+
resource, logLimitsSupplier, logRecordProcessor, clock, exceptionAttributeResolver);
6366
this.loggerComponentRegistry =
6467
new ComponentRegistry<>(
6568
instrumentationScopeInfo ->

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import io.opentelemetry.context.Context;
1313
import io.opentelemetry.sdk.common.Clock;
1414
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
15+
import io.opentelemetry.sdk.internal.DefaultExceptionAttributeResolver;
16+
import io.opentelemetry.sdk.internal.ExceptionAttributeResolver;
1517
import io.opentelemetry.sdk.internal.ScopeConfigurator;
1618
import io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder;
1719
import io.opentelemetry.sdk.logs.data.LogRecordData;
@@ -37,6 +39,8 @@ public final class SdkLoggerProviderBuilder {
3739
private Clock clock = Clock.getDefault();
3840
private ScopeConfiguratorBuilder<LoggerConfig> loggerConfiguratorBuilder =
3941
LoggerConfig.configuratorBuilder();
42+
private ExceptionAttributeResolver exceptionAttributeResolver =
43+
DefaultExceptionAttributeResolver.getInstance();
4044

4145
SdkLoggerProviderBuilder() {}
4246

@@ -168,13 +172,33 @@ SdkLoggerProviderBuilder addLoggerConfiguratorCondition(
168172
return this;
169173
}
170174

175+
/**
176+
* Set the exception attribute resolver, which resolves {@code exception.*} attributes an
177+
* exception is set on a log.
178+
*
179+
* <p>This method is experimental so not public. You may reflectively call it using {@link
180+
* SdkLoggerProviderUtil#setExceptionAttributeResolver(SdkLoggerProviderBuilder,
181+
* ExceptionAttributeResolver)}.
182+
*/
183+
SdkLoggerProviderBuilder setExceptionAttributeResolver(
184+
ExceptionAttributeResolver exceptionAttributeResolver) {
185+
requireNonNull(exceptionAttributeResolver, "exceptionAttributeResolver");
186+
this.exceptionAttributeResolver = exceptionAttributeResolver;
187+
return this;
188+
}
189+
171190
/**
172191
* Create a {@link SdkLoggerProvider} instance.
173192
*
174193
* @return an instance configured with the provided options
175194
*/
176195
public SdkLoggerProvider build() {
177196
return new SdkLoggerProvider(
178-
resource, logLimitsSupplier, logRecordProcessors, clock, loggerConfiguratorBuilder.build());
197+
resource,
198+
logLimitsSupplier,
199+
logRecordProcessors,
200+
clock,
201+
loggerConfiguratorBuilder.build(),
202+
exceptionAttributeResolver);
179203
}
180204
}

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkLoggerProviderUtil.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package io.opentelemetry.sdk.logs.internal;
77

88
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
9+
import io.opentelemetry.sdk.internal.ExceptionAttributeResolver;
910
import io.opentelemetry.sdk.internal.ScopeConfigurator;
1011
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
1112
import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder;
@@ -73,4 +74,20 @@ public static SdkLoggerProviderBuilder addLoggerConfiguratorCondition(
7374
}
7475
return sdkLoggerProviderBuilder;
7576
}
77+
78+
/** Reflectively set exception attribute resolver to the {@link SdkLoggerProviderBuilder}. */
79+
public static void setExceptionAttributeResolver(
80+
SdkLoggerProviderBuilder sdkLoggerProviderBuilder,
81+
ExceptionAttributeResolver exceptionAttributeResolver) {
82+
try {
83+
Method method =
84+
SdkLoggerProviderBuilder.class.getDeclaredMethod(
85+
"setExceptionAttributeResolver", ExceptionAttributeResolver.class);
86+
method.setAccessible(true);
87+
method.invoke(sdkLoggerProviderBuilder, exceptionAttributeResolver);
88+
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
89+
throw new IllegalStateException(
90+
"Error calling setExceptionAttributeResolver on SdkLoggerProviderBuilder", e);
91+
}
92+
}
7693
}

0 commit comments

Comments
 (0)