Skip to content
Draft
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
@@ -1,2 +1,7 @@
Comparing source compatibility of opentelemetry-sdk-trace-1.48.0-SNAPSHOT.jar against opentelemetry-sdk-trace-1.47.0.jar
No changes.
*** MODIFIED CLASS: PUBLIC ABSTRACT io.opentelemetry.sdk.trace.SpanLimits (not serializable)
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
+++ NEW METHOD: PUBLIC(+) boolean isExcludeExceptionStackTrace()
*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.SpanLimitsBuilder (not serializable)
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.SpanLimitsBuilder setExcludeExceptionStackTrace(boolean)
Comment on lines +2 to +7
Copy link
Member

Choose a reason for hiding this comment

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

it may be worth considering the future direction proposed in open-telemetry/opentelemetry-specification#4333 before adding public API surface

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for that link. I will review and consider how it impacts this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's a lot going on in that discussion. It sounds like even if the recommendation is that instrumentation emit exceptions as log events that there should still be some form of configuration to bridge that back to span events.

While it seems like a lot of things are up in the air have there been any discussions as to how that OTEP might impact the Java SDK and instrumentation?

Either way I think there's value in having some degree of control over how exceptions are interpreted and rendered into telemetry signals, including spans, so I would like to continue the conversation as to how that could be exposed in the SDK.

Copy link
Member

Choose a reason for hiding this comment

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

Either way I think there's value in having some degree of control over how exceptions are interpreted and rendered into telemetry signals

I agree. With logs / events right now, instrumentation is responsible for this. See how the logback instrumentation has the same SDK exception span event rendering logic embedded in it. When its an instrumentation concern, that means that every instrumentation has to handle it separately. In theory that means that there's a lot of control over the rendering, but in practice, there's really only control if the instrumentation libraries expose configuration options to control the rendering. So it becomes a decentralized problem instead of a problem centrally handled / configured in the SDK. Ouch.

@trask I wonder if we should add a method to LogRecordBuilder which allows users to attach an exception directly to the log record. Then, we could have a log SDK configuration option for configuring rendering which is consistent / symmetric with rendering configuration in the trace SDK.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's interesting to me that the OTel Log instrumentation for JUL, log4j and logback simply emit the entire stacktrace rather than using the formatting facilities that are provided. It definitely feels like this might be something that the OTel SDK should provide and that instrumentation should be abstracted away from those concerns to keep the behavior consistent and user-configurable. I'd be happy to help work on such a project.

Copy link
Member

Choose a reason for hiding this comment

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

@jack-berg since you're hopefully not trying to follow all of the logging discussions, here's the latest thought for attaching a terminal exception directly to a span: open-telemetry/opentelemetry-specification#4429 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

here's the latest thought for attaching a terminal exception directly to a span

👍 this also seems to align with @HaloFour's thought. Of course, all of this will have to be configurable given the fact that folks may be depending on the current stable behavior.

Copy link
Member

Choose a reason for hiding this comment

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

folks may be depending on the current stable behavior

the general strategy we're proposing in the spec:

  • no changes to any existing SDK behavior
  • introduce new API with new behavior
  • deprecate old API to signal that new code should use the new API
  • ask (at least stable) instrumentations to bump their major version when they move from the old API to the new API

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ask (at least stable) instrumentations to bump their major version when they move from the old API to the new API

Could that be an abstraction provided by the instrumentation API itself so that individual instrumentations don't need to be aware of how exceptions end up getting emitted?

Copy link
Member

Choose a reason for hiding this comment

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

