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) 23 24const ( 25 flagSampled = byte(1) 26 flagDebug = byte(2) 27) 28 29var ( 30 errEmptyTracerStateString = errors.New("Cannot convert empty string to tracer state") 31 errMalformedTracerStateString = errors.New("String does not match tracer state format") 32 33 emptyContext = SpanContext{} 34) 35 36// TraceID represents unique 128bit identifier of a trace 37type TraceID struct { 38 High, Low uint64 39} 40 41// SpanID represents unique 64bit identifier of a span 42type SpanID uint64 43 44// SpanContext represents propagated span identity and state 45type SpanContext struct { 46 // traceID represents globally unique ID of the trace. 47 // Usually generated as a random number. 48 traceID TraceID 49 50 // spanID represents span ID that must be unique within its trace, 51 // but does not have to be globally unique. 52 spanID SpanID 53 54 // parentID refers to the ID of the parent span. 55 // Should be 0 if the current span is a root span. 56 parentID SpanID 57 58 // flags is a bitmap containing such bits as 'sampled' and 'debug'. 59 flags byte 60 61 // Distributed Context baggage. The is a snapshot in time. 62 baggage map[string]string 63 64 // debugID can be set to some correlation ID when the context is being 65 // extracted from a TextMap carrier. 66 // 67 // See JaegerDebugHeader in constants.go 68 debugID string 69} 70 71// ForeachBaggageItem implements ForeachBaggageItem() of opentracing.SpanContext 72func (c SpanContext) ForeachBaggageItem(handler func(k, v string) bool) { 73 for k, v := range c.baggage { 74 if !handler(k, v) { 75 break 76 } 77 } 78} 79 80// IsSampled returns whether this trace was chosen for permanent storage 81// by the sampling mechanism of the tracer. 82func (c SpanContext) IsSampled() bool { 83 return (c.flags & flagSampled) == flagSampled 84} 85 86// IsDebug indicates whether sampling was explicitly requested by the service. 87func (c SpanContext) IsDebug() bool { 88 return (c.flags & flagDebug) == flagDebug 89} 90 91// IsValid indicates whether this context actually represents a valid trace. 92func (c SpanContext) IsValid() bool { 93 return c.traceID.IsValid() && c.spanID != 0 94} 95 96func (c SpanContext) String() string { 97 if c.traceID.High == 0 { 98 return fmt.Sprintf("%x:%x:%x:%x", c.traceID.Low, uint64(c.spanID), uint64(c.parentID), c.flags) 99 } 100 return fmt.Sprintf("%x%016x:%x:%x:%x", c.traceID.High, c.traceID.Low, uint64(c.spanID), uint64(c.parentID), c.flags) 101} 102 103// ContextFromString reconstructs the Context encoded in a string 104func ContextFromString(value string) (SpanContext, error) { 105 var context SpanContext 106 if value == "" { 107 return emptyContext, errEmptyTracerStateString 108 } 109 parts := strings.Split(value, ":") 110 if len(parts) != 4 { 111 return emptyContext, errMalformedTracerStateString 112 } 113 var err error 114 if context.traceID, err = TraceIDFromString(parts[0]); err != nil { 115 return emptyContext, err 116 } 117 if context.spanID, err = SpanIDFromString(parts[1]); err != nil { 118 return emptyContext, err 119 } 120 if context.parentID, err = SpanIDFromString(parts[2]); err != nil { 121 return emptyContext, err 122 } 123 flags, err := strconv.ParseUint(parts[3], 10, 8) 124 if err != nil { 125 return emptyContext, err 126 } 127 context.flags = byte(flags) 128 return context, nil 129} 130 131// TraceID returns the trace ID of this span context 132func (c SpanContext) TraceID() TraceID { 133 return c.traceID 134} 135 136// SpanID returns the span ID of this span context 137func (c SpanContext) SpanID() SpanID { 138 return c.spanID 139} 140 141// ParentID returns the parent span ID of this span context 142func (c SpanContext) ParentID() SpanID { 143 return c.parentID 144} 145 146// NewSpanContext creates a new instance of SpanContext 147func NewSpanContext(traceID TraceID, spanID, parentID SpanID, sampled bool, baggage map[string]string) SpanContext { 148 flags := byte(0) 149 if sampled { 150 flags = flagSampled 151 } 152 return SpanContext{ 153 traceID: traceID, 154 spanID: spanID, 155 parentID: parentID, 156 flags: flags, 157 baggage: baggage} 158} 159 160// CopyFrom copies data from ctx into this context, including span identity and baggage. 161// TODO This is only used by interop.go. Remove once TChannel Go supports OpenTracing. 162func (c *SpanContext) CopyFrom(ctx *SpanContext) { 163 c.traceID = ctx.traceID 164 c.spanID = ctx.spanID 165 c.parentID = ctx.parentID 166 c.flags = ctx.flags 167 if l := len(ctx.baggage); l > 0 { 168 c.baggage = make(map[string]string, l) 169 for k, v := range ctx.baggage { 170 c.baggage[k] = v 171 } 172 } else { 173 c.baggage = nil 174 } 175} 176 177// WithBaggageItem creates a new context with an extra baggage item. 178func (c SpanContext) WithBaggageItem(key, value string) SpanContext { 179 var newBaggage map[string]string 180 if c.baggage == nil { 181 newBaggage = map[string]string{key: value} 182 } else { 183 newBaggage = make(map[string]string, len(c.baggage)+1) 184 for k, v := range c.baggage { 185 newBaggage[k] = v 186 } 187 newBaggage[key] = value 188 } 189 // Use positional parameters so the compiler will help catch new fields. 190 return SpanContext{c.traceID, c.spanID, c.parentID, c.flags, newBaggage, ""} 191} 192 193// isDebugIDContainerOnly returns true when the instance of the context is only 194// used to return the debug/correlation ID from extract() method. This happens 195// in the situation when "jaeger-debug-id" header is passed in the carrier to 196// the extract() method, but the request otherwise has no span context in it. 197// Previously this would've returned opentracing.ErrSpanContextNotFound from the 198// extract method, but now it returns a dummy context with only debugID filled in. 199// 200// See JaegerDebugHeader in constants.go 201// See textMapPropagator#Extract 202func (c *SpanContext) isDebugIDContainerOnly() bool { 203 return !c.traceID.IsValid() && c.debugID != "" 204} 205 206// ------- TraceID ------- 207 208func (t TraceID) String() string { 209 if t.High == 0 { 210 return fmt.Sprintf("%x", t.Low) 211 } 212 return fmt.Sprintf("%x%016x", t.High, t.Low) 213} 214 215// TraceIDFromString creates a TraceID from a hexadecimal string 216func TraceIDFromString(s string) (TraceID, error) { 217 var hi, lo uint64 218 var err error 219 if len(s) > 32 { 220 return TraceID{}, fmt.Errorf("TraceID cannot be longer than 32 hex characters: %s", s) 221 } else if len(s) > 16 { 222 hiLen := len(s) - 16 223 if hi, err = strconv.ParseUint(s[0:hiLen], 16, 64); err != nil { 224 return TraceID{}, err 225 } 226 if lo, err = strconv.ParseUint(s[hiLen:], 16, 64); err != nil { 227 return TraceID{}, err 228 } 229 } else { 230 if lo, err = strconv.ParseUint(s, 16, 64); err != nil { 231 return TraceID{}, err 232 } 233 } 234 return TraceID{High: hi, Low: lo}, nil 235} 236 237// IsValid checks if the trace ID is valid, i.e. not zero. 238func (t TraceID) IsValid() bool { 239 return t.High != 0 || t.Low != 0 240} 241 242// ------- SpanID ------- 243 244func (s SpanID) String() string { 245 return fmt.Sprintf("%x", uint64(s)) 246} 247 248// SpanIDFromString creates a SpanID from a hexadecimal string 249func SpanIDFromString(s string) (SpanID, error) { 250 if len(s) > 16 { 251 return SpanID(0), fmt.Errorf("SpanID cannot be longer than 16 hex characters: %s", s) 252 } 253 id, err := strconv.ParseUint(s, 16, 64) 254 if err != nil { 255 return SpanID(0), err 256 } 257 return SpanID(id), nil 258} 259