Skip to content
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

Add ExceptionEventData which wraps events that contain exceptions #4162

Merged
merged 10 commits into from
Mar 31, 2022
22 changes: 2 additions & 20 deletions sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.internal.GuardedBy;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
Expand All @@ -22,9 +21,7 @@
import io.opentelemetry.sdk.trace.data.LinkData;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.io.PrintWriter;
import java.io.StringWriter;
import io.opentelemetry.sdk.trace.internal.data.ExceptionEventData;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -401,23 +398,8 @@ public ReadWriteSpan recordException(Throwable exception, Attributes additionalA
if (additionalAttributes == null) {
additionalAttributes = Attributes.empty();
}
long timestampNanos = clock.now();

AttributesBuilder attributes = Attributes.builder();
attributes.put(SemanticAttributes.EXCEPTION_TYPE, exception.getClass().getCanonicalName());
if (exception.getMessage() != null) {
attributes.put(SemanticAttributes.EXCEPTION_MESSAGE, exception.getMessage());
}
StringWriter writer = new StringWriter();
exception.printStackTrace(new PrintWriter(writer));
attributes.put(SemanticAttributes.EXCEPTION_STACKTRACE, writer.toString());
attributes.putAll(additionalAttributes);

addEvent(
SemanticAttributes.EXCEPTION_EVENT_NAME,
attributes.build(),
timestampNanos,
TimeUnit.NANOSECONDS);
addTimedEvent(ExceptionEventData.create(clock.now(), exception, additionalAttributes));
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import io.opentelemetry.sdk.trace.SpanLimits;
import javax.annotation.concurrent.Immutable;

/** Data representation of a event. */
/** Data representation of an event. */
@Immutable
public interface EventData {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.trace.internal.data;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.sdk.trace.data.EventData;

/**
* Data representation of an event for a recorded exception.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public interface ExceptionEventData extends EventData {
jkwatson marked this conversation as resolved.
Show resolved Hide resolved

/**
* Returns a new immutable {@link ExceptionEventData}.
*
* @param epochNanos epoch timestamp in nanos of the {@link ExceptionEventData}.
* @param exception the {@link Throwable exception} of the {@code Event}.
* @param additionalAttributes the additional attributes of the {@link ExceptionEventData}.
* @return a new immutable {@link ExceptionEventData}
*/
static ExceptionEventData create(
long epochNanos, Throwable exception, Attributes additionalAttributes) {
return ImmutableExceptionEventData.create(epochNanos, exception, additionalAttributes);
}

/**
* Return the {@link Throwable exception} of the {@link ExceptionEventData}.
*
* @return the {@link Throwable exception} of the {@link ExceptionEventData}
*/
Throwable getException();

/**
* Return the additional {@link Attributes attributes} of the {@link ExceptionEventData}.
*
* @return the additional {@link Attributes attributes} of the {@link ExceptionEventData}
*/
Attributes getAdditionalAttributes();
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this can be a package private method on ImmutableExceptionEventData instead of on this interface

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As with that stretch goal I wanted to enable an exporter to access the additional attributes without forcing the semantic attributes for the exception to be created.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.trace.internal.data;

import com.google.auto.value.AutoValue;
import com.google.auto.value.extension.memoized.Memoized;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.io.PrintWriter;
import java.io.StringWriter;
import javax.annotation.concurrent.Immutable;

/** An effectively immutable implementation of {@link ExceptionEventData}. */
@AutoValue
@Immutable
abstract class ImmutableExceptionEventData implements ExceptionEventData {

/**
* Returns a new immutable {@code Event}.
*
* @param epochNanos epoch timestamp in nanos of the {@code Event}.
* @param exception the {@link Throwable exception} of the {@code Event}.
* @param additionalAttributes the additional {@link Attributes} of the {@code Event}.
* @return a new immutable {@code Event<T>}
*/
static ExceptionEventData create(
long epochNanos, Throwable exception, Attributes additionalAttributes) {

return new AutoValue_ImmutableExceptionEventData(epochNanos, exception, additionalAttributes);
}

ImmutableExceptionEventData() {}

@Override
public final String getName() {
return SemanticAttributes.EXCEPTION_EVENT_NAME;
}

@Override
@Memoized
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to point out this changes the getAttributes for exceptions from a normal field to volatile field in the new codepath. I think it's OK just want to make sure everyone notices it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Going for the stretch goal of having ExceptionEventData not generate those attributes unless the exporter tries to use them. Assuming the exporter only calls getAttributes once it's probably not actually necessary to memoize them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If there is hesitation in deferring the generation of those attributes I can definitely back that off. When the interface was in tracing-incubation I felt it was worth exploring. The only attribute I'd prefer to avoid generating is exception.stacktrace as in my Spring codebases they can be quite large, but I don't think it'd a big deal to consider that later.

public Attributes getAttributes() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public Attributes getAttributes() {
public final Attributes getAttributes() {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Memoized methods can't be final in AutoValue.

Throwable exception = getException();
Attributes additionalAttributes = getAdditionalAttributes();
AttributesBuilder attributesBuilder = Attributes.builder();

attributesBuilder.put(
SemanticAttributes.EXCEPTION_TYPE, exception.getClass().getCanonicalName());
String message = exception.getMessage();
if (message != null) {
attributesBuilder.put(SemanticAttributes.EXCEPTION_MESSAGE, message);
}

StringWriter stringWriter = new StringWriter();
try (PrintWriter printWriter = new PrintWriter(stringWriter)) {
exception.printStackTrace(printWriter);
}
attributesBuilder.put(SemanticAttributes.EXCEPTION_STACKTRACE, stringWriter.toString());
attributesBuilder.putAll(additionalAttributes);

return attributesBuilder.build();
}

@Override
public final int getTotalAttributeCount() {
return getAttributes().size();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

/**
* Interfaces and implementations that are internal to OpenTelemetry.
*
* <p>All the content under this package and its subpackages are considered not part of the public
* API, and must not be used by users of the OpenTelemetry library.
*/
@ParametersAreNonnullByDefault
package io.opentelemetry.sdk.trace.internal.data;

import javax.annotation.ParametersAreNonnullByDefault;
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import io.opentelemetry.sdk.trace.data.LinkData;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.sdk.trace.internal.data.ExceptionEventData;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.io.PrintWriter;
import java.io.StringWriter;
Expand Down Expand Up @@ -894,6 +895,14 @@ void recordException() {
.put(SemanticAttributes.EXCEPTION_MESSAGE, "there was an exception")
.put(SemanticAttributes.EXCEPTION_STACKTRACE, stacktrace)
.build());

assertThat(event)
.isInstanceOfSatisfying(
ExceptionEventData.class,
exceptionEvent -> {
assertThat(exceptionEvent.getException()).isSameAs(exception);
assertThat(exceptionEvent.getAdditionalAttributes()).isEqualTo(Attributes.empty());
});
}

@Test
Expand Down Expand Up @@ -958,6 +967,20 @@ void recordException_additionalAttributes() {
.put("exception.message", "this is a precedence attribute")
.put("exception.stacktrace", stacktrace)
.build());

assertThat(event)
.isInstanceOfSatisfying(
ExceptionEventData.class,
exceptionEvent -> {
assertThat(exceptionEvent.getException()).isSameAs(exception);
assertThat(exceptionEvent.getAdditionalAttributes())
.isEqualTo(
Attributes.of(
stringKey("key1"),
"this is an additional attribute",
stringKey("exception.message"),
"this is a precedence attribute"));
});
}

@Test
Expand Down