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