Skip to content

Commit

Permalink
move fasthttp out of core library, and into integration package (#808)
Browse files Browse the repository at this point in the history
* move fasthttp out of core library, and into integration package

* move examples over

* add security agent headers to fasthttp object

* fix examples and external segment

* add fasthttp tests

* cleanup of go mods

* fix segment collection

* add security agent inbound write capture to wrapped handle func

* Update go.mod
  • Loading branch information
iamemilio committed Nov 16, 2023
1 parent b3ce5df commit c74d62c
Show file tree
Hide file tree
Showing 16 changed files with 349 additions and 209 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 0 additions & 12 deletions v3/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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.56.3
)

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.9.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/protobuf v1.30.0 // 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
11 changes: 11 additions & 0 deletions v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module client-example

go 1.19

require (
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
)

replace github.com/newrelic/go-agent/v3/integrations/nrfasthttp v1.0.0 => ../../
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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)
Expand Down
11 changes: 11 additions & 0 deletions v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module server-example

go 1.19

require (
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
)

replace github.com/newrelic/go-agent/v3/integrations/nrfasthttp v1.0.0 => ../../
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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())
Expand Down
6 changes: 2 additions & 4 deletions v3/integrations/nrfasthttp/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +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/stretchr/testify v1.8.4
github.com/valyala/fasthttp v1.48.0
github.com/newrelic/go-agent/v3 v3.28.0
github.com/valyala/fasthttp v1.49.0
)
replace github.com/newrelic/go-agent/v3 => ../..
74 changes: 74 additions & 0 deletions v3/integrations/nrfasthttp/instrumentation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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 (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
// (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) }
}

// 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
}

// 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)

if newrelic.IsSecurityAgentPresent() {
newrelic.GetSecurityAgentInterface().SendEvent("INBOUND_WRITE", resp.Body(), resp.Header())
}
handler(ctx)
}
}
56 changes: 56 additions & 0 deletions v3/integrations/nrfasthttp/instrumentation_test.go
Original file line number Diff line number Diff line change
@@ -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},
})
}
80 changes: 80 additions & 0 deletions v3/integrations/nrfasthttp/segment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package nrfasthttp

import (
"net/http"

"github.com/newrelic/go-agent/v3/newrelic"
"github.com/valyala/fasthttp"
"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
var ctx *fasthttp.RequestCtx

switch reqObject := request.(type) {

case *fasthttp.RequestCtx:
ctx = reqObject

case *fasthttp.Request:
ctx = &fasthttp.RequestCtx{}
reqObject.CopyTo(&ctx.Request)

default:
return nil
}

if nil == txn {
txn = transactionFromRequestContext(ctx)
}
req := &http.Request{}

fasthttpadaptor.ConvertRequest(ctx, req, true)
s := &newrelic.ExternalSegment{
StartTime: txn.StartSegmentNow(),
Request: req,
}

if newrelic.IsSecurityAgentPresent() {
secureAgentEvent = newrelic.GetSecurityAgentInterface().SendEvent("OUTBOUND", request)
s.SetSecureAgentEvent(secureAgentEvent)
}

if request != nil && req.Header != nil {
for key, values := range s.GetOutboundHeaders() {
for _, value := range values {
req.Header.Set(key, value)
}
}

if newrelic.IsSecurityAgentPresent() {
newrelic.GetSecurityAgentInterface().DistributedTraceHeaders(req, secureAgentEvent)
}

for k, values := range req.Header {
for _, value := range values {
ctx.Request.Header.Set(k, value)
}
}
}

return s
}

// FromContext extracts a transaction pointer from a fasthttp.RequestContext object
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
}
65 changes: 65 additions & 0 deletions v3/integrations/nrfasthttp/segment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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")

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)
defer fasthttp.ReleaseResponse(resp)

req.SetRequestURI("http://localhost:8080/hello")
req.Header.SetMethod("GET")

seg := StartExternalSegment(txn, req)
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},
})
}
Loading

0 comments on commit c74d62c

Please sign in to comment.