From a137f011c1f86d9010f4b4d4d11a75f7d02469c7 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Wed, 25 Oct 2023 15:33:57 -0400 Subject: [PATCH 1/9] move fasthttp out of core library, and into integration package --- v3/go.mod | 11 +++ v3/integrations/nrfasthttp/go.mod | 17 +++- v3/integrations/nrfasthttp/instrumentation.go | 66 +++++++++++++ .../nrfasthttp/instrumentation_test.go | 56 +++++++++++ v3/integrations/nrfasthttp/segment.go | 56 +++++++++++ v3/integrations/nrfasthttp/segment_test.go | 43 ++++++++ v3/newrelic/context.go | 14 --- v3/newrelic/instrumentation.go | 98 +++++-------------- v3/newrelic/internal_17_test.go | 43 -------- v3/newrelic/internal_context_test.go | 25 ----- v3/newrelic/segments.go | 50 ++++------ 11 files changed, 289 insertions(+), 190 deletions(-) create mode 100644 v3/integrations/nrfasthttp/instrumentation.go create mode 100644 v3/integrations/nrfasthttp/instrumentation_test.go create mode 100644 v3/integrations/nrfasthttp/segment.go create mode 100644 v3/integrations/nrfasthttp/segment_test.go diff --git a/v3/go.mod b/v3/go.mod index 02c244f6b..f3717dad6 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -8,6 +8,17 @@ require ( google.golang.org/grpc v1.54.0 ) +require ( + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/klauspost/compress v1.16.3 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect + google.golang.org/protobuf v1.28.1 // indirect +) + retract v3.22.0 // release process error corrected in v3.22.1 retract v3.25.0 // release process error corrected in v3.25.1 diff --git a/v3/integrations/nrfasthttp/go.mod b/v3/integrations/nrfasthttp/go.mod index 7f2b81134..a55eb4945 100644 --- a/v3/integrations/nrfasthttp/go.mod +++ b/v3/integrations/nrfasthttp/go.mod @@ -4,7 +4,20 @@ go 1.19 require ( github.com/newrelic/go-agent/v3 v3.26.0 - github.com/stretchr/testify v1.8.4 - github.com/valyala/fasthttp v1.48.0 + github.com/valyala/fasthttp v1.49.0 ) + +require ( + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/klauspost/compress v1.16.3 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect + google.golang.org/grpc v1.54.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect +) + replace github.com/newrelic/go-agent/v3 => ../.. diff --git a/v3/integrations/nrfasthttp/instrumentation.go b/v3/integrations/nrfasthttp/instrumentation.go new file mode 100644 index 000000000..5b3e528f0 --- /dev/null +++ b/v3/integrations/nrfasthttp/instrumentation.go @@ -0,0 +1,66 @@ +package nrfasthttp + +import ( + "net/http" + + "github.com/newrelic/go-agent/v3/newrelic" + "github.com/valyala/fasthttp" + "github.com/valyala/fasthttp/fasthttpadaptor" +) + +type fasthttpWrapperResponse struct { + ctx *fasthttp.RequestCtx +} + +func (rw fasthttpWrapperResponse) Header() http.Header { + hdrs := http.Header{} + rw.ctx.Request.Header.VisitAll(func(key, value []byte) { + hdrs.Add(string(key), string(value)) + }) + return hdrs +} + +func (rw fasthttpWrapperResponse) Write(b []byte) (int, error) { + return rw.ctx.Write(b) +} + +func (rw fasthttpWrapperResponse) WriteHeader(code int) { + rw.ctx.SetStatusCode(code) +} + +func WrapHandleFunc(app *newrelic.Application, pattern string, handler func(*fasthttp.RequestCtx), options ...newrelic.TraceOption) (string, func(*fasthttp.RequestCtx)) { + // add the wrapped function to the trace options as the source code reference point + // (to the beginning of the option list, so that the user can override this) + + p, h := WrapHandle(app, pattern, fasthttp.RequestHandler(handler), options...) + return p, func(ctx *fasthttp.RequestCtx) { h(ctx) } +} + +func WrapHandle(app *newrelic.Application, pattern string, handler fasthttp.RequestHandler, options ...newrelic.TraceOption) (string, fasthttp.RequestHandler) { + if app == nil { + return pattern, handler + } + + // add the wrapped function to the trace options as the source code reference point + // (but only if we know we're collecting CLM for this transaction and the user didn't already + // specify a different code location explicitly). + return pattern, func(ctx *fasthttp.RequestCtx) { + cache := newrelic.NewCachedCodeLocation() + txnOptionList := newrelic.AddCodeLevelMetricsTraceOptions(app, options, cache, handler) + method := string(ctx.Method()) + path := string(ctx.Path()) + txn := app.StartTransaction(method+" "+path, txnOptionList...) + ctx.SetUserValue("transaction", txn) + defer txn.End() + r := &http.Request{} + fasthttpadaptor.ConvertRequest(ctx, r, true) + resp := fasthttpWrapperResponse{ctx: ctx} + + txn.SetWebResponse(resp) + txn.SetWebRequestHTTP(r) + + // r = newrelic.RequestWithTransactionContext(r, txn) + + handler(ctx) + } +} diff --git a/v3/integrations/nrfasthttp/instrumentation_test.go b/v3/integrations/nrfasthttp/instrumentation_test.go new file mode 100644 index 000000000..844c3682b --- /dev/null +++ b/v3/integrations/nrfasthttp/instrumentation_test.go @@ -0,0 +1,56 @@ +package nrfasthttp + +import ( + "testing" + + "github.com/newrelic/go-agent/v3/internal" + "github.com/newrelic/go-agent/v3/newrelic" + "github.com/valyala/fasthttp" +) + +type myError struct{} + +func (e myError) Error() string { return "my msg" } + +func myErrorHandlerFastHTTP(ctx *fasthttp.RequestCtx) { + ctx.WriteString("noticing an error") + txn := ctx.UserValue("transaction").(*newrelic.Transaction) + txn.NoticeError(myError{}) +} + +func TestWrapHandleFastHTTPFunc(t *testing.T) { + singleCount := []float64{1, 0, 0, 0, 0, 0, 0} + app := createTestApp(true) + + _, wrappedHandler := WrapHandleFunc(app.Application, "/hello", myErrorHandlerFastHTTP) + + if wrappedHandler == nil { + t.Error("Error when creating a wrapped handler") + } + ctx := &fasthttp.RequestCtx{} + ctx.Request.Header.SetMethod("GET") + ctx.Request.SetRequestURI("/hello") + wrappedHandler(ctx) + app.ExpectErrors(t, []internal.WantError{{ + TxnName: "WebTransaction/Go/GET /hello", + Msg: "my msg", + Klass: "nrfasthttp.myError", + }}) + + app.ExpectMetrics(t, []internal.WantMetric{ + {Name: "WebTransaction/Go/GET /hello", Scope: "", Forced: true, Data: nil}, + {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, + {Name: "WebTransactionTotalTime/Go/GET /hello", Scope: "", Forced: false, Data: nil}, + {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, + {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, + {Name: "Apdex", Scope: "", Forced: true, Data: nil}, + {Name: "Apdex/Go/GET /hello", Scope: "", Forced: false, Data: nil}, + {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, + {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, + {Name: "Errors/all", Scope: "", Forced: true, Data: singleCount}, + {Name: "Errors/allWeb", Scope: "", Forced: true, Data: singleCount}, + {Name: "Errors/WebTransaction/Go/GET /hello", Scope: "", Forced: true, Data: singleCount}, + {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, + {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, + }) +} diff --git a/v3/integrations/nrfasthttp/segment.go b/v3/integrations/nrfasthttp/segment.go new file mode 100644 index 000000000..9de4991ab --- /dev/null +++ b/v3/integrations/nrfasthttp/segment.go @@ -0,0 +1,56 @@ +package nrfasthttp + +import ( + "net/http" + + "github.com/newrelic/go-agent/v3/newrelic" + "github.com/valyala/fasthttp" + "github.com/valyala/fasthttp/fasthttpadaptor" +) + +func StartExternalSegment(txn *newrelic.Transaction, ctx *fasthttp.RequestCtx) *newrelic.ExternalSegment { + var secureAgentEvent any + + if nil == txn { + txn = transactionFromRequestContext(ctx) + } + request := &http.Request{} + + fasthttpadaptor.ConvertRequest(ctx, request, true) + s := &newrelic.ExternalSegment{ + StartTime: txn.StartSegmentNow(), + Request: request, + } + + if newrelic.IsSecurityAgentPresent() { + secureAgentEvent = newrelic.GetSecurityAgentInterface().SendEvent("OUTBOUND", request) + s.SetSecureAgentEvent(secureAgentEvent) + } + + if request != nil && request.Header != nil { + for key, values := range s.GetOutboundHeaders() { + for _, value := range values { + request.Header.Set(key, value) + } + } + + if newrelic.IsSecurityAgentPresent() { + newrelic.GetSecurityAgentInterface().DistributedTraceHeaders(request, secureAgentEvent) + } + } + + return s +} + +func FromContext(ctx *fasthttp.RequestCtx) *newrelic.Transaction { + return transactionFromRequestContext(ctx) +} + +func transactionFromRequestContext(ctx *fasthttp.RequestCtx) *newrelic.Transaction { + if nil != ctx { + txn := ctx.UserValue("transaction").(*newrelic.Transaction) + return txn + } + + return nil +} diff --git a/v3/integrations/nrfasthttp/segment_test.go b/v3/integrations/nrfasthttp/segment_test.go new file mode 100644 index 000000000..aa245c0bd --- /dev/null +++ b/v3/integrations/nrfasthttp/segment_test.go @@ -0,0 +1,43 @@ +package nrfasthttp + +import ( + "testing" + + "github.com/newrelic/go-agent/v3/internal" + "github.com/newrelic/go-agent/v3/internal/integrationsupport" + "github.com/newrelic/go-agent/v3/newrelic" + "github.com/valyala/fasthttp" +) + +func createTestApp(dt bool) integrationsupport.ExpectApp { + return integrationsupport.NewTestApp(replyFn, integrationsupport.ConfigFullTraces, newrelic.ConfigDistributedTracerEnabled(dt)) +} + +var replyFn = func(reply *internal.ConnectReply) { + reply.SetSampleEverything() +} + +func TestExternalSegment(t *testing.T) { + app := createTestApp(false) + txn := app.StartTransaction("myTxn") + + req := fasthttp.AcquireRequest() + resp := fasthttp.AcquireResponse() + defer fasthttp.ReleaseRequest(req) + defer fasthttp.ReleaseResponse(resp) + + req.SetRequestURI("http://localhost:8080/hello") + req.Header.SetMethod("GET") + + ctx := &fasthttp.RequestCtx{} + seg := StartExternalSegment(txn, ctx) + defer seg.End() + + txn.End() + app.ExpectMetrics(t, []internal.WantMetric{ + {Name: "OtherTransaction/Go/myTxn", Scope: "", Forced: true, Data: nil}, + {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, + {Name: "OtherTransactionTotalTime/Go/myTxn", Scope: "", Forced: false, Data: nil}, + {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, + }) +} diff --git a/v3/newrelic/context.go b/v3/newrelic/context.go index 731dcb73f..5ce186f3d 100644 --- a/v3/newrelic/context.go +++ b/v3/newrelic/context.go @@ -8,7 +8,6 @@ import ( "net/http" "github.com/newrelic/go-agent/v3/internal" - "github.com/valyala/fasthttp" ) // NewContext returns a new context.Context that carries the provided @@ -53,16 +52,3 @@ func transactionFromRequestContext(req *http.Request) *Transaction { } return txn } - -func transactionFromRequestContextFastHTTP(ctx *fasthttp.RequestCtx) *Transaction { - var txn *Transaction - if nil != ctx { - txn := ctx.UserValue("transaction").(*Transaction) - return txn - } - - if txn != nil { - return txn - } - return nil -} diff --git a/v3/newrelic/instrumentation.go b/v3/newrelic/instrumentation.go index e4351a955..d0ffd7379 100644 --- a/v3/newrelic/instrumentation.go +++ b/v3/newrelic/instrumentation.go @@ -5,31 +5,8 @@ package newrelic import ( "net/http" - - "github.com/valyala/fasthttp" - "github.com/valyala/fasthttp/fasthttpadaptor" ) -type fasthttpWrapperResponse struct { - ctx *fasthttp.RequestCtx -} - -func (rw fasthttpWrapperResponse) Header() http.Header { - hdrs := http.Header{} - rw.ctx.Request.Header.VisitAll(func(key, value []byte) { - hdrs.Add(string(key), string(value)) - }) - return hdrs -} - -func (rw fasthttpWrapperResponse) Write(b []byte) (int, error) { - return rw.ctx.Write(b) -} - -func (rw fasthttpWrapperResponse) WriteHeader(code int) { - rw.ctx.SetStatusCode(code) -} - // instrumentation.go contains helpers built on the lower level api. // WrapHandle instruments http.Handler handlers with Transactions. To @@ -99,54 +76,37 @@ func WrapHandle(app *Application, pattern string, handler http.Handler, options }) } -func WrapHandleFastHTTP(app *Application, pattern string, handler fasthttp.RequestHandler, options ...TraceOption) (string, fasthttp.RequestHandler) { - if app == nil { - return pattern, handler - } - - // add the wrapped function to the trace options as the source code reference point - // (but only if we know we're collecting CLM for this transaction and the user didn't already - // specify a different code location explicitly). - cache := NewCachedCodeLocation() +// AddCodeLevelMetricsTraceOptions adds trace options to an existing slice of TraceOption objects depending on how code level metrics is configured +// in your application. +// Please call cache:=newrelic.NewCachedCodeLocation() before calling this function, and pass the cache to us in order to allow you to optimize the +// performance and accuracy of this function. +func AddCodeLevelMetricsTraceOptions(app *Application, options []TraceOption, cache *CachedCodeLocation, cachedLocations ...interface{}) []TraceOption { + var tOptions *traceOptSet + var txnOptionList []TraceOption - return pattern, func(ctx *fasthttp.RequestCtx) { - var tOptions *traceOptSet - var txnOptionList []TraceOption + if cache == nil { + return options + } - if app.app != nil && app.app.run != nil && app.app.run.Config.CodeLevelMetrics.Enabled { - tOptions = resolveCLMTraceOptions(options) - if tOptions != nil && !tOptions.SuppressCLM && (tOptions.DemandCLM || app.app.run.Config.CodeLevelMetrics.Scope == 0 || (app.app.run.Config.CodeLevelMetrics.Scope&TransactionCLM) != 0) { - // we are for sure collecting CLM here, so go to the trouble of collecting this code location if nothing else has yet. - if tOptions.LocationOverride == nil { - if loc, err := cache.FunctionLocation(handler); err == nil { - WithCodeLocation(loc)(tOptions) - } + if app.app != nil && app.app.run != nil && app.app.run.Config.CodeLevelMetrics.Enabled { + tOptions = resolveCLMTraceOptions(options) + if tOptions != nil && !tOptions.SuppressCLM && (tOptions.DemandCLM || app.app.run.Config.CodeLevelMetrics.Scope == 0 || (app.app.run.Config.CodeLevelMetrics.Scope&TransactionCLM) != 0) { + // we are for sure collecting CLM here, so go to the trouble of collecting this code location if nothing else has yet. + if tOptions.LocationOverride == nil { + if loc, err := cache.FunctionLocation(cachedLocations); err == nil { + WithCodeLocation(loc)(tOptions) } } } - if tOptions == nil { - // we weren't able to curate the options above, so pass whatever we were given downstream - txnOptionList = options - } else { - txnOptionList = append(txnOptionList, withPreparedOptions(tOptions)) - } - - method := string(ctx.Method()) - path := string(ctx.Path()) - txn := app.StartTransaction(method+" "+path, txnOptionList...) - ctx.SetUserValue("transaction", txn) - defer txn.End() - r := &http.Request{} - fasthttpadaptor.ConvertRequest(ctx, r, true) - resp := fasthttpWrapperResponse{ctx: ctx} - - txn.SetWebResponse(resp) - txn.SetWebRequestHTTP(r) - - r = RequestWithTransactionContext(r, txn) - - handler(ctx) } + if tOptions == nil { + // we weren't able to curate the options above, so pass whatever we were given downstream + txnOptionList = options + } else { + txnOptionList = append(txnOptionList, withPreparedOptions(tOptions)) + } + + return txnOptionList } // WrapHandleFunc instruments handler functions using Transactions. To @@ -184,14 +144,6 @@ func WrapHandleFunc(app *Application, pattern string, handler func(http.Response return p, func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } -func WrapHandleFuncFastHTTP(app *Application, pattern string, handler func(*fasthttp.RequestCtx), options ...TraceOption) (string, func(*fasthttp.RequestCtx)) { - // add the wrapped function to the trace options as the source code reference point - // (to the beginning of the option list, so that the user can override this) - - p, h := WrapHandleFastHTTP(app, pattern, fasthttp.RequestHandler(handler), options...) - return p, func(ctx *fasthttp.RequestCtx) { h(ctx) } -} - // WrapListen wraps an HTTP endpoint reference passed to functions like http.ListenAndServe, // which causes security scanning to be done for that incoming endpoint when vulnerability // scanning is enabled. It returns the endpoint string, so you can replace a call like diff --git a/v3/newrelic/internal_17_test.go b/v3/newrelic/internal_17_test.go index 5ba7b6c7e..82d1dc8f1 100644 --- a/v3/newrelic/internal_17_test.go +++ b/v3/newrelic/internal_17_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/newrelic/go-agent/v3/internal" - "github.com/valyala/fasthttp" ) func myErrorHandler(w http.ResponseWriter, req *http.Request) { @@ -19,48 +18,6 @@ func myErrorHandler(w http.ResponseWriter, req *http.Request) { txn.NoticeError(myError{}) } -func myErrorHandlerFastHTTP(ctx *fasthttp.RequestCtx) { - ctx.WriteString("noticing an error") - txn := ctx.UserValue("transaction").(*Transaction) - txn.NoticeError(myError{}) -} - -func TestWrapHandleFastHTTPFunc(t *testing.T) { - app := testApp(nil, ConfigDistributedTracerEnabled(true), t) - - _, wrappedHandler := WrapHandleFuncFastHTTP(app.Application, "/hello", myErrorHandlerFastHTTP) - - if wrappedHandler == nil { - t.Error("Error when creating a wrapped handler") - } - ctx := &fasthttp.RequestCtx{} - ctx.Request.Header.SetMethod("GET") - ctx.Request.SetRequestURI("/hello") - wrappedHandler(ctx) - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "WebTransaction/Go/GET /hello", - Msg: "my msg", - Klass: "newrelic.myError", - }}) - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "WebTransaction/Go/GET /hello", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/GET /hello", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/GET /hello", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "Errors/all", Scope: "", Forced: true, Data: singleCount}, - {Name: "Errors/allWeb", Scope: "", Forced: true, Data: singleCount}, - {Name: "Errors/WebTransaction/Go/GET /hello", Scope: "", Forced: true, Data: singleCount}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - }) -} - func TestWrapHandleFunc(t *testing.T) { app := testApp(nil, ConfigDistributedTracerEnabled(false), t) mux := http.NewServeMux() diff --git a/v3/newrelic/internal_context_test.go b/v3/newrelic/internal_context_test.go index 51372d382..1e15e61cd 100644 --- a/v3/newrelic/internal_context_test.go +++ b/v3/newrelic/internal_context_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/newrelic/go-agent/v3/internal" - "github.com/valyala/fasthttp" ) func TestWrapHandlerContext(t *testing.T) { @@ -37,30 +36,6 @@ func TestWrapHandlerContext(t *testing.T) { {Name: "Custom/mySegment", Scope: scope, Forced: false, Data: nil}, }) } -func TestExternalSegmentFastHTTP(t *testing.T) { - app := testApp(nil, ConfigDistributedTracerEnabled(false), t) - txn := app.StartTransaction("myTxn") - - req := fasthttp.AcquireRequest() - resp := fasthttp.AcquireResponse() - defer fasthttp.ReleaseRequest(req) - defer fasthttp.ReleaseResponse(resp) - - req.SetRequestURI("http://localhost:8080/hello") - req.Header.SetMethod("GET") - - ctx := &fasthttp.RequestCtx{} - seg := StartExternalSegmentFastHTTP(txn, ctx) - defer seg.End() - - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/myTxn", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/myTxn", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - }) -} func TestStartExternalSegmentNilTransaction(t *testing.T) { // Test that StartExternalSegment pulls the transaction from the diff --git a/v3/newrelic/segments.go b/v3/newrelic/segments.go index 65344033a..328db4283 100644 --- a/v3/newrelic/segments.go +++ b/v3/newrelic/segments.go @@ -5,9 +5,6 @@ package newrelic import ( "net/http" - - "github.com/valyala/fasthttp" - "github.com/valyala/fasthttp/fasthttpadaptor" ) // SegmentStartTime is created by Transaction.StartSegmentNow and marks the @@ -289,6 +286,23 @@ func (s *ExternalSegment) outboundHeaders() http.Header { return outboundHeaders(s) } +func (s *ExternalSegment) GetOutboundHeaders() http.Header { + return s.outboundHeaders() +} + +// SetSecureAgentEvent allows integration packages to set the secureAgentEvent +// for this external segment. That field is otherwise unexported and not available +// for other manipulation. +func (s *ExternalSegment) SetSecureAgentEvent(event any) { + s.secureAgentEvent = event +} + +// GetSecureAgentEvent retrieves the secureAgentEvent previously stored by +// a SetSecureAgentEvent method. +func (s *ExternalSegment) GetSecureAgentEvent() any { + return s.secureAgentEvent +} + // StartSegmentNow starts timing a segment. // // Deprecated: StartSegmentNow is deprecated and will be removed in a future @@ -340,36 +354,6 @@ func StartExternalSegment(txn *Transaction, request *http.Request) *ExternalSegm return s } -func StartExternalSegmentFastHTTP(txn *Transaction, ctx *fasthttp.RequestCtx) *ExternalSegment { - if nil == txn { - txn = transactionFromRequestContextFastHTTP(ctx) - } - request := &http.Request{} - - fasthttpadaptor.ConvertRequest(ctx, request, true) - s := &ExternalSegment{ - StartTime: txn.StartSegmentNow(), - Request: request, - } - if IsSecurityAgentPresent() { - s.secureAgentEvent = secureAgent.SendEvent("OUTBOUND", request) - } - - if request != nil && request.Header != nil { - for key, values := range s.outboundHeaders() { - for _, value := range values { - request.Header.Set(key, value) - } - } - - if IsSecurityAgentPresent() { - secureAgent.DistributedTraceHeaders(request, s.secureAgentEvent) - } - } - - return s -} - func addSpanAttr(start SegmentStartTime, key string, val interface{}) { if nil == start.thread { return From efcc4bd6cee66f68f1c72945e3112e1631f6f3b4 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Wed, 25 Oct 2023 16:08:35 -0400 Subject: [PATCH 2/9] move examples over --- v3/go.mod | 12 ------------ .../nrfasthttp}/examples/client-fasthttp/main.go | 0 .../nrfasthttp}/examples/server-fasthttp/main.go | 0 3 files changed, 12 deletions(-) rename v3/{ => integrations/nrfasthttp}/examples/client-fasthttp/main.go (100%) rename v3/{ => integrations/nrfasthttp}/examples/server-fasthttp/main.go (100%) diff --git a/v3/go.mod b/v3/go.mod index f3717dad6..a01c87ff1 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -4,21 +4,9 @@ go 1.19 require ( github.com/golang/protobuf v1.5.3 - github.com/valyala/fasthttp v1.49.0 google.golang.org/grpc v1.54.0 ) -require ( - github.com/andybalholm/brotli v1.0.5 // indirect - github.com/klauspost/compress v1.16.3 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect - google.golang.org/protobuf v1.28.1 // indirect -) - retract v3.22.0 // release process error corrected in v3.22.1 retract v3.25.0 // release process error corrected in v3.25.1 diff --git a/v3/examples/client-fasthttp/main.go b/v3/integrations/nrfasthttp/examples/client-fasthttp/main.go similarity index 100% rename from v3/examples/client-fasthttp/main.go rename to v3/integrations/nrfasthttp/examples/client-fasthttp/main.go diff --git a/v3/examples/server-fasthttp/main.go b/v3/integrations/nrfasthttp/examples/server-fasthttp/main.go similarity index 100% rename from v3/examples/server-fasthttp/main.go rename to v3/integrations/nrfasthttp/examples/server-fasthttp/main.go From dcba4568341747de18044cf84c82fbc1e2d23e83 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Wed, 1 Nov 2023 10:58:06 -0400 Subject: [PATCH 3/9] add security agent headers to fasthttp object --- v3/integrations/nrfasthttp/segment.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/v3/integrations/nrfasthttp/segment.go b/v3/integrations/nrfasthttp/segment.go index 9de4991ab..069cb8c25 100644 --- a/v3/integrations/nrfasthttp/segment.go +++ b/v3/integrations/nrfasthttp/segment.go @@ -37,6 +37,12 @@ func StartExternalSegment(txn *newrelic.Transaction, ctx *fasthttp.RequestCtx) * if newrelic.IsSecurityAgentPresent() { newrelic.GetSecurityAgentInterface().DistributedTraceHeaders(request, secureAgentEvent) } + + for k, values := range request.Header { + for _, value := range values { + ctx.Request.Header.Set(k, value) + } + } } return s From c2222b1c2018d6274f04e706d3b55cba6bd6d51b Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Mon, 6 Nov 2023 15:06:58 -0500 Subject: [PATCH 4/9] fix examples and external segment --- .../examples/client-fasthttp/go.mod | 24 +++++ .../examples/client-fasthttp/main.go | 4 +- .../examples/server-fasthttp/go.mod | 24 +++++ .../examples/server-fasthttp/main.go | 7 +- v3/integrations/nrfasthttp/segment.go | 91 ++++++++++++++----- v3/integrations/nrfasthttp/segment_test.go | 26 +++++- 6 files changed, 146 insertions(+), 30 deletions(-) create mode 100644 v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod create mode 100644 v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod diff --git a/v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod b/v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod new file mode 100644 index 000000000..b6d843750 --- /dev/null +++ b/v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod @@ -0,0 +1,24 @@ +module client-example + +go 1.19 + +require ( + github.com/newrelic/go-agent/v3 v3.26.0 + github.com/newrelic/go-agent/v3/integrations/nrfasthttp v1.0.0 + github.com/valyala/fasthttp v1.49.0 +) + +require ( + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/klauspost/compress v1.16.3 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect + google.golang.org/grpc v1.54.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect +) + +replace github.com/newrelic/go-agent/v3/integrations/nrfasthttp v1.0.0 => ../../ diff --git a/v3/integrations/nrfasthttp/examples/client-fasthttp/main.go b/v3/integrations/nrfasthttp/examples/client-fasthttp/main.go index 7a26b605f..bb958b840 100644 --- a/v3/integrations/nrfasthttp/examples/client-fasthttp/main.go +++ b/v3/integrations/nrfasthttp/examples/client-fasthttp/main.go @@ -7,6 +7,7 @@ import ( "os" "time" + "github.com/newrelic/go-agent/v3/integrations/nrfasthttp" newrelic "github.com/newrelic/go-agent/v3/newrelic" "github.com/valyala/fasthttp" ) @@ -20,8 +21,7 @@ func doRequest(txn *newrelic.Transaction) error { req.SetRequestURI("http://localhost:8080/hello") req.Header.SetMethod("GET") - ctx := &fasthttp.RequestCtx{} - seg := newrelic.StartExternalSegmentFastHTTP(txn, ctx) + seg := nrfasthttp.StartExternalSegment(txn, req) defer seg.End() err := fasthttp.Do(req, resp) diff --git a/v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod b/v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod new file mode 100644 index 000000000..0f98575c6 --- /dev/null +++ b/v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod @@ -0,0 +1,24 @@ +module server-example + +go 1.19 + +require ( + github.com/newrelic/go-agent/v3 v3.26.0 + github.com/newrelic/go-agent/v3/integrations/nrfasthttp v1.0.0 + github.com/valyala/fasthttp v1.49.0 +) + +require ( + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/klauspost/compress v1.16.3 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect + google.golang.org/grpc v1.54.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect +) + +replace github.com/newrelic/go-agent/v3/integrations/nrfasthttp v1.0.0 => ../../ diff --git a/v3/integrations/nrfasthttp/examples/server-fasthttp/main.go b/v3/integrations/nrfasthttp/examples/server-fasthttp/main.go index 8ed532670..bdb642f85 100644 --- a/v3/integrations/nrfasthttp/examples/server-fasthttp/main.go +++ b/v3/integrations/nrfasthttp/examples/server-fasthttp/main.go @@ -9,7 +9,8 @@ import ( "os" "time" - newrelic "github.com/newrelic/go-agent/v3/newrelic" + "github.com/newrelic/go-agent/v3/integrations/nrfasthttp" + "github.com/newrelic/go-agent/v3/newrelic" "github.com/valyala/fasthttp" ) @@ -39,8 +40,8 @@ func main() { if err := app.WaitForConnection(5 * time.Second); nil != err { fmt.Println(err) } - _, helloRoute := newrelic.WrapHandleFuncFastHTTP(app, "/hello", index) - _, errorRoute := newrelic.WrapHandleFuncFastHTTP(app, "/error", noticeError) + _, helloRoute := nrfasthttp.WrapHandleFunc(app, "/hello", index) + _, errorRoute := nrfasthttp.WrapHandleFunc(app, "/error", noticeError) handler := func(ctx *fasthttp.RequestCtx) { path := string(ctx.Path()) method := string(ctx.Method()) diff --git a/v3/integrations/nrfasthttp/segment.go b/v3/integrations/nrfasthttp/segment.go index 069cb8c25..9ce63b8e3 100644 --- a/v3/integrations/nrfasthttp/segment.go +++ b/v3/integrations/nrfasthttp/segment.go @@ -8,44 +8,89 @@ import ( "github.com/valyala/fasthttp/fasthttpadaptor" ) -func StartExternalSegment(txn *newrelic.Transaction, ctx *fasthttp.RequestCtx) *newrelic.ExternalSegment { +func StartExternalSegment(txn *newrelic.Transaction, request any) *newrelic.ExternalSegment { var secureAgentEvent any - if nil == txn { - txn = transactionFromRequestContext(ctx) - } - request := &http.Request{} + switch request.(type) { + // preserve prior functionality + case *fasthttp.RequestCtx: + ctx := request.(*fasthttp.RequestCtx) - fasthttpadaptor.ConvertRequest(ctx, request, true) - s := &newrelic.ExternalSegment{ - StartTime: txn.StartSegmentNow(), - Request: request, - } + if nil == txn { + txn = transactionFromRequestContext(ctx) + } + request := &http.Request{} - if newrelic.IsSecurityAgentPresent() { - secureAgentEvent = newrelic.GetSecurityAgentInterface().SendEvent("OUTBOUND", request) - s.SetSecureAgentEvent(secureAgentEvent) - } + fasthttpadaptor.ConvertRequest(ctx, request, true) + s := &newrelic.ExternalSegment{ + StartTime: txn.StartSegmentNow(), + Request: request, + } + + if newrelic.IsSecurityAgentPresent() { + secureAgentEvent = newrelic.GetSecurityAgentInterface().SendEvent("OUTBOUND", request) + s.SetSecureAgentEvent(secureAgentEvent) + } + + if request != nil && request.Header != nil { + for key, values := range s.GetOutboundHeaders() { + for _, value := range values { + request.Header.Set(key, value) + } + } + + if newrelic.IsSecurityAgentPresent() { + newrelic.GetSecurityAgentInterface().DistributedTraceHeaders(request, secureAgentEvent) + } - if request != nil && request.Header != nil { - for key, values := range s.GetOutboundHeaders() { - for _, value := range values { - request.Header.Set(key, value) + for k, values := range request.Header { + for _, value := range values { + ctx.Request.Header.Set(k, value) + } } } + return s + + case *fasthttp.Request: + req := request.(*fasthttp.Request) + request := &http.Request{} + + // it is ok to copy req here because we are not using its methods, just copying it into an http object + // for data collection + fasthttpadaptor.ConvertRequest(&fasthttp.RequestCtx{Request: *req}, request, true) + s := &newrelic.ExternalSegment{ + StartTime: txn.StartSegmentNow(), + Request: request, + } + if newrelic.IsSecurityAgentPresent() { - newrelic.GetSecurityAgentInterface().DistributedTraceHeaders(request, secureAgentEvent) + secureAgentEvent = newrelic.GetSecurityAgentInterface().SendEvent("OUTBOUND", request) + s.SetSecureAgentEvent(secureAgentEvent) } - for k, values := range request.Header { - for _, value := range values { - ctx.Request.Header.Set(k, value) + if request != nil && request.Header != nil { + for key, values := range s.GetOutboundHeaders() { + for _, value := range values { + request.Header.Set(key, value) + } + } + + if newrelic.IsSecurityAgentPresent() { + newrelic.GetSecurityAgentInterface().DistributedTraceHeaders(request, secureAgentEvent) + } + + for k, values := range request.Header { + for _, value := range values { + req.Header.Set(k, value) + } } } + + return s } - return s + return nil } func FromContext(ctx *fasthttp.RequestCtx) *newrelic.Transaction { diff --git a/v3/integrations/nrfasthttp/segment_test.go b/v3/integrations/nrfasthttp/segment_test.go index aa245c0bd..550e39a86 100644 --- a/v3/integrations/nrfasthttp/segment_test.go +++ b/v3/integrations/nrfasthttp/segment_test.go @@ -21,6 +21,29 @@ func TestExternalSegment(t *testing.T) { app := createTestApp(false) txn := app.StartTransaction("myTxn") + resp := fasthttp.AcquireResponse() + defer fasthttp.ReleaseResponse(resp) + + ctx := &fasthttp.RequestCtx{Request: fasthttp.Request{}} + ctx.Request.SetRequestURI("http://localhost:8080/hello") + ctx.Request.Header.SetMethod("GET") + + seg := StartExternalSegment(txn, ctx) + defer seg.End() + + txn.End() + app.ExpectMetrics(t, []internal.WantMetric{ + {Name: "OtherTransaction/Go/myTxn", Scope: "", Forced: true, Data: nil}, + {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, + {Name: "OtherTransactionTotalTime/Go/myTxn", Scope: "", Forced: false, Data: nil}, + {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, + }) +} + +func TestExternalSegmentRequest(t *testing.T) { + app := createTestApp(false) + txn := app.StartTransaction("myTxn") + req := fasthttp.AcquireRequest() resp := fasthttp.AcquireResponse() defer fasthttp.ReleaseRequest(req) @@ -29,8 +52,7 @@ func TestExternalSegment(t *testing.T) { req.SetRequestURI("http://localhost:8080/hello") req.Header.SetMethod("GET") - ctx := &fasthttp.RequestCtx{} - seg := StartExternalSegment(txn, ctx) + seg := StartExternalSegment(txn, req) defer seg.End() txn.End() From 05c8f287aa3c1e52ef0626d94a6ec8bcdb73af64 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Mon, 6 Nov 2023 15:08:11 -0500 Subject: [PATCH 5/9] add fasthttp tests --- .github/workflows/ci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f35e91398..6b5636a35 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,6 +27,8 @@ jobs: # v3 integrations - go-version: 1.19.x dirs: v3/integrations/nramqp + - go-version: 1.19.x + dirs: v3/integrations/nrfasthttp - go-version: 1.19.x dirs: v3/integrations/nrsarama - go-version: 1.19.x From cff899e2307a4f5d82b7957070a336e802d37558 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Mon, 6 Nov 2023 15:22:54 -0500 Subject: [PATCH 6/9] cleanup of go mods --- .../nrfasthttp/examples/client-fasthttp/go.mod | 15 +-------------- .../nrfasthttp/examples/server-fasthttp/go.mod | 15 +-------------- v3/integrations/nrfasthttp/go.mod | 17 +---------------- v3/integrations/nrfasthttp/instrumentation.go | 2 ++ v3/integrations/nrfasthttp/segment.go | 4 ++++ 5 files changed, 9 insertions(+), 44 deletions(-) diff --git a/v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod b/v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod index b6d843750..104505e67 100644 --- a/v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod +++ b/v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod @@ -3,22 +3,9 @@ module client-example go 1.19 require ( - github.com/newrelic/go-agent/v3 v3.26.0 + github.com/newrelic/go-agent/v3 v3.28.0 github.com/newrelic/go-agent/v3/integrations/nrfasthttp v1.0.0 github.com/valyala/fasthttp v1.49.0 ) -require ( - github.com/andybalholm/brotli v1.0.5 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/klauspost/compress v1.16.3 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect - google.golang.org/grpc v1.54.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect -) - replace github.com/newrelic/go-agent/v3/integrations/nrfasthttp v1.0.0 => ../../ diff --git a/v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod b/v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod index 0f98575c6..591d85885 100644 --- a/v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod +++ b/v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod @@ -3,22 +3,9 @@ module server-example go 1.19 require ( - github.com/newrelic/go-agent/v3 v3.26.0 + github.com/newrelic/go-agent/v3 v3.28.0 github.com/newrelic/go-agent/v3/integrations/nrfasthttp v1.0.0 github.com/valyala/fasthttp v1.49.0 ) -require ( - github.com/andybalholm/brotli v1.0.5 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/klauspost/compress v1.16.3 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect - google.golang.org/grpc v1.54.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect -) - replace github.com/newrelic/go-agent/v3/integrations/nrfasthttp v1.0.0 => ../../ diff --git a/v3/integrations/nrfasthttp/go.mod b/v3/integrations/nrfasthttp/go.mod index a55eb4945..5c97651a0 100644 --- a/v3/integrations/nrfasthttp/go.mod +++ b/v3/integrations/nrfasthttp/go.mod @@ -3,21 +3,6 @@ module github.com/newrelic/go-agent/v3/integrations/nrfasthttp go 1.19 require ( - github.com/newrelic/go-agent/v3 v3.26.0 + github.com/newrelic/go-agent/v3 v3.28.0 github.com/valyala/fasthttp v1.49.0 ) - -require ( - github.com/andybalholm/brotli v1.0.5 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/klauspost/compress v1.16.3 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect - google.golang.org/grpc v1.54.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect -) - -replace github.com/newrelic/go-agent/v3 => ../.. diff --git a/v3/integrations/nrfasthttp/instrumentation.go b/v3/integrations/nrfasthttp/instrumentation.go index 5b3e528f0..ea46b99f0 100644 --- a/v3/integrations/nrfasthttp/instrumentation.go +++ b/v3/integrations/nrfasthttp/instrumentation.go @@ -28,6 +28,7 @@ func (rw fasthttpWrapperResponse) WriteHeader(code int) { rw.ctx.SetStatusCode(code) } +// WrapHandleFunc wrapps a fasthttp handler function for automatic instrumentation func WrapHandleFunc(app *newrelic.Application, pattern string, handler func(*fasthttp.RequestCtx), options ...newrelic.TraceOption) (string, func(*fasthttp.RequestCtx)) { // add the wrapped function to the trace options as the source code reference point // (to the beginning of the option list, so that the user can override this) @@ -36,6 +37,7 @@ func WrapHandleFunc(app *newrelic.Application, pattern string, handler func(*fas return p, func(ctx *fasthttp.RequestCtx) { h(ctx) } } +// WrapHandle wraps a fasthttp request handler for automatic instrumentation func WrapHandle(app *newrelic.Application, pattern string, handler fasthttp.RequestHandler, options ...newrelic.TraceOption) (string, fasthttp.RequestHandler) { if app == nil { return pattern, handler diff --git a/v3/integrations/nrfasthttp/segment.go b/v3/integrations/nrfasthttp/segment.go index 9ce63b8e3..b9ca1c9f3 100644 --- a/v3/integrations/nrfasthttp/segment.go +++ b/v3/integrations/nrfasthttp/segment.go @@ -8,6 +8,9 @@ import ( "github.com/valyala/fasthttp/fasthttpadaptor" ) +// StartExternalSegment automatically creates and fills out a New Relic external segment for a given +// fasthttp request object. This function will accept either a fasthttp.Request or a fasthttp.RequestContext +// object as the request argument. func StartExternalSegment(txn *newrelic.Transaction, request any) *newrelic.ExternalSegment { var secureAgentEvent any @@ -93,6 +96,7 @@ func StartExternalSegment(txn *newrelic.Transaction, request any) *newrelic.Exte return nil } +// FromContext extracts a transaction pointer from a fasthttp.RequestContext object func FromContext(ctx *fasthttp.RequestCtx) *newrelic.Transaction { return transactionFromRequestContext(ctx) } From b630fee134c79a4ebb922a9a27b714a6d496b022 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Fri, 10 Nov 2023 13:28:34 -0500 Subject: [PATCH 7/9] fix segment collection --- v3/integrations/nrfasthttp/segment.go | 95 +++++++++------------------ 1 file changed, 32 insertions(+), 63 deletions(-) diff --git a/v3/integrations/nrfasthttp/segment.go b/v3/integrations/nrfasthttp/segment.go index b9ca1c9f3..aa480f5f0 100644 --- a/v3/integrations/nrfasthttp/segment.go +++ b/v3/integrations/nrfasthttp/segment.go @@ -13,87 +13,56 @@ import ( // object as the request argument. func StartExternalSegment(txn *newrelic.Transaction, request any) *newrelic.ExternalSegment { var secureAgentEvent any + var ctx *fasthttp.RequestCtx + + switch reqObject := request.(type) { - switch request.(type) { - // preserve prior functionality case *fasthttp.RequestCtx: - ctx := request.(*fasthttp.RequestCtx) + ctx = reqObject - if nil == txn { - txn = transactionFromRequestContext(ctx) - } - request := &http.Request{} + case *fasthttp.Request: + ctx = &fasthttp.RequestCtx{} + reqObject.CopyTo(&ctx.Request) - fasthttpadaptor.ConvertRequest(ctx, request, true) - s := &newrelic.ExternalSegment{ - StartTime: txn.StartSegmentNow(), - Request: request, - } + default: + return nil + } - if newrelic.IsSecurityAgentPresent() { - secureAgentEvent = newrelic.GetSecurityAgentInterface().SendEvent("OUTBOUND", request) - s.SetSecureAgentEvent(secureAgentEvent) - } + if nil == txn { + txn = transactionFromRequestContext(ctx) + } + req := &http.Request{} - if request != nil && request.Header != nil { - for key, values := range s.GetOutboundHeaders() { - for _, value := range values { - request.Header.Set(key, value) - } - } + fasthttpadaptor.ConvertRequest(ctx, req, true) + s := &newrelic.ExternalSegment{ + StartTime: txn.StartSegmentNow(), + Request: req, + } - if newrelic.IsSecurityAgentPresent() { - newrelic.GetSecurityAgentInterface().DistributedTraceHeaders(request, secureAgentEvent) - } + if newrelic.IsSecurityAgentPresent() { + secureAgentEvent = newrelic.GetSecurityAgentInterface().SendEvent("OUTBOUND", request) + s.SetSecureAgentEvent(secureAgentEvent) + } - for k, values := range request.Header { - for _, value := range values { - ctx.Request.Header.Set(k, value) - } + if request != nil && req.Header != nil { + for key, values := range s.GetOutboundHeaders() { + for _, value := range values { + req.Header.Set(key, value) } } - return s - - case *fasthttp.Request: - req := request.(*fasthttp.Request) - request := &http.Request{} - - // it is ok to copy req here because we are not using its methods, just copying it into an http object - // for data collection - fasthttpadaptor.ConvertRequest(&fasthttp.RequestCtx{Request: *req}, request, true) - s := &newrelic.ExternalSegment{ - StartTime: txn.StartSegmentNow(), - Request: request, - } - if newrelic.IsSecurityAgentPresent() { - secureAgentEvent = newrelic.GetSecurityAgentInterface().SendEvent("OUTBOUND", request) - s.SetSecureAgentEvent(secureAgentEvent) + newrelic.GetSecurityAgentInterface().DistributedTraceHeaders(req, secureAgentEvent) } - if request != nil && request.Header != nil { - for key, values := range s.GetOutboundHeaders() { - for _, value := range values { - request.Header.Set(key, value) - } - } - - if newrelic.IsSecurityAgentPresent() { - newrelic.GetSecurityAgentInterface().DistributedTraceHeaders(request, secureAgentEvent) - } - - for k, values := range request.Header { - for _, value := range values { - req.Header.Set(k, value) - } + for k, values := range req.Header { + for _, value := range values { + ctx.Request.Header.Set(k, value) } } - - return s } - return nil + return s } // FromContext extracts a transaction pointer from a fasthttp.RequestContext object From 107c958e82c6990b180add0cd1892c41b4a38efa Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Fri, 10 Nov 2023 13:32:27 -0500 Subject: [PATCH 8/9] add security agent inbound write capture to wrapped handle func --- v3/integrations/nrfasthttp/instrumentation.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/v3/integrations/nrfasthttp/instrumentation.go b/v3/integrations/nrfasthttp/instrumentation.go index ea46b99f0..9ce64769c 100644 --- a/v3/integrations/nrfasthttp/instrumentation.go +++ b/v3/integrations/nrfasthttp/instrumentation.go @@ -28,6 +28,11 @@ func (rw fasthttpWrapperResponse) WriteHeader(code int) { rw.ctx.SetStatusCode(code) } +func (rw fasthttpWrapperResponse) Body() string { + body := rw.ctx.Response.Body() + return string(body) +} + // WrapHandleFunc wrapps a fasthttp handler function for automatic instrumentation func WrapHandleFunc(app *newrelic.Application, pattern string, handler func(*fasthttp.RequestCtx), options ...newrelic.TraceOption) (string, func(*fasthttp.RequestCtx)) { // add the wrapped function to the trace options as the source code reference point @@ -61,8 +66,9 @@ func WrapHandle(app *newrelic.Application, pattern string, handler fasthttp.Requ txn.SetWebResponse(resp) txn.SetWebRequestHTTP(r) - // r = newrelic.RequestWithTransactionContext(r, txn) - + if newrelic.IsSecurityAgentPresent() { + newrelic.GetSecurityAgentInterface().SendEvent("INBOUND_WRITE", resp.Body(), resp.Header()) + } handler(ctx) } } From a0036ba5f1d99da192b83d69f238a7d634019996 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Thu, 16 Nov 2023 14:10:50 -0500 Subject: [PATCH 9/9] Update go.mod --- v3/go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/v3/go.mod b/v3/go.mod index f6f5f7da9..e064d1ccf 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -4,7 +4,6 @@ go 1.19 require ( github.com/golang/protobuf v1.5.3 - github.com/valyala/fasthttp v1.49.0 google.golang.org/grpc v1.56.3 )