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

Support addEvent Otel API #7408

Merged
merged 40 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3de8679
Initial span events implementation + tests
mtoffl01 Aug 9, 2024
d654056
use DDTags.SPAN_EVENTS constant for setting tag
mtoffl01 Aug 9, 2024
f2bb9ac
added more tests and used moshi for json encoding
mtoffl01 Aug 12, 2024
f8bc1db
remove unused method toString on SpanEvent instance
mtoffl01 Aug 12, 2024
fae43bc
Started work on recordException
mtoffl01 Aug 13, 2024
761ad85
Removed moshi dependency
mtoffl01 Aug 13, 2024
add0ef0
Merge branch 'master' into mtoff/span-events
mtoffl01 Aug 14, 2024
ea4d8df
Fixed case where custom attributes should override default excpeption…
mtoffl01 Aug 15, 2024
00e47a5
Finishing up record exception unit tests
mtoffl01 Aug 15, 2024
80b8adf
Added annotations for clarity
mtoffl01 Aug 15, 2024
4e84b96
nits: reverted little changes that were superfluous
mtoffl01 Aug 15, 2024
35dd084
nits: reverted little changes that were superfluous
mtoffl01 Aug 15, 2024
a7626bb
Include new helperClassNames
mtoffl01 Aug 16, 2024
c611567
Added support for not-stringifying span events attributes, while main…
mtoffl01 Aug 16, 2024
8e353f7
fixing attributes manipulation for arrays
mtoffl01 Aug 16, 2024
90bc619
Fixed json parsing for array Attributes values
mtoffl01 Aug 26, 2024
14db2ad
Added better javadocs to functions
mtoffl01 Aug 26, 2024
565e43d
Fix exception.escaped and timesource for SpanEvent
mtoffl01 Sep 3, 2024
7655446
Fix exception.escaped and timesource for SpanEvent
mtoffl01 Sep 3, 2024
3123288
Update internal-api/src/main/java/datadog/trace/bootstrap/instrumenta…
mtoffl01 Sep 3, 2024
b4ae914
Update internal-api/src/main/java/datadog/trace/bootstrap/instrumenta…
mtoffl01 Sep 3, 2024
a2026b2
Update javadoc
mtoffl01 Sep 3, 2024
6690d3f
merge w upstream
mtoffl01 Sep 3, 2024
ccca4f3
fix nits
mtoffl01 Sep 6, 2024
0f04383
change SpanEvent method to private-package access scope
mtoffl01 Sep 6, 2024
9efd98e
Changed span event Attributes to a separate class
mtoffl01 Sep 10, 2024
87a7698
Fix error stack formatting for exception.stacktrace tag
mtoffl01 Sep 11, 2024
e6d69db
Remove superfluous import
mtoffl01 Sep 11, 2024
4d9b456
Optimized defaultExceptionAttributes
mtoffl01 Sep 11, 2024
f8721e4
Reverted all previous changes to spanlinks
mtoffl01 Sep 11, 2024
c34e139
Remove missed changes to spanlinks files
mtoffl01 Sep 11, 2024
41692b6
Merge branch 'master' into mtoff/span-events
mtoffl01 Sep 11, 2024
6d6cbc3
Moved exception attributes processing into spanevent class
mtoffl01 Sep 12, 2024
71d4b69
Fix error meta behavior with record exception
mtoffl01 Sep 12, 2024
684170a
Update comments and fix little nits
mtoffl01 Sep 12, 2024
af71579
Improve javadoc
mtoffl01 Sep 12, 2024
5950ade
javadoc improvement
mtoffl01 Sep 12, 2024
4051d56
feat(otel): Improve convention handling
PerfectSlayer Sep 16, 2024
e143d28
feat(otel): Simplify time API usage
PerfectSlayer Sep 16, 2024
3af7937
feat(otel): Improve tests
PerfectSlayer Sep 16, 2024
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,6 +1,13 @@
package datadog.opentelemetry.shim.trace;

