1// Copyright The OpenTelemetry Authors
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 trace // import "go.opentelemetry.io/otel/sdk/trace"
16
17import (
18	"errors"
19	"fmt"
20	"reflect"
21	"sync"
22	"time"
23
24	"go.opentelemetry.io/otel"
25	"go.opentelemetry.io/otel/codes"
26	"go.opentelemetry.io/otel/label"
27	"go.opentelemetry.io/otel/trace"
28
29	export "go.opentelemetry.io/otel/sdk/export/trace"
30	"go.opentelemetry.io/otel/sdk/internal"
31)
32
33const (
34	errorTypeKey    = label.Key("error.type")
35	errorMessageKey = label.Key("error.message")
36	errorEventName  = "error"
37)
38
39var emptySpanContext = trace.SpanContext{}
40
41// span is an implementation of the OpenTelemetry Span API representing the
42// individual component of a trace.
43type span struct {
44	// mu protects the contents of this span.
45	mu sync.Mutex
46
47	// data contains information recorded about the span.
48	//
49	// It will be non-nil if we are exporting the span or recording events for it.
50	// Otherwise, data is nil, and the span is simply a carrier for the
51	// SpanContext, so that the trace ID is propagated.
52	data        *export.SpanData
53	spanContext trace.SpanContext
54
55	// attributes are capped at configured limit. When the capacity is reached an oldest entry
56	// is removed to create room for a new entry.
57	attributes *attributesMap
58
59	// messageEvents are stored in FIFO queue capped by configured limit.
60	messageEvents *evictedQueue
61
62	// links are stored in FIFO queue capped by configured limit.
63	links *evictedQueue
64
65	// endOnce ensures End is only called once.
66	endOnce sync.Once
67
68	// executionTracerTaskEnd ends the execution tracer span.
69	executionTracerTaskEnd func()
70
71	// tracer is the SDK tracer that created this span.
72	tracer *tracer
73}
74
75var _ trace.Span = &span{}
76
77func (s *span) SpanContext() trace.SpanContext {
78	if s == nil {
79		return trace.SpanContext{}
80	}
81	return s.spanContext
82}
83
84func (s *span) IsRecording() bool {
85	if s == nil {
86		return false
87	}
88	return s.data != nil
89}
90
91func (s *span) SetStatus(code codes.Code, msg string) {
92	if s == nil {
93		return
94	}
95	if !s.IsRecording() {
96		return
97	}
98	s.mu.Lock()
99	s.data.StatusCode = code
100	s.data.StatusMessage = msg
101	s.mu.Unlock()
102}
103
104func (s *span) SetAttributes(attributes ...label.KeyValue) {
105	if !s.IsRecording() {
106		return
107	}
108	s.copyToCappedAttributes(attributes...)
109}
110
111// End ends the span.
112//
113// The only SpanOption currently supported is WithTimestamp which will set the
114// end time for a Span's life-cycle.
115//
116// If this method is called while panicking an error event is added to the
117// Span before ending it and the panic is continued.
118func (s *span) End(options ...trace.SpanOption) {
119	if s == nil {
120		return
121	}
122
123	if recovered := recover(); recovered != nil {
124		// Record but don't stop the panic.
125		defer panic(recovered)
126		s.addEvent(
127			errorEventName,
128			trace.WithAttributes(
129				errorTypeKey.String(typeStr(recovered)),
130				errorMessageKey.String(fmt.Sprint(recovered)),
131			),
132		)
133	}
134
135	if s.executionTracerTaskEnd != nil {
136		s.executionTracerTaskEnd()
137	}
138	if !s.IsRecording() {
139		return
140	}
141	config := trace.NewSpanConfig(options...)
142	s.endOnce.Do(func() {
143		sps, ok := s.tracer.provider.spanProcessors.Load().(spanProcessorStates)
144		mustExportOrProcess := ok && len(sps) > 0
145		if mustExportOrProcess {
146			sd := s.makeSpanData()
147			if config.Timestamp.IsZero() {
148				sd.EndTime = internal.MonotonicEndTime(sd.StartTime)
149			} else {
150				sd.EndTime = config.Timestamp
151			}
152			for _, sp := range sps {
153				sp.sp.OnEnd(sd)
154			}
155		}
156	})
157}
158
159func (s *span) RecordError(err error, opts ...trace.EventOption) {
160	if s == nil || err == nil || !s.IsRecording() {
161		return
162	}
163
164	s.SetStatus(codes.Error, "")
165	opts = append(opts, trace.WithAttributes(
166		errorTypeKey.String(typeStr(err)),
167		errorMessageKey.String(err.Error()),
168	))
169	s.addEvent(errorEventName, opts...)
170}
171
172func typeStr(i interface{}) string {
173	t := reflect.TypeOf(i)
174	if t.PkgPath() == "" && t.Name() == "" {
175		// Likely a builtin type.
176		return t.String()
177	}
178	return fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
179}
180
181func (s *span) Tracer() trace.Tracer {
182	return s.tracer
183}
184
185func (s *span) AddEvent(name string, o ...trace.EventOption) {
186	if !s.IsRecording() {
187		return
188	}
189	s.addEvent(name, o...)
190}
191
192func (s *span) addEvent(name string, o ...trace.EventOption) {
193	c := trace.NewEventConfig(o...)
194
195	s.mu.Lock()
196	defer s.mu.Unlock()
197	s.messageEvents.add(export.Event{
198		Name:       name,
199		Attributes: c.Attributes,
200		Time:       c.Timestamp,
201	})
202}
203
204var errUninitializedSpan = errors.New("failed to set name on uninitialized span")
205
206func (s *span) SetName(name string) {
207	s.mu.Lock()
208	defer s.mu.Unlock()
209
210	if s.data == nil {
211		otel.Handle(errUninitializedSpan)
212		return
213	}
214	s.data.Name = name
215	// SAMPLING
216	noParent := !s.data.ParentSpanID.IsValid()
217	var ctx trace.SpanContext
218	if noParent {
219		ctx = trace.SpanContext{}
220	} else {
221		// FIXME: Where do we get the parent context from?
222		ctx = s.data.SpanContext
223	}
224	data := samplingData{
225		noParent:     noParent,
226		remoteParent: s.data.HasRemoteParent,
227		parent:       ctx,
228		name:         name,
229		cfg:          s.tracer.provider.config.Load().(*Config),
230		span:         s,
231		attributes:   s.data.Attributes,
232		links:        s.data.Links,
233		kind:         s.data.SpanKind,
234	}
235	sampled := makeSamplingDecision(data)
236
237	// Adding attributes directly rather than using s.SetAttributes()
238	// as s.mu is already locked and attempting to do so would deadlock.
239	for _, a := range sampled.Attributes {
240		s.attributes.add(a)
241	}
242}
243
244func (s *span) addLink(link trace.Link) {
245	if !s.IsRecording() {
246		return
247	}
248	s.mu.Lock()
249	defer s.mu.Unlock()
250	s.links.add(link)
251}
252
253// makeSpanData produces a SpanData representing the current state of the span.
254// It requires that s.data is non-nil.
255func (s *span) makeSpanData() *export.SpanData {
256	var sd export.SpanData
257	s.mu.Lock()
258	defer s.mu.Unlock()
259	sd = *s.data
260
261	s.attributes.toSpanData(&sd)
262
263	if len(s.messageEvents.queue) > 0 {
264		sd.MessageEvents = s.interfaceArrayToMessageEventArray()
265		sd.DroppedMessageEventCount = s.messageEvents.droppedCount
266	}
267	if len(s.links.queue) > 0 {
268		sd.Links = s.interfaceArrayToLinksArray()
269		sd.DroppedLinkCount = s.links.droppedCount
270	}
271	return &sd
272}
273
274func (s *span) interfaceArrayToLinksArray() []trace.Link {
275	linkArr := make([]trace.Link, 0)
276	for _, value := range s.links.queue {
277		linkArr = append(linkArr, value.(trace.Link))
278	}
279	return linkArr
280}
281
282func (s *span) interfaceArrayToMessageEventArray() []export.Event {
283	messageEventArr := make([]export.Event, 0)
284	for _, value := range s.messageEvents.queue {
285		messageEventArr = append(messageEventArr, value.(export.Event))
286	}
287	return messageEventArr
288}
289
290func (s *span) copyToCappedAttributes(attributes ...label.KeyValue) {
291	s.mu.Lock()
292	defer s.mu.Unlock()
293	for _, a := range attributes {
294		if a.Value.Type() != label.INVALID {
295			s.attributes.add(a)
296		}
297	}
298}
299
300func (s *span) addChild() {
301	if !s.IsRecording() {
302		return
303	}
304	s.mu.Lock()
305	s.data.ChildSpanCount++
306	s.mu.Unlock()
307}
308
309func startSpanInternal(tr *tracer, name string, parent trace.SpanContext, remoteParent bool, o *trace.SpanConfig) *span {
310	var noParent bool
311	span := &span{}
312	span.spanContext = parent
313
314	cfg := tr.provider.config.Load().(*Config)
315
316	if parent == emptySpanContext {
317		span.spanContext.TraceID = cfg.IDGenerator.NewTraceID()
318		noParent = true
319	}
320	span.spanContext.SpanID = cfg.IDGenerator.NewSpanID()
321	data := samplingData{
322		noParent:     noParent,
323		remoteParent: remoteParent,
324		parent:       parent,
325		name:         name,
326		cfg:          cfg,
327		span:         span,
328		attributes:   o.Attributes,
329		links:        o.Links,
330		kind:         o.SpanKind,
331	}
332	sampled := makeSamplingDecision(data)
333
334	if !span.spanContext.IsSampled() && !o.Record {
335		return span
336	}
337
338	startTime := o.Timestamp
339	if startTime.IsZero() {
340		startTime = time.Now()
341	}
342	span.data = &export.SpanData{
343		SpanContext:            span.spanContext,
344		StartTime:              startTime,
345		SpanKind:               trace.ValidateSpanKind(o.SpanKind),
346		Name:                   name,
347		HasRemoteParent:        remoteParent,
348		Resource:               cfg.Resource,
349		InstrumentationLibrary: tr.instrumentationLibrary,
350	}
351	span.attributes = newAttributesMap(cfg.MaxAttributesPerSpan)
352	span.messageEvents = newEvictedQueue(cfg.MaxEventsPerSpan)
353	span.links = newEvictedQueue(cfg.MaxLinksPerSpan)
354
355	span.SetAttributes(sampled.Attributes...)
356
357	if !noParent {
358		span.data.ParentSpanID = parent.SpanID
359	}
360
361	return span
362}
363
364type samplingData struct {
365	noParent     bool
366	remoteParent bool
367	parent       trace.SpanContext
368	name         string
369	cfg          *Config
370	span         *span
371	attributes   []label.KeyValue
372	links        []trace.Link
373	kind         trace.SpanKind
374}
375
376func makeSamplingDecision(data samplingData) SamplingResult {
377	sampler := data.cfg.DefaultSampler
378	spanContext := &data.span.spanContext
379	sampled := sampler.ShouldSample(SamplingParameters{
380		ParentContext:   data.parent,
381		TraceID:         spanContext.TraceID,
382		Name:            data.name,
383		HasRemoteParent: data.remoteParent,
384		Kind:            data.kind,
385		Attributes:      data.attributes,
386		Links:           data.links,
387	})
388	if sampled.Decision == RecordAndSample {
389		spanContext.TraceFlags |= trace.FlagsSampled
390	} else {
391		spanContext.TraceFlags &^= trace.FlagsSampled
392	}
393	return sampled
394}
395