1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-03-17 20:57:51 +02:00

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 <sam@samxie.me>
This commit is contained in:
jianwu 2024-08-21 08:33:56 -07:00 committed by GitHub
parent 83ae9bd0e3
commit fe6c67e7e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 163 additions and 16 deletions

View File

@ -20,6 +20,8 @@ The next release will require at least [Go 1.22].
See our [versioning policy](VERSIONING.md) for more information about these stability guarantees. (#5629)
- Add `InstrumentationScope` field to `SpanStub` in `go.opentelemetry.io/otel/sdk/trace/tracetest`, as a replacement for the deprecated `InstrumentationLibrary`. (#5627)
- Zero value of `SimpleProcessor` in `go.opentelemetry.io/otel/sdk/log` no longer panics. (#5665)
- Add `Walk` function to `TraceState` in `go.opentelemetry.io/otel/trace` to iterate all the key-value pairs. (#5651)
- Bridge the trace state in `go.opentelemetry.io/otel/bridge/opencensus`. (#5651)
- Support [Go 1.23]. (#5720)
### Changed

View File

@ -4,6 +4,8 @@
package oc2otel // import "go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel"
import (
"slices"
octrace "go.opencensus.io/trace"
"go.opentelemetry.io/otel/trace"
@ -14,9 +16,19 @@ func SpanContext(sc octrace.SpanContext) trace.SpanContext {
if sc.IsSampled() {
traceFlags = trace.FlagsSampled
}
entries := slices.Clone(sc.Tracestate.Entries())
slices.Reverse(entries)
tsOtel := trace.TraceState{}
for _, entry := range entries {
tsOtel, _ = tsOtel.Insert(entry.Key, entry.Value)
}
return trace.NewSpanContext(trace.SpanContextConfig{
TraceID: trace.TraceID(sc.TraceID),
SpanID: trace.SpanID(sc.SpanID),
TraceFlags: traceFlags,
TraceState: tsOtel,
})
}

View File

@ -6,6 +6,11 @@ package oc2otel
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opencensus.io/plugin/ochttp/propagation/tracecontext"
"go.opentelemetry.io/otel/bridge/opencensus/internal/otel2oc"
octrace "go.opencensus.io/trace"
"go.opencensus.io/trace/tracestate"
@ -13,10 +18,21 @@ import (
)
func TestSpanContextConversion(t *testing.T) {
tsOc, _ := tracestate.New(nil,
tracestate.Entry{Key: "key1", Value: "value1"},
tracestate.Entry{Key: "key2", Value: "value2"},
)
tsOtel := trace.TraceState{}
tsOtel, _ = tsOtel.Insert("key2", "value2")
tsOtel, _ = tsOtel.Insert("key1", "value1")
httpFormatOc := &tracecontext.HTTPFormat{}
for _, tc := range []struct {
description string
input octrace.SpanContext
expected trace.SpanContext
description string
input octrace.SpanContext
expected trace.SpanContext
expectedTracestate string
}{
{
description: "empty",
@ -47,23 +63,32 @@ func TestSpanContextConversion(t *testing.T) {
}),
},
{
description: "trace state is ignored",
description: "trace state should be propagated",
input: octrace.SpanContext{
TraceID: octrace.TraceID([16]byte{1}),
SpanID: octrace.SpanID([8]byte{2}),
Tracestate: &tracestate.Tracestate{},
Tracestate: tsOc,
},
expected: trace.NewSpanContext(trace.SpanContextConfig{
TraceID: trace.TraceID([16]byte{1}),
SpanID: trace.SpanID([8]byte{2}),
TraceID: trace.TraceID([16]byte{1}),
SpanID: trace.SpanID([8]byte{2}),
TraceState: tsOtel,
}),
expectedTracestate: "key1=value1,key2=value2",
},
} {
t.Run(tc.description, func(t *testing.T) {
output := SpanContext(tc.input)
if !output.Equal(tc.expected) {
t.Fatalf("Got %+v spancontext, expected %+v.", output, tc.expected)
}
assert.Equal(t, tc.expected, output)
// Ensure the otel tracestate and oc tracestate has the same header output
_, ts := httpFormatOc.SpanContextToHeaders(tc.input)
assert.Equal(t, tc.expectedTracestate, ts)
assert.Equal(t, tc.expectedTracestate, tc.expected.TraceState().String())
// The reverse conversion should yield the original input
input := otel2oc.SpanContext(output)
assert.Equal(t, tc.input, input)
})
}
}

View File

@ -5,6 +5,7 @@ package otel2oc // import "go.opentelemetry.io/otel/bridge/opencensus/internal/o
import (
octrace "go.opencensus.io/trace"
"go.opencensus.io/trace/tracestate"
"go.opentelemetry.io/otel/trace"
)
@ -15,9 +16,18 @@ func SpanContext(sc trace.SpanContext) octrace.SpanContext {
// OpenCensus doesn't expose functions to directly set sampled
to = 0x1
}
entries := make([]tracestate.Entry, 0, sc.TraceState().Len())
sc.TraceState().Walk(func(key, value string) bool {
entries = append(entries, tracestate.Entry{Key: key, Value: value})
return true
})
tsOc, _ := tracestate.New(nil, entries...)
return octrace.SpanContext{
TraceID: octrace.TraceID(sc.TraceID()),
SpanID: octrace.SpanID(sc.SpanID()),
TraceOptions: to,
Tracestate: tsOc,
}
}

View File

@ -6,16 +6,36 @@ package otel2oc
import (
"testing"
"go.opencensus.io/plugin/ochttp/propagation/tracecontext"
"go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel"
"github.com/stretchr/testify/assert"
"go.opencensus.io/trace/tracestate"
octrace "go.opencensus.io/trace"
"go.opentelemetry.io/otel/trace"
)
func TestSpanContextConversion(t *testing.T) {
tsOc, _ := tracestate.New(nil,
// Oc has a reverse order of TraceState entries compared to OTel
tracestate.Entry{Key: "key1", Value: "value1"},
tracestate.Entry{Key: "key2", Value: "value2"},
)
tsOtel := trace.TraceState{}
tsOtel, _ = tsOtel.Insert("key2", "value2")
tsOtel, _ = tsOtel.Insert("key1", "value1")
httpFormatOc := &tracecontext.HTTPFormat{}
for _, tc := range []struct {
description string
input trace.SpanContext
expected octrace.SpanContext
description string
input trace.SpanContext
expected octrace.SpanContext
expectedTracestate string
}{
{
description: "empty",
@ -45,12 +65,34 @@ func TestSpanContextConversion(t *testing.T) {
TraceOptions: octrace.TraceOptions(0),
},
},
{
description: "trace state should be propagated",
input: trace.NewSpanContext(trace.SpanContextConfig{
TraceID: trace.TraceID([16]byte{1}),
SpanID: trace.SpanID([8]byte{2}),
TraceState: tsOtel,
}),
expected: octrace.SpanContext{
TraceID: octrace.TraceID([16]byte{1}),
SpanID: octrace.SpanID([8]byte{2}),
TraceOptions: octrace.TraceOptions(0),
Tracestate: tsOc,
},
expectedTracestate: "key1=value1,key2=value2",
},
} {
t.Run(tc.description, func(t *testing.T) {
output := SpanContext(tc.input)
if output != tc.expected {
t.Fatalf("Got %+v spancontext, expected %+v.", output, tc.expected)
}
assert.Equal(t, tc.expected, output)
// Ensure the otel tracestate and oc tracestate has the same header output
_, ts := httpFormatOc.SpanContextToHeaders(tc.expected)
assert.Equal(t, tc.expectedTracestate, ts)
assert.Equal(t, tc.expectedTracestate, tc.input.TraceState().String())
// The reverse conversion should yield the original input
input := oc2otel.SpanContext(output)
assert.Equal(t, tc.input, input)
})
}
}

View File

@ -260,6 +260,16 @@ func (ts TraceState) Get(key string) string {
return ""
}
// Walk walks all key value pairs in the TraceState by calling f
// Iteration stops if f returns false.
func (ts TraceState) Walk(f func(key, value string) bool) {
for _, m := range ts.list {
if !f(m.Key, m.Value) {
break
}
}
}
// Insert adds a new list-member defined by the key/value pair to the
// TraceState. If a list-member already exists for the given key, that
// list-member's value is updated. The new or updated list-member is always

View File

@ -409,6 +409,52 @@ func TestTraceStateDelete(t *testing.T) {
}
}
func TestTraceStateWalk(t *testing.T) {
testCases := []struct {
name string
tracestate TraceState
num int
expected [][]string
}{
{
name: "With keys",
tracestate: TraceState{list: []member{
{Key: "key1", Value: "val1"},
{Key: "key2", Value: "val2"},
}},
num: 3,
expected: [][]string{{"key1", "val1"}, {"key2", "val2"}},
},
{
name: "With keys walk partially",
tracestate: TraceState{list: []member{
{Key: "key1", Value: "val1"},
{Key: "key2", Value: "val2"},
}},
num: 1,
expected: [][]string{{"key1", "val1"}},
},
{
name: "Without keys",
tracestate: TraceState{list: []member{}},
num: 2,
expected: [][]string{},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := [][]string{}
tc.tracestate.Walk(func(key, value string) bool {
got = append(got, []string{key, value})
return len(got) < tc.num
})
assert.Equal(t, tc.expected, got)
})
}
}
var insertTS = TraceState{list: []member{
{Key: "key1", Value: "val1"},
{Key: "key2", Value: "val2"},