-
Notifications
You must be signed in to change notification settings - Fork 3
/
tracer.go
147 lines (123 loc) · 3.71 KB
/
tracer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package fire
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/globalsign/mgo/bson"
"github.com/opentracing/opentracing-go"
)
// RootTracer is a middleware that can be used to create root trace span for an
// incoming request.
func RootTracer() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// split url
segments := strings.Split(r.URL.Path, "/")
// replace ids
for i, s := range segments {
if bson.IsObjectIdHex(s) {
segments[i] = ":id"
}
}
// construct name
path := strings.Join(segments, "/")
name := fmt.Sprintf("%s %s", r.Method, path)
// create root span from request
tracer := NewTracerFromRequest(r, name)
tracer.Tag("http.proto", r.Proto)
tracer.Tag("http.method", r.Method)
tracer.Tag("http.host", r.Host)
tracer.Tag("peer.address", r.RemoteAddr)
tracer.Log("http.url", r.URL.String())
tracer.Log("http.length", r.ContentLength)
tracer.Log("http.header", r.Header)
r = r.WithContext(tracer.Context(r.Context()))
defer tracer.Finish(true)
// call next handler
next.ServeHTTP(w, r)
})
}
}
// Tracer provides a simple wrapper around the opentracing API to instrument
// and trace code.
type Tracer struct {
root opentracing.Span
spans []opentracing.Span
}
// NewTracerFromRequest returns a new tracer that has a root span derived from
// the specified request. A span previously added to the request context using
// Context is automatically used as the parent.
func NewTracerFromRequest(r *http.Request, name string) *Tracer {
span, _ := opentracing.StartSpanFromContext(r.Context(), name)
return NewTracer(span)
}
// NewTracerWithRoot returns a new tracer that has a root span created with the
// specified name.
func NewTracerWithRoot(name string) *Tracer {
return NewTracer(opentracing.StartSpan(name))
}
// NewTracer returns a new tracer with the specified root span.
func NewTracer(root opentracing.Span) *Tracer {
return &Tracer{
root: root,
spans: make([]opentracing.Span, 0, 32),
}
}
// Push will add a new span on to the stack. Successful spans must be finished by
// calling Pop. If the code panics or an error is returned the last pushed span
// will be flagged with the error and a leftover spans are popped.
func (t *Tracer) Push(name string) {
// get context
var ctx opentracing.SpanContext
if len(t.spans) > 0 {
ctx = t.Last().Context()
} else {
ctx = t.root.Context()
}
// create new span
span := opentracing.StartSpan(name, opentracing.ChildOf(ctx))
// push span
t.spans = append(t.spans, span)
}
// Last returns the last pushed span or the root span.
func (t *Tracer) Last() opentracing.Span {
// return root if empty
if len(t.spans) == 0 {
return t.root
}
return t.spans[len(t.spans)-1]
}
// Tag adds a tag to the last pushed span.
func (t *Tracer) Tag(key string, value interface{}) {
t.Last().SetTag(key, value)
}
// Log adds a log to the last pushed span.
func (t *Tracer) Log(key string, value interface{}) {
t.Last().LogKV(key, value)
}
// Context returns a new context with the latest span stored as a reference for
// handlers that will call NewTracerFromRequest or similar.
func (t *Tracer) Context(ctx context.Context) context.Context {
return opentracing.ContextWithSpan(ctx, t.Last())
}
// Pop finishes and removes the last pushed span.
func (t *Tracer) Pop() {
// check list
if len(t.spans) == 0 {
return
}
// finish last span
t.Last().Finish()
// resize slice
t.spans = t.spans[:len(t.spans)-1]
}
// Finish will finish all leftover spans and the root span if requested.
func (t *Tracer) Finish(root bool) {
for _, span := range t.spans {
span.Finish()
}
if root {
t.root.Finish()
}
}