1// Copyright (c) 2017-2018 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	"sync"
19	"time"
20
21	"github.com/opentracing/opentracing-go"
22	"github.com/opentracing/opentracing-go/ext"
23	"github.com/opentracing/opentracing-go/log"
24)
25
26// Span implements opentracing.Span
27type Span struct {
28	sync.RWMutex
29
30	tracer *Tracer
31
32	context SpanContext
33
34	// The name of the "operation" this span is an instance of.
35	// Known as a "span name" in some implementations.
36	operationName string
37
38	// firstInProcess, if true, indicates that this span is the root of the (sub)tree
39	// of spans in the current process. In other words it's true for the root spans,
40	// and the ingress spans when the process joins another trace.
41	firstInProcess bool
42
43	// startTime is the timestamp indicating when the span began, with microseconds precision.
44	startTime time.Time
45
46	// duration returns duration of the span with microseconds precision.
47	// Zero value means duration is unknown.
48	duration time.Duration
49
50	// tags attached to this span
51	tags []Tag
52
53	// The span's "micro-log"
54	logs []opentracing.LogRecord
55
56	// references for this span
57	references []Reference
58
59	observer ContribSpanObserver
60}
61
62// Tag is a simple key value wrapper.
63// TODO deprecate in the next major release, use opentracing.Tag instead.
64type Tag struct {
65	key   string
66	value interface{}
67}
68
69// SetOperationName sets or changes the operation name.
70func (s *Span) SetOperationName(operationName string) opentracing.Span {
71	s.Lock()
72	defer s.Unlock()
73	if s.context.IsSampled() {
74		s.operationName = operationName
75	}
76	s.observer.OnSetOperationName(operationName)
77	return s
78}
79
80// SetTag implements SetTag() of opentracing.Span
81func (s *Span) SetTag(key string, value interface{}) opentracing.Span {
82	s.observer.OnSetTag(key, value)
83	if key == string(ext.SamplingPriority) && !setSamplingPriority(s, value) {
84		return s
85	}
86	s.Lock()
87	defer s.Unlock()
88	if s.context.IsSampled() {
89		s.setTagNoLocking(key, value)
90	}
91	return s
92}
93
94func (s *Span) setTagNoLocking(key string, value interface{}) {
95	s.tags = append(s.tags, Tag{key: key, value: value})
96}
97
98// LogFields implements opentracing.Span API
99func (s *Span) LogFields(fields ...log.Field) {
100	s.Lock()
101	defer s.Unlock()
102	if !s.context.IsSampled() {
103		return
104	}
105	s.logFieldsNoLocking(fields...)
106}
107
108// this function should only be called while holding a Write lock
109func (s *Span) logFieldsNoLocking(fields ...log.Field) {
110	lr := opentracing.LogRecord{
111		Fields:    fields,
112		Timestamp: time.Now(),
113	}
114	s.appendLog(lr)
115}
116
117// LogKV implements opentracing.Span API
118func (s *Span) LogKV(alternatingKeyValues ...interface{}) {
119	s.RLock()
120	sampled := s.context.IsSampled()
121	s.RUnlock()
122	if !sampled {
123		return
124	}
125	fields, err := log.InterleavedKVToFields(alternatingKeyValues...)
126	if err != nil {
127		s.LogFields(log.Error(err), log.String("function", "LogKV"))
128		return
129	}
130	s.LogFields(fields...)
131}
132
133// LogEvent implements opentracing.Span API
134func (s *Span) LogEvent(event string) {
135	s.Log(opentracing.LogData{Event: event})
136}
137
138// LogEventWithPayload implements opentracing.Span API
139func (s *Span) LogEventWithPayload(event string, payload interface{}) {
140	s.Log(opentracing.LogData{Event: event, Payload: payload})
141}
142
143// Log implements opentracing.Span API
144func (s *Span) Log(ld opentracing.LogData) {
145	s.Lock()
146	defer s.Unlock()
147	if s.context.IsSampled() {
148		if ld.Timestamp.IsZero() {
149			ld.Timestamp = s.tracer.timeNow()
150		}
151		s.appendLog(ld.ToLogRecord())
152	}
153}
154
155// this function should only be called while holding a Write lock
156func (s *Span) appendLog(lr opentracing.LogRecord) {
157	// TODO add logic to limit number of logs per span (issue #46)
158	s.logs = append(s.logs, lr)
159}
160
161// SetBaggageItem implements SetBaggageItem() of opentracing.SpanContext
162func (s *Span) SetBaggageItem(key, value string) opentracing.Span {
163	s.Lock()
164	defer s.Unlock()
165	s.tracer.setBaggage(s, key, value)
166	return s
167}
168
169// BaggageItem implements BaggageItem() of opentracing.SpanContext
170func (s *Span) BaggageItem(key string) string {
171	s.RLock()
172	defer s.RUnlock()
173	return s.context.baggage[key]
174}
175
176// Finish implements opentracing.Span API
177func (s *Span) Finish() {
178	s.FinishWithOptions(opentracing.FinishOptions{})
179}
180
181// FinishWithOptions implements opentracing.Span API
182func (s *Span) FinishWithOptions(options opentracing.FinishOptions) {
183	if options.FinishTime.IsZero() {
184		options.FinishTime = s.tracer.timeNow()
185	}
186	s.observer.OnFinish(options)
187	s.Lock()
188	if s.context.IsSampled() {
189		s.duration = options.FinishTime.Sub(s.startTime)
190		// Note: bulk logs are not subject to maxLogsPerSpan limit
191		if options.LogRecords != nil {
192			s.logs = append(s.logs, options.LogRecords...)
193		}
194		for _, ld := range options.BulkLogData {
195			s.logs = append(s.logs, ld.ToLogRecord())
196		}
197	}
198	s.Unlock()
199	// call reportSpan even for non-sampled traces, to return span to the pool
200	s.tracer.reportSpan(s)
201}
202
203// Context implements opentracing.Span API
204func (s *Span) Context() opentracing.SpanContext {
205	s.Lock()
206	defer s.Unlock()
207	return s.context
208}
209
210// Tracer implements opentracing.Span API
211func (s *Span) Tracer() opentracing.Tracer {
212	return s.tracer
213}
214
215func (s *Span) String() string {
216	s.RLock()
217	defer s.RUnlock()
218	return s.context.String()
219}
220
221// OperationName allows retrieving current operation name.
222func (s *Span) OperationName() string {
223	s.RLock()
224	defer s.RUnlock()
225	return s.operationName
226}
227
228func (s *Span) serviceName() string {
229	return s.tracer.serviceName
230}
231
232// setSamplingPriority returns true if the flag was updated successfully, false otherwise.
233func setSamplingPriority(s *Span, value interface{}) bool {
234	s.Lock()
235	defer s.Unlock()
236	val, ok := value.(uint16)
237	if !ok {
238		return false
239	}
240	if val == 0 {
241		s.context.flags = s.context.flags & (^flagSampled)
242		return true
243	}
244	if s.tracer.isDebugAllowed(s.operationName) {
245		s.context.flags = s.context.flags | flagDebug | flagSampled
246		return true
247	}
248	return false
249}
250