diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index afbf542ea09..ac8cde887f0 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -319,6 +319,8 @@ func (hs *HTTPServer) applyRoutes() { func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() { m := hs.macaron + m.Use(middleware.RequestTracing()) + m.Use(middleware.Logger(hs.Cfg)) if hs.Cfg.EnableGzip { diff --git a/pkg/middleware/request_tracing.go b/pkg/middleware/request_tracing.go index c35c2a00734..2a531c0533c 100644 --- a/pkg/middleware/request_tracing.go +++ b/pkg/middleware/request_tracing.go @@ -1,8 +1,10 @@ package middleware import ( + "context" "fmt" "net/http" + "strings" opentracing "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" @@ -10,20 +12,56 @@ import ( "gopkg.in/macaron.v1" ) -func RequestTracing(handler string) macaron.Handler { +type contextKey struct{} + +var routeOperationNameKey = contextKey{} + +// ProvideRouteOperationName creates a named middleware responsible for populating +// the context with the route operation name that can be used later in the request pipeline. +// Implements routing.RegisterNamedMiddleware. +func ProvideRouteOperationName(name string) macaron.Handler { return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) { + ctx := context.WithValue(c.Req.Context(), routeOperationNameKey, name) + c.Req.Request = c.Req.WithContext(ctx) + } +} + +// RouteOperationNameFromContext receives the route operation name from context, if set. +func RouteOperationNameFromContext(ctx context.Context) (string, bool) { + if val := ctx.Value(routeOperationNameKey); val != nil { + op, ok := val.(string) + return op, ok + } + + return "", false +} + +func RequestTracing() macaron.Handler { + return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) { + if strings.HasPrefix(c.Req.URL.Path, "/public/") || + c.Req.URL.Path == "robots.txt" { + c.Next() + return + } + rw := res.(macaron.ResponseWriter) tracer := opentracing.GlobalTracer() wireContext, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header)) - span := tracer.StartSpan(fmt.Sprintf("HTTP %s", handler), ext.RPCServerOption(wireContext)) - defer span.Finish() + span := tracer.StartSpan(fmt.Sprintf("HTTP %s %s", req.Method, req.URL.Path), ext.RPCServerOption(wireContext)) ctx := opentracing.ContextWithSpan(req.Context(), span) c.Req.Request = req.WithContext(ctx) c.Next() + // Only call span.Finish when a route operation name have been set, + // meaning that not set the span would not be reported. + if routeOperation, exists := RouteOperationNameFromContext(c.Req.Context()); exists { + defer span.Finish() + span.SetOperationName(fmt.Sprintf("HTTP %s %s", req.Method, routeOperation)) + } + status := rw.Status() ext.HTTPStatusCode.Set(span, uint16(status)) diff --git a/pkg/server/server.go b/pkg/server/server.go index fcd5132e6ce..dc3744aab60 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -273,7 +273,7 @@ func (s *Server) buildServiceGraph(services []*registry.Descriptor) error { objs := []interface{}{ bus.GetBus(), s.cfg, - routing.NewRouteRegister(middleware.RequestTracing, middleware.RequestMetrics(s.cfg)), + routing.NewRouteRegister(middleware.ProvideRouteOperationName, middleware.RequestMetrics(s.cfg)), localcache.New(5*time.Minute, 10*time.Minute), s, } diff --git a/pkg/services/contexthandler/contexthandler.go b/pkg/services/contexthandler/contexthandler.go index f858df42c1a..87216dc5318 100644 --- a/pkg/services/contexthandler/contexthandler.go +++ b/pkg/services/contexthandler/contexthandler.go @@ -22,6 +22,7 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" + cw "github.com/weaveworks/common/middleware" "gopkg.in/macaron.v1" ) @@ -70,6 +71,11 @@ func (h *ContextHandler) Middleware(c *macaron.Context) { Logger: log.New("context"), } + traceID, exists := cw.ExtractTraceID(c.Req.Request.Context()) + if exists { + ctx.Logger = ctx.Logger.New("traceID", traceID) + } + const headerName = "X-Grafana-Org-Id" orgID := int64(0) orgIDHeader := ctx.Req.Header.Get(headerName)