import static datadog.opentelemetry.shim.trace.OtelSpanEvent.EXCEPTION_MESSAGE_ATTRIBUTE_KEY;
import static datadog.opentelemetry.shim.trace.OtelSpanEvent.EXCEPTION_STACK_TRACE_ATTRIBUTE_KEY;
import static datadog.opentelemetry.shim.trace.OtelSpanEvent.EXCEPTION_TYPE_ATTRIBUTE_KEY;
import static datadog.trace.api.DDTags.ANALYTICS_SAMPLE_RATE;
import static datadog.trace.api.DDTags.ERROR_MSG;
import static datadog.trace.api.DDTags.ERROR_STACK;
import static datadog.trace.api.DDTags.ERROR_TYPE;
import static datadog.trace.api.DDTags.SPAN_EVENTS;
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND;
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CLIENT;
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CONSUMER;
Expand Down Expand Up @@ -125,6 +132,20 @@ public static void applyNamingConvention(AgentSpan span) {
}
}

public static void setEventsAsTag(AgentSpan span, List<OtelSpanEvent> events) {
if (events == null || events.isEmpty()) {
return;
}
span.setTag(SPAN_EVENTS, OtelSpanEvent.toTag(events));
}

public static void applySpanEventExceptionAttributesAsTags(
AgentSpan span, Attributes exceptionAttributes) {
span.setTag(ERROR_MSG, exceptionAttributes.get(EXCEPTION_MESSAGE_ATTRIBUTE_KEY));
span.setTag(ERROR_TYPE, exceptionAttributes.get(EXCEPTION_TYPE_ATTRIBUTE_KEY));
span.setTag(ERROR_STACK, exceptionAttributes.get(EXCEPTION_STACK_TRACE_ATTRIBUTE_KEY));
}

