-
Notifications
You must be signed in to change notification settings - Fork 357
refactor(propagation): Parse OpenTelemetry tracestate member #12405
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package datadog.trace.core.propagation.ptags; | ||
|
|
||
| final class OtelTraceState { | ||
| private final String value; | ||
| private final int inheritedPosition; | ||
|
|
||
| private OtelTraceState(String value, int inheritedPosition) { | ||
| this.value = value; | ||
| this.inheritedPosition = inheritedPosition; | ||
| } | ||
|
|
||
| static OtelTraceState parse(String raw, int inheritedPosition) { | ||
| if (raw == null || raw.isEmpty()) { | ||
| return null; | ||
| } | ||
| return new OtelTraceState(raw, inheritedPosition); | ||
| } | ||
|
|
||
| String getValue() { | ||
| return value; | ||
| } | ||
|
|
||
| int length() { | ||
| return value.length(); | ||
| } | ||
|
|
||
| int getInheritedPosition() { | ||
| return inheritedPosition; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ public class W3CPTagsCodec extends PTagsCodec { | |
|
|
||
| private static final int MAX_HEADER_SIZE = 256; | ||
| private static final String DATADOG_MEMBER_KEY = "dd="; | ||
| private static final String OTEL_MEMBER_KEY = "ot="; | ||
| private static final int EMPTY_SIZE = DATADOG_MEMBER_KEY.length(); // 3 | ||
| private static final char MEMBER_SEPARATOR = ','; | ||
| private static final char ELEMENT_SEPARATOR = ';'; | ||
|
|
@@ -49,13 +50,19 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { | |
| int ddMemberValueEnd = -1; // dd member value end position including OWS (exclusive) | ||
| int memberIndex = 0; | ||
| int ddMemberIndex = -1; | ||
| int otelMemberValueStart = -1; | ||
| int otelMemberValueEnd = -1; | ||
| int otelMemberPosition = 0; | ||
| int otherMemberPosition = 0; | ||
| while (memberStart < len) { | ||
| if (memberIndex == MAX_MEMBER_COUNT) { | ||
| // TODO should we return one with an error? | ||
| // TODO should we try to pick up the `dd` member anyway? | ||
| return tagsFactory.empty(); | ||
| } | ||
| if (ddMemberIndex == -1 && value.startsWith(DATADOG_MEMBER_KEY, memberStart)) { | ||
| boolean datadogMember = value.startsWith(DATADOG_MEMBER_KEY, memberStart); | ||
| boolean otelMember = value.startsWith(OTEL_MEMBER_KEY, memberStart); | ||
| if (ddMemberIndex == -1 && datadogMember) { | ||
| ddMemberStart = memberStart; | ||
| ddMemberIndex = memberIndex; | ||
| } | ||
|
|
@@ -68,6 +75,7 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { | |
| if (ddMemberValueStart == -1 && ddMemberIndex != -1) { | ||
| ddMemberValueStart = pos; | ||
| } | ||
| int memberValueStart = pos; | ||
| pos = validateMemberValue(value, pos); | ||
| if (pos < 0) { | ||
| // TODO should we return one with an error? | ||
|
|
@@ -76,6 +84,15 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { | |
| if (ddMemberValueEnd == -1 && ddMemberIndex != -1) { | ||
| ddMemberValueEnd = pos; | ||
| } | ||
| if (otelMemberValueStart == -1) { | ||
| if (otelMember) { | ||
| otelMemberValueStart = memberValueStart; | ||
| otelMemberValueEnd = stripTrailingOWC(value, memberValueStart, pos); | ||
| otelMemberPosition = otherMemberPosition; | ||
| } else if (!datadogMember) { | ||
| otherMemberPosition++; | ||
| } | ||
| } | ||
| memberStart = findNextMember(value, pos); | ||
| if (memberStart < 0) { | ||
| // TODO should we return one with an error? | ||
|
|
@@ -84,9 +101,15 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { | |
| memberIndex++; | ||
| } | ||
|
|
||
| OtelTraceState otelTraceState = | ||
| otelMemberValueStart < 0 | ||
| ? null | ||
| : OtelTraceState.parse( | ||
| value.substring(otelMemberValueStart, otelMemberValueEnd), otelMemberPosition); | ||
|
|
||
| if (ddMemberIndex == -1) { | ||
| // There was no dd member, so create an empty one with the _suffix_ | ||
| return empty(tagsFactory, value); | ||
| return empty(tagsFactory, value, otelTraceState); | ||
| } | ||
|
|
||
| List<TagElement> tagPairs = null; | ||
|
|
@@ -158,7 +181,13 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { | |
| if (tagKey.equals(TRACE_ID_TAG)) { | ||
| return tagsFactory.createInvalid(PROPAGATION_ERROR_MALFORMED_TID + tagValue); | ||
| } | ||
| return empty(tagsFactory, value, firstMemberStart, ddMemberStart, ddMemberValueEnd); | ||
| return empty( | ||
| tagsFactory, | ||
| value, | ||
| firstMemberStart, | ||
| ddMemberStart, | ||
| ddMemberValueEnd, | ||
| otelTraceState); | ||
| } | ||
| if (tagKey.equals(DECISION_MAKER_TAG)) { | ||
| decisionMakerTagValue = tagValue; | ||
|
|
@@ -201,7 +230,8 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { | |
| ddMemberValueEnd, | ||
| maxUnknownSize, | ||
| lastParentId, | ||
| orgPropagationMarkerTagValue); | ||
| orgPropagationMarkerTagValue, | ||
| otelTraceState); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -226,6 +256,10 @@ protected int estimateHeaderSize(PTags pTags) { | |
| // We assume there is no Datadog list-member | ||
| size += pTags.tracestate.length(); | ||
| } | ||
| OtelTraceState otelTraceState = pTags.getOtelTraceState(); | ||
| if (otelTraceState != null) { | ||
| size += OTEL_MEMBER_KEY.length() + otelTraceState.length() + 1; | ||
|
Comment on lines
+259
to
+261
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
perf: When the original tracestate length is already included above—either for a parsed AGENTS.md reference: AGENTS.md:L76-L79 Useful? React with 👍 / 👎. |
||
| } | ||
| return size; | ||
| } | ||
|
|
||
|
|
@@ -290,9 +324,8 @@ protected int appendSuffix(StringBuilder sb, PTags ptags, int size) { | |
| sb.setLength(0); | ||
| size = 0; | ||
| } | ||
| // Append all other non-Datadog list-members | ||
| int newSize = cleanUpAndAppendSuffix(sb, ptags, size); | ||
| if (newSize != size) { | ||
| // Append the managed OTel member and all other non-Datadog list-members | ||
| if (appendOtelAndVendorMembers(sb, ptags, size != 0)) { | ||
| // We don't care about the total size in bytes here, but only the fact that we added something | ||
| // that should be returned | ||
| size = Math.max(size, EMPTY_SIZE + 1); | ||
|
|
@@ -698,58 +731,113 @@ private static int cleanUpAndAppendUnknown(StringBuilder sb, W3CPTags w3CPTags, | |
| return size; | ||
| } | ||
|
|
||
| private static int cleanUpAndAppendSuffix(StringBuilder sb, PTags ptags, int size) { | ||
| private static boolean appendOtelAndVendorMembers( | ||
| StringBuilder sb, PTags ptags, boolean hasDatadogMember) { | ||
| String original = ptags.tracestate; | ||
| if (original == null) { | ||
| return size; | ||
| } | ||
| int ddMemberStart = (ptags instanceof W3CPTags) ? ((W3CPTags) ptags).ddMemberStart : -1; | ||
| int remainingMemberAllowed = size == 0 ? MAX_MEMBER_COUNT : MAX_MEMBER_COUNT - 1; | ||
| int len = original.length(); | ||
| int memberStart = findNextMember(original, 0); | ||
| while (memberStart < len) { | ||
| OtelTraceState otelTraceState = ptags.getOtelTraceState(); | ||
| int remainingMembers = MAX_MEMBER_COUNT - (hasDatadogMember ? 1 : 0); | ||
| int otherMemberPosition = 0; | ||
| boolean otelTraceStateAppended = false; | ||
| boolean memberAppended = false; | ||
| int len = original == null ? 0 : original.length(); | ||
| int memberStart = original == null ? 0 : findNextMember(original, 0); | ||
| while (memberStart < len && remainingMembers > 0) { | ||
| // Look for member end position | ||
| int memberEnd = original.indexOf(MEMBER_SEPARATOR, memberStart); | ||
| if (memberEnd < 0) { | ||
| memberEnd = len; | ||
| } | ||
| // Try to define Datadog member start if not already found | ||
| if (ddMemberStart == -1) { | ||
| if (original.startsWith(DATADOG_MEMBER_KEY, memberStart)) { | ||
| ddMemberStart = memberStart; | ||
| } | ||
| } | ||
| // Skip Datadog member (already added with prefix and tags) | ||
| if (memberStart != ddMemberStart) { | ||
| if (sb.length() > 0) { | ||
| sb.append(MEMBER_SEPARATOR); | ||
| size++; | ||
| boolean managedMember = | ||
| original.startsWith(DATADOG_MEMBER_KEY, memberStart) | ||
| || original.startsWith(OTEL_MEMBER_KEY, memberStart); | ||
| if (!managedMember) { | ||
| if (otelTraceState != null | ||
| && !otelTraceStateAppended | ||
| && otelTraceState.getInheritedPosition() == otherMemberPosition) { | ||
| appendMember(sb, OTEL_MEMBER_KEY, otelTraceState.getValue()); | ||
| remainingMembers--; | ||
| otelTraceStateAppended = true; | ||
| memberAppended = true; | ||
| if (remainingMembers == 0) { | ||
| break; | ||
| } | ||
| } | ||
| int end = stripTrailingOWC(original, memberStart, memberEnd); | ||
| sb.append(original, memberStart, end); | ||
| size += (end - memberStart); | ||
| remainingMemberAllowed--; | ||
| appendMember(sb, original, memberStart, end); | ||
| remainingMembers--; | ||
| otherMemberPosition++; | ||
| memberAppended = true; | ||
| } | ||
| // Check if remaining members are allowed | ||
| if (remainingMemberAllowed == 0) { | ||
| memberStart = len; | ||
| } else { | ||
| memberStart = findNextMember(original, memberEnd + 1); | ||
| memberStart = findNextMember(original, memberEnd + 1); | ||
| } | ||
| if (otelTraceState != null | ||
| && !otelTraceStateAppended | ||
| && remainingMembers > 0 | ||
| && otelTraceState.getInheritedPosition() == otherMemberPosition) { | ||
| appendMember(sb, OTEL_MEMBER_KEY, otelTraceState.getValue()); | ||
| memberAppended = true; | ||
| } | ||
| return memberAppended; | ||
| } | ||
|
|
||
| private static void appendMember(StringBuilder sb, String member, int start, int end) { | ||
| if (sb.length() != 0) { | ||
| sb.append(MEMBER_SEPARATOR); | ||
| } | ||
| sb.append(member, start, end); | ||
| } | ||
|
|
||
| private static void appendMember(StringBuilder sb, String key, String value) { | ||
| if (sb.length() != 0) { | ||
| sb.append(MEMBER_SEPARATOR); | ||
| } | ||
| sb.append(key).append(value); | ||
| } | ||
|
|
||
| static OtelTraceState extractOtelTraceState(String tracestate) { | ||
| if (tracestate == null || tracestate.isEmpty()) { | ||
| return null; | ||
| } | ||
| int otherMemberPosition = 0; | ||
| int memberStart = findNextMember(tracestate, 0); | ||
| while (memberStart < tracestate.length()) { | ||
| int memberValueStart = validateMemberKey(tracestate, memberStart); | ||
| if (memberValueStart < 0) { | ||
| return null; | ||
| } | ||
| int memberValueEnd = validateMemberValue(tracestate, memberValueStart); | ||
| if (memberValueEnd < 0) { | ||
| return null; | ||
| } | ||
| if (tracestate.startsWith(OTEL_MEMBER_KEY, memberStart)) { | ||
| int end = stripTrailingOWC(tracestate, memberValueStart, memberValueEnd); | ||
| return OtelTraceState.parse( | ||
| tracestate.substring(memberValueStart, end), otherMemberPosition); | ||
| } | ||
| if (!tracestate.startsWith(DATADOG_MEMBER_KEY, memberStart)) { | ||
| otherMemberPosition++; | ||
| } | ||
| memberStart = findNextMember(tracestate, memberValueEnd); | ||
| } | ||
| return size; | ||
| return null; | ||
| } | ||
|
|
||
| static W3CPTags empty(PTagsFactory factory, String original) { | ||
| return empty(factory, original, 0, -1, -1); | ||
| return empty(factory, original, extractOtelTraceState(original)); | ||
| } | ||
|
|
||
| private static W3CPTags empty( | ||
| PTagsFactory factory, String original, OtelTraceState otelTraceState) { | ||
| return empty(factory, original, 0, -1, -1, otelTraceState); | ||
| } | ||
|
|
||
| private static W3CPTags empty( | ||
| PTagsFactory factory, | ||
| String original, | ||
| int firstMemberStart, | ||
| int ddMemberStart, | ||
| int ddMemberValueEnd) { | ||
| int ddMemberValueEnd, | ||
| OtelTraceState otelTraceState) { | ||
| return new W3CPTags( | ||
| factory, | ||
| null, | ||
|
|
@@ -764,7 +852,8 @@ private static W3CPTags empty( | |
| ddMemberValueEnd, | ||
| 0, | ||
| null, | ||
| null); | ||
| null, | ||
| otelTraceState); | ||
| } | ||
|
|
||
| private static class W3CPTags extends PTags { | ||
|
|
@@ -799,7 +888,8 @@ public W3CPTags( | |
| int ddMemberValueEnd, | ||
| int maxUnknownSize, | ||
| CharSequence lastParentId, | ||
| TagValue orgPropagationMarkerTagValue) { | ||
| TagValue orgPropagationMarkerTagValue, | ||
| OtelTraceState otelTraceState) { | ||
| super( | ||
| factory, | ||
| tagPairs, | ||
|
|
@@ -815,6 +905,7 @@ public W3CPTags( | |
| this.ddMemberStart = ddMemberStart; | ||
| this.ddMemberValueEnd = ddMemberValueEnd; | ||
| this.maxUnknownSize = maxUnknownSize; | ||
| setOtelTraceState(otelTraceState); | ||
| } | ||
|
|
||
| @Override | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perf: With the default
DATADOG,TRACECONTEXTextraction order, matching Datadog and W3C headers causefromHeaderValueto parse the tracestate beforeHttpCodec.applyTraceContextToFirstContextpasses the same string here. CallingextractOtelTraceStatethen validates and scans it again—and allocates another substring/state whenotis present—on the inbound request path. Transfer the already-parsed state with the propagation tags instead of reparsing the raw header.AGENTS.md reference: AGENTS.md:L76-L79
Useful? React with 👍 / 👎.