Skip to content

Commit 27213f5

Browse files
committed
Merge main into feat/go-ci-visibility-parallel-retries
2 parents 647fe76 + 51e0cfb commit 27213f5

7 files changed

Lines changed: 762 additions & 6 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ static-analysis.datadog.yml
2828
coverage-*.txt
2929

3030
# Editors and Agents
31+
/.codex/
3132
/.cursor
3233
/.vscode
3334
/.claude/*

ddtrace/tracer/textmap.go

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,8 @@ func (*propagatorB3SingleHeader) extractTextMap(reader TextMapReader) (*SpanCont
820820
const (
821821
traceparentHeader = "traceparent"
822822
tracestateHeader = "tracestate"
823+
// tracestateDDMaxSize bounds the length of a `dd=` list-entry in tracestate.
824+
tracestateDDMaxSize = 256
823825
)
824826

825827
// propagatorW3c implements Propagator and injects/extracts span contexts
@@ -1076,7 +1078,7 @@ func composeTracestate(ctx *SpanContext, priority int, oldState string) string {
10761078
// with the `t.` prefix. Tag value must have all `=` signs replaced with a tilde (`~`).
10771079
key := sm.Mutate(keyDisallowedFn, k[len("_dd.p."):])
10781080
value := sm.Mutate(valueDisallowedFn, v)
1079-
if b.Len()+len(key)+len(value)+4 > 256 { // the +4 here is to account for the `t.` prefix, the `;` needed between the tags, and the `:` between the key and value
1081+
if b.Len()+len(key)+len(value)+4 > tracestateDDMaxSize { // the +4 here is to account for the `t.` prefix, the `;` needed between the tags, and the `:` between the key and value
10801082
return false
10811083
}
10821084
b.WriteString(";t.")
@@ -1242,13 +1244,16 @@ func parseTracestate(ctx *SpanContext, header string) {
12421244
// https://www.w3.org/TR/trace-context-1/#tracestate-header-field-values
12431245
return
12441246
}
1245-
// if multiple headers are present, they must be combined and stored
1246-
setPropagatingTag(ctx, tracestateHeader, header)
1247-
combined := strings.SplitSeq(strings.Trim(header, "\t "), ",")
1248-
for group := range combined {
1247+
hasOversizedDD := false
1248+
for group := range strings.SplitSeq(header, ",") {
1249+
group = strings.Trim(group, "\t ")
12491250
if !strings.HasPrefix(group, "dd=") {
12501251
continue
12511252
}
1253+
if len(group) > tracestateDDMaxSize {
1254+
hasOversizedDD = true
1255+
break
1256+
}
12521257
ddMembers := strings.Split(group[len("dd="):], ";")
12531258
dropDM := false
12541259
// indicate that backend could reparent this as a root
@@ -1299,6 +1304,30 @@ func parseTracestate(ctx *SpanContext, header string) {
12991304
}
13001305
}
13011306
}
1307+
// Store the propagating tag, rebuilding the header to exclude oversized
1308+
// dd= entries when present.
1309+
if !hasOversizedDD {
1310+
setPropagatingTag(ctx, tracestateHeader, header)
1311+
return
1312+
}
1313+
var cleaned strings.Builder
1314+
cleaned.Grow(len(header))
1315+
first := true
1316+
for entry := range strings.SplitSeq(header, ",") {
1317+
trimmed := strings.Trim(entry, "\t ")
1318+
if strings.HasPrefix(trimmed, "dd=") && len(trimmed) > tracestateDDMaxSize {
1319+
continue
1320+
}
1321+
if !first {
1322+
cleaned.WriteByte(',')
1323+
}
1324+
cleaned.WriteString(entry)
1325+
first = false
1326+
}
1327+
if cleaned.Len() == 0 {
1328+
return
1329+
}
1330+
setPropagatingTag(ctx, tracestateHeader, cleaned.String())
13021331
}
13031332

13041333
// extractTraceID128 extracts the trace id from v and populates the traceID

ddtrace/tracer/textmap_test.go

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,88 @@ func Test257CharacterDDTracestateLengh(t *testing.T) {
445445
ddTag := strings.SplitN(headers[tracestateHeader], ",", 2)[0]
446446
assert.Contains(ddTag, "s:2")
447447
assert.Regexp(regexp.MustCompile(`dd=[\w:,]+`), ddTag)
448-
assert.LessOrEqual(len(ddTag), 256) // one of the propagated tags will not be propagated
448+
assert.LessOrEqual(len(ddTag), tracestateDDMaxSize) // one of the propagated tags will not be propagated
449+
}
450+
451+
func TestExtractTracestateDropsOversizedDD(t *testing.T) {
452+
t.Setenv(headerPropagationStyle, "tracecontext")
453+
tracer, err := newTracer()
454+
require.NoError(t, err)
455+
defer tracer.Stop()
456+
457+
// Build a dd= entry that exceeds tracestateDDMaxSize.
458+
ddEntry := "dd=s:1;o:rum;p:0000000000000001;t.foo:" + strings.Repeat("a", tracestateDDMaxSize)
459+
require.Greater(t, len(ddEntry), tracestateDDMaxSize)
460+
rawTracestate := ddEntry + ",vendor1=v1,vendor2=v2"
461+
462+
headers := TextMapCarrier(map[string]string{
463+
traceparentHeader: "00-00000000000000000000000000000004-2222222222222222-01",
464+
tracestateHeader: rawTracestate,
465+
})
466+
sctx, err := tracer.Extract(headers)
467+
require.NoError(t, err)
468+
469+
// Oversized dd entry must not appear in the stored propagating tag.
470+
stored := sctx.trace.propagatingTag(tracestateHeader)
471+
assert.NotContains(t, stored, "dd=")
472+
assert.Contains(t, stored, "vendor1=v1")
473+
assert.Contains(t, stored, "vendor2=v2")
474+
// dd entry was not parsed, so its origin/reparentID were not extracted.
475+
assert.Empty(t, sctx.origin)
476+
assert.Empty(t, sctx.reparentID)
477+
// And no _dd.p.foo propagating tag was created from t.foo.
478+
assert.False(t, sctx.trace.hasPropagatingTag("_dd.p.foo"))
479+
}
480+
481+
func TestExtractTracestateKeepsDDAtBoundary(t *testing.T) {
482+
t.Setenv(headerPropagationStyle, "tracecontext")
483+
tracer, err := newTracer()
484+
require.NoError(t, err)
485+
defer tracer.Stop()
486+
487+
// dd= entry exactly at the limit should be kept and parsed.
488+
prefix := "dd=s:1;t.foo:"
489+
ddEntry := prefix + strings.Repeat("a", tracestateDDMaxSize-len(prefix))
490+
require.Equal(t, tracestateDDMaxSize, len(ddEntry))
491+
492+
headers := TextMapCarrier(map[string]string{
493+
traceparentHeader: "00-00000000000000000000000000000004-2222222222222222-01",
494+
tracestateHeader: ddEntry + ",vendor1=v1",
495+
})
496+
sctx, err := tracer.Extract(headers)
497+
require.NoError(t, err)
498+
499+
stored := sctx.trace.propagatingTag(tracestateHeader)
500+
assert.Contains(t, stored, "dd=")
501+
assert.Contains(t, stored, "vendor1=v1")
502+
assert.True(t, sctx.trace.hasPropagatingTag("_dd.p.foo"))
503+
}
504+
505+
func TestExtractTracestateDropsOversizedDDWithWhitespace(t *testing.T) {
506+
t.Setenv(headerPropagationStyle, "tracecontext")
507+
tracer, err := newTracer()
508+
require.NoError(t, err)
509+
defer tracer.Stop()
510+
511+
// Same as the basic oversized-dd case but with leading OWS on the dd entry.
512+
// W3C list-member parsing allows surrounding whitespace, so the prefix and
513+
// length check must trim each entry before evaluating it.
514+
ddEntry := " dd=s:1;o:rum;p:0000000000000001;t.foo:" + strings.Repeat("a", tracestateDDMaxSize)
515+
rawTracestate := "vendor1=v1," + ddEntry + ",vendor2=v2"
516+
517+
headers := TextMapCarrier(map[string]string{
518+
traceparentHeader: "00-00000000000000000000000000000004-2222222222222222-01",
519+
tracestateHeader: rawTracestate,
520+
})
521+
sctx, err := tracer.Extract(headers)
522+
require.NoError(t, err)
523+
524+
stored := sctx.trace.propagatingTag(tracestateHeader)
525+
assert.NotContains(t, stored, "dd=")
526+
assert.Contains(t, stored, "vendor1=v1")
527+
assert.Contains(t, stored, "vendor2=v2")
528+
assert.Empty(t, sctx.origin)
529+
assert.False(t, sctx.trace.hasPropagatingTag("_dd.p.foo"))
449530
}
450531

451532
func TestTextMapPropagator(t *testing.T) {

0 commit comments

Comments
 (0)