Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@

package io.opentelemetry.sdk.internal;

import static io.opentelemetry.sdk.internal.ExceptionAttributeResolver.EXCEPTION_MESSAGE;
import static io.opentelemetry.sdk.internal.ExceptionAttributeResolver.EXCEPTION_STACKTRACE;
import static io.opentelemetry.sdk.internal.ExceptionAttributeResolver.EXCEPTION_TYPE;

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;
Expand All @@ -22,13 +24,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 @@ -107,22 +102,24 @@ public static Object applyAttributeLengthLimit(Object value, int lengthLimit) {
}

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

String exceptionMessage = exception.getMessage();
String exceptionMessage =
exceptionAttributeResolver.getExceptionMessage(exception, maxAttributeLength);
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();
String stackTrace =
exceptionAttributeResolver.getExceptionStacktrace(exception, maxAttributeLength);
if (stackTrace != null) {
attributeConsumer.accept(EXCEPTION_STACKTRACE, stackTrace);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.internal;

import java.io.PrintWriter;
import java.io.StringWriter;
import javax.annotation.Nullable;

/**
* 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
@Nullable
public String getExceptionType(Throwable throwable, int maxAttributeLength) {
return throwable.getClass().getCanonicalName();
}

@Override
@Nullable
public String getExceptionMessage(Throwable throwable, int maxAttributeLength) {
return throwable.getMessage();
}

@Override
@Nullable
public String getExceptionStacktrace(Throwable throwable, int maxAttributeLength) {
StringWriter stringWriter = new StringWriter();
try (PrintWriter printWriter = new PrintWriter(stringWriter)) {
throwable.printStackTrace(printWriter);
}
return stringWriter.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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");

/**
* Resolve the {@link #EXCEPTION_TYPE} attribute from the {@code throwable}, or {@code null} if no
* value should be set.
*
* @param throwable the throwable
* @param maxAttributeLength the max attribute length that will be retained by the SDK. Responses
* are not required to conform to this limit, but implementations may incorporate this limit
* to avoid unnecessary compute.
*/
@Nullable
String getExceptionType(Throwable throwable, int maxAttributeLength);

/**
* Resolve the {@link #EXCEPTION_MESSAGE} attribute from the {@code throwable}, or {@code null} if
* no value should be set.
*
* @param throwable the throwable
* @param maxAttributeLength the max attribute length that will be retained by the SDK. Responses
* are not required to conform to this limit, but implementations may incorporate this limit
* to avoid unnecessary compute.
*/
@Nullable
String getExceptionMessage(Throwable throwable, int maxAttributeLength);

/**
* Resolve the {@link #EXCEPTION_STACKTRACE} attribute from the {@code throwable}, or {@code null}
* if no value should be set.
*
* @param throwable the throwable
* @param maxAttributeLength the max attribute length that will be retained by the SDK. Responses
* are not required to conform to this limit, but implementations may incorporate this limit
* to avoid unnecessary compute.
*/
@Nullable
String getExceptionStacktrace(Throwable throwable, int maxAttributeLength);
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ public ExtendedSdkLogRecordBuilder setException(Throwable throwable) {
return this;
}

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

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 @@ -53,10 +54,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 @@ -149,13 +153,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.SdkLoggerProviderBuilder;
import java.lang.reflect.InvocationTargetException;
Expand Down Expand Up @@ -55,4 +56,20 @@
"Error calling addLoggerConfiguratorCondition on SdkLoggerProviderBuilder", e);
}
}

/** 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 71 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#L70-L71

Added lines #L70 - L71 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