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 15package jaeger 16 17import ( 18 "errors" 19 "fmt" 20 "strconv" 21 "strings" 22 "sync" 23 24 "go.uber.org/atomic" 25) 26 27const ( 28 flagSampled = 1 29 flagDebug = 2 30 flagFirehose = 8 31) 32 33var ( 34 errEmptyTracerStateString = errors.New("Cannot convert empty string to tracer state") 35 errMalformedTracerStateString = errors.New("String does not match tracer state format") 36 37 emptyContext = SpanContext{} 38) 39 40// TraceID represents unique 128bit identifier of a trace 41type TraceID struct { 42 High, Low uint64 43} 44 45// SpanID represents unique 64bit identifier of a span 46type SpanID uint64 47 48// SpanContext represents propagated span identity and state 49type SpanContext struct { 50 // traceID represents globally unique ID of the trace. 51 // Usually generated as a random number. 52 traceID TraceID 53 54 // spanID represents span ID that must be unique within its trace, 55 // but does not have to be globally unique. 56 spanID SpanID 57 58 // parentID refers to the ID of the parent span. 59 // Should be 0 if the current span is a root span. 60 parentID SpanID 61 62 // Distributed Context baggage. The is a snapshot in time. 63 baggage map[string]string 64 65 // debugID can be set to some correlation ID when the context is being 66 // extracted from a TextMap carrier. 67 // 68 // See JaegerDebugHeader in constants.go 69 debugID string 70 71 // samplingState is shared across all spans 72 samplingState *samplingState 73 74 // remote indicates that span context represents a remote parent 75 remote bool 76} 77 78type samplingState struct { 79 // Span context's state flags that are propagated across processes. Only lower 8 bits are used. 80 // We use an int32 instead of byte to be able to use CAS operations. 81 stateFlags atomic.Int32 82 83 // When state is not final, sampling will be retried on other span write operations, 84 // like SetOperationName / SetTag, and the spans will remain writable. 85 final atomic.Bool 86 87 // localRootSpan stores the SpanID of the first span created in this process for a given trace. 88 localRootSpan SpanID 89 90 // extendedState allows samplers to keep intermediate state. 91 // The keys and values in this map are completely opaque: interface{} -> interface{}. 92 extendedState sync.Map 93} 94 95func (s *samplingState) isLocalRootSpan(id SpanID) bool { 96 return id == s.localRootSpan 97} 98 99func (s *samplingState) setFlag(newFlag int32) { 100 swapped := false 101 for !swapped { 102 old := s.stateFlags.Load() 103 swapped = s.stateFlags.CAS(old, old|newFlag) 104 } 105} 106 107func (s *samplingState) unsetFlag(newFlag int32) { 108 swapped := false 109 for !swapped { 110 old := s.stateFlags.Load() 111 swapped = s.stateFlags.CAS(old, old&^newFlag) 112 } 113} 114 115func (s *samplingState) setSampled() { 116 s.setFlag(flagSampled) 117} 118 119func (s *samplingState) unsetSampled() { 120 s.unsetFlag(flagSampled) 121} 122 123func (s *samplingState) setDebugAndSampled() { 124 s.setFlag(flagDebug | flagSampled) 125} 126 127func (s *samplingState) setFirehose() { 128 s.setFlag(flagFirehose) 129} 130 131func (s *samplingState) setFlags(flags byte) { 132 s.stateFlags.Store(int32(flags)) 133} 134 135func (s *samplingState) setFinal() { 136 s.final.Store(true) 137} 138 139func (s *samplingState) flags() byte { 140 return byte(s.stateFlags.Load()) 141} 142 143func (s *samplingState) isSampled() bool { 144 return s.stateFlags.Load()&flagSampled == flagSampled 145} 146 147func (s *samplingState) isDebug() bool { 148 return s.stateFlags.Load()&flagDebug == flagDebug 149} 150 151func (s *samplingState) isFirehose() bool { 152 return s.stateFlags.Load()&flagFirehose == flagFirehose 153} 154 155func (s *samplingState) isFinal() bool { 156 return s.final.Load() 157} 158 159func (s *samplingState) extendedStateForKey(key interface{}, initValue func() interface{}) interface{} { 160 if value, ok := s.extendedState.Load(key); ok { 161 return value 162 } 163 value := initValue() 164 value, _ = s.extendedState.LoadOrStore(key, value) 165 return value 166} 167 168// ForeachBaggageItem implements ForeachBaggageItem() of opentracing.SpanContext 169func (c SpanContext) ForeachBaggageItem(handler func(k, v string) bool) { 170 for k, v := range c.baggage { 171 if !handler(k, v) { 172 break 173 } 174 } 175} 176 177// IsSampled returns whether this trace was chosen for permanent storage 178// by the sampling mechanism of the tracer. 179func (c SpanContext) IsSampled() bool { 180 return c.samplingState.isSampled() 181} 182 183// IsDebug indicates whether sampling was explicitly requested by the service. 184func (c SpanContext) IsDebug() bool { 185 return c.samplingState.isDebug() 186} 187 188// IsSamplingFinalized indicates whether the sampling decision has been finalized. 189func (c SpanContext) IsSamplingFinalized() bool { 190 return c.samplingState.isFinal() 191} 192 193// IsFirehose indicates whether the firehose flag was set 194func (c SpanContext) IsFirehose() bool { 195 return c.samplingState.isFirehose() 196} 197 198// ExtendedSamplingState returns the custom state object for a given key. If the value for this key does not exist, 199// it is initialized via initValue function. This state can be used by samplers (e.g. x.PrioritySampler). 200func (c SpanContext) ExtendedSamplingState(key interface{}, initValue func() interface{}) interface{} { 201 return c.samplingState.extendedStateForKey(key, initValue) 202} 203 204// IsValid indicates whether this context actually represents a valid trace. 205func (c SpanContext) IsValid() bool { 206 return c.traceID.IsValid() && c.spanID != 0 207} 208 209// SetFirehose enables firehose mode for this trace. 210func (c SpanContext) SetFirehose() { 211 c.samplingState.setFirehose() 212} 213 214func (c SpanContext) String() string { 215 var flags int32 216 if c.samplingState != nil { 217 flags = c.samplingState.stateFlags.Load() 218 } 219 if c.traceID.High == 0 { 220 return fmt.Sprintf("%016x:%016x:%016x:%x", c.traceID.Low, uint64(c.spanID), uint64(c.parentID), flags) 221 } 222 return fmt.Sprintf("%016x%016x:%016x:%016x:%x", c.traceID.High, c.traceID.Low, uint64(c.spanID), uint64(c.parentID), flags) 223} 224 225// ContextFromString reconstructs the Context encoded in a string 226func ContextFromString(value string) (SpanContext, error) { 227 var context SpanContext 228 if value == "" { 229 return emptyContext, errEmptyTracerStateString 230 } 231 parts := strings.Split(value, ":") 232 if len(parts) != 4 { 233 return emptyContext, errMalformedTracerStateString 234 } 235 var err error 236 if context.traceID, err = TraceIDFromString(parts[0]); err != nil { 237 return emptyContext, err 238 } 239 if context.spanID, err = SpanIDFromString(parts[1]); err != nil { 240 return emptyContext, err 241 } 242 if context.parentID, err = SpanIDFromString(parts[2]); err != nil { 243 return emptyContext, err 244 } 245 flags, err := strconv.ParseUint(parts[3], 10, 8) 246 if err != nil { 247 return emptyContext, err 248 } 249 context.samplingState = &samplingState{} 250 context.samplingState.setFlags(byte(flags)) 251 return context, nil 252} 253 254// TraceID returns the trace ID of this span context 255func (c SpanContext) TraceID() TraceID { 256 return c.traceID 257} 258 259// SpanID returns the span ID of this span context 260func (c SpanContext) SpanID() SpanID { 261 return c.spanID 262} 263 264// ParentID returns the parent span ID of this span context 265func (c SpanContext) ParentID() SpanID { 266 return c.parentID 267} 268 269// Flags returns the bitmap containing such bits as 'sampled' and 'debug'. 270func (c SpanContext) Flags() byte { 271 return c.samplingState.flags() 272} 273 274// Span can be written to if it is sampled or the sampling decision has not been finalized. 275func (c SpanContext) isWriteable() bool { 276 state := c.samplingState 277 return !state.isFinal() || state.isSampled() 278} 279 280func (c SpanContext) isSamplingFinalized() bool { 281 return c.samplingState.isFinal() 282} 283 284// NewSpanContext creates a new instance of SpanContext 285func NewSpanContext(traceID TraceID, spanID, parentID SpanID, sampled bool, baggage map[string]string) SpanContext { 286 samplingState := &samplingState{} 287 if sampled { 288 samplingState.setSampled() 289 } 290 291 return SpanContext{ 292 traceID: traceID, 293 spanID: spanID, 294 parentID: parentID, 295 samplingState: samplingState, 296 baggage: baggage} 297} 298 299// CopyFrom copies data from ctx into this context, including span identity and baggage. 300// TODO This is only used by interop.go. Remove once TChannel Go supports OpenTracing. 301func (c *SpanContext) CopyFrom(ctx *SpanContext) { 302 c.traceID = ctx.traceID 303 c.spanID = ctx.spanID 304 c.parentID = ctx.parentID 305 c.samplingState = ctx.samplingState 306 if l := len(ctx.baggage); l > 0 { 307 c.baggage = make(map[string]string, l) 308 for k, v := range ctx.baggage { 309 c.baggage[k] = v 310 } 311 } else { 312 c.baggage = nil 313 } 314} 315 316// WithBaggageItem creates a new context with an extra baggage item. 317// Delete a baggage item if provided blank value. 318// 319// The SpanContext is designed to be immutable and passed by value. As such, 320// it cannot contain any locks, and should only hold immutable data, including baggage. 321// Another reason for why baggage is immutable is when the span context is passed 322// as a parent when starting a new span. The new span's baggage cannot affect the parent 323// span's baggage, so the child span either needs to take a copy of the parent baggage 324// (which is expensive and unnecessary since baggage rarely changes in the life span of 325// a trace), or it needs to do a copy-on-write, which is the approach taken here. 326func (c SpanContext) WithBaggageItem(key, value string) SpanContext { 327 var newBaggage map[string]string 328 // unset baggage item 329 if value == "" { 330 if _, ok := c.baggage[key]; !ok { 331 return c 332 } 333 newBaggage = make(map[string]string, len(c.baggage)) 334 for k, v := range c.baggage { 335 newBaggage[k] = v 336 } 337 delete(newBaggage, key) 338 return SpanContext{c.traceID, c.spanID, c.parentID, newBaggage, "", c.samplingState, c.remote} 339 } 340 if c.baggage == nil { 341 newBaggage = map[string]string{key: value} 342 } else { 343 newBaggage = make(map[string]string, len(c.baggage)+1) 344 for k, v := range c.baggage { 345 newBaggage[k] = v 346 } 347 newBaggage[key] = value 348 } 349 // Use positional parameters so the compiler will help catch new fields. 350 return SpanContext{c.traceID, c.spanID, c.parentID, newBaggage, "", c.samplingState, c.remote} 351} 352 353// isDebugIDContainerOnly returns true when the instance of the context is only 354// used to return the debug/correlation ID from extract() method. This happens 355// in the situation when "jaeger-debug-id" header is passed in the carrier to 356// the extract() method, but the request otherwise has no span context in it. 357// Previously this would've returned opentracing.ErrSpanContextNotFound from the 358// extract method, but now it returns a dummy context with only debugID filled in. 359// 360// See JaegerDebugHeader in constants.go 361// See TextMapPropagator#Extract 362func (c *SpanContext) isDebugIDContainerOnly() bool { 363 return !c.traceID.IsValid() && c.debugID != "" 364} 365 366// ------- TraceID ------- 367 368func (t TraceID) String() string { 369 if t.High == 0 { 370 return fmt.Sprintf("%016x", t.Low) 371 } 372 return fmt.Sprintf("%016x%016x", t.High, t.Low) 373} 374 375// TraceIDFromString creates a TraceID from a hexadecimal string 376func TraceIDFromString(s string) (TraceID, error) { 377 var hi, lo uint64 378 var err error 379 if len(s) > 32 { 380 return TraceID{}, fmt.Errorf("TraceID cannot be longer than 32 hex characters: %s", s) 381 } else if len(s) > 16 { 382 hiLen := len(s) - 16 383 if hi, err = strconv.ParseUint(s[0:hiLen], 16, 64); err != nil { 384 return TraceID{}, err 385 } 386 if lo, err = strconv.ParseUint(s[hiLen:], 16, 64); err != nil { 387 return TraceID{}, err 388 } 389 } else { 390 if lo, err = strconv.ParseUint(s, 16, 64); err != nil { 391 return TraceID{}, err 392 } 393 } 394 return TraceID{High: hi, Low: lo}, nil 395} 396 397// IsValid checks if the trace ID is valid, i.e. not zero. 398func (t TraceID) IsValid() bool { 399 return t.High != 0 || t.Low != 0 400} 401 402// ------- SpanID ------- 403 404func (s SpanID) String() string { 405 return fmt.Sprintf("%016x", uint64(s)) 406} 407 408// SpanIDFromString creates a SpanID from a hexadecimal string 409func SpanIDFromString(s string) (SpanID, error) { 410 if len(s) > 16 { 411 return SpanID(0), fmt.Errorf("SpanID cannot be longer than 16 hex characters: %s", s) 412 } 413 id, err := strconv.ParseUint(s, 16, 64) 414 if err != nil { 415 return SpanID(0), err 416 } 417 return SpanID(id), nil 418} 419