Skip to content

Commit eb9153e

Browse files
authored
Merge pull request #75 from netlify/add-tracing-package
add tracing framework
2 parents 920e809 + 03f4c7d commit eb9153e

File tree

10 files changed

+429
-1
lines changed

10 files changed

+429
-1
lines changed

go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,20 @@ require (
2424
github.com/lib/pq v1.0.0 // indirect
2525
github.com/magiconair/properties v0.0.0-20190110142458-7757cc9fdb85 // indirect
2626
github.com/mitchellh/mapstructure v1.1.2 // indirect
27-
github.com/nats-io/gnatsd v1.4.1
27+
github.com/nats-io/gnatsd v1.4.1 // indirect
2828
github.com/nats-io/go-nats v1.3.0
2929
github.com/nats-io/go-nats-streaming v0.3.4
3030
github.com/nats-io/nats-streaming-server v0.12.2 // indirect
3131
github.com/nats-io/nuid v0.0.0-20180712044959-3024a71c3cbe // indirect
3232
github.com/onsi/ginkgo v1.8.0 // indirect
3333
github.com/onsi/gomega v1.5.0 // indirect
34+
github.com/opentracing/opentracing-go v1.1.0
3435
github.com/pascaldekloe/goe v0.1.0 // indirect
3536
github.com/pelletier/go-toml v0.0.0-20181124002727-27c6b39a135b // indirect
37+
github.com/philhofer/fwd v1.0.0 // indirect
3638
github.com/pkg/errors v0.8.0
3739
github.com/prometheus/procfs v0.0.0-20190403104016-ea9eea638872 // indirect
40+
github.com/satori/go.uuid v1.2.0
3841
github.com/shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d
3942
github.com/sirupsen/logrus v1.0.5
4043
github.com/spf13/afero v1.2.1 // indirect
@@ -44,9 +47,11 @@ require (
4447
github.com/spf13/pflag v1.0.0 // indirect
4548
github.com/spf13/viper v1.0.2
4649
github.com/stretchr/testify v1.2.2
50+
github.com/tinylib/msgp v1.1.0 // indirect
4751
go.etcd.io/bbolt v1.3.2 // indirect
4852
golang.org/x/crypto v0.0.0-20161031180806-9477e0b78b9a // indirect
4953
google.golang.org/appengine v1.5.0 // indirect
54+
gopkg.in/DataDog/dd-trace-go.v1 v1.12.1
5055
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
5156
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
5257
gopkg.in/yaml.v2 v2.2.2 // indirect

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,22 @@ github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
7575
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
7676
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
7777
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
78+
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
79+
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
7880
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
7981
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
8082
github.com/pelletier/go-toml v0.0.0-20181124002727-27c6b39a135b h1:q2XlNu7oriSkQtLMfX18sYspQfYOCihpMRZNGHT1VQY=
8183
github.com/pelletier/go-toml v0.0.0-20181124002727-27c6b39a135b/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
84+
github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
85+
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
8286
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
8387
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
8488
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
8589
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
8690
github.com/prometheus/procfs v0.0.0-20190403104016-ea9eea638872 h1:0aNv3xC7DmQoy1/x1sMh18g+fihWW68LL13i8ao9kl4=
8791
github.com/prometheus/procfs v0.0.0-20190403104016-ea9eea638872/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
92+
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
93+
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
8894
github.com/shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:2ZhZbXlQKtsDYOx922QgmzuxNqhj8WcG1XlH/uIWw+Q=
8995
github.com/shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:DmcHeT/UuSDXaCVb8IijmL+fHX+FK9TLy98W7mfDXXg=
9096
github.com/sirupsen/logrus v1.0.5 h1:8c8b5uO0zS4X6RPl/sd1ENwSkIc0/H2PaHxE3udaE8I=
@@ -103,6 +109,8 @@ github.com/spf13/viper v1.0.2 h1:Ncr3ZIuJn322w2k1qmzXDnkLAdQMlJqBa9kfAH+irso=
103109
github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
104110
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
105111
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
112+
github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
113+
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
106114
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
107115
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
108116
golang.org/x/crypto v0.0.0-20161031180806-9477e0b78b9a h1:Esnda2KnXoH6s+xVTf9zaZg3o4p6d9C25WBNxxDHwOU=
@@ -119,6 +127,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
119127
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
120128
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
121129
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
130+
gopkg.in/DataDog/dd-trace-go.v1 v1.12.1 h1:zkyLw+Uq6BvGwy5hFeLVI1ePgOkqJswFPL1uOx6SSA4=
131+
gopkg.in/DataDog/dd-trace-go.v1 v1.12.1/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
122132
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
123133
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
124134
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

tracing/config.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package tracing
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/opentracing/opentracing-go"
7+
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/opentracer"
8+
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
9+
)
10+
11+
const (
12+
HeaderNFDebugLogging = "X-NF-Debug-Logging"
13+
HeaderRequestUUID = "X-BB-CLIENT-REQUEST-UUID"
14+
)
15+
16+
type Config struct {
17+
Enabled bool `default:"false"`
18+
Host string `default:"localhost"`
19+
Port string `default:"8126"`
20+
Tags map[string]string
21+
}
22+
23+
func Configure(tc *Config, svcName string) {
24+
var t opentracing.Tracer = opentracing.NoopTracer{}
25+
if tc.Enabled {
26+
tracerAddr := fmt.Sprintf("%s:%s", tc.Host, tc.Port)
27+
tracerOps := []tracer.StartOption{
28+
tracer.WithServiceName(svcName),
29+
tracer.WithAgentAddr(tracerAddr),
30+
}
31+
32+
for k, v := range tc.Tags {
33+
tracerOps = append(tracerOps, tracer.WithGlobalTag(k, v))
34+
}
35+
36+
t = opentracer.New(tracerOps...)
37+
}
38+
opentracing.SetGlobalTracer(t)
39+
}

tracing/context.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package tracing
2+
3+
import (
4+
"context"
5+
"net/http"
6+
)
7+
8+
type contextKey string
9+
10+
const tracerKey = contextKey("nf-tracer-key")
11+
12+
func WrapWithTracer(r *http.Request, rt *RequestTracer) *http.Request {
13+
return r.WithContext(context.WithValue(r.Context(), tracerKey, rt))
14+
}
15+
16+
func GetTracer(r *http.Request) *RequestTracer {
17+
val := r.Context().Value(tracerKey)
18+
if val == nil {
19+
return nil
20+
}
21+
entry, ok := val.(*RequestTracer)
22+
if ok {
23+
return entry
24+
}
25+
return nil
26+
}

tracing/logging.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package tracing
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/sirupsen/logrus"
7+
)
8+
9+
const (
10+
logKey = contextKey("nf-log-key")
11+
)
12+
13+
func requestLogger(r *http.Request, log logrus.FieldLogger) logrus.FieldLogger {
14+
if r.Header.Get(HeaderNFDebugLogging) != "" {
15+
logger := logrus.New()
16+
logger.SetLevel(logrus.DebugLevel)
17+
18+
if entry, ok := log.(*logrus.Entry); ok {
19+
log = logger.WithFields(entry.Data)
20+
}
21+
}
22+
23+
reqID := RequestID(r)
24+
25+
log = log.WithFields(logrus.Fields{
26+
"request_id": reqID,
27+
})
28+
return log
29+
}
30+
31+
func GetLogger(r *http.Request) logrus.FieldLogger {
32+
entry := GetTracer(r)
33+
if entry == nil {
34+
return logrus.NewEntry(logrus.StandardLogger())
35+
}
36+
return entry.FieldLogger
37+
}
38+
39+
// SetLogField will add the field to this log line and every one following
40+
func SetLogField(r *http.Request, key string, value interface{}) logrus.FieldLogger {
41+
entry := GetTracer(r)
42+
if entry == nil {
43+
return logrus.StandardLogger().WithField(key, value)
44+
}
45+
return entry.SetLogField(key, value)
46+
}
47+
48+
// SetLogFields will add the fields to this log line and every one following
49+
func SetLogFields(r *http.Request, fields logrus.Fields) logrus.FieldLogger {
50+
entry := GetTracer(r)
51+
if entry == nil {
52+
return logrus.StandardLogger().WithFields(fields)
53+
}
54+
55+
return entry.SetLogFields(fields)
56+
}
57+
58+
// SetFinalField will add a field to the canonical line created at in Finish. It will add
59+
// it to this line, but not every log line in between
60+
func SetFinalField(r *http.Request, key string, value interface{}) logrus.FieldLogger {
61+
entry := GetTracer(r)
62+
if entry == nil {
63+
return logrus.StandardLogger().WithField(key, value)
64+
}
65+
66+
entry.FinalFields[key] = value
67+
return entry.FieldLogger.WithField(key, value)
68+
}

