Opentracing support

This commit is contained in:
Michael 2018-01-10 17:48:04 +01:00 committed by Traefiker
parent 8394549857
commit 30ffba78e6
272 changed files with 44352 additions and 63 deletions

View file

@ -0,0 +1,42 @@
package tracing
import (
"fmt"
"net/http"
"github.com/containous/traefik/log"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/urfave/negroni"
)
type entryPointMiddleware struct {
entryPoint string
*Tracing
}
// NewEntryPoint creates a new middleware that the incoming request
func (t *Tracing) NewEntryPoint(name string) negroni.Handler {
log.Debug("Added entrypoint tracing middleware")
return &entryPointMiddleware{Tracing: t, entryPoint: name}
}
func (e *entryPointMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
opNameFunc := func(r *http.Request) string {
return fmt.Sprintf("Entrypoint %s %s", e.entryPoint, r.Host)
}
ctx, _ := e.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
span := e.StartSpan(opNameFunc(r), ext.RPCServerOption(ctx))
ext.Component.Set(span, e.ServiceName)
LogRequest(span, r)
ext.SpanKindRPCServer.Set(span)
w = &statusCodeTracker{w, 200}
r = r.WithContext(opentracing.ContextWithSpan(r.Context(), span))
next(w, r)
LogResponseCode(span, w.(*statusCodeTracker).status)
span.Finish()
}

View file

@ -0,0 +1,46 @@
package tracing
import (
"fmt"
"net/http"
"github.com/containous/traefik/log"
"github.com/opentracing/opentracing-go/ext"
"github.com/urfave/negroni"
)
type forwarderMiddleware struct {
frontend string
backend string
opName string
*Tracing
}
// NewForwarderMiddleware creates a new forwarder middleware that traces the outgoing request
func (t *Tracing) NewForwarderMiddleware(frontend, backend string) negroni.Handler {
log.Debugf("Added outgoing tracing middleware %s", frontend)
return &forwarderMiddleware{
Tracing: t,
frontend: frontend,
backend: backend,
opName: fmt.Sprintf("forward %s/%s", frontend, backend),
}
}
func (f *forwarderMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
span, r, finish := StartSpan(r, f.opName, true)
defer finish()
span.SetTag("frontend.name", f.frontend)
span.SetTag("backend.name", f.backend)
ext.HTTPMethod.Set(span, r.Method)
ext.HTTPUrl.Set(span, r.URL.String())
span.SetTag("http.host", r.Host)
InjectRequestHeaders(r)
w = &statusCodeTracker{w, 200}
next(w, r)
LogResponseCode(span, w.(*statusCodeTracker).status)
}

View file

@ -0,0 +1,52 @@
package jaeger
import (
"io"
"github.com/containous/traefik/log"
"github.com/opentracing/opentracing-go"
jaegercfg "github.com/uber/jaeger-client-go/config"
jaegerlog "github.com/uber/jaeger-client-go/log"
jaegermet "github.com/uber/jaeger-lib/metrics"
)
// Name sets the name of this tracer
const Name = "jaeger"
// Config provides configuration settings for a jaeger tracer
type Config struct {
SamplingServerURL string `description:"set the sampling server url." export:"false"`
SamplingType string `description:"set the sampling type." export:"true"`
SamplingParam float64 `description:"set the sampling parameter." export:"true"`
}
// Setup sets up the tracer
func (c *Config) Setup(componentName string) (opentracing.Tracer, io.Closer, error) {
jcfg := jaegercfg.Configuration{
Sampler: &jaegercfg.SamplerConfig{
SamplingServerURL: c.SamplingServerURL,
Type: c.SamplingType,
Param: c.SamplingParam,
},
Reporter: &jaegercfg.ReporterConfig{
LogSpans: true,
},
}
jLogger := jaegerlog.StdLogger
jMetricsFactory := jaegermet.NullFactory
// Initialize tracer with a logger and a metrics factory
closer, err := jcfg.InitGlobalTracer(
componentName,
jaegercfg.Logger(jLogger),
jaegercfg.Metrics(jMetricsFactory),
)
if err != nil {
log.Warnf("Could not initialize jaeger tracer: %s", err.Error())
return nil, nil, err
}
log.Debugf("jaeger tracer configured", err)
return opentracing.GlobalTracer(), closer, nil
}

View file

