2018-11-14 10:18:03 +01:00
package tracing
import (
"context"
2019-03-14 09:30:04 +01:00
"errors"
2018-11-14 10:18:03 +01:00
"fmt"
"io"
"net/http"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
2020-09-16 15:46:04 +02:00
"github.com/traefik/traefik/v2/pkg/log"
2018-11-14 10:18:03 +01:00
)
type contextKey int
const (
// SpanKindNoneEnum Span kind enum none.
SpanKindNoneEnum ext . SpanKindEnum = "none"
tracingKey contextKey = iota
)
// WithTracing Adds Tracing into the context.
func WithTracing ( ctx context . Context , tracing * Tracing ) context . Context {
return context . WithValue ( ctx , tracingKey , tracing )
}
// FromContext Gets Tracing from context.
func FromContext ( ctx context . Context ) ( * Tracing , error ) {
if ctx == nil {
panic ( "nil context" )
}
tracer , ok := ctx . Value ( tracingKey ) . ( * Tracing )
if ! ok {
2019-03-14 09:30:04 +01:00
return nil , errors . New ( "unable to find tracing in the context" )
2018-11-14 10:18:03 +01:00
}
return tracer , nil
}
2019-06-28 00:16:04 +02:00
// Backend is an abstraction for tracking backend (Jaeger, Zipkin, ...).
type Backend interface {
2018-11-14 10:18:03 +01:00
Setup ( componentName string ) ( opentracing . Tracer , io . Closer , error )
}
// Tracing middleware.
type Tracing struct {
2021-09-29 10:40:14 +02:00
ServiceName string ` description:"Sets the name for this service" export:"true" `
SpanNameLimit int ` description:"Sets the maximum character limit for span names (default 0 = no limit)" export:"true" `
2018-11-14 10:18:03 +01:00
tracer opentracing . Tracer
closer io . Closer
}
// NewTracing Creates a Tracing.
2019-06-28 00:16:04 +02:00
func NewTracing ( serviceName string , spanNameLimit int , tracingBackend Backend ) ( * Tracing , error ) {
2018-11-14 10:18:03 +01:00
tracing := & Tracing {
ServiceName : serviceName ,
SpanNameLimit : spanNameLimit ,
}
var err error
2019-06-28 00:16:04 +02:00
tracing . tracer , tracing . closer , err = tracingBackend . Setup ( serviceName )
2018-11-14 10:18:03 +01:00
if err != nil {
return nil , err
}
return tracing , nil
}
// StartSpan delegates to opentracing.Tracer.
func ( t * Tracing ) StartSpan ( operationName string , opts ... opentracing . StartSpanOption ) opentracing . Span {
return t . tracer . StartSpan ( operationName , opts ... )
}
// StartSpanf delegates to StartSpan.
func ( t * Tracing ) StartSpanf ( r * http . Request , spanKind ext . SpanKindEnum , opPrefix string , opParts [ ] string , separator string , opts ... opentracing . StartSpanOption ) ( opentracing . Span , * http . Request , func ( ) ) {
operationName := generateOperationName ( opPrefix , opParts , separator , t . SpanNameLimit )
return StartSpan ( r , operationName , spanKind , opts ... )
}
// Inject delegates to opentracing.Tracer.
2020-07-07 14:42:03 +02:00
func ( t * Tracing ) Inject ( sm opentracing . SpanContext , format , carrier interface { } ) error {
2018-11-14 10:18:03 +01:00
return t . tracer . Inject ( sm , format , carrier )
}
// Extract delegates to opentracing.Tracer.
2020-07-07 14:42:03 +02:00
func ( t * Tracing ) Extract ( format , carrier interface { } ) ( opentracing . SpanContext , error ) {
2018-11-14 10:18:03 +01:00
return t . tracer . Extract ( format , carrier )
}
// IsEnabled determines if tracing was successfully activated.
func ( t * Tracing ) IsEnabled ( ) bool {
2019-10-09 11:48:04 +02:00
return t != nil && t . tracer != nil
2018-11-14 10:18:03 +01:00
}
2020-05-11 12:06:07 +02:00
// Close tracer.
2018-11-14 10:18:03 +01:00
func ( t * Tracing ) Close ( ) {
if t . closer != nil {
err := t . closer . Close ( )
if err != nil {
log . WithoutContext ( ) . Warn ( err )
}
}
}
// LogRequest used to create span tags from the request.
func LogRequest ( span opentracing . Span , r * http . Request ) {
if span != nil && r != nil {
ext . HTTPMethod . Set ( span , r . Method )
ext . HTTPUrl . Set ( span , r . URL . String ( ) )
span . SetTag ( "http.host" , r . Host )
}
}
// LogResponseCode used to log response code in span.
func LogResponseCode ( span opentracing . Span , code int ) {
if span != nil {
ext . HTTPStatusCode . Set ( span , uint16 ( code ) )
2021-04-14 05:20:03 -05:00
if code >= http . StatusInternalServerError {
2018-11-14 10:18:03 +01:00
ext . Error . Set ( span , true )
}
}
}
// GetSpan used to retrieve span from request context.
func GetSpan ( r * http . Request ) opentracing . Span {
return opentracing . SpanFromContext ( r . Context ( ) )
}
// InjectRequestHeaders used to inject OpenTracing headers into the request.
func InjectRequestHeaders ( r * http . Request ) {
if span := GetSpan ( r ) ; span != nil {
err := opentracing . GlobalTracer ( ) . Inject (
span . Context ( ) ,
opentracing . HTTPHeaders ,
2019-12-02 14:18:08 +01:00
opentracing . HTTPHeadersCarrier ( r . Header ) )
2018-11-14 10:18:03 +01:00
if err != nil {
log . FromContext ( r . Context ( ) ) . Error ( err )
}
}
}
// LogEventf logs an event to the span in the request context.
func LogEventf ( r * http . Request , format string , args ... interface { } ) {
if span := GetSpan ( r ) ; span != nil {
span . LogKV ( "event" , fmt . Sprintf ( format , args ... ) )
}
}
2020-05-11 12:06:07 +02:00
// StartSpan starts a new span from the one in the request context.
2018-11-14 10:18:03 +01:00
func StartSpan ( r * http . Request , operationName string , spanKind ext . SpanKindEnum , opts ... opentracing . StartSpanOption ) ( opentracing . Span , * http . Request , func ( ) ) {
span , ctx := opentracing . StartSpanFromContext ( r . Context ( ) , operationName , opts ... )
switch spanKind {
case ext . SpanKindRPCClientEnum :
ext . SpanKindRPCClient . Set ( span )
case ext . SpanKindRPCServerEnum :
ext . SpanKindRPCServer . Set ( span )
case ext . SpanKindProducerEnum :
ext . SpanKindProducer . Set ( span )
case ext . SpanKindConsumerEnum :
ext . SpanKindConsumer . Set ( span )
default :
// noop
}
r = r . WithContext ( ctx )
2019-10-09 11:48:04 +02:00
return span , r , func ( ) { span . Finish ( ) }
2018-11-14 10:18:03 +01:00
}
// SetError flags the span associated with this request as in error.
func SetError ( r * http . Request ) {
if span := GetSpan ( r ) ; span != nil {
ext . Error . Set ( span , true )
}
}
// SetErrorWithEvent flags the span associated with this request as in error and log an event.
func SetErrorWithEvent ( r * http . Request , format string , args ... interface { } ) {
SetError ( r )
LogEventf ( r , format , args ... )
}