tracing/middleware.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package tracing
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/sirupsen/logrus"
7+
)
8+
9+
func Middleware(log logrus.FieldLogger, svcName string) func(http.Handler) http.Handler {
10+
return func(next http.Handler) http.Handler {
11+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
12+
TrackRequest(w, r, log, svcName, next)
13+
})
14+
}
15+
}

tracing/req_tracer.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package tracing
2+
3+
import (
4+
"net/http"
5+
"time"
6+
7+
opentracing "github.com/opentracing/opentracing-go"
8+
"github.com/sirupsen/logrus"
9+
)
10+
11+
type RequestTracer struct {
12+
*trackingWriter
13+
logrus.FieldLogger
14+
15+
RequestID string
16+
FinalFields map[string]interface{}
17+
18+
remoteAddr string
19+
method string
20+
originalURL string
21+
referrer string
22+
span opentracing.Span
23+
start time.Time
24+
}
25+
26+
func NewTracer(w http.ResponseWriter, r *http.Request, log logrus.FieldLogger, service string) (http.ResponseWriter, *http.Request, *RequestTracer) {
27+
reqID := RequestID(r)
28+
log = requestLogger(r, log)
29+
r, span := WrapWithSpan(r, reqID, service)
30+
trackWriter := &trackingWriter{
31+
writer: w,
32+
log: log,
33+
}
34+
35+
rt := &RequestTracer{
36+
originalURL: r.URL.String(),
37+
method: r.Method,
38+
referrer: r.Referer(),
39+
remoteAddr: r.RemoteAddr,
40+
41+
RequestID: reqID,
42+
span: span,
43+
trackingWriter: trackWriter,
44+
FieldLogger: log,
45+
FinalFields: make(map[string]interface{}),
46+
}
47+
r = WrapWithTracer(r, rt)
48+
49+
return rt, r, rt
50+
}
51+
52+
func (rt *RequestTracer) Start() {
53+
rt.start = time.Now()
54+
rt.WithFields(logrus.Fields{
55+
"method": rt.method,
56+
"remote_addr": rt.remoteAddr,
57+
"referer": rt.referrer,
58+
"url": rt.originalURL,
59+
}).Info("Starting Request")
60+
}
61+
62+
func (rt *RequestTracer) Finish() {
63+
dur := time.Since(rt.start)
64+
65+
fields := logrus.Fields{}
66+
for k, v := range rt.FinalFields {
67+
fields[k] = v
68+
}
69+
70+
fields["status_code"] = rt.trackingWriter.status
71+
fields["rsp_bytes"] = rt.trackingWriter.rspBytes
72+
fields["url"] = rt.originalURL
73+
fields["method"] = rt.method
74+
fields["dur"] = dur.String()
75+
fields["dur_ns"] = dur.Nanoseconds()
76+
77+
rt.span.Finish()
78+
rt.WithFields(fields).Info("Completed Request")
79+
}
80+
81+
func (rt *RequestTracer) SetLogField(key string, value interface{}) logrus.FieldLogger {
82+
rt.FieldLogger = rt.FieldLogger.WithField(key, value)
83+
return rt.FieldLogger
84+
}
85+
86+
func (rt *RequestTracer) SetLogFields(fields logrus.Fields) logrus.FieldLogger {
87+
rt.FieldLogger = rt.FieldLogger.WithFields(fields)
88+
return rt.FieldLogger
89+
}

