gitlab.com/gitlab-org/labkit@v1.21.0/tracing/outbound_http.go (about)

     1  package tracing
     2  
     3  import (
     4  	"crypto/tls"
     5  	"net/http"
     6  	"net/http/httptrace"
     7  
     8  	opentracing "github.com/opentracing/opentracing-go"
     9  	"github.com/opentracing/opentracing-go/ext"
    10  	otlog "github.com/opentracing/opentracing-go/log"
    11  	logkit "gitlab.com/gitlab-org/labkit/log"
    12  )
    13  
    14  type tracingRoundTripper struct {
    15  	delegate http.RoundTripper
    16  	config   roundTripperConfig
    17  }
    18  
    19  func (c tracingRoundTripper) RoundTrip(req *http.Request) (res *http.Response, e error) {
    20  	tracer := opentracing.GlobalTracer()
    21  	if tracer == nil {
    22  		return c.delegate.RoundTrip(req)
    23  	}
    24  
    25  	ctx := req.Context()
    26  
    27  	var parentCtx opentracing.SpanContext
    28  	parentSpan := opentracing.SpanFromContext(ctx)
    29  	if parentSpan != nil {
    30  		parentCtx = parentSpan.Context()
    31  	}
    32  
    33  	// start a new Span to wrap HTTP request
    34  	span := opentracing.StartSpan(
    35  		c.config.getOperationName(req),
    36  		opentracing.ChildOf(parentCtx),
    37  	)
    38  	defer span.Finish()
    39  
    40  	ctx = opentracing.ContextWithSpan(ctx, span)
    41  
    42  	// attach ClientTrace to the Context, and Context to request
    43  	trace := newClientTrace(span)
    44  	ctx = httptrace.WithClientTrace(ctx, trace)
    45  	req = req.WithContext(ctx)
    46  
    47  	ext.SpanKindRPCClient.Set(span)
    48  	ext.HTTPUrl.Set(span, req.URL.String())
    49  	ext.HTTPMethod.Set(span, req.Method)
    50  
    51  	carrier := opentracing.HTTPHeadersCarrier(req.Header)
    52  	err := span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, carrier)
    53  
    54  	if err != nil {
    55  		logkit.ContextLogger(ctx).WithError(err).Error("tracing span injection failed")
    56  	}
    57  
    58  	response, err := c.delegate.RoundTrip(req)
    59  
    60  	if err != nil {
    61  		span.LogFields(
    62  			otlog.String("event", "roundtrip error"),
    63  			otlog.Object("error", err),
    64  		)
    65  	} else {
    66  		span.LogFields(
    67  			otlog.String("event", "roundtrip complete"),
    68  			otlog.Int("status", response.StatusCode),
    69  		)
    70  	}
    71  
    72  	return response, err
    73  }
    74  
    75  func newClientTrace(span opentracing.Span) *httptrace.ClientTrace {
    76  	trace := &clientTrace{span: span}
    77  	return &httptrace.ClientTrace{
    78  		GotFirstResponseByte: trace.gotFirstResponseByte,
    79  		ConnectStart:         trace.connectStart,
    80  		ConnectDone:          trace.connectDone,
    81  		TLSHandshakeStart:    trace.tlsHandshakeStart,
    82  		TLSHandshakeDone:     trace.tlsHandshakeDone,
    83  		WroteHeaders:         trace.wroteHeaders,
    84  		WroteRequest:         trace.wroteRequest,
    85  	}
    86  }
    87  
    88  // clientTrace holds a reference to the Span and
    89  // provides methods used as ClientTrace callbacks.
    90  type clientTrace struct {
    91  	span opentracing.Span
    92  }
    93  
    94  func (h *clientTrace) gotFirstResponseByte() {
    95  	h.span.LogFields(otlog.String("event", "got first response byte"))
    96  }
    97  
    98  func (h *clientTrace) connectStart(network, addr string) {
    99  	h.span.LogFields(
   100  		otlog.String("event", "connect started"),
   101  		otlog.String("network", network),
   102  		otlog.String("addr", addr),
   103  	)
   104  }
   105  
   106  func (h *clientTrace) connectDone(network, addr string, err error) {
   107  	h.span.LogFields(
   108  		otlog.String("event", "connect done"),
   109  		otlog.String("network", network),
   110  		otlog.String("addr", addr),
   111  		otlog.Object("error", err),
   112  	)
   113  }
   114  
   115  func (h *clientTrace) tlsHandshakeStart() {
   116  	h.span.LogFields(otlog.String("event", "tls handshake started"))
   117  }
   118  
   119  func (h *clientTrace) tlsHandshakeDone(state tls.ConnectionState, err error) {
   120  	h.span.LogFields(
   121  		otlog.String("event", "tls handshake done"),
   122  		otlog.Object("error", err),
   123  	)
   124  }
   125  
   126  func (h *clientTrace) wroteHeaders() {
   127  	h.span.LogFields(otlog.String("event", "headers written"))
   128  }
   129  
   130  func (h *clientTrace) wroteRequest(info httptrace.WroteRequestInfo) {
   131  	h.span.LogFields(
   132  		otlog.String("event", "request written"),
   133  		otlog.Object("error", info.Err),
   134  	)
   135  }
   136  
   137  // NewRoundTripper acts as a "client-middleware" for outbound http requests
   138  // adding instrumentation to the outbound request and then delegating to the underlying
   139  // transport.
   140  func NewRoundTripper(delegate http.RoundTripper, opts ...RoundTripperOption) http.RoundTripper {
   141  	config := applyRoundTripperOptions(opts)
   142  	return &tracingRoundTripper{delegate: delegate, config: config}
   143  }