Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Comment thread
jack-berg marked this conversation as resolved.
Outdated

/**
* 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 @@ public static void addLoggerConfiguratorCondition(
"Error calling addLoggerConfiguratorCondition on SdkLoggerProviderBuilder", e);
}
}

/** Reflectively set exception attribute resolver to the {@link SdkLoggerProviderBuilder}. */
public static void setExceptionAttributeResolver(
Comment thread
jack-berg marked this conversation as resolved.
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(
"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