1package middleware
2
3import (
4	"context"
5	"fmt"
6	"net/http"
7	"strings"
8
9	opentracing "github.com/opentracing/opentracing-go"
10	"github.com/opentracing/opentracing-go/ext"
11
12	"github.com/grafana/grafana/pkg/web"
13)
14
15type contextKey struct{}
16
17var routeOperationNameKey = contextKey{}
18
19// ProvideRouteOperationName creates a named middleware responsible for populating
20// the context with the route operation name that can be used later in the request pipeline.
21// Implements routing.RegisterNamedMiddleware.
22func ProvideRouteOperationName(name string) web.Handler {
23	return func(res http.ResponseWriter, req *http.Request, c *web.Context) {
24		ctx := context.WithValue(c.Req.Context(), routeOperationNameKey, name)
25		c.Req = c.Req.WithContext(ctx)
26	}
27}
28
29// RouteOperationNameFromContext receives the route operation name from context, if set.
30func RouteOperationNameFromContext(ctx context.Context) (string, bool) {
31	if val := ctx.Value(routeOperationNameKey); val != nil {
32		op, ok := val.(string)
33		return op, ok
34	}
35
36	return "", false
37}
38
39func RequestTracing() web.Handler {
40	return func(res http.ResponseWriter, req *http.Request, c *web.Context) {
41		if strings.HasPrefix(c.Req.URL.Path, "/public/") ||
42			c.Req.URL.Path == "robots.txt" {
43			c.Next()
44			return
45		}
46
47		rw := res.(web.ResponseWriter)
48
49		tracer := opentracing.GlobalTracer()
50		wireContext, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
51		span := tracer.StartSpan(fmt.Sprintf("HTTP %s %s", req.Method, req.URL.Path), ext.RPCServerOption(wireContext))
52
53		ctx := opentracing.ContextWithSpan(req.Context(), span)
54		c.Req = req.WithContext(ctx)
55		c.Map(c.Req)
56
57		c.Next()
58
59		// Only call span.Finish when a route operation name have been set,
60		// meaning that not set the span would not be reported.
61		if routeOperation, exists := RouteOperationNameFromContext(c.Req.Context()); exists {
62			defer span.Finish()
63			span.SetOperationName(fmt.Sprintf("HTTP %s %s", req.Method, routeOperation))
64		}
65
66		status := rw.Status()
67
68		ext.HTTPStatusCode.Set(span, uint16(status))
69		ext.HTTPUrl.Set(span, req.RequestURI)
70		ext.HTTPMethod.Set(span, req.Method)
71		if status >= 400 {
72			ext.Error.Set(span, true)
73		}
74	}
75}
76