blob: 42fd64b5882a2508d199695215650e154a88f0fb [file] [log] [blame]
Matteo Scandoloa4285862020-12-01 18:10:10 -08001// Copyright (c) 2017 Uber Technologies, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package jaeger
16
17import (
18 "bytes"
19 "encoding/binary"
20 "fmt"
21 "io"
22 "log"
23 "net/url"
24 "strings"
25 "sync"
26
27 opentracing "github.com/opentracing/opentracing-go"
28)
29
30// Injector is responsible for injecting SpanContext instances in a manner suitable
31// for propagation via a format-specific "carrier" object. Typically the
32// injection will take place across an RPC boundary, but message queues and
33// other IPC mechanisms are also reasonable places to use an Injector.
34type Injector interface {
35 // Inject takes `SpanContext` and injects it into `carrier`. The actual type
36 // of `carrier` depends on the `format` passed to `Tracer.Inject()`.
37 //
38 // Implementations may return opentracing.ErrInvalidCarrier or any other
39 // implementation-specific error if injection fails.
40 Inject(ctx SpanContext, carrier interface{}) error
41}
42
43// Extractor is responsible for extracting SpanContext instances from a
44// format-specific "carrier" object. Typically the extraction will take place
45// on the server side of an RPC boundary, but message queues and other IPC
46// mechanisms are also reasonable places to use an Extractor.
47type Extractor interface {
48 // Extract decodes a SpanContext instance from the given `carrier`,
49 // or (nil, opentracing.ErrSpanContextNotFound) if no context could
50 // be found in the `carrier`.
51 Extract(carrier interface{}) (SpanContext, error)
52}
53
54// TextMapPropagator is a combined Injector and Extractor for TextMap format
55type TextMapPropagator struct {
56 headerKeys *HeadersConfig
57 metrics Metrics
58 encodeValue func(string) string
59 decodeValue func(string) string
60}
61
62// NewTextMapPropagator creates a combined Injector and Extractor for TextMap format
63func NewTextMapPropagator(headerKeys *HeadersConfig, metrics Metrics) *TextMapPropagator {
64 return &TextMapPropagator{
65 headerKeys: headerKeys,
66 metrics: metrics,
67 encodeValue: func(val string) string {
68 return val
69 },
70 decodeValue: func(val string) string {
71 return val
72 },
73 }
74}
75
76// NewHTTPHeaderPropagator creates a combined Injector and Extractor for HTTPHeaders format
77func NewHTTPHeaderPropagator(headerKeys *HeadersConfig, metrics Metrics) *TextMapPropagator {
78 return &TextMapPropagator{
79 headerKeys: headerKeys,
80 metrics: metrics,
81 encodeValue: func(val string) string {
82 return url.QueryEscape(val)
83 },
84 decodeValue: func(val string) string {
85 // ignore decoding errors, cannot do anything about them
86 if v, err := url.QueryUnescape(val); err == nil {
87 return v
88 }
89 return val
90 },
91 }
92}
93
94// BinaryPropagator is a combined Injector and Extractor for Binary format
95type BinaryPropagator struct {
96 tracer *Tracer
97 buffers sync.Pool
98}
99
100// NewBinaryPropagator creates a combined Injector and Extractor for Binary format
101func NewBinaryPropagator(tracer *Tracer) *BinaryPropagator {
102 return &BinaryPropagator{
103 tracer: tracer,
104 buffers: sync.Pool{New: func() interface{} { return &bytes.Buffer{} }},
105 }
106}
107
108// Inject implements Injector of TextMapPropagator
109func (p *TextMapPropagator) Inject(
110 sc SpanContext,
111 abstractCarrier interface{},
112) error {
113 textMapWriter, ok := abstractCarrier.(opentracing.TextMapWriter)
114 if !ok {
115 return opentracing.ErrInvalidCarrier
116 }
117
118 // Do not encode the string with trace context to avoid accidental double-encoding
119 // if people are using opentracing < 0.10.0. Our colon-separated representation
120 // of the trace context is already safe for HTTP headers.
121 textMapWriter.Set(p.headerKeys.TraceContextHeaderName, sc.String())
122 for k, v := range sc.baggage {
123 safeKey := p.addBaggageKeyPrefix(k)
124 safeVal := p.encodeValue(v)
125 textMapWriter.Set(safeKey, safeVal)
126 }
127 return nil
128}
129
130// Extract implements Extractor of TextMapPropagator
131func (p *TextMapPropagator) Extract(abstractCarrier interface{}) (SpanContext, error) {
132 textMapReader, ok := abstractCarrier.(opentracing.TextMapReader)
133 if !ok {
134 return emptyContext, opentracing.ErrInvalidCarrier
135 }
136 var ctx SpanContext
137 var baggage map[string]string
138 err := textMapReader.ForeachKey(func(rawKey, value string) error {
139 key := strings.ToLower(rawKey) // TODO not necessary for plain TextMap
140 if key == p.headerKeys.TraceContextHeaderName {
141 var err error
142 safeVal := p.decodeValue(value)
143 if ctx, err = ContextFromString(safeVal); err != nil {
144 return err
145 }
146 } else if key == p.headerKeys.JaegerDebugHeader {
147 ctx.debugID = p.decodeValue(value)
148 } else if key == p.headerKeys.JaegerBaggageHeader {
149 if baggage == nil {
150 baggage = make(map[string]string)
151 }
152 for k, v := range p.parseCommaSeparatedMap(value) {
153 baggage[k] = v
154 }
155 } else if strings.HasPrefix(key, p.headerKeys.TraceBaggageHeaderPrefix) {
156 if baggage == nil {
157 baggage = make(map[string]string)
158 }
159 safeKey := p.removeBaggageKeyPrefix(key)
160 safeVal := p.decodeValue(value)
161 baggage[safeKey] = safeVal
162 }
163 return nil
164 })
165 if err != nil {
166 p.metrics.DecodingErrors.Inc(1)
167 return emptyContext, err
168 }
169 if !ctx.traceID.IsValid() && ctx.debugID == "" && len(baggage) == 0 {
170 return emptyContext, opentracing.ErrSpanContextNotFound
171 }
172 ctx.baggage = baggage
173 return ctx, nil
174}
175
176// Inject implements Injector of BinaryPropagator
177func (p *BinaryPropagator) Inject(
178 sc SpanContext,
179 abstractCarrier interface{},
180) error {
181 carrier, ok := abstractCarrier.(io.Writer)
182 if !ok {
183 return opentracing.ErrInvalidCarrier
184 }
185
186 // Handle the tracer context
187 if err := binary.Write(carrier, binary.BigEndian, sc.traceID); err != nil {
188 return err
189 }
190 if err := binary.Write(carrier, binary.BigEndian, sc.spanID); err != nil {
191 return err
192 }
193 if err := binary.Write(carrier, binary.BigEndian, sc.parentID); err != nil {
194 return err
195 }
196 if err := binary.Write(carrier, binary.BigEndian, sc.samplingState.flags()); err != nil {
197 return err
198 }
199
200 // Handle the baggage items
201 if err := binary.Write(carrier, binary.BigEndian, int32(len(sc.baggage))); err != nil {
202 return err
203 }
204 for k, v := range sc.baggage {
205 if err := binary.Write(carrier, binary.BigEndian, int32(len(k))); err != nil {
206 return err
207 }
208 io.WriteString(carrier, k)
209 if err := binary.Write(carrier, binary.BigEndian, int32(len(v))); err != nil {
210 return err
211 }
212 io.WriteString(carrier, v)
213 }
214
215 return nil
216}
217
218// Extract implements Extractor of BinaryPropagator
219func (p *BinaryPropagator) Extract(abstractCarrier interface{}) (SpanContext, error) {
220 carrier, ok := abstractCarrier.(io.Reader)
221 if !ok {
222 return emptyContext, opentracing.ErrInvalidCarrier
223 }
224 var ctx SpanContext
225 ctx.samplingState = &samplingState{}
226
227 if err := binary.Read(carrier, binary.BigEndian, &ctx.traceID); err != nil {
228 return emptyContext, opentracing.ErrSpanContextCorrupted
229 }
230 if err := binary.Read(carrier, binary.BigEndian, &ctx.spanID); err != nil {
231 return emptyContext, opentracing.ErrSpanContextCorrupted
232 }
233 if err := binary.Read(carrier, binary.BigEndian, &ctx.parentID); err != nil {
234 return emptyContext, opentracing.ErrSpanContextCorrupted
235 }
236
237 var flags byte
238 if err := binary.Read(carrier, binary.BigEndian, &flags); err != nil {
239 return emptyContext, opentracing.ErrSpanContextCorrupted
240 }
241 ctx.samplingState.setFlags(flags)
242
243 // Handle the baggage items
244 var numBaggage int32
245 if err := binary.Read(carrier, binary.BigEndian, &numBaggage); err != nil {
246 return emptyContext, opentracing.ErrSpanContextCorrupted
247 }
248 if iNumBaggage := int(numBaggage); iNumBaggage > 0 {
249 ctx.baggage = make(map[string]string, iNumBaggage)
250 buf := p.buffers.Get().(*bytes.Buffer)
251 defer p.buffers.Put(buf)
252
253 var keyLen, valLen int32
254 for i := 0; i < iNumBaggage; i++ {
255 if err := binary.Read(carrier, binary.BigEndian, &keyLen); err != nil {
256 return emptyContext, opentracing.ErrSpanContextCorrupted
257 }
258 buf.Reset()
259 buf.Grow(int(keyLen))
260 if n, err := io.CopyN(buf, carrier, int64(keyLen)); err != nil || int32(n) != keyLen {
261 return emptyContext, opentracing.ErrSpanContextCorrupted
262 }
263 key := buf.String()
264
265 if err := binary.Read(carrier, binary.BigEndian, &valLen); err != nil {
266 return emptyContext, opentracing.ErrSpanContextCorrupted
267 }
268 buf.Reset()
269 buf.Grow(int(valLen))
270 if n, err := io.CopyN(buf, carrier, int64(valLen)); err != nil || int32(n) != valLen {
271 return emptyContext, opentracing.ErrSpanContextCorrupted
272 }
273 ctx.baggage[key] = buf.String()
274 }
275 }
276
277 return ctx, nil
278}
279
280// Converts a comma separated key value pair list into a map
281// e.g. key1=value1, key2=value2, key3 = value3
282// is converted to map[string]string { "key1" : "value1",
283// "key2" : "value2",
284// "key3" : "value3" }
285func (p *TextMapPropagator) parseCommaSeparatedMap(value string) map[string]string {
286 baggage := make(map[string]string)
287 value, err := url.QueryUnescape(value)
288 if err != nil {
289 log.Printf("Unable to unescape %s, %v", value, err)
290 return baggage
291 }
292 for _, kvpair := range strings.Split(value, ",") {
293 kv := strings.Split(strings.TrimSpace(kvpair), "=")
294 if len(kv) == 2 {
295 baggage[kv[0]] = kv[1]
296 } else {
297 log.Printf("Malformed value passed in for %s", p.headerKeys.JaegerBaggageHeader)
298 }
299 }
300 return baggage
301}
302
303// Converts a baggage item key into an http header format,
304// by prepending TraceBaggageHeaderPrefix and encoding the key string
305func (p *TextMapPropagator) addBaggageKeyPrefix(key string) string {
306 // TODO encodeBaggageKeyAsHeader add caching and escaping
307 return fmt.Sprintf("%v%v", p.headerKeys.TraceBaggageHeaderPrefix, key)
308}
309
310func (p *TextMapPropagator) removeBaggageKeyPrefix(key string) string {
311 // TODO decodeBaggageHeaderKey add caching and escaping
312 return key[len(p.headerKeys.TraceBaggageHeaderPrefix):]
313}