tracing/req_tracer_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package tracing
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
9+
logtest "github.com/sirupsen/logrus/hooks/test"
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestTracerLogging(t *testing.T) {
14+
// func NewTracer(w http.ResponseWriter, r *http.Request, log logrus.FieldLogger, service string) (http.ResponseWriter, *http.Request, *RequestTracer) {
15+
rec := httptest.NewRecorder()
16+
req := httptest.NewRequest(http.MethodGet, "http://whatever.com/something", nil)
17+
18+
log, hook := logtest.NewNullLogger()
19+
20+
_, r, rt := NewTracer(rec, req, log, t.Name())
21+
22+
rt.Start()
23+
e := hook.LastEntry()
24+
assert.Equal(t, 5, len(e.Data))
25+
assert.NotEmpty(t, e.Data["request_id"])
26+
assert.NotEmpty(t, e.Data["remote_addr"])
27+
assert.Empty(t, e.Data["referrer"])
28+
assert.NotEmpty(t, e.Data["method"])
29+
assert.Equal(t, "http://whatever.com/something", e.Data["url"])
30+
fmt.Println(e.Data)
31+
32+
_ = SetLogField(r, "first", "second")
33+
SetFinalField(r, "final", "line").Info("should have the final here")
34+
e = hook.LastEntry()
35+
assert.Equal(t, 3, len(e.Data))
36+
assert.NotEmpty(t, e.Data["request_id"])
37+
assert.Equal(t, "line", e.Data["final"])
38+
assert.Equal(t, "second", e.Data["first"])
39+
fmt.Println(e.Data)
40+
41+
rt.Info("Shouldn't have the final line")
42+
e = hook.LastEntry()
43+
assert.Equal(t, 2, len(e.Data))
44+
assert.NotEmpty(t, e.Data["request_id"])
45+
assert.Equal(t, "second", e.Data["first"])
46+
fmt.Println(e.Data)
47+
48+
rt.WriteHeader(http.StatusOK)
49+
rt.Write([]byte{0, 1, 2, 3})
50+
rt.Finish()
51+
e = hook.LastEntry()
52+
53+
assert.Equal(t, 9, len(e.Data))
54+
55+
// the automatic fields
56+
assert.NotEmpty(t, e.Data["dur"])
57+
assert.NotEmpty(t, e.Data["dur_ns"])
58+
assert.NotEmpty(t, e.Data["request_id"])
59+
assert.Equal(t, 4, e.Data["rsp_bytes"])
60+
assert.Equal(t, 200, e.Data["status_code"])
61+
assert.Equal(t, "http://whatever.com/something", e.Data["url"])
62+
assert.Equal(t, "GET", e.Data["method"])
63+
64+
// the value that we added above
65+
assert.Equal(t, "second", e.Data["first"])
66+
assert.Equal(t, "line", e.Data["final"])
67+
}

0 commit comments

Comments
 (0)