Skip to content
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

Add TraceState to SpanContext in API #1340

Merged
merged 15 commits into from
Dec 21, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- A `TextMapPropagator` and associated `TextMapCarrier` are added to the `go.opentelemetry.io/otel/oteltest` package to test TextMap type propagators and their use. (#1259)
- `SpanContextFromContext` returns `SpanContext` from context. (#1255)
- Add an opencensus to opentelemetry tracing bridge. (#1305)
- `TraceState` has been added to `SpanContext`. (#1340)

### Changed

Expand Down
22 changes: 11 additions & 11 deletions exporters/otlp/internal/transform/span.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,17 @@ func span(sd *export.SpanData) *tracepb.Span {
}

s := &tracepb.Span{
TraceId: sd.SpanContext.TraceID[:],
SpanId: sd.SpanContext.SpanID[:],
Status: status(sd.StatusCode, sd.StatusMessage),
StartTimeUnixNano: uint64(sd.StartTime.UnixNano()),
EndTimeUnixNano: uint64(sd.EndTime.UnixNano()),
Links: links(sd.Links),
Kind: spanKind(sd.SpanKind),
Name: sd.Name,
Attributes: Attributes(sd.Attributes),
Events: spanEvents(sd.MessageEvents),
// TODO (rghetia): Add Tracestate: when supported.
TraceId: sd.SpanContext.TraceID[:],
SpanId: sd.SpanContext.SpanID[:],
TraceState: sd.SpanContext.TraceState.String(),
Status: status(sd.StatusCode, sd.StatusMessage),
StartTimeUnixNano: uint64(sd.StartTime.UnixNano()),
EndTimeUnixNano: uint64(sd.EndTime.UnixNano()),
Links: links(sd.Links),
Kind: spanKind(sd.SpanKind),
Name: sd.Name,
Attributes: Attributes(sd.Attributes),
Events: spanEvents(sd.MessageEvents),
DroppedAttributesCount: uint32(sd.DroppedAttributeCount),
DroppedEventsCount: uint32(sd.DroppedMessageEventCount),
DroppedLinksCount: uint32(sd.DroppedLinkCount),
Expand Down
7 changes: 5 additions & 2 deletions exporters/otlp/internal/transform/span_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,12 @@ func TestSpanData(t *testing.T) {
// March 31, 2020 5:01:26 1234nanos (UTC)
startTime := time.Unix(1585674086, 1234)
endTime := startTime.Add(10 * time.Second)
traceState, _ := trace.TraceStateFromKeyValues(label.String("key1", "val1"), label.String("key2", "val2"))
spanData := &export.SpanData{
SpanContext: trace.SpanContext{
TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
SpanID: trace.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
SpanID: trace.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
TraceState: traceState,
},
SpanKind: trace.SpanKindServer,
ParentSpanID: trace.SpanID{0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8},
Expand Down Expand Up @@ -266,6 +268,7 @@ func TestSpanData(t *testing.T) {
TraceId: []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
SpanId: []byte{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
ParentSpanId: []byte{0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8},
TraceState: "key1=val1,key2=val2",
Name: spanData.Name,
Kind: tracepb.Span_SPAN_KIND_SERVER,
StartTimeUnixNano: uint64(startTime.UnixNano()),
Expand Down
13 changes: 10 additions & 3 deletions exporters/stdout/trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,16 @@ func TestExporter_ExportSpan(t *testing.T) {
now := time.Now()
traceID, _ := trace.TraceIDFromHex("0102030405060708090a0b0c0d0e0f10")
spanID, _ := trace.SpanIDFromHex("0102030405060708")
traceState, _ := trace.TraceStateFromKeyValues(label.String("key", "val"))
keyValue := "value"
doubleValue := 123.456
resource := resource.NewWithAttributes(label.String("rk1", "rv11"))

testSpan := &export.SpanData{
SpanContext: trace.SpanContext{
TraceID: traceID,
SpanID: spanID,
TraceID: traceID,
SpanID: spanID,
TraceState: traceState,
},
Name: "/foo",
StartTime: now,
Expand All @@ -76,7 +78,12 @@ func TestExporter_ExportSpan(t *testing.T) {
got := b.String()
expectedOutput := `[{"SpanContext":{` +
`"TraceID":"0102030405060708090a0b0c0d0e0f10",` +
`"SpanID":"0102030405060708","TraceFlags":0},` +
`"SpanID":"0102030405060708","TraceFlags":0,` +
`"TraceState":[` +
`{` +
`"Key":"key",` +
`"Value":{"Type":"STRING","Value":"val"}` +
`}]},` +
`"ParentSpanID":"0000000000000000",` +
`"SpanKind":1,` +
`"Name":"/foo",` +
Expand Down
13 changes: 8 additions & 5 deletions sdk/trace/trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,11 +314,15 @@ func TestStartSpanWithParent(t *testing.T) {
t.Error(err)
}

ts, err := trace.TraceStateFromKeyValues(label.String("k", "v"))
if err != nil {
t.Error(err)
}
sc2 := trace.SpanContext{
TraceID: tid,
SpanID: sid,
TraceFlags: 0x1,
//Tracestate: testTracestate,
TraceState: ts,
}
_, s3 := tr.Start(trace.ContextWithRemoteSpanContext(ctx, sc2), "span3-sampled-parent2")
if err := checkChild(sc2, s3); err != nil {
Expand Down Expand Up @@ -767,10 +771,9 @@ func checkChild(p trace.SpanContext, apiSpan trace.Span) error {
if got, want := s.spanContext.TraceFlags, p.TraceFlags; got != want {
return fmt.Errorf("got child trace options %d, want %d", got, want)
}
// TODO [rgheita] : Fix tracestate test
//if got, want := c.spanContext.Tracestate, p.Tracestate; got != want {
// return fmt.Errorf("got child tracestate %v, want %v", got, want)
//}
if got, want := s.spanContext.TraceState, p.TraceState; got != want {
return fmt.Errorf("got child tracestate %v, want %v", got, want)
}
return nil
}

Expand Down
159 changes: 159 additions & 0 deletions trace/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"context"
"encoding/hex"
"encoding/json"
"regexp"
"strings"

"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/label"
Expand All @@ -42,6 +44,18 @@ const (

errInvalidSpanIDLength errorConst = "hex encoded span-id must have length equals to 16"
errNilSpanID errorConst = "span-id can't be all zero"

// based on the W3C Trace Context specification, see https://www.w3.org/TR/trace-context-1/#tracestate-header
traceStateKeyFormat = `[a-z][_0-9a-z\-\*\/]{0,255}`
traceStateKeyFormatWithMultiTenantVendor = `[a-z][_0-9a-z\-\*\/]{0,240}@[a-z][_0-9a-z\-\*\/]{0,13}`
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
traceStateValueFormat = `[\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e]`

traceStateMaxListMembers = 32

errInvalidTraceStateKeyValue errorConst = "provided key or value is not valid according to the" +
"W3C Trace Context specification"
errInvalidTraceStateMembersNumber errorConst = "trace state would exceed the maximum limit of members (32)"
errInvalidTraceStateDuplicate errorConst = "trace state key/value pairs with duplicate keys provided"
)

type errorConst string
Expand Down Expand Up @@ -157,11 +171,156 @@ func decodeHex(h string, b []byte) error {
return nil
}

// TraceState provides additional vendor-specific trace identification information
// across different distributed tracing systems. It represents an immutable list consisting
// of key/value pairs. There can be a maximum of 32 entries in the list.
//
// Key and value of each list member must be valid according to the W3C Trace Context specification
// (see https://www.w3.org/TR/trace-context-1/#key and https://www.w3.org/TR/trace-context-1/#value
// respectively).
//
// Trace state must be valid according to the W3C Trace Context specification at all times. All
// mutating operations validate their input and, in case of valid parameters, return a new TraceState.
type TraceState struct { //nolint:golint
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
kvs []label.KeyValue
}

var _ json.Marshaler = &TraceState{}
var keyFormatRegExp = regexp.MustCompile(
`^((` + traceStateKeyFormat + `)|(` + traceStateKeyFormatWithMultiTenantVendor + `))$`,
)
var valueFormatRegExp = regexp.MustCompile(`^(` + traceStateValueFormat + `)$`)

// MarshalJSON implements a custom marshal function to encode trace state.
func (ts *TraceState) MarshalJSON() ([]byte, error) {
return json.Marshal(ts.kvs)
}

// String returns trace state as a string valid according to the
// W3C Trace Context specification.
func (ts *TraceState) String() string {
if ts == nil {
return ""
}

var sb strings.Builder

for i, kv := range ts.kvs {
sb.WriteString((string)(kv.Key))
sb.WriteByte('=')
sb.WriteString(kv.Value.Emit())

if i != len(ts.kvs)-1 {
sb.WriteByte(',')
}
}

return sb.String()
}

// Get returns a value for given key from the trace state.
// If no key is found or provided key is invalid, returns an empty value.
func (ts *TraceState) Get(key label.Key) label.Value {
if ts == nil || !isTraceStateKeyValid(key) {
return label.Value{}
}

for _, kv := range ts.kvs {
if kv.Key == key {
return kv.Value
}
}

return label.Value{}
}

// Insert adds a new key/value, if one doesn't exists; otherwise updates the existing entry.
// The new or updated entry is always inserted at the beginning of the TraceState, i.e.
// on the left side, as per the W3C Trace Context specification requirement.
func (ts *TraceState) Insert(entry label.KeyValue) (TraceState, error) {
if !isTraceStateKeyValueValid(entry) {
return TraceState{}, errInvalidTraceStateKeyValue
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
}

if ts == nil {
return TraceState{[]label.KeyValue{entry}}, nil
}

ckvs := ts.copyKVsAndDeleteEntry(entry.Key)
if len(ckvs)+1 > traceStateMaxListMembers {
return TraceState{}, errInvalidTraceStateMembersNumber
}

ckvs = append(ckvs, label.KeyValue{})
copy(ckvs[1:], ckvs)
ckvs[0] = entry

return TraceState{ckvs}, nil
}

// Delete removes specified entry from the trace state.
func (ts *TraceState) Delete(key label.Key) (TraceState, error) {
if ts == nil {
return TraceState{}, nil
}

if !isTraceStateKeyValid(key) {
return TraceState{}, errInvalidTraceStateKeyValue
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
}

return TraceState{ts.copyKVsAndDeleteEntry(key)}, nil
}

func (ts *TraceState) copyKVsAndDeleteEntry(key label.Key) []label.KeyValue {
ckvs := make([]label.KeyValue, len(ts.kvs))
copy(ckvs, ts.kvs)
for i, kv := range ts.kvs {
if kv.Key == key {
ckvs = append(ckvs[:i], ckvs[i+1:]...)
break
}
}

return ckvs
}

// TraceStateFromKeyValues is a convenience method to create a new TraceState from
// provided key/value pairs.
func TraceStateFromKeyValues(kvs ...label.KeyValue) (*TraceState, error) { //nolint:golint
if len(kvs) > traceStateMaxListMembers {
return nil, errInvalidTraceStateMembersNumber
}

km := make(map[label.Key]bool)
for _, kv := range kvs {
if !isTraceStateKeyValueValid(kv) {
return nil, errInvalidTraceStateKeyValue
}
_, ok := km[kv.Key]
if ok {
return nil, errInvalidTraceStateDuplicate
}
km[kv.Key] = true
}

return &TraceState{kvs}, nil
}

func isTraceStateKeyValid(key label.Key) bool {
return keyFormatRegExp.MatchString(string(key))
}

func isTraceStateKeyValueValid(kv label.KeyValue) bool {
return isTraceStateKeyValid(kv.Key) &&
valueFormatRegExp.MatchString(kv.Value.Emit())
}

// SpanContext contains identifying trace information about a Span.
type SpanContext struct {
TraceID TraceID
SpanID SpanID
TraceFlags byte
TraceState *TraceState
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
}

// IsValid returns if the SpanContext is valid. A valid span context has a
Expand Down
Loading