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