Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
86 changes: 77 additions & 9 deletions propagators/aws/xray/propagator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
// SPDX-License-Identifier: Apache-2.0

package xray // import "go.opentelemetry.io/contrib/propagators/aws/xray"

import (
"context"
"errors"
"strconv"
"strings"

"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)
Expand All @@ -16,9 +17,11 @@ const (
traceHeaderKey = "X-Amzn-Trace-Id"
traceHeaderDelimiter = ";"
kvDelimiter = "="
lineageDelimiter = ":"
traceIDKey = "Root"
sampleFlagKey = "Sampled"
parentIDKey = "Parent"
lineageKey = "Lineage"
traceIDVersion = "1"
traceIDDelimiter = "-"
isSampled = "1"
Expand All @@ -31,6 +34,14 @@ const (
traceIDDelimitterIndex2 = 10
traceIDFirstPartLength = 8
sampledFlagLength = 1

lineageMaxLength = 18
lineageMinLength = 12
lineageHashLength = 8
lineageMaxCounter1 = 32767
lineageMaxCounter2 = 255
lineageMinCounter = 0
invalidLineage = "-1:11111111:0"
)

var (
Expand Down Expand Up @@ -66,34 +77,45 @@ func (xray Propagator) Inject(ctx context.Context, carrier propagation.TextMapCa
if sc.TraceFlags() == traceFlagSampled {
samplingFlag = isSampled
}

headers := []string{
traceIDKey, kvDelimiter, xrayTraceID, traceHeaderDelimiter, parentIDKey,
kvDelimiter, parentID.String(), traceHeaderDelimiter, sampleFlagKey, kvDelimiter, samplingFlag,
}

carrier.Set(traceHeaderKey, strings.Join(headers, ""))
contextBaggage := baggage.FromContext(ctx)
lineage := contextBaggage.Member(lineageKey)

if lineage.Key() != "" {
headers = append(headers, traceHeaderDelimiter, lineageKey, kvDelimiter, lineage.Value())
}

carrier.Set(traceHeaderKey, strings.Join(headers, "")[:256])
}

// Extract gets a context from the carrier if it contains AWS X-Ray headers.
func (xray Propagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context {
// extract tracing information
if header := carrier.Get(traceHeaderKey); header != "" {
sc, err := extract(header)
newContext, sc, err := extract(ctx, header)
if err == nil && sc.IsValid() {
return trace.ContextWithRemoteSpanContext(ctx, sc)
return trace.ContextWithRemoteSpanContext(newContext, sc)
}
}
return ctx
}

// extract extracts Span Context from context.
func extract(headerVal string) (trace.SpanContext, error) {
func extract(ctx context.Context, headerVal string) (context.Context, trace.SpanContext, error) {
var (
scc = trace.SpanContextConfig{}
err error
delimiterIndex int
part string
)

currBaggage := baggage.FromContext(ctx)

pos := 0
for pos < len(headerVal) {
delimiterIndex = indexOf(headerVal, traceHeaderDelimiter, pos)
Expand All @@ -107,26 +129,38 @@ func extract(headerVal string) (trace.SpanContext, error) {
}
equalsIndex := strings.Index(part, kvDelimiter)
if equalsIndex < 0 {
return empty, errInvalidTraceHeader
return ctx, empty, errInvalidTraceHeader
}
value := part[equalsIndex+1:]
if strings.HasPrefix(part, traceIDKey) {
scc.TraceID, err = parseTraceID(value)
if err != nil {
return empty, err
return ctx, empty, err
}
} else if strings.HasPrefix(part, parentIDKey) {
// extract parentId
scc.SpanID, err = trace.SpanIDFromHex(value)
if err != nil {
return empty, errInvalidSpanIDLength
return ctx, empty, errInvalidSpanIDLength
}
} else if strings.HasPrefix(part, sampleFlagKey) {
// extract traceflag
scc.TraceFlags = parseTraceFlag(value)
} else if strings.HasPrefix(part, lineageKey) {
// extract lineage
lineageHeader := parseLineageHeader(value)
if isValidLineage(lineageHeader) {
lineageBaggage, _ := baggage.NewMember(lineageKey, lineageHeader)
currBaggage, _ = currBaggage.SetMember(lineageBaggage)
}
}
}
return trace.NewSpanContext(scc), nil

if currBaggage.Len() > 0 {
ctx = baggage.ContextWithBaggage(ctx, currBaggage)
}

return ctx, trace.NewSpanContext(scc), nil
}

// indexOf returns position of the first occurrence of a substr in str starting at pos index.
Expand Down Expand Up @@ -167,6 +201,40 @@ func parseTraceFlag(xraySampledFlag string) trace.TraceFlags {
return trace.FlagsSampled
}

func parseLineageHeader(xrayLineageHeader string) string {
numOfDelimiters := strings.Count(xrayLineageHeader, lineageDelimiter)
if len(xrayLineageHeader) < lineageMinLength ||
len(xrayLineageHeader) > lineageMaxLength ||
numOfDelimiters != 2 {
return invalidLineage
}

return xrayLineageHeader
}

func isValidLineage(lineage string) bool {
split := strings.Split(lineage, lineageDelimiter)
hash := split[1]
counter1 := parseIntWithBase(split[0], 10)
counter2 := parseIntWithBase(split[2], 10)

isHashValid := len(hash) == lineageHashLength && parseIntWithBase(hash, 16) != -1
isValidCounter2 := counter2 <= lineageMaxCounter2 && counter2 >= lineageMinCounter
isValidCounter1 := counter1 <= lineageMaxCounter1 && counter1 >= lineageMinCounter

return isHashValid && isValidCounter1 && isValidCounter2
}

func parseIntWithBase(s string, base int) int64 {
val, err := strconv.ParseInt(s, base, 64)

if err != nil {
return -1
}

return val
}

// Fields returns list of fields used by HTTPTextFormat.
func (xray Propagator) Fields() []string {
return []string{traceHeaderKey}
Expand Down
146 changes: 145 additions & 1 deletion propagators/aws/xray/propagator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/propagation"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -76,7 +77,7 @@ func TestAwsXrayExtract(t *testing.T) {
test.parentSpanID, traceHeaderDelimiter, sampleFlagKey, kvDelimiter, test.samplingFlag,
}, "")

sc, err := extract(headerVal)
_, sc, err := extract(context.Background(), headerVal)

info := []interface{}{
"trace ID: %q, parent span ID: %q, sampling flag: %q",
Expand All @@ -93,6 +94,149 @@ func TestAwsXrayExtract(t *testing.T) {
}
}

func TestAwsXrayExtractWithLineage(t *testing.T) {
testData := []struct {
lineage string

expectedBaggage map[string]string
}{
{
lineage: "32767:e65a2c4d:255",
expectedBaggage: map[string]string{
"Lineage": "32767:e65a2c4d:255",
},
},
{
lineage: "32767:e65a2c4d:255",
expectedBaggage: map[string]string{
"Lineage": "32767:e65a2c4d:255",
"cat": "bark",
"dog": "meow",
},
},
{
lineage: "1::",
expectedBaggage: map[string]string{},
},
{
lineage: "1",
expectedBaggage: map[string]string{},
},
{
lineage: "",
expectedBaggage: map[string]string{},
},
{
lineage: ":",
expectedBaggage: map[string]string{},
},
{
lineage: "::",
expectedBaggage: map[string]string{},
},
{
lineage: "1:badc0de:13",
expectedBaggage: map[string]string{},
},
{
lineage: ":fbadc0de:13",
expectedBaggage: map[string]string{},
},
{
lineage: "1:fbadc0de:",
expectedBaggage: map[string]string{},
},
{
lineage: "1::1",
expectedBaggage: map[string]string{},
},
{
lineage: "65535:fbadc0de:255",
expectedBaggage: map[string]string{},
},
{
lineage: "-213:e65a2c4d:255",
expectedBaggage: map[string]string{},
},
{
lineage: "213:e65a2c4d:-22",
expectedBaggage: map[string]string{},
},
}

p := Propagator{}

for _, test := range testData {
carrier := make(map[string]string)
members := []baggage.Member{}
expectedSpanContext := trace.NewSpanContext(trace.SpanContextConfig{
TraceID: traceID,
SpanID: parentSpanID,
TraceFlags: trace.TraceFlags(0x00),
Remote: true,
})

for key, value := range test.expectedBaggage {
member, _ := baggage.NewMember(key, value)
members = append(members, member)
}

expectedBaggage, _ := baggage.New(members...)
carrier[traceHeaderKey] = "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=0;Lineage=" + test.lineage

ctx := baggage.ContextWithBaggage(context.Background(), expectedBaggage)

if len(test.expectedBaggage) == 0 {
ctx = context.Background()
}

actualContext := p.Extract(ctx, propagation.MapCarrier(carrier))
spanContext := trace.SpanContextFromContext(actualContext)

assert.Equal(t, baggage.FromContext(actualContext), expectedBaggage)
assert.Equal(t, spanContext, expectedSpanContext)
}
}

func TestAwsXrayInjectWithLineage(t *testing.T) {
testData := []struct {
expectedBaggage map[string]string
}{
{
expectedBaggage: map[string]string{
"Lineage": "32767:e65a2c4d:255",
"cat": "bark",
"dog": "meow",
},
},
{
expectedBaggage: map[string]string{
"Lineage": "32767:e65a2c4d:255",
},
},
}

p := Propagator{}

for _, test := range testData {
carrier := make(map[string]string)
members := []baggage.Member{}

for key, value := range test.expectedBaggage {
member, _ := baggage.NewMember(key, value)
members = append(members, member)
}

expectedBaggage, _ := baggage.New(members...)

carrier[traceHeaderKey] = "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=0;Lineage=32767:e65a2c4d:255"

p.Inject(baggage.ContextWithBaggage(context.Background(), expectedBaggage), propagation.MapCarrier(carrier))

assert.Equal(t, carrier[traceHeaderKey], "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=0;Lineage=32767:e65a2c4d:255")
}
}

func BenchmarkPropagatorExtract(b *testing.B) {
propagator := Propagator{}

Expand Down