/
middleware.go
112 lines (95 loc) 路 3.51 KB
/
middleware.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
package tracinghttp
import (
"errors"
"io"
"net/http"
"github.com/bwplotka/tracing-go/tracing"
"github.com/felixge/httpsnoop"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/propagation"
semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
)
// Middleware instruments net/http server.
// The "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" module instruments many too things.
// Here we want to just focus on tracing.
type Middleware struct {
tracer *tracing.Tracer
}
func NewMiddleware(tracer *tracing.Tracer) *Middleware {
return &Middleware{tracer: tracer}
}
func (m *Middleware) WrapHandler(name string, next http.Handler) http.HandlerFunc {
propagator := propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})
return func(w http.ResponseWriter, r *http.Request) {
ctx, span := m.tracer.StartSpan(name, tracing.WithTracerStartSpanContext(
propagator.Extract(r.Context(), propagation.HeaderCarrier(r.Header))),
)
span.SetAttributes(attrToKv(semconv.NetAttributesFromHTTPRequest("tcp", r)...))
span.SetAttributes(attrToKv(semconv.EndUserAttributesFromHTTPRequest(r)...))
span.SetAttributes(attrToKv(semconv.HTTPServerAttributesFromHTTPRequest(name, "", r)...))
// TODO(bwplotka): Add option to turn this off, this might be too much - we are getting in the world of profiling.
readRecordFunc := func(n int64) {
span.AddEvent("read", string(otelhttp.ReadBytesKey), n)
}
var bw bodyWrapper
if r.Body != nil {
bw.ReadCloser = r.Body
bw.record = readRecordFunc
r.Body = &bw
}
// TODO(bwplotka): Add option to turn this off, this might be too much - we are getting in the world of profiling.
writeRecordFunc := func(n int64) {
span.AddEvent("write", string(otelhttp.WroteBytesKey), n)
}
rww := &respWriterWrapper{ResponseWriter: w, record: writeRecordFunc}
w = httpsnoop.Wrap(w, httpsnoop.Hooks{
Header: func(httpsnoop.HeaderFunc) httpsnoop.HeaderFunc {
return rww.Header
},
Write: func(httpsnoop.WriteFunc) httpsnoop.WriteFunc {
return rww.Write
},
WriteHeader: func(httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
return rww.WriteHeader
},
})
// Perform handler.
next.ServeHTTP(w, r.WithContext(ctx))
var postServeAttrs []interface{}
// Request behaviour.
if bw.readBytes > 0 {
postServeAttrs = append(postServeAttrs, string(otelhttp.ReadBytesKey), bw.readBytes)
}
if bw.lastReadError != nil && bw.lastReadError != io.EOF {
postServeAttrs = append(postServeAttrs, string(otelhttp.ReadErrorKey), bw.lastReadError.Error())
}
// Writer behaviour.
if rww.writtenBytes > 0 {
postServeAttrs = append(postServeAttrs, string(otelhttp.WroteBytesKey), rww.writtenBytes)
}
if rww.lastWriteErr != nil && rww.lastWriteErr != io.EOF {
postServeAttrs = append(postServeAttrs, string(otelhttp.WriteErrorKey), rww.lastWriteErr.Error())
}
if rww.statusCode > 0 {
postServeAttrs = append(postServeAttrs, attrToKv(semconv.HTTPAttributesFromHTTPStatusCode(rww.statusCode)...))
}
span.SetAttributes(postServeAttrs...)
if rww.statusCode == http.StatusOK {
span.End(nil)
return
}
span.End(errors.New("non-200 status code"))
}
}
func attrToKv(kvs ...attribute.KeyValue) []interface{} {
if len(kvs) == 0 {
return nil
}
keyvals := make([]interface{}, 0, len(kvs)*2)
for _, kv := range kvs {
keyvals = append(keyvals, string(kv.Key))
keyvals = append(keyvals, kv.Value.AsString())
}
return keyvals
}