-
Notifications
You must be signed in to change notification settings - Fork 1
/
uber.go
154 lines (131 loc) · 4.68 KB
/
uber.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// Copyright (c) 2022 Arista Networks, Inc. All rights reserved.
// Arista Networks, Inc. Confidential and Proprietary.
// Some portions derived from the TraceContext propagator
// Copyright The OpenTelemetry Authors (Apache License, Version 2.0)
package otel
import (
"context"
"encoding/hex"
"fmt"
"regexp"
"strconv"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)
const (
uberHeader = "uber-trace-id"
)
// UberTraceContext is a propagator that supports the "Jaeger native propagation
// format", better known as the "uber-trace-id" header. See:
// https://www.jaegertracing.io/docs/1.40/client-libraries/#propagation-format
//
// UberTraceContext will propagate the uber-trace-id header to guarantee traces
// employing this type of header are not broken. It is up to the users of this
// propagator to choose if they want to participate in a trace by modifying the
// uber-trace-id header and relevant parts of the uber-trace-id header
// containing their proprietary information.
//
// UberTraceContext operates on the came principle as the upstream TraceContext
// propagator, which injects and extracts the "W3C trace context format", better
// known as the "traceparent" header.
//
// When a CompositeTextMapPropagator combines TraceContext and UberTraceContext
// propagators, SpanContexts will be propagated forward as both types of header,
// and both inbound header types will be extractable into a local SpanContext
// (with the later-defined propagator's header taking overriding precedence).
type UberTraceContext struct{}
var _ propagation.TextMapPropagator = UberTraceContext{}
var uberHdrRegExp = regexp.MustCompile("^(?P<traceID>[0-9a-f]{1,32}):(?P<spanID>[0-9a-f]{1,16}):(?P<parentSpanID>[0-9a-f]{1,16}):(?P<traceFlags>[0-9a-f]{1,2})(?:-.*)?$")
// Inject sets uber-trace-id from the Context into the carrier.
func (tc UberTraceContext) Inject(ctx context.Context, carrier propagation.TextMapCarrier) {
sc := trace.SpanContextFromContext(ctx)
if !sc.IsValid() {
return
}
// Clear all flags other than the trace-context supported sampling bit.
// The following flags would otherwise be valid for Jaeger:
// flagSampled = 1
// flagDebug = 2
// flagFirehose = 8
flags := sc.TraceFlags() & trace.FlagsSampled
h := fmt.Sprintf("%s:%s:%016x:%s",
sc.TraceID(), // trace-id
sc.SpanID(), // span-id
0, // parent-span-id (deprecated in spec)
flags, // flags
)
carrier.Set(uberHeader, h)
}
// Extract reads uber-trace-id from the carrier into a returned Context.
//
// The returned Context will be a copy of ctx and contain the extracted
// uber-trace-id as the remote SpanContext. If the extracted uber-trace-id
// is invalid, the passed ctx will be returned directly instead.
func (tc UberTraceContext) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context {
sc := tc.extract(carrier)
if !sc.IsValid() {
return ctx
}
return trace.ContextWithRemoteSpanContext(ctx, sc)
}
func (tc UberTraceContext) extract(carrier propagation.TextMapCarrier) trace.SpanContext {
h := carrier.Get(uberHeader)
if h == "" {
return trace.SpanContext{}
}
matches := uberHdrRegExp.FindStringSubmatch(h)
if len(matches) < 5 { // four subgroups plus the overall match
return trace.SpanContext{}
}
var scc trace.SpanContextConfig
traceID, err := decodeHexID(matches[1], 16) // 128 bits
if err != nil {
return trace.SpanContext{}
}
copy(scc.TraceID[:], traceID[:16])
spanID, err := decodeHexID(matches[2], 8) // 64 bits
if err != nil {
return trace.SpanContext{}
}
copy(scc.SpanID[:], spanID[:8])
flags, err := strconv.ParseInt(matches[4], 16, 64)
if err != nil {
return trace.SpanContext{}
}
// Clear all flags other than the trace-context supported sampling bit.
scc.TraceFlags = trace.TraceFlags(flags) & trace.FlagsSampled
sc := trace.NewSpanContext(scc)
if !sc.IsValid() {
return trace.SpanContext{}
}
return sc
}
// Fields returns the keys whose values are set with Inject.
func (tc UberTraceContext) Fields() []string {
return []string{uberHeader}
}
// The spec states that receivers must accept trace and span ID's
// shorter than the expected field size, and zero-pad them on the left;
// and states that a zero value is not a valid trace or span ID.
func decodeHexID(s string, size int) ([]byte, error) {
var allZeroes bool = true
data, err := hex.DecodeString(s)
if err != nil {
return nil, err
}
if size < len(data) {
return nil, fmt.Errorf("string too large for size")
}
pad := size - len(data)
ret := make([]byte, size, size)
for i, x := range data {
ret[i+pad] = x
if x != 0 {
allZeroes = false
}
}
if allZeroes {
return nil, fmt.Errorf("zero value is invalid")
}
return ret, nil
}