@ -0,0 +1,150 @@
package tracing
import (
"fmt"
"io"
"net/http"
"github.com/containous/traefik/log"
"github.com/containous/traefik/middlewares/tracing/jaeger"
"github.com/containous/traefik/middlewares/tracing/zipkin"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
)
// Tracing middleware
type Tracing struct {
Backend string `description:"Selects the tracking backend ('jaeger','zipkin')." export:"true"`
ServiceName string `description:"Set the name for this service" export:"true"`
Jaeger *jaeger.Config `description:"Settings for jaeger"`
Zipkin *zipkin.Config `description:"Settings for zipkin"`
opentracing.Tracer
closer io.Closer
}
// Backend describes things we can use to setup tracing
type Backend interface {
Setup(serviceName string) (opentracing.Tracer, io.Closer, error)
}
type statusCodeTracker struct {
http.ResponseWriter
status int
}
func (s *statusCodeTracker) WriteHeader(status int) {
s.status = status
s.ResponseWriter.WriteHeader(status)
}
// Setup Tracing middleware
func (t *Tracing) Setup() {
var err error
switch t.Backend {
case jaeger.Name:
t.Tracer, t.closer, err = t.Jaeger.Setup(t.ServiceName)
case zipkin.Name:
t.Tracer, t.closer, err = t.Zipkin.Setup(t.ServiceName)
default:
log.Warnf("Unknown tracer %q", t.Backend)
return
}
if err != nil {
log.Warnf("Could not initialize %s tracing: %v", t.Backend, err)
return
}
return
}
// IsEnabled determines if tracing was successfully activated
func (t *Tracing) IsEnabled() bool {
if t == nil || t.Tracer == nil {
return false
}
return true
}
// Close tracer
func (t *Tracing) Close() {
if t.closer != nil {
t.closer.Close()
}
}
// 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))
if code >= 400 {
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 {
opentracing.GlobalTracer().Inject(
span.Context(),
opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(r.Header))
}
}
// 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...))
}
}
// StartSpan starts a new span from the one in the request context
func StartSpan(r *http.Request, operationName string, spanKinClient bool, opts ...opentracing.StartSpanOption) (opentracing.Span, *http.Request, func()) {
span, ctx := opentracing.StartSpanFromContext(r.Context(), operationName, opts...)
if spanKinClient {
ext.SpanKindRPCClient.Set(span)
}
r = r.WithContext(ctx)
return span, r, func() {
span.Finish()
}
}
// 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)
}
}
// SetErrorAndDebugLog flags the span associated with this request as in error and create a debug log
func SetErrorAndDebugLog(r *http.Request, format string, args ...interface{}) {
SetError(r)
log.Debugf(format, args...)
LogEventf(r, format, args...)
}
// SetErrorAndWarnLog flags the span associated with this request as in error and create a debug log
func SetErrorAndWarnLog(r *http.Request, format string, args ...interface{}) {
SetError(r)
log.Warnf(format, args...)
LogEventf(r, format, args...)
}

View file

@ -0,0 +1,66 @@
package tracing
import (
"net/http"
"github.com/urfave/negroni"
)
// NegroniHandlerWrapper is used to wrap negroni handler middleware
type NegroniHandlerWrapper struct {
name string
next negroni.Handler
clientSpanKind bool
}
// HTTPHandlerWrapper is used to wrap http handler middleware
type HTTPHandlerWrapper struct {
name string
handler http.Handler
clientSpanKind bool
}
// NewNegroniHandlerWrapper return a negroni.Handler struct
func (t *Tracing) NewNegroniHandlerWrapper(name string, handler negroni.Handler, clientSpanKind bool) negroni.Handler {
if t.IsEnabled() && handler != nil {
return &NegroniHandlerWrapper{
name: name,
next: handler,
clientSpanKind: clientSpanKind,
}
}
return handler
}
// NewHTTPHandlerWrapper return a http.Handler struct
func (t *Tracing) NewHTTPHandlerWrapper(name string, handler http.Handler, clientSpanKind bool) http.Handler {
if t.IsEnabled() && handler != nil {
return &HTTPHandlerWrapper{
name: name,
handler: handler,
clientSpanKind: clientSpanKind,
}
}
return handler
}
func (t *NegroniHandlerWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
var finish func()
_, r, finish = StartSpan(r, t.name, t.clientSpanKind)
defer finish()
if t.next != nil {
t.next.ServeHTTP(rw, r, next)
}
}
func (t *HTTPHandlerWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
var finish func()
_, r, finish = StartSpan(r, t.name, t.clientSpanKind)
defer finish()
if t.handler != nil {
t.handler.ServeHTTP(rw, r)
}
}

View file

@ -0,0 +1,40 @@
package zipkin
import (
"io"
opentracing "github.com/opentracing/opentracing-go"
zipkin "github.com/openzipkin/zipkin-go-opentracing"
)
// Name sets the name of this tracer
const Name = "zipkin"
// Config provides configuration settings for a zipkin tracer
type Config struct {
HTTPEndpoint string `description:"HTTP Endpoint to report traces to." export:"false"`
SameSpan bool `description:"Use ZipKin SameSpan RPC style traces." export:"true"`
ID128Bit bool `description:"Use ZipKin 128 bit root span IDs." export:"true"`
Debug bool `description:"Enable Zipkin debug." export:"true"`
}
// Setup sets up the tracer
func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error) {
collector, err := zipkin.NewHTTPCollector(c.HTTPEndpoint)
recorder := zipkin.NewRecorder(collector, c.Debug, "0.0.0.0:0", serviceName)
tracer, err := zipkin.NewTracer(
recorder,
zipkin.ClientServerSameSpan(c.SameSpan),
zipkin.TraceID128Bit(c.ID128Bit),
zipkin.DebugMode(c.Debug),
)
if err != nil {
return nil, nil, err
}
// Without this, child spans are getting the NOOP tracer
opentracing.SetGlobalTracer(tracer)
return tracer, collector, nil
}