github.com/newrelic/go-agent@v3.26.0+incompatible/_integrations/nrb3/nrb3.go (about)

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package nrb3
     5  
     6  import (
     7  	"net/http"
     8  	"time"
     9  
    10  	newrelic "github.com/newrelic/go-agent"
    11  	"github.com/newrelic/go-agent/internal"
    12  )
    13  
    14  func init() { internal.TrackUsage("integration", "b3") }
    15  
    16  // NewRoundTripper creates an `http.RoundTripper` to instrument external
    17  // requests.  The RoundTripper returned creates an external segment and adds B3
    18  // tracing headers to each request if and only if a `newrelic.Transaction`
    19  // (https://godoc.org/github.com/newrelic/go-agent#Transaction) is found in the
    20  // `http.Request`'s context.  It then delegates to the original RoundTripper
    21  // provided (or http.DefaultTransport if none is provided).
    22  func NewRoundTripper(original http.RoundTripper) http.RoundTripper {
    23  	if nil == original {
    24  		original = http.DefaultTransport
    25  	}
    26  	return &b3Transport{
    27  		idGen:    internal.NewTraceIDGenerator(int64(time.Now().UnixNano())),
    28  		original: original,
    29  	}
    30  }
    31  
    32  // cloneRequest mimics implementation of
    33  // https://godoc.org/github.com/google/go-github/github#BasicAuthTransport.RoundTrip
    34  func cloneRequest(r *http.Request) *http.Request {
    35  	// shallow copy of the struct
    36  	r2 := new(http.Request)
    37  	*r2 = *r
    38  	// deep copy of the Header
    39  	r2.Header = make(http.Header, len(r.Header))
    40  	for k, s := range r.Header {
    41  		r2.Header[k] = append([]string(nil), s...)
    42  	}
    43  	return r2
    44  }
    45  
    46  type b3Transport struct {
    47  	idGen    *internal.TraceIDGenerator
    48  	original http.RoundTripper
    49  }
    50  
    51  func txnSampled(txn newrelic.Transaction) string {
    52  	if txn.IsSampled() {
    53  		return "1"
    54  	}
    55  	return "0"
    56  }
    57  
    58  func addHeader(request *http.Request, key, val string) {
    59  	if val != "" {
    60  		request.Header.Add(key, val)
    61  	}
    62  }
    63  
    64  func (t *b3Transport) RoundTrip(request *http.Request) (*http.Response, error) {
    65  	if txn := newrelic.FromContext(request.Context()); nil != txn {
    66  		// The specification of http.RoundTripper requires that the request is never modified.
    67  		request = cloneRequest(request)
    68  		segment := &newrelic.ExternalSegment{
    69  			StartTime: newrelic.StartSegmentNow(txn),
    70  			Request:   request,
    71  		}
    72  		defer segment.End()
    73  
    74  		md := txn.GetTraceMetadata()
    75  		addHeader(request, "X-B3-TraceId", md.TraceID)
    76  		addHeader(request, "X-B3-SpanId", t.idGen.GenerateTraceID())
    77  		addHeader(request, "X-B3-ParentSpanId", md.SpanID)
    78  		addHeader(request, "X-B3-Sampled", txnSampled(txn))
    79  	}
    80  
    81  	return t.original.RoundTrip(request)
    82  }