Girish Gowdra | 631ef3d | 2020-06-15 10:45:52 -0700 | [diff] [blame] | 1 | // 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 | |
| 15 | package jaeger |
| 16 | |
| 17 | import ( |
| 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. |
| 34 | type 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. |
| 47 | type 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 |
| 55 | type 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 |
| 63 | func 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 |
| 77 | func 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 |
| 95 | type BinaryPropagator struct { |
| 96 | tracer *Tracer |
| 97 | buffers sync.Pool |
| 98 | } |
| 99 | |
| 100 | // NewBinaryPropagator creates a combined Injector and Extractor for Binary format |
| 101 | func 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 |
| 109 | func (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 |
| 131 | func (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 |
| 177 | func (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 | |
khenaidoo | 106c61a | 2021-08-11 18:05:46 -0400 | [diff] [blame] | 218 | // W3C limits https://github.com/w3c/baggage/blob/master/baggage/HTTP_HEADER_FORMAT.md#limits |
| 219 | const ( |
| 220 | maxBinaryBaggage = 180 |
| 221 | maxBinaryNameValueLen = 4096 |
| 222 | ) |
| 223 | |
Girish Gowdra | 631ef3d | 2020-06-15 10:45:52 -0700 | [diff] [blame] | 224 | // Extract implements Extractor of BinaryPropagator |
| 225 | func (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 | } |
khenaidoo | 106c61a | 2021-08-11 18:05:46 -0400 | [diff] [blame] | 254 | if numBaggage > maxBinaryBaggage { |
| 255 | return emptyContext, opentracing.ErrSpanContextCorrupted |
| 256 | } |
Girish Gowdra | 631ef3d | 2020-06-15 10:45:52 -0700 | [diff] [blame] | 257 | 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 | } |
khenaidoo | 106c61a | 2021-08-11 18:05:46 -0400 | [diff] [blame] | 277 | if keyLen+valLen > maxBinaryNameValueLen { |
| 278 | return emptyContext, opentracing.ErrSpanContextCorrupted |
| 279 | } |
Girish Gowdra | 631ef3d | 2020-06-15 10:45:52 -0700 | [diff] [blame] | 280 | 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" } |
| 297 | func (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 { |
khenaidoo | 106c61a | 2021-08-11 18:05:46 -0400 | [diff] [blame] | 307 | baggage[strings.TrimSpace(kv[0])] = kv[1] |
Girish Gowdra | 631ef3d | 2020-06-15 10:45:52 -0700 | [diff] [blame] | 308 | } 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 |
| 317 | func (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 | |
| 322 | func (p *TextMapPropagator) removeBaggageKeyPrefix(key string) string { |
| 323 | // TODO decodeBaggageHeaderKey add caching and escaping |
| 324 | return key[len(p.headerKeys.TraceBaggageHeaderPrefix):] |
| 325 | } |