Skip to content

Commit a5a4bd6

Browse files
committed
fixup
1 parent 97c41c0 commit a5a4bd6

2 files changed

Lines changed: 112 additions & 1 deletion

File tree

trace/attachmentprocessor/span.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,26 @@ type transformedSpan struct {
2424

2525
// NewTransformedSpan creates a transformedSpan that overrides the given
2626
// attribute keys with new values. All other attributes are preserved.
27+
// Override entries for keys not present in the original span are appended
28+
// to the attribute list rather than silently dropped.
2729
func NewTransformedSpan(delegate sdktrace.ReadOnlySpan, overrides map[attribute.Key]string) sdktrace.ReadOnlySpan {
2830
origAttrs := delegate.Attributes()
29-
newAttrs := make([]attribute.KeyValue, 0, len(origAttrs))
31+
newAttrs := make([]attribute.KeyValue, 0, len(origAttrs)+len(overrides))
32+
seen := make(map[attribute.Key]bool, len(overrides))
3033
for _, a := range origAttrs {
3134
if v, ok := overrides[a.Key]; ok {
3235
newAttrs = append(newAttrs, attribute.String(string(a.Key), v))
36+
seen[a.Key] = true
3337
} else {
3438
newAttrs = append(newAttrs, a)
3539
}
3640
}
41+
// Append overrides for keys that weren't already on the span.
42+
for k, v := range overrides {
43+
if !seen[k] {
44+
newAttrs = append(newAttrs, attribute.String(string(k), v))
45+
}
46+
}
3747
return transformedSpan{
3848
ReadOnlySpan: delegate,
3949
attrs: newAttrs,
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package attachmentprocessor
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"go.opentelemetry.io/otel/attribute"
9+
sdktrace "go.opentelemetry.io/otel/sdk/trace"
10+
"go.opentelemetry.io/otel/sdk/trace/tracetest"
11+
)
12+
13+
// makeReadOnlySpan creates a real ReadOnlySpan with the given attributes for testing.
14+
func makeReadOnlySpan(t *testing.T, attrs ...attribute.KeyValue) sdktrace.ReadOnlySpan {
15+
t.Helper()
16+
exporter := tracetest.NewInMemoryExporter()
17+
tp := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter))
18+
_, span := tp.Tracer("test").Start(context.Background(), "test-span")
19+
span.SetAttributes(attrs...)
20+
span.End()
21+
22+
stubs := exporter.GetSpans()
23+
if len(stubs) != 1 {
24+
t.Fatalf("expected 1 span, got %d", len(stubs))
25+
}
26+
return stubs[0].Snapshot()
27+
}
28+
29+
func TestNewTransformedSpan_OverridesExistingKey(t *testing.T) {
30+
orig := makeReadOnlySpan(t,
31+
attribute.String("braintrust.input_json", "original"),
32+
attribute.String("other.attr", "keep-me"),
33+
)
34+
35+
transformed := NewTransformedSpan(orig, map[attribute.Key]string{
36+
"braintrust.input_json": "replaced",
37+
})
38+
39+
attrs := transformed.Attributes()
40+
got := make(map[string]string)
41+
for _, a := range attrs {
42+
got[string(a.Key)] = a.Value.AsString()
43+
}
44+
45+
assert.Equal(t, "replaced", got["braintrust.input_json"])
46+
assert.Equal(t, "keep-me", got["other.attr"])
47+
assert.Len(t, attrs, 2)
48+
}
49+
50+
func TestNewTransformedSpan_AppendsNewKey(t *testing.T) {
51+
orig := makeReadOnlySpan(t,
52+
attribute.String("existing", "value"),
53+
)
54+
55+
transformed := NewTransformedSpan(orig, map[attribute.Key]string{
56+
"new.key": "new-value",
57+
})
58+
59+
attrs := transformed.Attributes()
60+
got := make(map[string]string)
61+
for _, a := range attrs {
62+
got[string(a.Key)] = a.Value.AsString()
63+
}
64+
65+
// The new key should be appended, not silently dropped.
66+
assert.Equal(t, "value", got["existing"])
67+
assert.Equal(t, "new-value", got["new.key"])
68+
assert.Len(t, attrs, 2)
69+
}
70+
71+
func TestNewTransformedSpan_MixedOverrideAndAppend(t *testing.T) {
72+
orig := makeReadOnlySpan(t,
73+
attribute.String("braintrust.input_json", "orig-in"),
74+
attribute.String("other", "preserved"),
75+
)
76+
77+
transformed := NewTransformedSpan(orig, map[attribute.Key]string{
78+
"braintrust.input_json": "new-in",
79+
"braintrust.output_json": "new-out", // not on original
80+
})
81+
82+
got := make(map[string]string)
83+
for _, a := range transformed.Attributes() {
84+
got[string(a.Key)] = a.Value.AsString()
85+
}
86+
87+
assert.Equal(t, "new-in", got["braintrust.input_json"])
88+
assert.Equal(t, "new-out", got["braintrust.output_json"])
89+
assert.Equal(t, "preserved", got["other"])
90+
assert.Len(t, transformed.Attributes(), 3)
91+
}
92+
93+
func TestNewTransformedSpan_PreservesDelegateMethods(t *testing.T) {
94+
orig := makeReadOnlySpan(t, attribute.String("k", "v"))
95+
transformed := NewTransformedSpan(orig, map[attribute.Key]string{})
96+
97+
assert.Equal(t, orig.Name(), transformed.Name())
98+
assert.Equal(t, orig.SpanContext(), transformed.SpanContext())
99+
assert.Equal(t, orig.StartTime(), transformed.StartTime())
100+
assert.Equal(t, orig.EndTime(), transformed.EndTime())
101+
}

0 commit comments

Comments
 (0)