Skip to content

Add ExtendedLogRecordBuilder#setException #7182

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 11, 2025
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -40,6 +40,11 @@
return this;
}

@Override
public ExtendedLogRecordBuilder setException(Throwable throwable) {
return this;

Check warning on line 45 in api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedDefaultLogger.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L45 was not covered by tests
}

@Override
public LogRecordBuilder setTimestamp(long timestamp, TimeUnit unit) {
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ public interface ExtendedLogRecordBuilder extends LogRecordBuilder {
* record with a non-empty event name is an Event.
*/
ExtendedLogRecordBuilder setEventName(String eventName);

/** Set standard {@code exception.*} attributes based on the {@code throwable}. */
ExtendedLogRecordBuilder setException(Throwable throwable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
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 @@ -19,6 +22,13 @@
*/
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 @@ -95,4 +105,26 @@ 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);
}
}
}
1 change: 1 addition & 0 deletions sdk/logs/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ testing {
dependencies {
implementation(project(":sdk:testing"))
implementation(project(":api:incubator"))
implementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating")
implementation("com.google.guava:guava")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ public ExtendedSdkLogRecordBuilder setEventName(String eventName) {
return this;
}

@Override
public ExtendedSdkLogRecordBuilder setException(Throwable throwable) {
super.setException(throwable);
return this;
}

@Override
public ExtendedSdkLogRecordBuilder setTimestamp(long timestamp, TimeUnit unit) {
super.setTimestamp(timestamp, unit);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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.AttributesMap;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -46,6 +47,17 @@
return this;
}

// accessible via ExtendedSdkLogRecordBuilder
SdkLogRecordBuilder setException(Throwable throwable) {
if (throwable == null) {
return this;

Check warning on line 53 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L53 was not covered by tests
}

AttributeUtil.addExceptionAttributes(throwable, this::setAttribute);

return this;
}

@Override
public SdkLogRecordBuilder setTimestamp(long timestamp, TimeUnit unit) {
this.timestampEpochNanos = unit.toNanos(timestamp);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.logs;

import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE;
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE;
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE;

import io.opentelemetry.api.incubator.logs.ExtendedLogRecordBuilder;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor;
import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter;
import org.junit.jupiter.api.Test;

class ExtendedLoggerBuilderTest {

private final InMemoryLogRecordExporter exporter = InMemoryLogRecordExporter.create();
private final SdkLoggerProviderBuilder loggerProviderBuilder =
SdkLoggerProvider.builder().addLogRecordProcessor(SimpleLogRecordProcessor.create(exporter));

@Test
void setException() {
Logger logger = loggerProviderBuilder.build().get("logger");

((ExtendedLogRecordBuilder) logger.logRecordBuilder())
.setException(new Exception("error"))
.emit();

assertThat(exporter.getFinishedLogRecordItems())
.satisfiesExactly(
logRecord ->
assertThat(logRecord)
.hasAttributesSatisfyingExactly(
equalTo(EXCEPTION_TYPE, "java.lang.Exception"),
equalTo(EXCEPTION_MESSAGE, "error"),
satisfies(
EXCEPTION_STACKTRACE,
stacktrace ->
stacktrace.startsWith(
"java.lang.Exception: error" + System.lineSeparator()))));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.sdk.trace.internal.ExtendedSpanProcessor;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -117,13 +115,6 @@ private enum EndState {
@Nullable
private Thread spanEndingThread;

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 SdkSpan(
SpanContext context,
String name,
Expand Down Expand Up @@ -466,7 +457,6 @@ public ReadWriteSpan recordException(Throwable exception) {
}

@Override
@SuppressWarnings("unchecked")
public ReadWriteSpan recordException(Throwable exception, Attributes additionalAttributes) {
if (exception == null) {
return this;
Expand All @@ -478,23 +468,8 @@ public ReadWriteSpan recordException(Throwable exception, Attributes additionalA
AttributesMap attributes =
AttributesMap.create(
spanLimits.getMaxNumberOfAttributes(), spanLimits.getMaxAttributeValueLength());
String exceptionName = exception.getClass().getCanonicalName();
String exceptionMessage = exception.getMessage();
StringWriter stringWriter = new StringWriter();
try (PrintWriter printWriter = new PrintWriter(stringWriter)) {
exception.printStackTrace(printWriter);
}
String stackTrace = stringWriter.toString();

if (exceptionName != null) {
attributes.put(EXCEPTION_TYPE, exceptionName);
}
if (exceptionMessage != null) {
attributes.put(EXCEPTION_MESSAGE, exceptionMessage);
}
if (stackTrace != null) {
attributes.put(EXCEPTION_STACKTRACE, stackTrace);
}
AttributeUtil.addExceptionAttributes(exception, attributes::put);

additionalAttributes.forEach(attributes::put);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import static java.util.Objects.requireNonNull;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.sdk.common.Clock;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
Expand Down Expand Up @@ -36,8 +37,8 @@ public final class SdkTracerProviderBuilder {
TracerConfig.configuratorBuilder();

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

/**
* Assign an {@link IdGenerator}. {@link IdGenerator} will be used each time a {@link
* io.opentelemetry.api.trace.Span} is started.
* Assign an {@link IdGenerator}. {@link IdGenerator} will be used each time a {@link Span} is
* started.
*
* <p>The {@code idGenerator} must be thread-safe and return immediately (no remote calls, as
* contention free as possible).
Expand Down Expand Up @@ -97,8 +98,7 @@ public SdkTracerProviderBuilder addResource(Resource resource) {
* <p>This method is equivalent to calling {@link #setSpanLimits(Supplier)} like this {@code
* #setSpanLimits(() -> spanLimits)}.
*
* @param spanLimits the limits that will be used for every {@link
* io.opentelemetry.api.trace.Span}.
* @param spanLimits the limits that will be used for every {@link Span}.
* @return this
*/
public SdkTracerProviderBuilder setSpanLimits(SpanLimits spanLimits) {
Expand All @@ -109,13 +109,13 @@ public SdkTracerProviderBuilder setSpanLimits(SpanLimits spanLimits) {

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

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

/**
* Add a SpanProcessor to the span pipeline that will be built. {@link SpanProcessor} will be
* called each time a {@link io.opentelemetry.api.trace.Span} is started or ended.
* called each time a {@link Span} is started or ended.
*
* <p>The {@code spanProcessor} must be thread-safe and return immediately (no remote calls, as
* contention free as possible).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ final class TracerSharedState {
this.resource = resource;
this.spanLimitsSupplier = spanLimitsSupplier;
this.sampler = sampler;
activeSpanProcessor = SpanProcessor.composite(spanProcessors);
this.activeSpanProcessor = SpanProcessor.composite(spanProcessors);
}

Clock getClock() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
import static io.opentelemetry.api.common.AttributeKey.longKey;
import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static java.util.stream.Collectors.joining;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
Expand Down Expand Up @@ -1534,9 +1534,7 @@ void testAsSpanData() {
Resource resource = this.resource;
Attributes attributes = TestUtils.generateRandomAttributes();
AttributesMap attributesWithCapacity = AttributesMap.create(32, Integer.MAX_VALUE);
attributes.forEach(
(attributeKey, object) ->
attributesWithCapacity.put((AttributeKey<Object>) attributeKey, object));
attributes.forEach(attributesWithCapacity::put);
Attributes event1Attributes = TestUtils.generateRandomAttributes();
Attributes event2Attributes = TestUtils.generateRandomAttributes();
SpanContext context =
Expand Down
Loading
Loading