1/*
2 *  Copyright 2018 Expedia Group.
3 *
4 *     Licensed under the Apache License, Version 2.0 (the "License");
5 *     you may not use this file except in compliance with the License.
6 *     You may obtain a copy of the License at
7 *
8 *         http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *     Unless required by applicable law or agreed to in writing, software
11 *     distributed under the License is distributed on an "AS IS" BASIS,
12 *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 *     See the License for the specific language governing permissions and
14 *     limitations under the License.
15 *
16 */
17
18package haystack
19
20import (
21	"io"
22	"time"
23
24	"github.com/google/uuid"
25	"github.com/opentracing/opentracing-go"
26	"github.com/opentracing/opentracing-go/ext"
27)
28
29/*Tracer implements the opentracing.tracer*/
30type Tracer struct {
31	serviceName     string
32	logger          Logger
33	dispatcher      Dispatcher
34	commonTags      []opentracing.Tag
35	timeNow         func() time.Time
36	idGenerator     func() string
37	propagators     map[interface{}]Propagator
38	useDualSpanMode bool
39}
40
41/*NewTracer creates a new tracer*/
42func NewTracer(
43	serviceName string,
44	dispatcher Dispatcher,
45	options ...TracerOption,
46) (opentracing.Tracer, io.Closer) {
47	tracer := &Tracer{
48		serviceName:     serviceName,
49		dispatcher:      dispatcher,
50		useDualSpanMode: false,
51	}
52	tracer.propagators = make(map[interface{}]Propagator)
53	tracer.propagators[opentracing.TextMap] = NewDefaultTextMapPropagator()
54	tracer.propagators[opentracing.HTTPHeaders] = NewTextMapPropagator(PropagatorOpts{}, URLCodex{})
55	for _, option := range options {
56		option(tracer)
57	}
58
59	if tracer.timeNow == nil {
60		tracer.timeNow = time.Now
61	}
62
63	if tracer.logger == nil {
64		tracer.logger = NullLogger{}
65	}
66
67	if tracer.idGenerator == nil {
68		tracer.idGenerator = func() string {
69			_uuid, err := uuid.NewUUID()
70			if err != nil {
71				panic(err)
72			}
73			return _uuid.String()
74		}
75	}
76
77	dispatcher.SetLogger(tracer.logger)
78	return tracer, tracer
79}
80
81/*StartSpan starts a new span*/
82func (tracer *Tracer) StartSpan(
83	operationName string,
84	options ...opentracing.StartSpanOption,
85) opentracing.Span {
86	sso := opentracing.StartSpanOptions{}
87
88	for _, o := range options {
89		o.Apply(&sso)
90	}
91
92	if sso.StartTime.IsZero() {
93		sso.StartTime = tracer.timeNow()
94	}
95
96	var followsFromIsParent = false
97	var parent *SpanContext
98
99	for _, ref := range sso.References {
100		if ref.Type == opentracing.ChildOfRef {
101			if parent == nil || followsFromIsParent {
102				parent = ref.ReferencedContext.(*SpanContext)
103			}
104		} else if ref.Type == opentracing.FollowsFromRef {
105			if parent == nil {
106				parent = ref.ReferencedContext.(*SpanContext)
107				followsFromIsParent = true
108			}
109		}
110	}
111
112	spanContext := tracer.createSpanContext(parent, tracer.isServerSpan(sso.Tags))
113
114	span := &_Span{
115		tracer:        tracer,
116		context:       spanContext,
117		operationName: operationName,
118		startTime:     sso.StartTime,
119		duration:      0,
120	}
121
122	for _, tag := range tracer.Tags() {
123		span.SetTag(tag.Key, tag.Value)
124	}
125	for k, v := range sso.Tags {
126		span.SetTag(k, v)
127	}
128
129	return span
130}
131
132func (tracer *Tracer) isServerSpan(spanTags map[string]interface{}) bool {
133	if spanKind, ok := spanTags[string(ext.SpanKind)]; ok && spanKind == "server" {
134		return true
135	}
136	return false
137}
138
139func (tracer *Tracer) createSpanContext(parent *SpanContext, isServerSpan bool) *SpanContext {
140	if parent == nil || !parent.IsValid() {
141		return &SpanContext{
142			TraceID: tracer.idGenerator(),
143			SpanID:  tracer.idGenerator(),
144		}
145	}
146
147	// This is a check to see if the tracer is configured to support single
148	// single span type (Zipkin style shared span id) or
149	// dual span type (client and server having their own span ids ).
150	// a. If tracer is not of dualSpanType and if it is a server span then we
151	// just return the parent context with the same shared span ids
152	// b. If tracer is not of dualSpanType and if the parent context is an extracted one from the wire
153	// then we assume this is the first span in the server and so just return the parent context
154	// with the same shared span ids
155	if !tracer.useDualSpanMode && (isServerSpan || parent.IsExtractedContext) {
156		return &SpanContext{
157			TraceID:            parent.TraceID,
158			SpanID:             parent.SpanID,
159			ParentID:           parent.ParentID,
160			Baggage:            parent.Baggage,
161			IsExtractedContext: false,
162		}
163	}
164	return &SpanContext{
165		TraceID:            parent.TraceID,
166		SpanID:             tracer.idGenerator(),
167		ParentID:           parent.SpanID,
168		Baggage:            parent.Baggage,
169		IsExtractedContext: false,
170	}
171}
172
173/*Inject implements Inject() method of opentracing.Tracer*/
174func (tracer *Tracer) Inject(ctx opentracing.SpanContext, format interface{}, carrier interface{}) error {
175	c, ok := ctx.(*SpanContext)
176	if !ok {
177		return opentracing.ErrInvalidSpanContext
178	}
179	if injector, ok := tracer.propagators[format]; ok {
180		return injector.Inject(c, carrier)
181	}
182	return opentracing.ErrUnsupportedFormat
183}
184
185/*Extract implements Extract() method of opentracing.Tracer*/
186func (tracer *Tracer) Extract(
187	format interface{},
188	carrier interface{},
189) (opentracing.SpanContext, error) {
190	if extractor, ok := tracer.propagators[format]; ok {
191		return extractor.Extract(carrier)
192	}
193	return nil, opentracing.ErrUnsupportedFormat
194}
195
196/*Tags return all common tags */
197func (tracer *Tracer) Tags() []opentracing.Tag {
198	return tracer.commonTags
199}
200
201/*DispatchSpan dispatches the span to a dispatcher*/
202func (tracer *Tracer) DispatchSpan(span *_Span) {
203	if tracer.dispatcher != nil {
204		tracer.dispatcher.Dispatch(span)
205	}
206}
207
208/*Close closes the tracer*/
209func (tracer *Tracer) Close() error {
210	if tracer.dispatcher != nil {
211		tracer.dispatcher.Close()
212	}
213	return nil
214}
215