Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

move fasthttp out of core library, and into integration package #808

Merged
merged 10 commits into from
Nov 16, 2023
1 change: 0 additions & 1 deletion v3/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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.54.0
)

Expand Down
iamemilio marked this conversation as resolved.
Show resolved Hide resolved
File renamed without changes.
17 changes: 15 additions & 2 deletions v3/integrations/nrfasthttp/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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 => ../..
66 changes: 66 additions & 0 deletions v3/integrations/nrfasthttp/instrumentation.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
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},
})
}
56 changes: 56 additions & 0 deletions v3/integrations/nrfasthttp/segment.go
Original file line number Diff line number Diff line change
@@ -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 {
iamemilio marked this conversation as resolved.
Show resolved Hide resolved
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
}
43 changes: 43 additions & 0 deletions v3/integrations/nrfasthttp/segment_test.go
Original file line number Diff line number Diff line change
@@ -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},
})
}
14 changes: 0 additions & 14 deletions v3/newrelic/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Loading