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

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package newrelic
     5  
     6  import (
     7  	"net/http"
     8  )
     9  
    10  // instrumentation.go contains helpers built on the lower level api.
    11  
    12  // WrapHandle instruments http.Handler handlers with transactions.  To
    13  // instrument this code:
    14  //
    15  //    http.Handle("/foo", myHandler)
    16  //
    17  // Perform this replacement:
    18  //
    19  //    http.Handle(newrelic.WrapHandle(app, "/foo", myHandler))
    20  //
    21  // WrapHandle adds the Transaction to the request's context.  Access it using
    22  // FromContext to add attributes, create segments, or notice errors:
    23  //
    24  //	func myHandler(rw ResponseWriter, req *Request) {
    25  //		if txn := newrelic.FromContext(req.Context()); nil != txn {
    26  //			txn.AddAttribute("customerLevel", "gold")
    27  //		}
    28  //	}
    29  //
    30  // This function is safe to call if app is nil.
    31  func WrapHandle(app Application, pattern string, handler http.Handler) (string, http.Handler) {
    32  	if app == nil {
    33  		return pattern, handler
    34  	}
    35  	return pattern, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    36  		txn := app.StartTransaction(pattern, w, r)
    37  		defer txn.End()
    38  
    39  		r = RequestWithTransactionContext(r, txn)
    40  
    41  		handler.ServeHTTP(txn, r)
    42  	})
    43  }
    44  
    45  // WrapHandleFunc instruments handler functions using transactions.  To
    46  // instrument this code:
    47  //
    48  //	http.HandleFunc("/users", func(w http.ResponseWriter, req *http.Request) {
    49  //		io.WriteString(w, "users page")
    50  //	})
    51  //
    52  // Perform this replacement:
    53  //
    54  //	http.HandleFunc(WrapHandleFunc(app, "/users", func(w http.ResponseWriter, req *http.Request) {
    55  //		io.WriteString(w, "users page")
    56  //	}))
    57  //
    58  // WrapHandleFunc adds the Transaction to the request's context.  Access it using
    59  // FromContext to add attributes, create segments, or notice errors:
    60  //
    61  //	http.HandleFunc(WrapHandleFunc(app, "/users", func(w http.ResponseWriter, req *http.Request) {
    62  //		if txn := newrelic.FromContext(req.Context()); nil != txn {
    63  //			txn.AddAttribute("customerLevel", "gold")
    64  //		}
    65  //		io.WriteString(w, "users page")
    66  //	}))
    67  //
    68  // This function is safe to call if app is nil.
    69  func WrapHandleFunc(app Application, pattern string, handler func(http.ResponseWriter, *http.Request)) (string, func(http.ResponseWriter, *http.Request)) {
    70  	p, h := WrapHandle(app, pattern, http.HandlerFunc(handler))
    71  	return p, func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) }
    72  }
    73  
    74  // NewRoundTripper creates an http.RoundTripper to instrument external requests
    75  // and add distributed tracing headers.  The RoundTripper returned creates an
    76  // external segment before delegating to the original RoundTripper provided (or
    77  // http.DefaultTransport if none is provided).  If the Transaction parameter is
    78  // nil then the RoundTripper will look for a Transaction in the request's
    79  // context (using FromContext).  Using a nil Transaction is STRONGLY recommended
    80  // because it allows the same RoundTripper (and client) to be reused for
    81  // multiple transactions.
    82  func NewRoundTripper(txn Transaction, original http.RoundTripper) http.RoundTripper {
    83  	return roundTripperFunc(func(request *http.Request) (*http.Response, error) {
    84  		// The specification of http.RoundTripper requires that the request is never modified.
    85  		request = cloneRequest(request)
    86  		segment := StartExternalSegment(txn, request)
    87  
    88  		if nil == original {
    89  			original = http.DefaultTransport
    90  		}
    91  		response, err := original.RoundTrip(request)
    92  
    93  		segment.Response = response
    94  		segment.End()
    95  
    96  		return response, err
    97  	})
    98  }
    99  
   100  // cloneRequest mimics implementation of
   101  // https://godoc.org/github.com/google/go-github/github#BasicAuthTransport.RoundTrip
   102  func cloneRequest(r *http.Request) *http.Request {
   103  	// shallow copy of the struct
   104  	r2 := new(http.Request)
   105  	*r2 = *r
   106  	// deep copy of the Header
   107  	r2.Header = make(http.Header, len(r.Header))
   108  	for k, s := range r.Header {
   109  		r2.Header[k] = append([]string(nil), s...)
   110  	}
   111  	return r2
   112  }
   113  
   114  type roundTripperFunc func(*http.Request) (*http.Response, error)
   115  
   116  func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }