Opentracing support
This commit is contained in:
parent
8394549857
commit
30ffba78e6
272 changed files with 44352 additions and 63 deletions
42
middlewares/tracing/entrypoint.go
Normal file
42
middlewares/tracing/entrypoint.go
Normal 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()
|
||||
}
|
46
middlewares/tracing/forwarder.go
Normal file
46
middlewares/tracing/forwarder.go
Normal 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)
|
||||
}
|
52
middlewares/tracing/jaeger/jaeger.go
Normal file
52
middlewares/tracing/jaeger/jaeger.go
Normal 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
|
||||
}
|
150
middlewares/tracing/tracing.go
Normal file
150
middlewares/tracing/tracing.go
Normal 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...)
|
||||
}
|
66
middlewares/tracing/wrapper.go
Normal file
66
middlewares/tracing/wrapper.go
Normal 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)
|
||||
}
|
||||
|
||||
}
|
40
middlewares/tracing/zipkin/zipkin.go
Normal file
40
middlewares/tracing/zipkin/zipkin.go
Normal 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue