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
|
package tracing
import (
"crypto/tls"
"net/http"
"net/http/httptrace"
opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
otlog "github.com/opentracing/opentracing-go/log"
logkit "gitlab.com/gitlab-org/labkit/log"
)
type tracingRoundTripper struct {
delegate http.RoundTripper
config roundTripperConfig
}
func (c tracingRoundTripper) RoundTrip(req *http.Request) (res *http.Response, e error) {
tracer := opentracing.GlobalTracer()
if tracer == nil {
return c.delegate.RoundTrip(req)
}
ctx := req.Context()
var parentCtx opentracing.SpanContext
parentSpan := opentracing.SpanFromContext(ctx)
if parentSpan != nil {
parentCtx = parentSpan.Context()
}
// start a new Span to wrap HTTP request
span := opentracing.StartSpan(
c.config.getOperationName(req),
opentracing.ChildOf(parentCtx),
)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
// attach ClientTrace to the Context, and Context to request
trace := newClientTrace(span)
ctx = httptrace.WithClientTrace(ctx, trace)
req = req.WithContext(ctx)
ext.SpanKindRPCClient.Set(span)
ext.HTTPUrl.Set(span, req.URL.String())
ext.HTTPMethod.Set(span, req.Method)
carrier := opentracing.HTTPHeadersCarrier(req.Header)
err := span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, carrier)
if err != nil {
logkit.ContextLogger(ctx).WithError(err).Error("tracing span injection failed")
}
response, err := c.delegate.RoundTrip(req)
if err != nil {
span.LogFields(
otlog.String("event", "roundtrip error"),
otlog.Object("error", err),
)
} else {
span.LogFields(
otlog.String("event", "roundtrip complete"),
otlog.Int("status", response.StatusCode),
)
}
return response, err
}
func newClientTrace(span opentracing.Span) *httptrace.ClientTrace {
trace := &clientTrace{span: span}
return &httptrace.ClientTrace{
GotFirstResponseByte: trace.gotFirstResponseByte,
ConnectStart: trace.connectStart,
ConnectDone: trace.connectDone,
TLSHandshakeStart: trace.tlsHandshakeStart,
TLSHandshakeDone: trace.tlsHandshakeDone,
WroteHeaders: trace.wroteHeaders,
WroteRequest: trace.wroteRequest,
}
}
// clientTrace holds a reference to the Span and
// provides methods used as ClientTrace callbacks.
type clientTrace struct {
span opentracing.Span
}
func (h *clientTrace) gotFirstResponseByte() {
h.span.LogFields(otlog.String("event", "got first response byte"))
}
func (h *clientTrace) connectStart(network, addr string) {
h.span.LogFields(
otlog.String("event", "connect started"),
otlog.String("network", network),
otlog.String("addr", addr),
)
}
func (h *clientTrace) connectDone(network, addr string, err error) {
h.span.LogFields(
otlog.String("event", "connect done"),
otlog.String("network", network),
otlog.String("addr", addr),
otlog.Object("error", err),
)
}
func (h *clientTrace) tlsHandshakeStart() {
h.span.LogFields(otlog.String("event", "tls handshake started"))
}
func (h *clientTrace) tlsHandshakeDone(state tls.ConnectionState, err error) {
h.span.LogFields(
otlog.String("event", "tls handshake done"),
otlog.Object("error", err),
)
}
func (h *clientTrace) wroteHeaders() {
h.span.LogFields(otlog.String("event", "headers written"))
}
func (h *clientTrace) wroteRequest(info httptrace.WroteRequestInfo) {
h.span.LogFields(
otlog.String("event", "request written"),
otlog.Object("error", info.Err),
)
}
// NewRoundTripper acts as a "client-middleware" for outbound http requests
// adding instrumentation to the outbound request and then delegating to the underlying
// transport.
func NewRoundTripper(delegate http.RoundTripper, opts ...RoundTripperOption) http.RoundTripper {
config := applyRoundTripperOptions(opts)
return &tracingRoundTripper{delegate: delegate, config: config}
}
|