👍 if you're specifically referring to io.opentelemetry.instrumentation:opentelemetry-instrumentation-api

Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ void stringRepresentation() {
+ "clock=SystemClock{}, "
+ "idGenerator=RandomIdGenerator{}, "
+ "resource=Resource{schemaUrl=null, attributes={service.name=\"otel-test\"}}, "
+ "spanLimitsSupplier=SpanLimitsValue{maxNumberOfAttributes=128, maxNumberOfEvents=128, maxNumberOfLinks=128, maxNumberOfAttributesPerEvent=128, maxNumberOfAttributesPerLink=128, maxAttributeValueLength=2147483647}, "
+ "spanLimitsSupplier=SpanLimitsValue{maxNumberOfAttributes=128, maxNumberOfEvents=128, maxNumberOfLinks=128, maxNumberOfAttributesPerEvent=128, maxNumberOfAttributesPerLink=128, maxAttributeValueLength=2147483647, excludeExceptionStackTrace=false}, "
+ "sampler=ParentBased{root:AlwaysOnSampler,remoteParentSampled:AlwaysOnSampler,remoteParentNotSampled:AlwaysOffSampler,localParentSampled:AlwaysOnSampler,localParentNotSampled:AlwaysOffSampler}, "
+ "spanProcessor=SimpleSpanProcessor{spanExporter=MultiSpanExporter{spanExporters=[MockSpanExporter{}, MockSpanExporter{}]}, exportUnsampledSpans=false}"
+ "}, "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,11 +479,14 @@ public ReadWriteSpan recordException(Throwable exception, Attributes additionalA
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 = null;
if (!spanLimits.isExcludeExceptionStackTrace()) {
StringWriter stringWriter = new StringWriter();
try (PrintWriter printWriter = new PrintWriter(stringWriter)) {
exception.printStackTrace(printWriter);
}
stackTrace = stringWriter.toString();
}
String stackTrace = stringWriter.toString();

if (exceptionName != null) {
attributes.put(EXCEPTION_TYPE, exceptionName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,16 @@
int maxNumLinks,
int maxNumAttributesPerEvent,
int maxNumAttributesPerLink,
int maxAttributeLength) {
int maxAttributeLength,
boolean excludeExceptionStackTrace) {
return new AutoValue_SpanLimits_SpanLimitsValue(
maxNumAttributes,
maxNumEvents,
maxNumLinks,
maxNumAttributesPerEvent,
maxNumAttributesPerLink,
maxAttributeLength);
maxAttributeLength,
excludeExceptionStackTrace);
}

/**
Expand Down Expand Up @@ -102,6 +104,15 @@
return DEFAULT_SPAN_MAX_ATTRIBUTE_LENGTH;
}

/**
* Returns whether exception stack trace should be excluded from exception event attributes.
*
* @return whether exception stack trace should be excluded from exception event attributes.
*/
public boolean isExcludeExceptionStackTrace() {
return false;

Check warning on line 113 in sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SpanLimits.java

View check run for this annotation

Codecov / codecov/patch

sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SpanLimits.java#L113

Added line #L113 was not covered by tests
}

/**
* Returns a {@link SpanLimitsBuilder} initialized to the same property values as the current
* instance.
Expand All @@ -116,7 +127,8 @@
.setMaxNumberOfLinks(getMaxNumberOfLinks())
.setMaxNumberOfAttributesPerEvent(getMaxNumberOfAttributesPerEvent())
.setMaxNumberOfAttributesPerLink(getMaxNumberOfAttributesPerLink())
.setMaxAttributeValueLength(getMaxAttributeValueLength());
.setMaxAttributeValueLength(getMaxAttributeValueLength())
.setExcludeExceptionStackTrace(isExcludeExceptionStackTrace());
}

@AutoValue
Expand All @@ -129,5 +141,12 @@
*/
@Override
public abstract int getMaxAttributeValueLength();

/**
* Override {@link SpanLimits#isExcludeExceptionStackTrace()} to be abstract so autovalue can
* implement it.
*/
@Override
public abstract boolean isExcludeExceptionStackTrace();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public final class SpanLimitsBuilder {
private int maxNumAttributesPerEvent = DEFAULT_SPAN_MAX_NUM_ATTRIBUTES_PER_EVENT;
private int maxNumAttributesPerLink = DEFAULT_SPAN_MAX_NUM_ATTRIBUTES_PER_LINK;
private int maxAttributeValueLength = SpanLimits.DEFAULT_SPAN_MAX_ATTRIBUTE_LENGTH;
private boolean excludeExceptionStackTrace = false;

SpanLimitsBuilder() {}

Expand Down Expand Up @@ -109,6 +110,18 @@ public SpanLimitsBuilder setMaxAttributeValueLength(int maxAttributeValueLength)
return this;
}

/**
* Sets whether exception stacktraces should be excluded from exception event attributes.
*
* @param excludeExceptionStackTrace whether exception stacktraces should be excluded from
* exception event attributes.
* @return this.
*/
public SpanLimitsBuilder setExcludeExceptionStackTrace(boolean excludeExceptionStackTrace) {
this.excludeExceptionStackTrace = excludeExceptionStackTrace;
return this;
}

/** Builds and returns a {@link SpanLimits} with the values of this builder. */
public SpanLimits build() {
return SpanLimits.create(
Expand All @@ -117,6 +130,7 @@ public SpanLimits build() {
maxNumLinks,
maxNumAttributesPerEvent,
maxNumAttributesPerLink,
maxAttributeValueLength);
maxAttributeValueLength,
excludeExceptionStackTrace);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1277,6 +1277,38 @@ void recordException_additionalAttributes() {
});
}

@Test
void recordException_excludeStackTrace() {
IllegalStateException exception = new IllegalStateException("there was an exception");
SdkSpan span = createTestSpan(SpanLimits.builder().setExcludeExceptionStackTrace(true).build());

testClock.advance(Duration.ofNanos(1000));
long timestamp = testClock.now();

// make sure that span attributes don't leak down to the exception event
span.setAttribute("spankey", "val");

span.recordException(exception);

List<EventData> events = span.toSpanData().getEvents();
assertThat(events).hasSize(1);
EventData event = events.get(0);
assertThat(event.getName()).isEqualTo("exception");
assertThat(event.getEpochNanos()).isEqualTo(timestamp);
assertThat(event.getAttributes().get(stringKey("exception.message")))
.isEqualTo("there was an exception");
assertThat(event.getAttributes().get(stringKey("exception.type")))
.isEqualTo(exception.getClass().getName());
assertThat(event.getAttributes().get(stringKey("exception.stacktrace"))).isNull();
assertThat(event.getAttributes().size()).isEqualTo(2);
assertThat(event)
.isInstanceOfSatisfying(
ExceptionEventData.class,
exceptionEvent -> {
assertThat(exceptionEvent.getException()).isSameAs(exception);
});
}

@Test
void badArgsIgnored() {
SdkSpan span = createTestRootSpan();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ void defaultSpanLimits() {
assertThat(SpanLimits.getDefault().getMaxNumberOfLinks()).isEqualTo(128);
assertThat(SpanLimits.getDefault().getMaxNumberOfAttributesPerEvent()).isEqualTo(128);
assertThat(SpanLimits.getDefault().getMaxNumberOfAttributesPerLink()).isEqualTo(128);
assertThat(SpanLimits.getDefault().isExcludeExceptionStackTrace()).isEqualTo(false);
}

@Test
Expand All @@ -32,12 +33,14 @@ void updateSpanLimits_All() {
.setMaxNumberOfLinks(11)
.setMaxNumberOfAttributesPerEvent(1)
.setMaxNumberOfAttributesPerLink(2)
.setExcludeExceptionStackTrace(true)
.build();
assertThat(spanLimits.getMaxNumberOfAttributes()).isEqualTo(8);
assertThat(spanLimits.getMaxNumberOfEvents()).isEqualTo(10);
assertThat(spanLimits.getMaxNumberOfLinks()).isEqualTo(11);
assertThat(spanLimits.getMaxNumberOfAttributesPerEvent()).isEqualTo(1);
assertThat(spanLimits.getMaxNumberOfAttributesPerLink()).isEqualTo(2);
assertThat(spanLimits.isExcludeExceptionStackTrace()).isEqualTo(true);

// Preserves values
SpanLimits spanLimitsDupe = spanLimits.toBuilder().build();
Expand Down
Loading