From 8339965ee073b6d1421e381d12493c41a558cb38 Mon Sep 17 00:00:00 2001 From: aayush-ap Date: Tue, 22 Aug 2023 04:20:18 +0000 Subject: [PATCH 01/22] minor fix for complete security disable flag --- v3/integrations/nrsecurityagent/nrsecurityagent.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3/integrations/nrsecurityagent/nrsecurityagent.go b/v3/integrations/nrsecurityagent/nrsecurityagent.go index acc994eca..c7264d7ad 100644 --- a/v3/integrations/nrsecurityagent/nrsecurityagent.go +++ b/v3/integrations/nrsecurityagent/nrsecurityagent.go @@ -37,7 +37,7 @@ func defaultSecurityConfig() SecurityConfig { // If env is set to false,the security module is not loaded func isSecurityAgentEnabled() bool { if env := os.Getenv("NEW_RELIC_SECURITY_AGENT_ENABLED"); env != "" { - if b, err := strconv.ParseBool("false"); err == nil { + if b, err := strconv.ParseBool(env); err == nil { return b } } From f999c5786071f8d5f08c481818b7cd706630b8cb Mon Sep 17 00:00:00 2001 From: mirackara Date: Fri, 25 Aug 2023 14:09:40 -0500 Subject: [PATCH 02/22] Create FastHTTP Client Functions --- .../nrfasthttp/nrfasthttp_client.go | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 v3/integrations/nrfasthttp/nrfasthttp_client.go diff --git a/v3/integrations/nrfasthttp/nrfasthttp_client.go b/v3/integrations/nrfasthttp/nrfasthttp_client.go new file mode 100644 index 000000000..98960f5fc --- /dev/null +++ b/v3/integrations/nrfasthttp/nrfasthttp_client.go @@ -0,0 +1,21 @@ +package nrfasthttp + +import ( + newrelic "github.com/newrelic/go-agent/v3/newrelic" + "github.com/valyala/fasthttp" +) + +type FastHTTPClient interface { + Do(req *fasthttp.Request, resp *fasthttp.Response) error +} + +func Do(client FastHTTPClient, txn *newrelic.Transaction, req *fasthttp.Request, res *fasthttp.Response) error { + seg := txn.StartSegment("fasthttp-do") + err := client.Do(req, res) + if err != nil { + txn.NoticeError(err) + } + seg.End() + + return err +} From 79146fb6763e76a872c4b0c78ca225d9bd458758 Mon Sep 17 00:00:00 2001 From: mirackara Date: Fri, 25 Aug 2023 14:09:51 -0500 Subject: [PATCH 03/22] FastHTTP Request Integration --- .../nrfasthttp/nrfasthttp_request.go | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 v3/integrations/nrfasthttp/nrfasthttp_request.go diff --git a/v3/integrations/nrfasthttp/nrfasthttp_request.go b/v3/integrations/nrfasthttp/nrfasthttp_request.go new file mode 100644 index 000000000..66ae112f6 --- /dev/null +++ b/v3/integrations/nrfasthttp/nrfasthttp_request.go @@ -0,0 +1,52 @@ +package nrfasthttp + +import ( + "net/http" + + newrelic "github.com/newrelic/go-agent/v3/newrelic" + "github.com/valyala/fasthttp" + "github.com/valyala/fasthttp/fasthttpadaptor" +) + +func GetTransaction(ctx *fasthttp.RequestCtx) *newrelic.Transaction { + txn := ctx.UserValue("transaction") + + if txn == nil { + return nil + } + + return txn.(*newrelic.Transaction) +} + +func NRHandler(app *newrelic.Application, original fasthttp.RequestHandler) fasthttp.RequestHandler { + return func(ctx *fasthttp.RequestCtx) { + // Ignore reporting transaction for browser requesting .ico files + if string(ctx.Path()) == "/favicon.ico" { + original(ctx) + return + } + + txn := app.StartTransaction(string(ctx.Path())) + defer txn.End() + ctx.SetUserValue("transaction", txn) + + segRequest := txn.StartSegment("fasthttp-set-request") + // Set transaction attributes + txn.AddAttribute("method", string(ctx.Method())) + txn.AddAttribute("path", string(ctx.Path())) + // convert fasthttp request to http request + r := &http.Request{} + fasthttpadaptor.ConvertRequest(ctx, r, true) + + txn.SetWebRequestHTTP(r) + txn.InsertDistributedTraceHeaders(r.Header) + segRequest.End() + + original(ctx) + // Set Web Response + seg := txn.StartSegment("fasthttp-set-response") + resp := fasthttpWrapperResponse{ctx: ctx, txn: txn} + txn.SetWebResponse(resp) + seg.End() + } +} From a36c2134459f9e3c0f39c3e9ec5a3bc0bca2d90d Mon Sep 17 00:00:00 2001 From: mirackara Date: Fri, 25 Aug 2023 14:10:13 -0500 Subject: [PATCH 04/22] FastHTTP example file --- v3/integrations/nrfasthttp/example/main.go | 56 ++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 v3/integrations/nrfasthttp/example/main.go diff --git a/v3/integrations/nrfasthttp/example/main.go b/v3/integrations/nrfasthttp/example/main.go new file mode 100644 index 000000000..68ed289c7 --- /dev/null +++ b/v3/integrations/nrfasthttp/example/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" + "os" + "time" + + "github.com/newrelic/go-agent/v3/integrations/nrfasthttp" + + newrelic "github.com/newrelic/go-agent/v3/newrelic" + + "github.com/valyala/fasthttp" +) + +func main() { + // Initialize New Relic + app, err := newrelic.NewApplication( + newrelic.ConfigAppName("httprouter App"), + newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")), + newrelic.ConfigDebugLogger(os.Stdout), + newrelic.ConfigDistributedTracerEnabled(true), + ) + if err != nil { + fmt.Println(err) + return + } + if err := app.WaitForConnection(5 * time.Second); nil != err { + fmt.Println(err) + } + + // Define your handler + handler := nrfasthttp.NRHandler(app, func(ctx *fasthttp.RequestCtx) { + txn := nrfasthttp.GetTransaction(ctx) + client := &fasthttp.Client{} + + req := fasthttp.AcquireRequest() + defer fasthttp.ReleaseRequest(req) + req.SetRequestURI("http://example.com") + + res := fasthttp.AcquireResponse() + defer fasthttp.ReleaseResponse(res) + + // Call nrfasthttp.Do instead of fasthttp.Do + err := nrfasthttp.Do(client, txn, req, res) + + if err != nil { + fmt.Println("Request failed: ", err) + return + } + // Your handler logic here... + ctx.WriteString("Hello World") + }) + + // Start the server with the instrumented handler + fasthttp.ListenAndServe(":8080", handler) +} From dd7d252df9a6370461905f3f3babf04e9ada3461 Mon Sep 17 00:00:00 2001 From: mirackara Date: Fri, 25 Aug 2023 14:11:20 -0500 Subject: [PATCH 05/22] FastHTTP Request Integration --- v3/integrations/nrfasthttp/nrfasthttp_request.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3/integrations/nrfasthttp/nrfasthttp_request.go b/v3/integrations/nrfasthttp/nrfasthttp_request.go index 66ae112f6..a21c79acd 100644 --- a/v3/integrations/nrfasthttp/nrfasthttp_request.go +++ b/v3/integrations/nrfasthttp/nrfasthttp_request.go @@ -45,7 +45,7 @@ func NRHandler(app *newrelic.Application, original fasthttp.RequestHandler) fast original(ctx) // Set Web Response seg := txn.StartSegment("fasthttp-set-response") - resp := fasthttpWrapperResponse{ctx: ctx, txn: txn} + resp := fasthttpWrapperResponse{ctx: ctx} txn.SetWebResponse(resp) seg.End() } From fc0b173749f473dee41b3eedf5bd9b3c68bef8c8 Mon Sep 17 00:00:00 2001 From: mirackara Date: Fri, 25 Aug 2023 14:11:29 -0500 Subject: [PATCH 06/22] FastHTTP Response file --- .../nrfasthttp/nrfasthttp_response.go | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 v3/integrations/nrfasthttp/nrfasthttp_response.go diff --git a/v3/integrations/nrfasthttp/nrfasthttp_response.go b/v3/integrations/nrfasthttp/nrfasthttp_response.go new file mode 100644 index 000000000..36882bd3e --- /dev/null +++ b/v3/integrations/nrfasthttp/nrfasthttp_response.go @@ -0,0 +1,27 @@ +package nrfasthttp + +import ( + "net/http" + + "github.com/valyala/fasthttp" +) + +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) +} From 3108963562097f7b6c777658c70f689ee9e8a3e8 Mon Sep 17 00:00:00 2001 From: mirackara Date: Fri, 25 Aug 2023 14:11:35 -0500 Subject: [PATCH 07/22] mod file --- v3/integrations/nrfasthttp/go.mod | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 v3/integrations/nrfasthttp/go.mod diff --git a/v3/integrations/nrfasthttp/go.mod b/v3/integrations/nrfasthttp/go.mod new file mode 100644 index 000000000..3a36ce9a8 --- /dev/null +++ b/v3/integrations/nrfasthttp/go.mod @@ -0,0 +1,23 @@ +module github.com/newrelic/go-agent/v3/integrations/nrfasthttp + +go 1.19 + +require ( + github.com/newrelic/go-agent/v3 v3.23.1 + github.com/valyala/fasthttp v1.48.0 +) + +require ( + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/klauspost/compress v1.16.7 // 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 => ../.. From 19abf992c414ab6be1612390b5cc4f3b621aea5d Mon Sep 17 00:00:00 2001 From: aayush-ap Date: Mon, 28 Aug 2023 11:53:04 +0000 Subject: [PATCH 08/22] update security agent version --- v3/integrations/nrsecurityagent/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3/integrations/nrsecurityagent/go.mod b/v3/integrations/nrsecurityagent/go.mod index 2e805f3b8..990ec08c0 100644 --- a/v3/integrations/nrsecurityagent/go.mod +++ b/v3/integrations/nrsecurityagent/go.mod @@ -3,7 +3,7 @@ module github.com/newrelic/go-agent/v3/integrations/nrsecurityagent go 1.19 require ( - github.com/newrelic/csec-go-agent v0.3.0 + github.com/newrelic/csec-go-agent v0.4.0 github.com/newrelic/go-agent/v3 v3.24.1 github.com/newrelic/go-agent/v3/integrations/nrsqlite3 v1.2.0 gopkg.in/yaml.v2 v2.4.0 From 55af84ad489ee084c67939e6c35d7053e12aa565 Mon Sep 17 00:00:00 2001 From: mirackara Date: Mon, 28 Aug 2023 15:18:58 -0500 Subject: [PATCH 09/22] supportability metric --- v3/integrations/nrfasthttp/nrfasthttp_request.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/v3/integrations/nrfasthttp/nrfasthttp_request.go b/v3/integrations/nrfasthttp/nrfasthttp_request.go index a21c79acd..5a300c224 100644 --- a/v3/integrations/nrfasthttp/nrfasthttp_request.go +++ b/v3/integrations/nrfasthttp/nrfasthttp_request.go @@ -3,11 +3,14 @@ package nrfasthttp import ( "net/http" + internal "github.com/newrelic/go-agent/v3/internal" newrelic "github.com/newrelic/go-agent/v3/newrelic" "github.com/valyala/fasthttp" "github.com/valyala/fasthttp/fasthttpadaptor" ) +func init() { internal.TrackUsage("integration", "framework", "fasthttp") } + func GetTransaction(ctx *fasthttp.RequestCtx) *newrelic.Transaction { txn := ctx.UserValue("transaction") From 349fe30940ca3a8d9b610fb939aa1aacf0a1a6ab Mon Sep 17 00:00:00 2001 From: mirackara Date: Mon, 28 Aug 2023 17:51:56 -0500 Subject: [PATCH 10/22] Created unit tests and removed extraneous file --- v3/integrations/nrfasthttp/go.mod | 16 +-- .../nrfasthttp/nrfasthttp_request.go | 20 ++++ .../nrfasthttp/nrfasthttp_response.go | 27 ----- v3/integrations/nrfasthttp/nrfasthttp_test.go | 101 ++++++++++++++++++ 4 files changed, 122 insertions(+), 42 deletions(-) delete mode 100644 v3/integrations/nrfasthttp/nrfasthttp_response.go create mode 100644 v3/integrations/nrfasthttp/nrfasthttp_test.go diff --git a/v3/integrations/nrfasthttp/go.mod b/v3/integrations/nrfasthttp/go.mod index 3a36ce9a8..d4e207230 100644 --- a/v3/integrations/nrfasthttp/go.mod +++ b/v3/integrations/nrfasthttp/go.mod @@ -4,20 +4,6 @@ go 1.19 require ( github.com/newrelic/go-agent/v3 v3.23.1 + github.com/stretchr/testify v1.8.4 github.com/valyala/fasthttp v1.48.0 ) - -require ( - github.com/andybalholm/brotli v1.0.5 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/klauspost/compress v1.16.7 // 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/nrfasthttp_request.go b/v3/integrations/nrfasthttp/nrfasthttp_request.go index 5a300c224..1b61dc6eb 100644 --- a/v3/integrations/nrfasthttp/nrfasthttp_request.go +++ b/v3/integrations/nrfasthttp/nrfasthttp_request.go @@ -11,6 +11,26 @@ import ( func init() { internal.TrackUsage("integration", "framework", "fasthttp") } +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 GetTransaction(ctx *fasthttp.RequestCtx) *newrelic.Transaction { txn := ctx.UserValue("transaction") diff --git a/v3/integrations/nrfasthttp/nrfasthttp_response.go b/v3/integrations/nrfasthttp/nrfasthttp_response.go deleted file mode 100644 index 36882bd3e..000000000 --- a/v3/integrations/nrfasthttp/nrfasthttp_response.go +++ /dev/null @@ -1,27 +0,0 @@ -package nrfasthttp - -import ( - "net/http" - - "github.com/valyala/fasthttp" -) - -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) -} diff --git a/v3/integrations/nrfasthttp/nrfasthttp_test.go b/v3/integrations/nrfasthttp/nrfasthttp_test.go new file mode 100644 index 000000000..5554e72fb --- /dev/null +++ b/v3/integrations/nrfasthttp/nrfasthttp_test.go @@ -0,0 +1,101 @@ +package nrfasthttp + +import ( + "github.com/newrelic/go-agent/v3/internal" + "github.com/newrelic/go-agent/v3/internal/integrationsupport" + newrelic "github.com/newrelic/go-agent/v3/newrelic" + + "errors" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/valyala/fasthttp" +) + +type mockFastHTTPClient struct { + err error +} + +func (m *mockFastHTTPClient) Do(req *fasthttp.Request, resp *fasthttp.Response) error { + + if m.err != nil { + return m.err + } + return nil +} + +func TestFastHTTPWrapperResponseHeader(t *testing.T) { + ctx := &fasthttp.RequestCtx{} + ctx.Request.Header.Set("X-Test-Header", "test") + wrapper := fasthttpWrapperResponse{ctx: ctx} + hdrs := wrapper.Header() + assert.Equal(t, "test", hdrs.Get("X-Test-Header")) +} + +func TestFastHTTPWrapperResponseWrite(t *testing.T) { + ctx := &fasthttp.RequestCtx{} + wrapper := fasthttpWrapperResponse{ctx: ctx} + + w, err := wrapper.Write([]byte("Hello World!")) + assert.Nil(t, err) + assert.Equal(t, 12, w) + assert.Equal(t, "Hello World!", string(ctx.Response.Body())) +} + +func TestFastHTTPWrapperResponseWriteHeader(t *testing.T) { + ctx := &fasthttp.RequestCtx{} + wrapper := fasthttpWrapperResponse{ctx: ctx} + wrapper.WriteHeader(http.StatusForbidden) + assert.Equal(t, http.StatusForbidden, ctx.Response.StatusCode()) +} + +func TestGetTransaction(t *testing.T) { + ctx := &fasthttp.RequestCtx{} + txn := &newrelic.Transaction{} + ctx.SetUserValue("transaction", txn) + assert.Equal(t, txn, GetTransaction(ctx)) +} + +func TestNRHandler(t *testing.T) { + app := integrationsupport.NewBasicTestApp() + original := func(ctx *fasthttp.RequestCtx) { + ctx.WriteString("Hello World!") + } + nrhandler := NRHandler(app.Application, original) + ctx := &fasthttp.RequestCtx{} + nrhandler(ctx) + assert.Equal(t, "Hello World!", string(ctx.Response.Body())) + app.ExpectMetrics(t, []internal.WantMetric{ + {Name: "WebTransaction"}, + {Name: "Apdex/Go/"}, + {Name: "Custom/fasthttp-set-response"}, + {Name: "WebTransactionTotalTime/Go/"}, + {Name: "Custom/fasthttp-set-request"}, + {Name: "WebTransaction/Go/"}, + {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all"}, + {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb"}, + {Name: "WebTransactionTotalTime"}, + {Name: "Apdex"}, + {Name: "Custom/fasthttp-set-request", Scope: "WebTransaction/Go/", Forced: false, Data: nil}, + {Name: "Custom/fasthttp-set-response", Scope: "WebTransaction/Go/", Forced: false, Data: nil}, + {Name: "HttpDispatcher"}, + }) +} + +func TestDo(t *testing.T) { + client := &mockFastHTTPClient{} + txn := &newrelic.Transaction{} + req := &fasthttp.Request{} + resp := &fasthttp.Response{} + + // check for no error + err := Do(client, txn, req, resp) + assert.NoError(t, err) + + // check for error + client.err = errors.New("ahh!!") + err = Do(client, txn, req, resp) + + assert.Error(t, err) +} From 7f56709de4c4a9c8a15689b5a981f5666734c60e Mon Sep 17 00:00:00 2001 From: mirackara Date: Wed, 6 Sep 2023 13:30:04 -0500 Subject: [PATCH 11/22] Moved FastHTTP to internal instrumentation --- v3/examples/server-fasthttp/main.go | 55 ++++++++++ v3/go.mod | 15 +++ v3/integrations/nrfasthttp/example/main.go | 56 ---------- .../nrfasthttp/nrfasthttp_client.go | 21 ---- .../nrfasthttp/nrfasthttp_request.go | 75 ------------- v3/integrations/nrfasthttp/nrfasthttp_test.go | 101 ------------------ v3/newrelic/instrumentation.go | 91 +++++++++++++++- v3/newrelic/internal_17_test.go | 32 ++++++ 8 files changed, 188 insertions(+), 258 deletions(-) create mode 100644 v3/examples/server-fasthttp/main.go delete mode 100644 v3/integrations/nrfasthttp/example/main.go delete mode 100644 v3/integrations/nrfasthttp/nrfasthttp_client.go delete mode 100644 v3/integrations/nrfasthttp/nrfasthttp_request.go delete mode 100644 v3/integrations/nrfasthttp/nrfasthttp_test.go diff --git a/v3/examples/server-fasthttp/main.go b/v3/examples/server-fasthttp/main.go new file mode 100644 index 000000000..cf3f3557f --- /dev/null +++ b/v3/examples/server-fasthttp/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "errors" + "fmt" + "os" + "time" + + newrelic "github.com/newrelic/go-agent/v3/newrelic" + + "github.com/valyala/fasthttp" +) + +func index(ctx *fasthttp.RequestCtx) { + ctx.WriteString("Hello World") +} + +func noticeError(ctx *fasthttp.RequestCtx) { + ctx.WriteString("noticing an error") + txn := ctx.UserValue("transaction").(*newrelic.Transaction) + txn.NoticeError(errors.New("my error message")) +} + +func main() { + // Initialize New Relic + app, err := newrelic.NewApplication( + newrelic.ConfigAppName("FastHTTP App"), + newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")), + newrelic.ConfigDebugLogger(os.Stdout), + newrelic.ConfigDistributedTracerEnabled(true), + ) + if err != nil { + fmt.Println(err) + return + } + if err := app.WaitForConnection(5 * time.Second); nil != err { + fmt.Println(err) + } + _, helloRoute := newrelic.WrapHandleFuncFastHTTP(app, "/hello", index) + _, errorRoute := newrelic.WrapHandleFuncFastHTTP(app, "/error", noticeError) + handler := func(ctx *fasthttp.RequestCtx) { + path := string(ctx.Path()) + method := string(ctx.Method()) + + switch { + case method == "GET" && path == "/hello": + helloRoute(ctx) + case method == "GET" && path == "/error": + errorRoute(ctx) + } + } + + // Start the server with the instrumented handler + fasthttp.ListenAndServe(":8080", handler) +} diff --git a/v3/go.mod b/v3/go.mod index 01b5abf73..48f2396f4 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -1,7 +1,22 @@ module github.com/newrelic/go-agent/v3 + go 1.18 + 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 diff --git a/v3/integrations/nrfasthttp/example/main.go b/v3/integrations/nrfasthttp/example/main.go deleted file mode 100644 index 68ed289c7..000000000 --- a/v3/integrations/nrfasthttp/example/main.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "fmt" - "os" - "time" - - "github.com/newrelic/go-agent/v3/integrations/nrfasthttp" - - newrelic "github.com/newrelic/go-agent/v3/newrelic" - - "github.com/valyala/fasthttp" -) - -func main() { - // Initialize New Relic - app, err := newrelic.NewApplication( - newrelic.ConfigAppName("httprouter App"), - newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")), - newrelic.ConfigDebugLogger(os.Stdout), - newrelic.ConfigDistributedTracerEnabled(true), - ) - if err != nil { - fmt.Println(err) - return - } - if err := app.WaitForConnection(5 * time.Second); nil != err { - fmt.Println(err) - } - - // Define your handler - handler := nrfasthttp.NRHandler(app, func(ctx *fasthttp.RequestCtx) { - txn := nrfasthttp.GetTransaction(ctx) - client := &fasthttp.Client{} - - req := fasthttp.AcquireRequest() - defer fasthttp.ReleaseRequest(req) - req.SetRequestURI("http://example.com") - - res := fasthttp.AcquireResponse() - defer fasthttp.ReleaseResponse(res) - - // Call nrfasthttp.Do instead of fasthttp.Do - err := nrfasthttp.Do(client, txn, req, res) - - if err != nil { - fmt.Println("Request failed: ", err) - return - } - // Your handler logic here... - ctx.WriteString("Hello World") - }) - - // Start the server with the instrumented handler - fasthttp.ListenAndServe(":8080", handler) -} diff --git a/v3/integrations/nrfasthttp/nrfasthttp_client.go b/v3/integrations/nrfasthttp/nrfasthttp_client.go deleted file mode 100644 index 98960f5fc..000000000 --- a/v3/integrations/nrfasthttp/nrfasthttp_client.go +++ /dev/null @@ -1,21 +0,0 @@ -package nrfasthttp - -import ( - newrelic "github.com/newrelic/go-agent/v3/newrelic" - "github.com/valyala/fasthttp" -) - -type FastHTTPClient interface { - Do(req *fasthttp.Request, resp *fasthttp.Response) error -} - -func Do(client FastHTTPClient, txn *newrelic.Transaction, req *fasthttp.Request, res *fasthttp.Response) error { - seg := txn.StartSegment("fasthttp-do") - err := client.Do(req, res) - if err != nil { - txn.NoticeError(err) - } - seg.End() - - return err -} diff --git a/v3/integrations/nrfasthttp/nrfasthttp_request.go b/v3/integrations/nrfasthttp/nrfasthttp_request.go deleted file mode 100644 index 1b61dc6eb..000000000 --- a/v3/integrations/nrfasthttp/nrfasthttp_request.go +++ /dev/null @@ -1,75 +0,0 @@ -package nrfasthttp - -import ( - "net/http" - - internal "github.com/newrelic/go-agent/v3/internal" - newrelic "github.com/newrelic/go-agent/v3/newrelic" - "github.com/valyala/fasthttp" - "github.com/valyala/fasthttp/fasthttpadaptor" -) - -func init() { internal.TrackUsage("integration", "framework", "fasthttp") } - -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 GetTransaction(ctx *fasthttp.RequestCtx) *newrelic.Transaction { - txn := ctx.UserValue("transaction") - - if txn == nil { - return nil - } - - return txn.(*newrelic.Transaction) -} - -func NRHandler(app *newrelic.Application, original fasthttp.RequestHandler) fasthttp.RequestHandler { - return func(ctx *fasthttp.RequestCtx) { - // Ignore reporting transaction for browser requesting .ico files - if string(ctx.Path()) == "/favicon.ico" { - original(ctx) - return - } - - txn := app.StartTransaction(string(ctx.Path())) - defer txn.End() - ctx.SetUserValue("transaction", txn) - - segRequest := txn.StartSegment("fasthttp-set-request") - // Set transaction attributes - txn.AddAttribute("method", string(ctx.Method())) - txn.AddAttribute("path", string(ctx.Path())) - // convert fasthttp request to http request - r := &http.Request{} - fasthttpadaptor.ConvertRequest(ctx, r, true) - - txn.SetWebRequestHTTP(r) - txn.InsertDistributedTraceHeaders(r.Header) - segRequest.End() - - original(ctx) - // Set Web Response - seg := txn.StartSegment("fasthttp-set-response") - resp := fasthttpWrapperResponse{ctx: ctx} - txn.SetWebResponse(resp) - seg.End() - } -} diff --git a/v3/integrations/nrfasthttp/nrfasthttp_test.go b/v3/integrations/nrfasthttp/nrfasthttp_test.go deleted file mode 100644 index 5554e72fb..000000000 --- a/v3/integrations/nrfasthttp/nrfasthttp_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package nrfasthttp - -import ( - "github.com/newrelic/go-agent/v3/internal" - "github.com/newrelic/go-agent/v3/internal/integrationsupport" - newrelic "github.com/newrelic/go-agent/v3/newrelic" - - "errors" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/valyala/fasthttp" -) - -type mockFastHTTPClient struct { - err error -} - -func (m *mockFastHTTPClient) Do(req *fasthttp.Request, resp *fasthttp.Response) error { - - if m.err != nil { - return m.err - } - return nil -} - -func TestFastHTTPWrapperResponseHeader(t *testing.T) { - ctx := &fasthttp.RequestCtx{} - ctx.Request.Header.Set("X-Test-Header", "test") - wrapper := fasthttpWrapperResponse{ctx: ctx} - hdrs := wrapper.Header() - assert.Equal(t, "test", hdrs.Get("X-Test-Header")) -} - -func TestFastHTTPWrapperResponseWrite(t *testing.T) { - ctx := &fasthttp.RequestCtx{} - wrapper := fasthttpWrapperResponse{ctx: ctx} - - w, err := wrapper.Write([]byte("Hello World!")) - assert.Nil(t, err) - assert.Equal(t, 12, w) - assert.Equal(t, "Hello World!", string(ctx.Response.Body())) -} - -func TestFastHTTPWrapperResponseWriteHeader(t *testing.T) { - ctx := &fasthttp.RequestCtx{} - wrapper := fasthttpWrapperResponse{ctx: ctx} - wrapper.WriteHeader(http.StatusForbidden) - assert.Equal(t, http.StatusForbidden, ctx.Response.StatusCode()) -} - -func TestGetTransaction(t *testing.T) { - ctx := &fasthttp.RequestCtx{} - txn := &newrelic.Transaction{} - ctx.SetUserValue("transaction", txn) - assert.Equal(t, txn, GetTransaction(ctx)) -} - -func TestNRHandler(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - original := func(ctx *fasthttp.RequestCtx) { - ctx.WriteString("Hello World!") - } - nrhandler := NRHandler(app.Application, original) - ctx := &fasthttp.RequestCtx{} - nrhandler(ctx) - assert.Equal(t, "Hello World!", string(ctx.Response.Body())) - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "WebTransaction"}, - {Name: "Apdex/Go/"}, - {Name: "Custom/fasthttp-set-response"}, - {Name: "WebTransactionTotalTime/Go/"}, - {Name: "Custom/fasthttp-set-request"}, - {Name: "WebTransaction/Go/"}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all"}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb"}, - {Name: "WebTransactionTotalTime"}, - {Name: "Apdex"}, - {Name: "Custom/fasthttp-set-request", Scope: "WebTransaction/Go/", Forced: false, Data: nil}, - {Name: "Custom/fasthttp-set-response", Scope: "WebTransaction/Go/", Forced: false, Data: nil}, - {Name: "HttpDispatcher"}, - }) -} - -func TestDo(t *testing.T) { - client := &mockFastHTTPClient{} - txn := &newrelic.Transaction{} - req := &fasthttp.Request{} - resp := &fasthttp.Response{} - - // check for no error - err := Do(client, txn, req, resp) - assert.NoError(t, err) - - // check for error - client.err = errors.New("ahh!!") - err = Do(client, txn, req, resp) - - assert.Error(t, err) -} diff --git a/v3/newrelic/instrumentation.go b/v3/newrelic/instrumentation.go index a118aef9f..dc5bf9a45 100644 --- a/v3/newrelic/instrumentation.go +++ b/v3/newrelic/instrumentation.go @@ -5,18 +5,41 @@ 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 // instrument this code: // -// http.Handle("/foo", myHandler) +// http.Handle("/foo", myHandler) // // Perform this replacement: // -// http.Handle(newrelic.WrapHandle(app, "/foo", myHandler)) +// http.Handle(newrelic.WrapHandle(app, "/foo", myHandler)) // // WrapHandle adds the Transaction to the request's context. Access it using // FromContext to add attributes, create segments, or notice errors: @@ -76,6 +99,56 @@ 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() + + return pattern, func(ctx *fasthttp.RequestCtx) { + var tOptions *traceOptSet + var txnOptionList []TraceOption + + 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 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) + } +} + // WrapHandleFunc instruments handler functions using Transactions. To // instrument this code: // @@ -111,15 +184,23 @@ 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 // -// http.ListenAndServe(":8000", nil) +// http.ListenAndServe(":8000", nil) +// // with -// http.ListenAndServe(newrelic.WrapListen(":8000"), nil) // +// http.ListenAndServe(newrelic.WrapListen(":8000"), nil) func WrapListen(endpoint string) string { secureAgent.SendEvent("APP_INFO", endpoint) return endpoint diff --git a/v3/newrelic/internal_17_test.go b/v3/newrelic/internal_17_test.go index 82d1dc8f1..58b9bfd3d 100644 --- a/v3/newrelic/internal_17_test.go +++ b/v3/newrelic/internal_17_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/newrelic/go-agent/v3/internal" + "github.com/valyala/fasthttp" ) func myErrorHandler(w http.ResponseWriter, req *http.Request) { @@ -18,6 +19,37 @@ func myErrorHandler(w http.ResponseWriter, req *http.Request) { txn.NoticeError(myError{}) } +func TestWrapHandleFastHTTPFunc(t *testing.T) { + app := testApp(nil, ConfigDistributedTracerEnabled(true), t) + + // Define your handler + handler := func(ctx *fasthttp.RequestCtx) { + ctx.WriteString("Hello World") + } + + _, wrappedHandler := WrapHandleFuncFastHTTP(app.Application, "/hello", handler) + + 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.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}, + }) +} + func TestWrapHandleFunc(t *testing.T) { app := testApp(nil, ConfigDistributedTracerEnabled(false), t) mux := http.NewServeMux() From de5ca156ce16a1581ece240ba318f952a65374e1 Mon Sep 17 00:00:00 2001 From: mirackara Date: Thu, 7 Sep 2023 14:40:14 -0500 Subject: [PATCH 12/22] Added testing for errors --- v3/newrelic/internal_17_test.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/v3/newrelic/internal_17_test.go b/v3/newrelic/internal_17_test.go index 58b9bfd3d..5ba7b6c7e 100644 --- a/v3/newrelic/internal_17_test.go +++ b/v3/newrelic/internal_17_test.go @@ -19,15 +19,16 @@ 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) - // Define your handler - handler := func(ctx *fasthttp.RequestCtx) { - ctx.WriteString("Hello World") - } - - _, wrappedHandler := WrapHandleFuncFastHTTP(app.Application, "/hello", handler) + _, wrappedHandler := WrapHandleFuncFastHTTP(app.Application, "/hello", myErrorHandlerFastHTTP) if wrappedHandler == nil { t.Error("Error when creating a wrapped handler") @@ -36,6 +37,11 @@ func TestWrapHandleFastHTTPFunc(t *testing.T) { 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}, @@ -47,6 +53,11 @@ func TestWrapHandleFastHTTPFunc(t *testing.T) { {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}, }) } From 607c7348775a0bdfa03c04194bd4406dd1e5a56a Mon Sep 17 00:00:00 2001 From: Julien Erard Date: Mon, 11 Sep 2023 17:08:56 +0200 Subject: [PATCH 13/22] chore: add logs-in-context example with logrus --- .../server-http-logs-in-context/main.go | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 v3/examples/server-http-logs-in-context/main.go diff --git a/v3/examples/server-http-logs-in-context/main.go b/v3/examples/server-http-logs-in-context/main.go new file mode 100644 index 000000000..7ce3a5782 --- /dev/null +++ b/v3/examples/server-http-logs-in-context/main.go @@ -0,0 +1,97 @@ +// Copyright 2020 New Relic Corporation. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// An application that illustrates Distributed Tracing with Logs-in-Context +// when using http.Server or similar frameworks. +package main + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "time" + + "github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrlogrus" + newrelic "github.com/newrelic/go-agent/v3/newrelic" + "github.com/sirupsen/logrus" +) + +type handler struct { + App *newrelic.Application +} + +func (h *handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { + // The call to StartTransaction must include the response writer and the + // request. + txn := h.App.StartTransaction("server-txn") + defer txn.End() + + txnLogger := logrus.WithContext(newrelic.NewContext(context.Background(), txn)) + + writer = txn.SetWebResponse(writer) + txn.SetWebRequestHTTP(req) + + if req.URL.String() == "/segments" { + defer txn.StartSegment("f1").End() + + txnLogger.Infof("/segments just started") + + func() { + defer txn.StartSegment("f2").End() + + io.WriteString(writer, "segments!") + time.Sleep(10 * time.Millisecond) + + txnLogger.Infof("segment func just about to complete") + }() + time.Sleep(10 * time.Millisecond) + } else { + // Transaction.WriteHeader has to be used instead of invoking + // WriteHeader on the response writer. + writer.WriteHeader(http.StatusNotFound) + } + txnLogger.Infof("handler completing") +} + +func makeApplication() (*newrelic.Application, error) { + app, err := newrelic.NewApplication( + newrelic.ConfigAppName("HTTP Server App"), + newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")), + ) + if nil != err { + return nil, err + } + nrlogrusFormatter := nrlogrus.NewFormatter(app, &logrus.TextFormatter{}) + logrus.SetFormatter(nrlogrusFormatter) + // Alternatively and if preferred, create a new logger and use that logger + // for logging with + // log := logrus.New() + // log.SetFormatter(nrlogrusFormatter) + + // Wait for the application to connect. + if err = app.WaitForConnection(5 * time.Second); nil != err { + return nil, err + } + + return app, nil +} + +func main() { + + app, err := makeApplication() + if nil != err { + fmt.Println(err) + os.Exit(1) + } + + logrus.Infof("Application Starting") + + server := http.Server{ + Addr: ":8000", + Handler: &handler{App: app}, + } + + server.ListenAndServe() +} From 5115a58441b88892dc4b060fd330673ff90db5da Mon Sep 17 00:00:00 2001 From: Julien Erard Date: Mon, 11 Sep 2023 19:58:56 +0200 Subject: [PATCH 14/22] chore: move example to specific folder --- .../nrlogrus}/examples/server-http-logs-in-context/main.go | 0 v3/integrations/nrlogrus/{example => examples/server}/main.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename v3/{ => integrations/nrlogrus}/examples/server-http-logs-in-context/main.go (100%) rename v3/integrations/nrlogrus/{example => examples/server}/main.go (100%) diff --git a/v3/examples/server-http-logs-in-context/main.go b/v3/integrations/nrlogrus/examples/server-http-logs-in-context/main.go similarity index 100% rename from v3/examples/server-http-logs-in-context/main.go rename to v3/integrations/nrlogrus/examples/server-http-logs-in-context/main.go diff --git a/v3/integrations/nrlogrus/example/main.go b/v3/integrations/nrlogrus/examples/server/main.go similarity index 100% rename from v3/integrations/nrlogrus/example/main.go rename to v3/integrations/nrlogrus/examples/server/main.go From f1750def36f854ecb68d3db62aec68b12866d9cc Mon Sep 17 00:00:00 2001 From: mirackara Date: Mon, 11 Sep 2023 19:19:43 -0500 Subject: [PATCH 15/22] FastHTTP external segments/Client example --- v3/examples/client-fasthttp/main.go | 62 +++++++++++++++++++++++++++++ v3/newrelic/context.go | 10 +++++ v3/newrelic/segments.go | 27 +++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 v3/examples/client-fasthttp/main.go diff --git a/v3/examples/client-fasthttp/main.go b/v3/examples/client-fasthttp/main.go new file mode 100644 index 000000000..7a26b605f --- /dev/null +++ b/v3/examples/client-fasthttp/main.go @@ -0,0 +1,62 @@ +// Copyright 2020 New Relic Corporation. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +package main + +import ( + "fmt" + "os" + "time" + + newrelic "github.com/newrelic/go-agent/v3/newrelic" + "github.com/valyala/fasthttp" +) + +func doRequest(txn *newrelic.Transaction) error { + 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 := newrelic.StartExternalSegmentFastHTTP(txn, ctx) + defer seg.End() + + err := fasthttp.Do(req, resp) + if err != nil { + return err + } + + fmt.Println("Response Code is ", resp.StatusCode()) + return nil + +} + +func main() { + app, err := newrelic.NewApplication( + newrelic.ConfigAppName("Client App"), + newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")), + newrelic.ConfigDebugLogger(os.Stdout), + newrelic.ConfigDistributedTracerEnabled(true), + ) + + if err := app.WaitForConnection(5 * time.Second); nil != err { + fmt.Println(err) + } + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + txn := app.StartTransaction("client-txn") + err = doRequest(txn) + if err != nil { + txn.NoticeError(err) + } + txn.End() + + // Shut down the application to flush data to New Relic. + app.Shutdown(10 * time.Second) +} diff --git a/v3/newrelic/context.go b/v3/newrelic/context.go index 5ce186f3d..991feb64b 100644 --- a/v3/newrelic/context.go +++ b/v3/newrelic/context.go @@ -8,6 +8,7 @@ import ( "net/http" "github.com/newrelic/go-agent/v3/internal" + "github.com/valyala/fasthttp" ) // NewContext returns a new context.Context that carries the provided @@ -52,3 +53,12 @@ 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 + } + return txn +} diff --git a/v3/newrelic/segments.go b/v3/newrelic/segments.go index a9c96d52c..f45735687 100644 --- a/v3/newrelic/segments.go +++ b/v3/newrelic/segments.go @@ -5,6 +5,9 @@ package newrelic import ( "net/http" + + "github.com/valyala/fasthttp" + "github.com/valyala/fasthttp/fasthttpadaptor" ) // SegmentStartTime is created by Transaction.StartSegmentNow and marks the @@ -334,6 +337,30 @@ 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, + } + 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) + } + } + secureAgent.DistributedTraceHeaders(request, s.secureAgentEvent) + } + + return s +} + func addSpanAttr(start SegmentStartTime, key string, val interface{}) { if nil == start.thread { return From ef22a97ead73e5b5f57ec2bc58832875e626765f Mon Sep 17 00:00:00 2001 From: mirackara Date: Mon, 11 Sep 2023 19:19:57 -0500 Subject: [PATCH 16/22] License for Server Example --- v3/examples/server-fasthttp/main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/v3/examples/server-fasthttp/main.go b/v3/examples/server-fasthttp/main.go index cf3f3557f..8ed532670 100644 --- a/v3/examples/server-fasthttp/main.go +++ b/v3/examples/server-fasthttp/main.go @@ -1,3 +1,6 @@ +// Copyright 2020 New Relic Corporation. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + package main import ( From aed3dde33888a9a01f08d68af7e5619c6483e910 Mon Sep 17 00:00:00 2001 From: mirackara Date: Thu, 14 Sep 2023 12:04:22 -0500 Subject: [PATCH 17/22] Added test for external segment/minor fixes --- v3/newrelic/context.go | 6 +++++- v3/newrelic/internal_context_test.go | 29 ++++++++++++++++++++++++++++ v3/newrelic/segments.go | 10 ++++++++-- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/v3/newrelic/context.go b/v3/newrelic/context.go index 991feb64b..731dcb73f 100644 --- a/v3/newrelic/context.go +++ b/v3/newrelic/context.go @@ -60,5 +60,9 @@ func transactionFromRequestContextFastHTTP(ctx *fasthttp.RequestCtx) *Transactio txn := ctx.UserValue("transaction").(*Transaction) return txn } - return txn + + if txn != nil { + return txn + } + return nil } diff --git a/v3/newrelic/internal_context_test.go b/v3/newrelic/internal_context_test.go index 1e15e61cd..64b73c08f 100644 --- a/v3/newrelic/internal_context_test.go +++ b/v3/newrelic/internal_context_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/newrelic/go-agent/v3/internal" + "github.com/valyala/fasthttp" ) func TestWrapHandlerContext(t *testing.T) { @@ -36,6 +37,34 @@ 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() + + err := fasthttp.Do(req, resp) + 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}, + }) + if err != nil { + t.Error(err) + } +} 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 f45735687..9869cc402 100644 --- a/v3/newrelic/segments.go +++ b/v3/newrelic/segments.go @@ -348,14 +348,20 @@ func StartExternalSegmentFastHTTP(txn *Transaction, ctx *fasthttp.RequestCtx) *E StartTime: txn.StartSegmentNow(), Request: request, } - s.secureAgentEvent = secureAgent.SendEvent("OUTBOUND", 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) } } - secureAgent.DistributedTraceHeaders(request, s.secureAgentEvent) + + if IsSecurityAgentPresent() { + secureAgent.DistributedTraceHeaders(request, s.secureAgentEvent) + } } return s From 5b2310b0cb056a21e09ab5c6c20b42637f3ef155 Mon Sep 17 00:00:00 2001 From: Mirac Kara <55501260+mirackara@users.noreply.github.com> Date: Thu, 14 Sep 2023 13:06:29 -0500 Subject: [PATCH 18/22] FastHTTP Integration (#774) Added Support For FastHTTP --- v3/examples/client-fasthttp/main.go | 62 +++++++++++++++++++ v3/examples/server-fasthttp/main.go | 58 ++++++++++++++++++ v3/go.mod | 1 + v3/integrations/nrfasthttp/go.mod | 9 +++ v3/newrelic/context.go | 14 +++++ v3/newrelic/instrumentation.go | 91 ++++++++++++++++++++++++++-- v3/newrelic/internal_17_test.go | 43 +++++++++++++ v3/newrelic/internal_context_test.go | 29 +++++++++ v3/newrelic/segments.go | 33 ++++++++++ 9 files changed, 335 insertions(+), 5 deletions(-) create mode 100644 v3/examples/client-fasthttp/main.go create mode 100644 v3/examples/server-fasthttp/main.go create mode 100644 v3/integrations/nrfasthttp/go.mod diff --git a/v3/examples/client-fasthttp/main.go b/v3/examples/client-fasthttp/main.go new file mode 100644 index 000000000..7a26b605f --- /dev/null +++ b/v3/examples/client-fasthttp/main.go @@ -0,0 +1,62 @@ +// Copyright 2020 New Relic Corporation. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +package main + +import ( + "fmt" + "os" + "time" + + newrelic "github.com/newrelic/go-agent/v3/newrelic" + "github.com/valyala/fasthttp" +) + +func doRequest(txn *newrelic.Transaction) error { + 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 := newrelic.StartExternalSegmentFastHTTP(txn, ctx) + defer seg.End() + + err := fasthttp.Do(req, resp) + if err != nil { + return err + } + + fmt.Println("Response Code is ", resp.StatusCode()) + return nil + +} + +func main() { + app, err := newrelic.NewApplication( + newrelic.ConfigAppName("Client App"), + newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")), + newrelic.ConfigDebugLogger(os.Stdout), + newrelic.ConfigDistributedTracerEnabled(true), + ) + + if err := app.WaitForConnection(5 * time.Second); nil != err { + fmt.Println(err) + } + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + txn := app.StartTransaction("client-txn") + err = doRequest(txn) + if err != nil { + txn.NoticeError(err) + } + txn.End() + + // Shut down the application to flush data to New Relic. + app.Shutdown(10 * time.Second) +} diff --git a/v3/examples/server-fasthttp/main.go b/v3/examples/server-fasthttp/main.go new file mode 100644 index 000000000..8ed532670 --- /dev/null +++ b/v3/examples/server-fasthttp/main.go @@ -0,0 +1,58 @@ +// Copyright 2020 New Relic Corporation. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "errors" + "fmt" + "os" + "time" + + newrelic "github.com/newrelic/go-agent/v3/newrelic" + + "github.com/valyala/fasthttp" +) + +func index(ctx *fasthttp.RequestCtx) { + ctx.WriteString("Hello World") +} + +func noticeError(ctx *fasthttp.RequestCtx) { + ctx.WriteString("noticing an error") + txn := ctx.UserValue("transaction").(*newrelic.Transaction) + txn.NoticeError(errors.New("my error message")) +} + +func main() { + // Initialize New Relic + app, err := newrelic.NewApplication( + newrelic.ConfigAppName("FastHTTP App"), + newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")), + newrelic.ConfigDebugLogger(os.Stdout), + newrelic.ConfigDistributedTracerEnabled(true), + ) + if err != nil { + fmt.Println(err) + return + } + if err := app.WaitForConnection(5 * time.Second); nil != err { + fmt.Println(err) + } + _, helloRoute := newrelic.WrapHandleFuncFastHTTP(app, "/hello", index) + _, errorRoute := newrelic.WrapHandleFuncFastHTTP(app, "/error", noticeError) + handler := func(ctx *fasthttp.RequestCtx) { + path := string(ctx.Path()) + method := string(ctx.Method()) + + switch { + case method == "GET" && path == "/hello": + helloRoute(ctx) + case method == "GET" && path == "/error": + errorRoute(ctx) + } + } + + // Start the server with the instrumented handler + fasthttp.ListenAndServe(":8080", handler) +} diff --git a/v3/go.mod b/v3/go.mod index d00562a5a..910c288d1 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -4,6 +4,7 @@ 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 ) diff --git a/v3/integrations/nrfasthttp/go.mod b/v3/integrations/nrfasthttp/go.mod new file mode 100644 index 000000000..d4e207230 --- /dev/null +++ b/v3/integrations/nrfasthttp/go.mod @@ -0,0 +1,9 @@ +module github.com/newrelic/go-agent/v3/integrations/nrfasthttp + +go 1.19 + +require ( + github.com/newrelic/go-agent/v3 v3.23.1 + github.com/stretchr/testify v1.8.4 + github.com/valyala/fasthttp v1.48.0 +) diff --git a/v3/newrelic/context.go b/v3/newrelic/context.go index 5ce186f3d..731dcb73f 100644 --- a/v3/newrelic/context.go +++ b/v3/newrelic/context.go @@ -8,6 +8,7 @@ import ( "net/http" "github.com/newrelic/go-agent/v3/internal" + "github.com/valyala/fasthttp" ) // NewContext returns a new context.Context that carries the provided @@ -52,3 +53,16 @@ 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 4e37e5316..e4351a955 100644 --- a/v3/newrelic/instrumentation.go +++ b/v3/newrelic/instrumentation.go @@ -5,18 +5,41 @@ 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 // instrument this code: // -// http.Handle("/foo", myHandler) +// http.Handle("/foo", myHandler) // // Perform this replacement: // -// http.Handle(newrelic.WrapHandle(app, "/foo", myHandler)) +// http.Handle(newrelic.WrapHandle(app, "/foo", myHandler)) // // WrapHandle adds the Transaction to the request's context. Access it using // FromContext to add attributes, create segments, or notice errors: @@ -76,6 +99,56 @@ 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() + + return pattern, func(ctx *fasthttp.RequestCtx) { + var tOptions *traceOptSet + var txnOptionList []TraceOption + + 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 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) + } +} + // WrapHandleFunc instruments handler functions using Transactions. To // instrument this code: // @@ -111,15 +184,23 @@ 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 // -// http.ListenAndServe(":8000", nil) +// http.ListenAndServe(":8000", nil) +// // with -// http.ListenAndServe(newrelic.WrapListen(":8000"), nil) // +// http.ListenAndServe(newrelic.WrapListen(":8000"), nil) func WrapListen(endpoint string) string { if IsSecurityAgentPresent() { secureAgent.SendEvent("APP_INFO", endpoint) diff --git a/v3/newrelic/internal_17_test.go b/v3/newrelic/internal_17_test.go index 82d1dc8f1..5ba7b6c7e 100644 --- a/v3/newrelic/internal_17_test.go +++ b/v3/newrelic/internal_17_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/newrelic/go-agent/v3/internal" + "github.com/valyala/fasthttp" ) func myErrorHandler(w http.ResponseWriter, req *http.Request) { @@ -18,6 +19,48 @@ 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 1e15e61cd..64b73c08f 100644 --- a/v3/newrelic/internal_context_test.go +++ b/v3/newrelic/internal_context_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/newrelic/go-agent/v3/internal" + "github.com/valyala/fasthttp" ) func TestWrapHandlerContext(t *testing.T) { @@ -36,6 +37,34 @@ 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() + + err := fasthttp.Do(req, resp) + 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}, + }) + if err != nil { + t.Error(err) + } +} 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 91f8fcc5a..65344033a 100644 --- a/v3/newrelic/segments.go +++ b/v3/newrelic/segments.go @@ -5,6 +5,9 @@ package newrelic import ( "net/http" + + "github.com/valyala/fasthttp" + "github.com/valyala/fasthttp/fasthttpadaptor" ) // SegmentStartTime is created by Transaction.StartSegmentNow and marks the @@ -337,6 +340,36 @@ 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 09b6facd2098888ec3c7b07a0c7dc69c4dcc1cdd Mon Sep 17 00:00:00 2001 From: Mirac Kara <55501260+mirackara@users.noreply.github.com> Date: Thu, 14 Sep 2023 13:09:02 -0500 Subject: [PATCH 19/22] V3.25.0 Changelog (#781) * V3.25.0 * update version --- CHANGELOG.md | 12 ++++++++++++ v3/newrelic/version.go | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5719b313b..082c9f34a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## 3.25.0 +### Added + * Added Support for FastHTTP package + * Added newrelic.WrapHandleFuncFastHTTP() and newrelic.StartExternalSegmentFastHTTP() functions to instrument fasthttp context and create wrapped handlers. These functions work similarly to the existing ones for net/http + * Added client-fasthttp and server-fasthttp examples to help get started with FastHTTP integration + + +### Support statement +We use the latest version of the Go language. At minimum, you should be using no version of Go older than what is supported by the Go team themselves (i.e., Go versions 1.19 and later are supported). +We recommend updating to the latest agent version as soon as it’s available. If you can’t upgrade to the latest version, update your agents to a version no more than 90 days old. Read more about keeping agents up to date. (https://docs.newrelic.com/docs/new-relic-solutions/new-relic-one/install-configure/update-new-relic-agent/) +See the [Go agent EOL Policy](/docs/apm/agents/go-agent/get-started/go-agent-eol-policy/) for details about supported versions of the Go agent and third-party components. + ## 3.24.1 ### Fixed * Performance improvement around calls to security agent. In some cases, unnecessary setup operations were being performed even if there was no security agent present to use that. These are now conditional on the security agent being present in the application (note that this will enable the setup code if the security agent is *present* in the application, regardless of whether it's currently enabled to run). This affects: diff --git a/v3/newrelic/version.go b/v3/newrelic/version.go index b1a8adefd..c6b63256f 100644 --- a/v3/newrelic/version.go +++ b/v3/newrelic/version.go @@ -11,7 +11,7 @@ import ( const ( // Version is the full string version of this Go Agent. - Version = "3.24.1" + Version = "3.25.0" ) var ( From ba9626f7650a4bedad6d8e376b35208982888786 Mon Sep 17 00:00:00 2001 From: Steve Willoughby Date: Thu, 14 Sep 2023 12:17:49 -0700 Subject: [PATCH 20/22] corrected changelog for 3.25 release --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 082c9f34a..92ae88fa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * Added newrelic.WrapHandleFuncFastHTTP() and newrelic.StartExternalSegmentFastHTTP() functions to instrument fasthttp context and create wrapped handlers. These functions work similarly to the existing ones for net/http * Added client-fasthttp and server-fasthttp examples to help get started with FastHTTP integration +### Fixed + * Corrected a bug where the security agent failed to correctly parse the `NEW_RELIC_SECURITY_AGENT_ENABLED` environment variable. ### Support statement We use the latest version of the Go language. At minimum, you should be using no version of Go older than what is supported by the Go team themselves (i.e., Go versions 1.19 and later are supported). @@ -15,13 +17,14 @@ See the [Go agent EOL Policy](/docs/apm/agents/go-agent/get-started/go-agent-eol * Performance improvement around calls to security agent. In some cases, unnecessary setup operations were being performed even if there was no security agent present to use that. These are now conditional on the security agent being present in the application (note that this will enable the setup code if the security agent is *present* in the application, regardless of whether it's currently enabled to run). This affects: * Base agent code (updated to v3.24.1) * `nrmongo` integration (updated to v1.1.1) + * Resolved a race condition caused by the above-mentioned calls to the security agent. * Fixed unit tests for integrations which were failing because code level metrics are enabled by default now: * `nrawssdk-v1` (updated to v1.1.2) * `nrawssdk-v2` (updated to v1.2.2) * `nrecho-v3` (updated to v1.0.2) * `nrecho-v4` (updated to v1.0.4) - * `nrhttprouter` (updated to + * `nrhttprouter` (updated to v1.0.2) * `nrlambda` (updated to v1.2.2) * `nrnats` (updated to v1.1.5) * `nrredis-v8` (updated to v1.0.1) From 31ecf8c6ef872b9c2290f44a9181b5f60491f5c5 Mon Sep 17 00:00:00 2001 From: mirackara Date: Mon, 18 Sep 2023 11:10:20 -0500 Subject: [PATCH 21/22] Fixed test not passing --- v3/newrelic/internal_context_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/v3/newrelic/internal_context_test.go b/v3/newrelic/internal_context_test.go index 64b73c08f..c96f7b04a 100644 --- a/v3/newrelic/internal_context_test.go +++ b/v3/newrelic/internal_context_test.go @@ -53,7 +53,6 @@ func TestExternalSegmentFastHTTP(t *testing.T) { seg := StartExternalSegmentFastHTTP(txn, ctx) defer seg.End() - err := fasthttp.Do(req, resp) txn.End() app.ExpectMetrics(t, []internal.WantMetric{ {Name: "OtherTransaction/Go/myTxn", Scope: "", Forced: true, Data: nil}, @@ -61,9 +60,7 @@ func TestExternalSegmentFastHTTP(t *testing.T) { {Name: "OtherTransactionTotalTime/Go/myTxn", Scope: "", Forced: false, Data: nil}, {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, }) - if err != nil { - t.Error(err) - } + } func TestStartExternalSegmentNilTransaction(t *testing.T) { From 1488692f48f55d9ad6b6ed14799ede341351f458 Mon Sep 17 00:00:00 2001 From: Mirac Kara <55501260+mirackara@users.noreply.github.com> Date: Mon, 18 Sep 2023 11:14:13 -0500 Subject: [PATCH 22/22] Update segments.go Removed extra function --- v3/newrelic/segments.go | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/v3/newrelic/segments.go b/v3/newrelic/segments.go index 637387545..65344033a 100644 --- a/v3/newrelic/segments.go +++ b/v3/newrelic/segments.go @@ -370,36 +370,6 @@ func StartExternalSegmentFastHTTP(txn *Transaction, ctx *fasthttp.RequestCtx) *E 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