1package tracing 2 3import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "net/http" 9 10 "github.com/opentracing/opentracing-go" 11 "github.com/opentracing/opentracing-go/ext" 12 "github.com/traefik/traefik/v2/pkg/log" 13) 14 15type contextKey int 16 17const ( 18 // SpanKindNoneEnum Span kind enum none. 19 SpanKindNoneEnum ext.SpanKindEnum = "none" 20 tracingKey contextKey = iota 21) 22 23// WithTracing Adds Tracing into the context. 24func WithTracing(ctx context.Context, tracing *Tracing) context.Context { 25 return context.WithValue(ctx, tracingKey, tracing) 26} 27 28// FromContext Gets Tracing from context. 29func FromContext(ctx context.Context) (*Tracing, error) { 30 if ctx == nil { 31 panic("nil context") 32 } 33 34 tracer, ok := ctx.Value(tracingKey).(*Tracing) 35 if !ok { 36 return nil, errors.New("unable to find tracing in the context") 37 } 38 return tracer, nil 39} 40 41// Backend is an abstraction for tracking backend (Jaeger, Zipkin, ...). 42type Backend interface { 43 Setup(componentName string) (opentracing.Tracer, io.Closer, error) 44} 45 46// Tracing middleware. 47type Tracing struct { 48 ServiceName string `description:"Sets the name for this service" export:"true"` 49 SpanNameLimit int `description:"Sets the maximum character limit for span names (default 0 = no limit)" export:"true"` 50 51 tracer opentracing.Tracer 52 closer io.Closer 53} 54 55// NewTracing Creates a Tracing. 56func NewTracing(serviceName string, spanNameLimit int, tracingBackend Backend) (*Tracing, error) { 57 tracing := &Tracing{ 58 ServiceName: serviceName, 59 SpanNameLimit: spanNameLimit, 60 } 61 62 var err error 63 tracing.tracer, tracing.closer, err = tracingBackend.Setup(serviceName) 64 if err != nil { 65 return nil, err 66 } 67 return tracing, nil 68} 69 70// StartSpan delegates to opentracing.Tracer. 71func (t *Tracing) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span { 72 return t.tracer.StartSpan(operationName, opts...) 73} 74 75// StartSpanf delegates to StartSpan. 76func (t *Tracing) StartSpanf(r *http.Request, spanKind ext.SpanKindEnum, opPrefix string, opParts []string, separator string, opts ...opentracing.StartSpanOption) (opentracing.Span, *http.Request, func()) { 77 operationName := generateOperationName(opPrefix, opParts, separator, t.SpanNameLimit) 78 79 return StartSpan(r, operationName, spanKind, opts...) 80} 81 82// Inject delegates to opentracing.Tracer. 83func (t *Tracing) Inject(sm opentracing.SpanContext, format, carrier interface{}) error { 84 return t.tracer.Inject(sm, format, carrier) 85} 86 87// Extract delegates to opentracing.Tracer. 88func (t *Tracing) Extract(format, carrier interface{}) (opentracing.SpanContext, error) { 89 return t.tracer.Extract(format, carrier) 90} 91 92// IsEnabled determines if tracing was successfully activated. 93func (t *Tracing) IsEnabled() bool { 94 return t != nil && t.tracer != nil 95} 96 97// Close tracer. 98func (t *Tracing) Close() { 99 if t.closer != nil { 100 err := t.closer.Close() 101 if err != nil { 102 log.WithoutContext().Warn(err) 103 } 104 } 105} 106 107// LogRequest used to create span tags from the request. 108func LogRequest(span opentracing.Span, r *http.Request) { 109 if span != nil && r != nil { 110 ext.HTTPMethod.Set(span, r.Method) 111 ext.HTTPUrl.Set(span, r.URL.String()) 112 span.SetTag("http.host", r.Host) 113 } 114} 115 116// LogResponseCode used to log response code in span. 117func LogResponseCode(span opentracing.Span, code int) { 118 if span != nil { 119 ext.HTTPStatusCode.Set(span, uint16(code)) 120 if code >= http.StatusInternalServerError { 121 ext.Error.Set(span, true) 122 } 123 } 124} 125 126// GetSpan used to retrieve span from request context. 127func GetSpan(r *http.Request) opentracing.Span { 128 return opentracing.SpanFromContext(r.Context()) 129} 130 131// InjectRequestHeaders used to inject OpenTracing headers into the request. 132func InjectRequestHeaders(r *http.Request) { 133 if span := GetSpan(r); span != nil { 134 err := opentracing.GlobalTracer().Inject( 135 span.Context(), 136 opentracing.HTTPHeaders, 137 opentracing.HTTPHeadersCarrier(r.Header)) 138 if err != nil { 139 log.FromContext(r.Context()).Error(err) 140 } 141 } 142} 143 144// LogEventf logs an event to the span in the request context. 145func LogEventf(r *http.Request, format string, args ...interface{}) { 146 if span := GetSpan(r); span != nil { 147 span.LogKV("event", fmt.Sprintf(format, args...)) 148 } 149} 150 151// StartSpan starts a new span from the one in the request context. 152func StartSpan(r *http.Request, operationName string, spanKind ext.SpanKindEnum, opts ...opentracing.StartSpanOption) (opentracing.Span, *http.Request, func()) { 153 span, ctx := opentracing.StartSpanFromContext(r.Context(), operationName, opts...) 154 155 switch spanKind { 156 case ext.SpanKindRPCClientEnum: 157 ext.SpanKindRPCClient.Set(span) 158 case ext.SpanKindRPCServerEnum: 159 ext.SpanKindRPCServer.Set(span) 160 case ext.SpanKindProducerEnum: 161 ext.SpanKindProducer.Set(span) 162 case ext.SpanKindConsumerEnum: 163 ext.SpanKindConsumer.Set(span) 164 default: 165 // noop 166 } 167 168 r = r.WithContext(ctx) 169 return span, r, func() { span.Finish() } 170} 171 172// SetError flags the span associated with this request as in error. 173func SetError(r *http.Request) { 174 if span := GetSpan(r); span != nil { 175 ext.Error.Set(span, true) 176 } 177} 178 179// SetErrorWithEvent flags the span associated with this request as in error and log an event. 180func SetErrorWithEvent(r *http.Request, format string, args ...interface{}) { 181 SetError(r) 182 LogEventf(r, format, args...) 183} 184