Skip to content

Commit fe6c67e

Browse files
jianwuXSAM
andauthored
OpenCensus bridge to support TraceState (#5651)
# Summary This is to fix issue: #5642 The original logic skips copying TraceState when convert Spans between OTel and OC. This PR also updated the OTel TraceState to expose the Keys function for the propagation purpose. --------- Co-authored-by: Sam Xie <[email protected]>
1 parent 83ae9bd commit fe6c67e

File tree

7 files changed

+163
-16
lines changed

7 files changed

+163
-16
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ The next release will require at least [Go 1.22].
2020
See our [versioning policy](VERSIONING.md) for more information about these stability guarantees. (#5629)
2121
- Add `InstrumentationScope` field to `SpanStub` in `go.opentelemetry.io/otel/sdk/trace/tracetest`, as a replacement for the deprecated `InstrumentationLibrary`. (#5627)
2222
- Zero value of `SimpleProcessor` in `go.opentelemetry.io/otel/sdk/log` no longer panics. (#5665)
23+
- Add `Walk` function to `TraceState` in `go.opentelemetry.io/otel/trace` to iterate all the key-value pairs. (#5651)
24+
- Bridge the trace state in `go.opentelemetry.io/otel/bridge/opencensus`. (#5651)
2325
- Support [Go 1.23]. (#5720)
2426

2527
### Changed

bridge/opencensus/internal/oc2otel/span_context.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
package oc2otel // import "go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel"
55

66
import (
7+
"slices"
8+
79
octrace "go.opencensus.io/trace"
810

911
"go.opentelemetry.io/otel/trace"
@@ -14,9 +16,19 @@ func SpanContext(sc octrace.SpanContext) trace.SpanContext {
1416
if sc.IsSampled() {
1517
traceFlags = trace.FlagsSampled
1618
}
19+
20+
entries := slices.Clone(sc.Tracestate.Entries())
21+
slices.Reverse(entries)
22+
23+
tsOtel := trace.TraceState{}
24+
for _, entry := range entries {
25+
tsOtel, _ = tsOtel.Insert(entry.Key, entry.Value)
26+
}
27+
1728
return trace.NewSpanContext(trace.SpanContextConfig{
1829
TraceID: trace.TraceID(sc.TraceID),
1930
SpanID: trace.SpanID(sc.SpanID),
2031
TraceFlags: traceFlags,
32+
TraceState: tsOtel,
2133
})
2234
}

bridge/opencensus/internal/oc2otel/span_context_test.go

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,33 @@ package oc2otel
66
import (
77
"testing"
88

9+
"github.com/stretchr/testify/assert"
10+
"go.opencensus.io/plugin/ochttp/propagation/tracecontext"
11+
12+
"go.opentelemetry.io/otel/bridge/opencensus/internal/otel2oc"
13+
914
octrace "go.opencensus.io/trace"
1015
"go.opencensus.io/trace/tracestate"
1116

1217
"go.opentelemetry.io/otel/trace"
1318
)
1419

1520
func TestSpanContextConversion(t *testing.T) {
21+
tsOc, _ := tracestate.New(nil,
22+
tracestate.Entry{Key: "key1", Value: "value1"},
23+
tracestate.Entry{Key: "key2", Value: "value2"},
24+
)
25+
tsOtel := trace.TraceState{}
26+
tsOtel, _ = tsOtel.Insert("key2", "value2")
27+
tsOtel, _ = tsOtel.Insert("key1", "value1")
28+
29+
httpFormatOc := &tracecontext.HTTPFormat{}
30+
1631
for _, tc := range []struct {
17-
description string
18-
input octrace.SpanContext
19-
expected trace.SpanContext
32+
description string
33+
input octrace.SpanContext
34+
expected trace.SpanContext
35+
expectedTracestate string
2036
}{
2137
{
2238
description: "empty",
@@ -47,23 +63,32 @@ func TestSpanContextConversion(t *testing.T) {
4763
}),
4864
},
4965
{
50-
description: "trace state is ignored",
66+
description: "trace state should be propagated",
5167
input: octrace.SpanContext{
5268
TraceID: octrace.TraceID([16]byte{1}),
5369
SpanID: octrace.SpanID([8]byte{2}),
54-
Tracestate: &tracestate.Tracestate{},
70+
Tracestate: tsOc,
5571
},
5672
expected: trace.NewSpanContext(trace.SpanContextConfig{
57-
TraceID: trace.TraceID([16]byte{1}),
58-
SpanID: trace.SpanID([8]byte{2}),
73+
TraceID: trace.TraceID([16]byte{1}),
74+
SpanID: trace.SpanID([8]byte{2}),
75+
TraceState: tsOtel,
5976
}),
77+
expectedTracestate: "key1=value1,key2=value2",
6078
},
6179
} {
6280
t.Run(tc.description, func(t *testing.T) {
6381
output := SpanContext(tc.input)
64-
if !output.Equal(tc.expected) {
65-
t.Fatalf("Got %+v spancontext, expected %+v.", output, tc.expected)
66-
}
82+
assert.Equal(t, tc.expected, output)
83+
84+
// Ensure the otel tracestate and oc tracestate has the same header output
85+
_, ts := httpFormatOc.SpanContextToHeaders(tc.input)
86+
assert.Equal(t, tc.expectedTracestate, ts)
87+
assert.Equal(t, tc.expectedTracestate, tc.expected.TraceState().String())
88+
89+
// The reverse conversion should yield the original input
90+
input := otel2oc.SpanContext(output)
91+
assert.Equal(t, tc.input, input)
6792
})
6893
}
6994
}

bridge/opencensus/internal/otel2oc/span_context.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package otel2oc // import "go.opentelemetry.io/otel/bridge/opencensus/internal/o
55

66
import (
77
octrace "go.opencensus.io/trace"
8+
"go.opencensus.io/trace/tracestate"
89

910
"go.opentelemetry.io/otel/trace"
1011
)
@@ -15,9 +16,18 @@ func SpanContext(sc trace.SpanContext) octrace.SpanContext {
1516
// OpenCensus doesn't expose functions to directly set sampled
1617
to = 0x1
1718
}
19+
20+
entries := make([]tracestate.Entry, 0, sc.TraceState().Len())
21+
sc.TraceState().Walk(func(key, value string) bool {
22+
entries = append(entries, tracestate.Entry{Key: key, Value: value})
23+
return true
24+
})
25+
tsOc, _ := tracestate.New(nil, entries...)
26+
1827
return octrace.SpanContext{
1928
TraceID: octrace.TraceID(sc.TraceID()),
2029
SpanID: octrace.SpanID(sc.SpanID()),
2130
TraceOptions: to,
31+
Tracestate: tsOc,
2232
}
2333
}

bridge/opencensus/internal/otel2oc/span_context_test.go

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,36 @@ package otel2oc
66
import (
77
"testing"
88

9+
"go.opencensus.io/plugin/ochttp/propagation/tracecontext"
10+
11+
"go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel"
12+
13+
"github.com/stretchr/testify/assert"
14+
15+
"go.opencensus.io/trace/tracestate"
16+
917
octrace "go.opencensus.io/trace"
1018

1119
"go.opentelemetry.io/otel/trace"
1220
)
1321

1422
func TestSpanContextConversion(t *testing.T) {
23+
tsOc, _ := tracestate.New(nil,
24+
// Oc has a reverse order of TraceState entries compared to OTel
25+
tracestate.Entry{Key: "key1", Value: "value1"},
26+
tracestate.Entry{Key: "key2", Value: "value2"},
27+
)
28+
tsOtel := trace.TraceState{}
29+
tsOtel, _ = tsOtel.Insert("key2", "value2")
30+
tsOtel, _ = tsOtel.Insert("key1", "value1")
31+
32+
httpFormatOc := &tracecontext.HTTPFormat{}
33+
1534
for _, tc := range []struct {
16-
description string
17-
input trace.SpanContext
18-
expected octrace.SpanContext
35+
description string
36+
input trace.SpanContext
37+
expected octrace.SpanContext
38+
expectedTracestate string
1939
}{
2040
{
2141
description: "empty",
@@ -45,12 +65,34 @@ func TestSpanContextConversion(t *testing.T) {
4565
TraceOptions: octrace.TraceOptions(0),
4666
},
4767
},
68+
{
69+
description: "trace state should be propagated",
70+
input: trace.NewSpanContext(trace.SpanContextConfig{
71+
TraceID: trace.TraceID([16]byte{1}),
72+
SpanID: trace.SpanID([8]byte{2}),
73+
TraceState: tsOtel,
74+
}),
75+
expected: octrace.SpanContext{
76+
TraceID: octrace.TraceID([16]byte{1}),
77+
SpanID: octrace.SpanID([8]byte{2}),
78+
TraceOptions: octrace.TraceOptions(0),
79+
Tracestate: tsOc,
80+
},
81+
expectedTracestate: "key1=value1,key2=value2",
82+
},
4883
} {
4984
t.Run(tc.description, func(t *testing.T) {
5085
output := SpanContext(tc.input)
51-
if output != tc.expected {
52-
t.Fatalf("Got %+v spancontext, expected %+v.", output, tc.expected)
53-
}
86+
assert.Equal(t, tc.expected, output)
87+
88+
// Ensure the otel tracestate and oc tracestate has the same header output
89+
_, ts := httpFormatOc.SpanContextToHeaders(tc.expected)
90+
assert.Equal(t, tc.expectedTracestate, ts)
91+
assert.Equal(t, tc.expectedTracestate, tc.input.TraceState().String())
92+
93+
// The reverse conversion should yield the original input
94+
input := oc2otel.SpanContext(output)
95+
assert.Equal(t, tc.input, input)
5496
})
5597
}
5698
}

trace/tracestate.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,16 @@ func (ts TraceState) Get(key string) string {
260260
return ""
261261
}
262262

263+
// Walk walks all key value pairs in the TraceState by calling f
264+
// Iteration stops if f returns false.
265+
func (ts TraceState) Walk(f func(key, value string) bool) {
266+
for _, m := range ts.list {
267+
if !f(m.Key, m.Value) {
268+
break
269+
}
270+
}
271+
}
272+
263273
// Insert adds a new list-member defined by the key/value pair to the
264274
// TraceState. If a list-member already exists for the given key, that
265275
// list-member's value is updated. The new or updated list-member is always

trace/tracestate_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,52 @@ func TestTraceStateDelete(t *testing.T) {
409409
}
410410
}
411411

412+
func TestTraceStateWalk(t *testing.T) {
413+
testCases := []struct {
414+
name string
415+
tracestate TraceState
416+
num int
417+
expected [][]string
418+
}{
419+
{
420+
name: "With keys",
421+
tracestate: TraceState{list: []member{
422+
{Key: "key1", Value: "val1"},
423+
{Key: "key2", Value: "val2"},
424+
}},
425+
num: 3,
426+
expected: [][]string{{"key1", "val1"}, {"key2", "val2"}},
427+
},
428+
{
429+
name: "With keys walk partially",
430+
tracestate: TraceState{list: []member{
431+
{Key: "key1", Value: "val1"},
432+
{Key: "key2", Value: "val2"},
433+
}},
434+
num: 1,
435+
expected: [][]string{{"key1", "val1"}},
436+
},
437+
438+
{
439+
name: "Without keys",
440+
tracestate: TraceState{list: []member{}},
441+
num: 2,
442+
expected: [][]string{},
443+
},
444+
}
445+
446+
for _, tc := range testCases {
447+
t.Run(tc.name, func(t *testing.T) {
448+
got := [][]string{}
449+
tc.tracestate.Walk(func(key, value string) bool {
450+
got = append(got, []string{key, value})
451+
return len(got) < tc.num
452+
})
453+
assert.Equal(t, tc.expected, got)
454+
})
455+
}
456+
}
457+
412458
var insertTS = TraceState{list: []member{
413459
{Key: "key1", Value: "val1"},
414460
{Key: "key2", Value: "val2"},

0 commit comments

Comments
 (0)