blob: e06459b98f3b42cac779611b471c493e238c70c4 [file] [log] [blame]
khenaidooc6c7bda2020-06-17 17:20:18 -04001// 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
khenaidood948f772021-08-11 17:49:24 -0400218// W3C limits https://github.com/w3c/baggage/blob/master/baggage/HTTP_HEADER_FORMAT.md#limits
219const (
220 maxBinaryBaggage = 180
221 maxBinaryNameValueLen = 4096
222)
223
khenaidooc6c7bda2020-06-17 17:20:18 -0400224// Extract implements Extractor of BinaryPropagator
225func (p *BinaryPropagator) Extract(abstractCarrier interface{}) (SpanContext, error) {
226 carrier, ok := abstractCarrier.(io.Reader)
227 if !ok {
228 return emptyContext, opentracing.ErrInvalidCarrier
229 }
230 var ctx SpanContext
231 ctx.samplingState = &samplingState{}
232
233 if err := binary.Read(carrier, binary.BigEndian, &ctx.traceID); err != nil {
234 return emptyContext, opentracing.ErrSpanContextCorrupted
235 }
236 if err := binary.Read(carrier, binary.BigEndian, &ctx.spanID); err != nil {
237 return emptyContext, opentracing.ErrSpanContextCorrupted
238 }
239 if err := binary.Read(carrier, binary.BigEndian, &ctx.parentID); err != nil {
240 return emptyContext, opentracing.ErrSpanContextCorrupted
241 }
242
243 var flags byte
244 if err := binary.Read(carrier, binary.BigEndian, &flags); err != nil {
245 return emptyContext, opentracing.ErrSpanContextCorrupted
246 }
247 ctx.samplingState.setFlags(flags)
248
249 // Handle the baggage items
250 var numBaggage int32
251 if err := binary.Read(carrier, binary.BigEndian, &numBaggage); err != nil {
252 return emptyContext, opentracing.ErrSpanContextCorrupted
253 }
khenaidood948f772021-08-11 17:49:24 -0400254 if numBaggage > maxBinaryBaggage {
255 return emptyContext, opentracing.ErrSpanContextCorrupted
256 }
khenaidooc6c7bda2020-06-17 17:20:18 -0400257 if iNumBaggage := int(numBaggage); iNumBaggage > 0 {
258 ctx.baggage = make(map[string]string, iNumBaggage)
259 buf := p.buffers.Get().(*bytes.Buffer)
260 defer p.buffers.Put(buf)
261
262 var keyLen, valLen int32
263 for i := 0; i < iNumBaggage; i++ {
264 if err := binary.Read(carrier, binary.BigEndian, &keyLen); err != nil {
265 return emptyContext, opentracing.ErrSpanContextCorrupted
266 }
267 buf.Reset()
268 buf.Grow(int(keyLen))
269 if n, err := io.CopyN(buf, carrier, int64(keyLen)); err != nil || int32(n) != keyLen {
270 return emptyContext, opentracing.ErrSpanContextCorrupted
271 }
272 key := buf.String()
273
274 if err := binary.Read(carrier, binary.BigEndian, &valLen); err != nil {
275 return emptyContext, opentracing.ErrSpanContextCorrupted
276 }
khenaidood948f772021-08-11 17:49:24 -0400277 if keyLen+valLen > maxBinaryNameValueLen {
278 return emptyContext, opentracing.ErrSpanContextCorrupted
279 }
khenaidooc6c7bda2020-06-17 17:20:18 -0400280 buf.Reset()
281 buf.Grow(int(valLen))
282 if n, err := io.CopyN(buf, carrier, int64(valLen)); err != nil || int32(n) != valLen {
283 return emptyContext, opentracing.ErrSpanContextCorrupted
284 }
285 ctx.baggage[key] = buf.String()
286 }
287 }
288
289 return ctx, nil
290}
291
292// Converts a comma separated key value pair list into a map
293// e.g. key1=value1, key2=value2, key3 = value3
294// is converted to map[string]string { "key1" : "value1",
295// "key2" : "value2",
296// "key3" : "value3" }
297func (p *TextMapPropagator) parseCommaSeparatedMap(value string) map[string]string {
298 baggage := make(map[string]string)
299 value, err := url.QueryUnescape(value)
300 if err != nil {
301 log.Printf("Unable to unescape %s, %v", value, err)
302 return baggage
303 }
304 for _, kvpair := range strings.Split(value, ",") {
305 kv := strings.Split(strings.TrimSpace(kvpair), "=")
306 if len(kv) == 2 {
khenaidood948f772021-08-11 17:49:24 -0400307 baggage[strings.TrimSpace(kv[0])] = kv[1]
khenaidooc6c7bda2020-06-17 17:20:18 -0400308 } else {
309 log.Printf("Malformed value passed in for %s", p.headerKeys.JaegerBaggageHeader)
310 }
311 }
312 return baggage
313}
314
315// Converts a baggage item key into an http header format,
316// by prepending TraceBaggageHeaderPrefix and encoding the key string
317func (p *TextMapPropagator) addBaggageKeyPrefix(key string) string {
318 // TODO encodeBaggageKeyAsHeader add caching and escaping
319 return fmt.Sprintf("%v%v", p.headerKeys.TraceBaggageHeaderPrefix, key)
320}
321
322func (p *TextMapPropagator) removeBaggageKeyPrefix(key string) string {
323 // TODO decodeBaggageHeaderKey add caching and escaping
324 return key[len(p.headerKeys.TraceBaggageHeaderPrefix):]
325}