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