private static String computeOperationName(AgentSpan span) {
Object spanKingTag = span.getTag(SPAN_KIND);
SpanKind spanKind =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

import static datadog.opentelemetry.shim.trace.OtelConventions.applyNamingConvention;
import static datadog.opentelemetry.shim.trace.OtelConventions.applyReservedAttribute;
import static datadog.opentelemetry.shim.trace.OtelConventions.applySpanEventExceptionAttributesAsTags;
import static datadog.opentelemetry.shim.trace.OtelConventions.setEventsAsTag;
import static datadog.opentelemetry.shim.trace.OtelSpanEvent.EXCEPTION_SPAN_EVENT_NAME;
import static datadog.opentelemetry.shim.trace.OtelSpanEvent.initializeExceptionAttributes;
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
import static io.opentelemetry.api.trace.StatusCode.ERROR;
import static io.opentelemetry.api.trace.StatusCode.OK;
Expand All @@ -10,14 +14,14 @@
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.AttachableWrapper;
import datadog.trace.bootstrap.instrumentation.api.ErrorPriorities;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.TraceFlags;
import io.opentelemetry.api.trace.TraceState;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.annotation.ParametersAreNonnullByDefault;
Expand All @@ -27,6 +31,8 @@ public class OtelSpan implements Span {
private final AgentSpan delegate;
private StatusCode statusCode;
private boolean recording;
/** Span events ({@code null} until an event is added). */
private List<OtelSpanEvent> events;

public OtelSpan(AgentSpan delegate) {
this.delegate = delegate;
Expand Down Expand Up @@ -71,13 +77,23 @@ public <T> Span setAttribute(AttributeKey<T> key, T value) {

@Override
public Span addEvent(String name, Attributes attributes) {
// Not supported
if (this.recording) {
if (this.events == null) {
this.events = new ArrayList<>();
}
this.events.add(new OtelSpanEvent(name, attributes));
}
return this;
}

@Override
public Span addEvent(String name, Attributes attributes, long timestamp, TimeUnit unit) {
// Not supported
if (this.recording) {
if (this.events == null) {
this.events = new ArrayList<>();
}
this.events.add(new OtelSpanEvent(name, attributes, timestamp, unit));
}
return this;
}

Expand All @@ -100,8 +116,12 @@ public Span setStatus(StatusCode statusCode, String description) {
@Override
public Span recordException(Throwable exception, Attributes additionalAttributes) {
if (this.recording) {
// Store exception as span tags as span events are not supported yet
this.delegate.addThrowable(exception, ErrorPriorities.UNSET);
if (this.events == null) {
this.events = new ArrayList<>();
}
additionalAttributes = initializeExceptionAttributes(exception, additionalAttributes);
applySpanEventExceptionAttributesAsTags(this.delegate, additionalAttributes);
this.events.add(new OtelSpanEvent(EXCEPTION_SPAN_EVENT_NAME, additionalAttributes));
}
return this;
}
Expand All @@ -118,13 +138,15 @@ public Span updateName(String name) {
public void end() {
this.recording = false;
applyNamingConvention(this.delegate);
setEventsAsTag(this.delegate, this.events);
this.delegate.finish();
}

@Override
public void end(long timestamp, TimeUnit unit) {
this.recording = false;
applyNamingConvention(this.delegate);
setEventsAsTag(this.delegate, this.events);
this.delegate.finish(unit.toMicros(timestamp));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package datadog.opentelemetry.shim.trace;

import datadog.trace.api.time.SystemTimeSource;
import datadog.trace.api.time.TimeSource;
import edu.umd.cs.findbugs.annotations.NonNull;
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.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public class OtelSpanEvent {
public static final String EXCEPTION_SPAN_EVENT_NAME = "exception";
public static final AttributeKey<String> EXCEPTION_MESSAGE_ATTRIBUTE_KEY =
AttributeKey.stringKey("exception.message");
public static final AttributeKey<String> EXCEPTION_TYPE_ATTRIBUTE_KEY =
AttributeKey.stringKey("exception.type");
public static final AttributeKey<String> EXCEPTION_STACK_TRACE_ATTRIBUTE_KEY =
AttributeKey.stringKey("exception.stacktrace");

// TODO TimeSource instance is not retrieved from CoreTracer
private static TimeSource timeSource = SystemTimeSource.INSTANCE;

private final String name;
private final String attributes;
/** Event timestamp in nanoseconds. */
private final long timestamp;

public OtelSpanEvent(String name, Attributes attributes) {
this.name = name;
this.attributes = AttributesJsonParser.toJson(attributes);
this.timestamp = OtelSpanEvent.timeSource.getCurrentTimeNanos();
}

public OtelSpanEvent(String name, Attributes attributes, long timestamp, TimeUnit unit) {
this.name = name;
this.attributes = AttributesJsonParser.toJson(attributes);
this.timestamp = unit.toNanos(timestamp);
}

@NonNull
public static String toTag(List<OtelSpanEvent> events) {
StringBuilder builder = new StringBuilder("[");
for (OtelSpanEvent event : events) {
if (builder.length() > 1) {
builder.append(',');
}
builder.append(event.toJson());
}
return builder.append(']').toString();
}

/**
* Make sure exception related attributes are presents and generates them if needed.
*
* <p>All exception span events get the following reserved attributes: {@link
* #EXCEPTION_MESSAGE_ATTRIBUTE_KEY}, {@link #EXCEPTION_TYPE_ATTRIBUTE_KEY} and {@link
* #EXCEPTION_STACK_TRACE_ATTRIBUTE_KEY}. If additionalAttributes contains a reserved key, the
* value in additionalAttributes is used. Else, the value is determined from the provided
* Throwable.
*
* @param exception The Throwable from which to build reserved attributes
* @param additionalAttributes The user-provided attributes
* @return An {@link Attributes} collection with exception attributes.
*/
static Attributes initializeExceptionAttributes(
Throwable exception, Attributes additionalAttributes) {
// Create an AttributesBuilder with the additionalAttributes provided
AttributesBuilder builder = additionalAttributes.toBuilder();
// Handle exception message
String value = additionalAttributes.get(EXCEPTION_MESSAGE_ATTRIBUTE_KEY);
if (value == null) {
value = exception.getMessage();
builder.put(EXCEPTION_MESSAGE_ATTRIBUTE_KEY, value);
}
// Handle exception type
value = additionalAttributes.get(EXCEPTION_TYPE_ATTRIBUTE_KEY);
if (value == null) {
value = exception.getClass().getName();
builder.put(EXCEPTION_TYPE_ATTRIBUTE_KEY, value);
}
// Handle exception stacktrace
value = additionalAttributes.get(EXCEPTION_STACK_TRACE_ATTRIBUTE_KEY);
if (value == null) {
value = stringifyErrorStack(exception);
builder.put(EXCEPTION_STACK_TRACE_ATTRIBUTE_KEY, value);
}
return builder.build();
}

static String stringifyErrorStack(Throwable error) {
final StringWriter errorString = new StringWriter();
error.printStackTrace(new PrintWriter(errorString));
return errorString.toString();
}

/** Helper class for JSON-encoding {@link OtelSpanEvent} {@link #attributes}. */
public static class AttributesJsonParser {
public static String toJson(Attributes attributes) {
if (attributes == null || attributes.isEmpty()) {
return "";
}
StringBuilder jsonBuilder = new StringBuilder();
jsonBuilder.append('{');

Set<Map.Entry<AttributeKey<?>, Object>> entrySet = attributes.asMap().entrySet();

for (Map.Entry<AttributeKey<?>, Object> entry : entrySet) {
if (jsonBuilder.length() > 1) {
jsonBuilder.append(',');
}
// AttributeKey type has method `getKey()` that "stringifies" the key
String key = entry.getKey().getKey();
Object value = entry.getValue();
// Escape key and append it
jsonBuilder.append('"').append(escapeJson(key)).append("\":");
// Append value to jsonBuilder
appendValue(value, jsonBuilder);
}
jsonBuilder.append('}');
return jsonBuilder.toString();
}
/**
* Recursively adds the value of an {@link Attributes} to the active StringBuilder in JSON
* format, depending on the value's type.
*
* @param value The value to append
* @param jsonBuilder The active {@link StringBuilder}
*/
private static void appendValue(Object value, StringBuilder jsonBuilder) {
// Append value based on its type
if (value instanceof String) {
jsonBuilder.append('"').append(escapeJson((String) value)).append('"');
} else if (value instanceof List) {
jsonBuilder.append('[');
List<?> valArray = (List<?>) value;
for (int i = 0; i < valArray.size(); i++) {
if (i > 0) {
jsonBuilder.append(',');
}
appendValue(valArray.get(i), jsonBuilder);
}
jsonBuilder.append(']');
} else if (value instanceof Number || value instanceof Boolean) {
jsonBuilder.append(value);
} else {
jsonBuilder.append("null"); // null for unsupported types
}
}

private static String escapeJson(String value) {
return value
.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\b", "\\b")
.replace("\f", "\\f")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
}

public static void setTimeSource(TimeSource newTimeSource) {
timeSource = newTimeSource;
}

public String toJson() {
StringBuilder builder =
new StringBuilder(
"{\"time_unix_nano\":" + this.timestamp + ",\"name\":\"" + this.name + "\"");
if (!this.attributes.isEmpty()) {
builder.append(",\"attributes\":").append(this.attributes);
}
return builder.append('}').toString();
}

@Override
public String toString() {
return "OtelSpanEvent{timestamp="
+ this.timestamp
+ ", name='"
+ this.name
+ "', attributes='"
+ this.attributes
+ "'}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,12 @@ public String[] helperClassNames() {
"datadog.opentelemetry.shim.trace.OtelSpanBuilder",
"datadog.opentelemetry.shim.trace.OtelSpanBuilder$1",
"datadog.opentelemetry.shim.trace.OtelSpanContext",
"datadog.opentelemetry.shim.trace.OtelSpanEvent$AttributesJsonParser",
"datadog.opentelemetry.shim.trace.OtelSpanLink",
"datadog.opentelemetry.shim.trace.OtelTracer",
"datadog.opentelemetry.shim.trace.OtelTracerBuilder",
"datadog.opentelemetry.shim.trace.OtelTracerProvider",
"datadog.opentelemetry.shim.trace.OtelSpanEvent",
};
}

Expand Down
Loading