Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,9 @@
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Predicate;

/**
Expand All @@ -22,13 +19,6 @@
*/
public final class AttributeUtil {

private static final AttributeKey<String> EXCEPTION_TYPE =
AttributeKey.stringKey("exception.type");
private static final AttributeKey<String> EXCEPTION_MESSAGE =
AttributeKey.stringKey("exception.message");
private static final AttributeKey<String> EXCEPTION_STACKTRACE =
AttributeKey.stringKey("exception.stacktrace");

private AttributeUtil() {}

/**
Expand Down Expand Up @@ -105,26 +95,4 @@ public static Object applyAttributeLengthLimit(Object value, int lengthLimit) {
}
return value;
}

public static void addExceptionAttributes(
Throwable exception, BiConsumer<AttributeKey<String>, String> attributeConsumer) {
String exceptionType = exception.getClass().getCanonicalName();
if (exceptionType != null) {
attributeConsumer.accept(EXCEPTION_TYPE, exceptionType);
}

String exceptionMessage = exception.getMessage();
if (exceptionMessage != null) {
attributeConsumer.accept(EXCEPTION_MESSAGE, exceptionMessage);
}

StringWriter stringWriter = new StringWriter();
try (PrintWriter printWriter = new PrintWriter(stringWriter)) {
exception.printStackTrace(printWriter);
}
String stackTrace = stringWriter.toString();
if (stackTrace != null) {
attributeConsumer.accept(EXCEPTION_STACKTRACE, stackTrace);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
* <p>WARNING: In order to reduce memory allocation, this class extends {@link HashMap} when it
* would be more appropriate to delegate. The problem with extending is that we don't enforce that
* all {@link HashMap} methods for reading / writing data conform to the configured attribute
* limits. Therefore, it's easy to accidentally call something like {@link Map#putAll(Map)} or
* {@link Map#put(Object, Object)} and bypass the restrictions (see <a
* limits. Therefore, it's easy to accidentally call something like {@link Map#putAll(Map)} and
* bypass the restrictions (see <a
* href="https://github.com/open-telemetry/opentelemetry-java/issues/7135">#7135</a>). Callers MUST
* take care to only call methods from {@link AttributesMap}, and not {@link HashMap}.
*
Expand Down Expand Up @@ -58,14 +58,22 @@
*/
@Override
@Nullable
public Object put(AttributeKey<?> key, Object value) {
public Object put(AttributeKey<?> key, @Nullable Object value) {
if (value == null) {
return null;

Check warning on line 63 in sdk/common/src/main/java/io/opentelemetry/sdk/internal/AttributesMap.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L63 was not covered by tests
Comment on lines -61 to +63
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this change have any effect on existing behaviors that we should be aware of?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it shouldn't.

I wasn't quite remembering why I added this in the first place, so I retraced my steps and determined:

  • ExceptionAttributeResolver.AttributeSetter#setAttribute accepts a @Nullable T value. We want this because we recently updated Span, LogRecord setAttribute methods to accept @Nullable values.
  • AttributeMap#putIfCapacity acts as the implementation for this method
  • AttributeMap#putIfCapacity delegates to AttributeMap#put (I want both methods to reduce accidental mistakes of bypassing the validation, as discussed in the javadoc of this class)

}
totalAddedValues++;
if (size() >= capacity && !containsKey(key)) {
return null;
}
return super.put(key, AttributeUtil.applyAttributeLengthLimit(value, lengthLimit));
}

/** Generic overload of {@link #put(AttributeKey, Object)}. */
public <T> void putIfCapacity(AttributeKey<T> key, @Nullable T value) {
put(key, value);
}

/** Get the total number of attributes added, including those dropped for capacity limits. */
public int getTotalAddedValues() {
return totalAddedValues;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.internal;

import java.io.PrintWriter;
import java.io.StringWriter;

/**
* This class is internal and experimental. Its APIs are unstable and can change at any time. Its
* APIs (or a version of them) may be promoted to the public stable API in the future, but no
* guarantees are made.
*/
public final class DefaultExceptionAttributeResolver implements ExceptionAttributeResolver {

private static final DefaultExceptionAttributeResolver INSTANCE =
new DefaultExceptionAttributeResolver();

private DefaultExceptionAttributeResolver() {}

public static ExceptionAttributeResolver getInstance() {
return INSTANCE;
}

@Override
public void setExceptionAttributes(
AttributeSetter attributeSetter, Throwable throwable, int maxAttributeLength) {
String exceptionType = throwable.getClass().getCanonicalName();
if (exceptionType != null) {
attributeSetter.setAttribute(ExceptionAttributeResolver.EXCEPTION_TYPE, exceptionType);
}

String exceptionMessage = throwable.getMessage();
if (exceptionMessage != null) {
attributeSetter.setAttribute(ExceptionAttributeResolver.EXCEPTION_MESSAGE, exceptionMessage);
}

StringWriter stringWriter = new StringWriter();
try (PrintWriter printWriter = new PrintWriter(stringWriter)) {
throwable.printStackTrace(printWriter);
}
String exceptionStacktrace = stringWriter.toString();
if (exceptionStacktrace != null) {
attributeSetter.setAttribute(
ExceptionAttributeResolver.EXCEPTION_STACKTRACE, exceptionStacktrace);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.internal;

import io.opentelemetry.api.common.AttributeKey;
import javax.annotation.Nullable;

/**
* Implementations resolve {@code exception.*} attributes attached to span events, logs, etc.
*
* <p>This class is internal and experimental. Its APIs are unstable and can change at any time. Its
* APIs (or a version of them) may be promoted to the public stable API in the future, but no
* guarantees are made.
*/
public interface ExceptionAttributeResolver {

AttributeKey<String> EXCEPTION_TYPE = AttributeKey.stringKey("exception.type");
AttributeKey<String> EXCEPTION_MESSAGE = AttributeKey.stringKey("exception.message");
AttributeKey<String> EXCEPTION_STACKTRACE = AttributeKey.stringKey("exception.stacktrace");

void setExceptionAttributes(
AttributeSetter attributeSetter, Throwable throwable, int maxAttributeLength);

/**
* This class is internal and experimental. Its APIs are unstable and can change at any time. Its
* APIs (or a version of them) may be promoted to the public stable API in the future, but no
* guarantees are made.
*/
// TODO(jack-berg): Consider promoting to opentelemetry and extending with Span, LogRecordBuilder,
// AttributeBuilder, AttributesMap etc.
interface AttributeSetter {
<T> void setAttribute(AttributeKey<T> key, @Nullable T value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,23 @@
}

/** Add the attribute key value pair, applying capacity and length limits. */
public <T> void put(ExtendedAttributeKey<T> key, T value) {
@Override
@Nullable
public Object put(ExtendedAttributeKey<?> key, @Nullable Object value) {
if (value == null) {
return null;

Check warning on line 56 in sdk/common/src/main/java/io/opentelemetry/sdk/internal/ExtendedAttributesMap.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L56 was not covered by tests
}
totalAddedValues++;
// TODO(jack-berg): apply capcity to nested entries
// TODO(jack-berg): apply capacity to nested entries
if (size() >= capacity && !containsKey(key)) {
return;
return null;

Check warning on line 61 in sdk/common/src/main/java/io/opentelemetry/sdk/internal/ExtendedAttributesMap.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L61 was not covered by tests
}
// TODO(jack-berg): apply limits to nested entries
super.put(key, AttributeUtil.applyAttributeLengthLimit(value, lengthLimit));
return super.put(key, AttributeUtil.applyAttributeLengthLimit(value, lengthLimit));
}

public <T> void putIfCapacity(ExtendedAttributeKey<T> key, @Nullable T value) {
put(key, value);

Check warning on line 68 in sdk/common/src/main/java/io/opentelemetry/sdk/internal/ExtendedAttributesMap.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L68 was not covered by tests
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.AttributeUtil;
import io.opentelemetry.sdk.internal.ExtendedAttributesMap;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -42,7 +41,12 @@ public ExtendedSdkLogRecordBuilder setException(Throwable throwable) {
return this;
}

AttributeUtil.addExceptionAttributes(throwable, this::setAttribute);
loggerSharedState
.getExceptionAttributeResolver()
.setExceptionAttributes(
this::setAttribute,
throwable,
loggerSharedState.getLogLimits().getMaxAttributeValueLength());

return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import io.opentelemetry.sdk.common.Clock;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.internal.ExceptionAttributeResolver;
import io.opentelemetry.sdk.resources.Resource;
import java.util.function.Supplier;
import javax.annotation.Nullable;
Expand All @@ -21,17 +22,20 @@ final class LoggerSharedState {
private final Supplier<LogLimits> logLimitsSupplier;
private final LogRecordProcessor logRecordProcessor;
private final Clock clock;
private final ExceptionAttributeResolver exceptionAttributeResolver;
@Nullable private volatile CompletableResultCode shutdownResult = null;

LoggerSharedState(
Resource resource,
Supplier<LogLimits> logLimitsSupplier,
LogRecordProcessor logRecordProcessor,
Clock clock) {
Clock clock,
ExceptionAttributeResolver exceptionAttributeResolver) {
this.resource = resource;
this.logLimitsSupplier = logLimitsSupplier;
this.logRecordProcessor = logRecordProcessor;
this.clock = clock;
this.exceptionAttributeResolver = exceptionAttributeResolver;
}

Resource getResource() {
Expand All @@ -50,6 +54,10 @@ Clock getClock() {
return clock;
}

ExceptionAttributeResolver getExceptionAttributeResolver() {
return exceptionAttributeResolver;
}

boolean hasBeenShutdown() {
return shutdownResult != null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ComponentRegistry;
import io.opentelemetry.sdk.internal.ExceptionAttributeResolver;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.logs.internal.LoggerConfig;
import io.opentelemetry.sdk.resources.Resource;
Expand Down Expand Up @@ -56,10 +57,12 @@ public static SdkLoggerProviderBuilder builder() {
Supplier<LogLimits> logLimitsSupplier,
List<LogRecordProcessor> processors,
Clock clock,
ScopeConfigurator<LoggerConfig> loggerConfigurator) {
ScopeConfigurator<LoggerConfig> loggerConfigurator,
ExceptionAttributeResolver exceptionAttributeResolver) {
LogRecordProcessor logRecordProcessor = LogRecordProcessor.composite(processors);
this.sharedState =
new LoggerSharedState(resource, logLimitsSupplier, logRecordProcessor, clock);
new LoggerSharedState(
resource, logLimitsSupplier, logRecordProcessor, clock, exceptionAttributeResolver);
this.loggerComponentRegistry =
new ComponentRegistry<>(
instrumentationScopeInfo ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.common.Clock;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.DefaultExceptionAttributeResolver;
import io.opentelemetry.sdk.internal.ExceptionAttributeResolver;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder;
import io.opentelemetry.sdk.logs.data.LogRecordData;
Expand All @@ -37,6 +39,8 @@ public final class SdkLoggerProviderBuilder {
private Clock clock = Clock.getDefault();
private ScopeConfiguratorBuilder<LoggerConfig> loggerConfiguratorBuilder =
LoggerConfig.configuratorBuilder();
private ExceptionAttributeResolver exceptionAttributeResolver =
DefaultExceptionAttributeResolver.getInstance();

SdkLoggerProviderBuilder() {}

Expand Down Expand Up @@ -168,13 +172,33 @@ SdkLoggerProviderBuilder addLoggerConfiguratorCondition(
return this;
}

/**
* Set the exception attribute resolver, which resolves {@code exception.*} attributes an
* exception is set on a log.
*
* <p>This method is experimental so not public. You may reflectively call it using {@link
* SdkLoggerProviderUtil#setExceptionAttributeResolver(SdkLoggerProviderBuilder,
* ExceptionAttributeResolver)}.
*/
SdkLoggerProviderBuilder setExceptionAttributeResolver(
ExceptionAttributeResolver exceptionAttributeResolver) {
requireNonNull(exceptionAttributeResolver, "exceptionAttributeResolver");
this.exceptionAttributeResolver = exceptionAttributeResolver;
return this;
}

/**
* Create a {@link SdkLoggerProvider} instance.
*
* @return an instance configured with the provided options
*/
public SdkLoggerProvider build() {
return new SdkLoggerProvider(
resource, logLimitsSupplier, logRecordProcessors, clock, loggerConfiguratorBuilder.build());
resource,
logLimitsSupplier,
logRecordProcessors,
clock,
loggerConfiguratorBuilder.build(),
exceptionAttributeResolver);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package io.opentelemetry.sdk.logs.internal;

import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ExceptionAttributeResolver;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder;
Expand Down Expand Up @@ -73,4 +74,20 @@
}
return sdkLoggerProviderBuilder;
}

/** Reflectively set exception attribute resolver to the {@link SdkLoggerProviderBuilder}. */
public static void setExceptionAttributeResolver(
SdkLoggerProviderBuilder sdkLoggerProviderBuilder,
ExceptionAttributeResolver exceptionAttributeResolver) {
try {
Method method =
SdkLoggerProviderBuilder.class.getDeclaredMethod(
"setExceptionAttributeResolver", ExceptionAttributeResolver.class);
method.setAccessible(true);
method.invoke(sdkLoggerProviderBuilder, exceptionAttributeResolver);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(

Check warning on line 89 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkLoggerProviderUtil.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkLoggerProviderUtil.java#L88-L89

Added lines #L88 - L89 were not covered by tests
"Error calling setExceptionAttributeResolver on SdkLoggerProviderBuilder", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import io.opentelemetry.sdk.common.Clock;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.internal.DefaultExceptionAttributeResolver;
import io.opentelemetry.sdk.resources.Resource;
import org.junit.jupiter.api.Test;

Expand All @@ -24,7 +25,11 @@ void shutdown() {
when(logRecordProcessor.shutdown()).thenReturn(code);
LoggerSharedState state =
new LoggerSharedState(
Resource.empty(), LogLimits::getDefault, logRecordProcessor, Clock.getDefault());
Resource.empty(),
LogLimits::getDefault,
logRecordProcessor,
Clock.getDefault(),
DefaultExceptionAttributeResolver.getInstance());
state.shutdown();
state.shutdown();
verify(logRecordProcessor, times(1)).shutdown();
Expand Down
Loading
Loading