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