File: outbound_http.go

package info (click to toggle)
golang-gitlab-gitlab-org-labkit 1.17.0-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,092 kB
  • sloc: sh: 210; javascript: 49; makefile: 4
file content (143 lines) | stat: -rw-r--r-- 3,884 bytes parent folder | download | duplicates (4)
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}
}