diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b84cf296..d216f52ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## 3.33.1 +### Added + - Increased max span events default limit to 2,000 to align with agent specifications + - Added support for gRPC API endpoints and HTTP status codes in the nrsecurity integration + - Added feature to detect route of an incoming request for all supported frameworks in the nrsecurity integration. + - Updated support for latest New Relic Security Agent release. +### Fixed + - Fixed an issue with nrzap attributes not properly being forwarded + - Improved comments on nropenai + - Fixed a minor bug relating to ExpectStatusCodes in `app_run.go` +### 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. +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.33.0 ### Added - Support for Zap Field Attributes diff --git a/v3/integrations/logcontext-v2/logWriter/go.mod b/v3/integrations/logcontext-v2/logWriter/go.mod index 6f5b7e47c..cadf5f5cc 100644 --- a/v3/integrations/logcontext-v2/logWriter/go.mod +++ b/v3/integrations/logcontext-v2/logWriter/go.mod @@ -3,7 +3,7 @@ module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/logWriter go 1.20 require ( - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrwriter v1.0.0 ) diff --git a/v3/integrations/logcontext-v2/nrlogrus/go.mod b/v3/integrations/logcontext-v2/nrlogrus/go.mod index 16e4e2805..c31af8d84 100644 --- a/v3/integrations/logcontext-v2/nrlogrus/go.mod +++ b/v3/integrations/logcontext-v2/nrlogrus/go.mod @@ -3,7 +3,7 @@ module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrlogrus go 1.20 require ( - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/sirupsen/logrus v1.8.1 ) diff --git a/v3/integrations/logcontext-v2/nrslog/go.mod b/v3/integrations/logcontext-v2/nrslog/go.mod index cb9c8a719..763031b83 100644 --- a/v3/integrations/logcontext-v2/nrslog/go.mod +++ b/v3/integrations/logcontext-v2/nrslog/go.mod @@ -2,7 +2,7 @@ module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrslog go 1.20 -require github.com/newrelic/go-agent/v3 v3.32.0 +require github.com/newrelic/go-agent/v3 v3.33.1 replace github.com/newrelic/go-agent/v3 => ../../.. diff --git a/v3/integrations/logcontext-v2/nrwriter/go.mod b/v3/integrations/logcontext-v2/nrwriter/go.mod index 10f545e23..ed2a0e9c2 100644 --- a/v3/integrations/logcontext-v2/nrwriter/go.mod +++ b/v3/integrations/logcontext-v2/nrwriter/go.mod @@ -2,7 +2,7 @@ module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrwriter go 1.20 -require github.com/newrelic/go-agent/v3 v3.32.0 +require github.com/newrelic/go-agent/v3 v3.33.1 replace github.com/newrelic/go-agent/v3 => ../../.. diff --git a/v3/integrations/logcontext-v2/nrzap/go.mod b/v3/integrations/logcontext-v2/nrzap/go.mod index d7a336e3a..a813c150e 100644 --- a/v3/integrations/logcontext-v2/nrzap/go.mod +++ b/v3/integrations/logcontext-v2/nrzap/go.mod @@ -3,7 +3,7 @@ module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrzap go 1.20 require ( - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 go.uber.org/zap v1.24.0 ) diff --git a/v3/integrations/logcontext-v2/nrzap/nrzap.go b/v3/integrations/logcontext-v2/nrzap/nrzap.go index ae5a7ff8f..40bb34369 100644 --- a/v3/integrations/logcontext-v2/nrzap/nrzap.go +++ b/v3/integrations/logcontext-v2/nrzap/nrzap.go @@ -15,8 +15,9 @@ func init() { internal.TrackUsage("integration", "logcontext-v2", "zap") } // NewRelicZapCore implements zap.Core type NewRelicZapCore struct { - core zapcore.Core - nr newrelicApplicationState + fields []zap.Field + core zapcore.Core + nr newrelicApplicationState } // newrelicApplicationState is a private struct that stores newrelic application data @@ -147,7 +148,7 @@ func WrapBackgroundCore(core zapcore.Core, app *newrelic.Application) (*NewRelic // Errors will be returned if the zapcore object is nil, or if the application is nil. It is up to the user to decide // how to handle the case where the newrelic.Transaction is nil. // In the case that the newrelic.Application is nil, a valid NewRelicZapCore object will still be returned. -func WrapTransactionCore(core zapcore.Core, txn *newrelic.Transaction) (*NewRelicZapCore, error) { +func WrapTransactionCore(core zapcore.Core, txn *newrelic.Transaction) (zapcore.Core, error) { if core == nil { return nil, ErrNilZapcore } @@ -167,9 +168,10 @@ func WrapTransactionCore(core zapcore.Core, txn *newrelic.Transaction) (*NewReli // With makes a copy of a NewRelicZapCore with new zap.Fields. It calls zapcore.With() on the zap core object // then makes a deepcopy of the NewRelicApplicationState object so the original // object can be deallocated when it's no longer in scope. -func (c NewRelicZapCore) With(fields []zap.Field) zapcore.Core { - return NewRelicZapCore{ - core: c.core.With(fields), +func (c *NewRelicZapCore) With(fields []zap.Field) zapcore.Core { + return &NewRelicZapCore{ + core: c.core.With(fields), + fields: append(fields, c.fields...), nr: newrelicApplicationState{ c.nr.app, c.nr.txn, @@ -178,24 +180,25 @@ func (c NewRelicZapCore) With(fields []zap.Field) zapcore.Core { } // Check simply calls zapcore.Check on the Core object. -func (c NewRelicZapCore) Check(entry zapcore.Entry, checkedEntry *zapcore.CheckedEntry) *zapcore.CheckedEntry { +func (c *NewRelicZapCore) Check(entry zapcore.Entry, checkedEntry *zapcore.CheckedEntry) *zapcore.CheckedEntry { ce := c.core.Check(entry, checkedEntry) ce.AddCore(entry, c) return ce } // Write wraps zapcore.Write and captures the log entry and sends that data to New Relic. -func (c NewRelicZapCore) Write(entry zapcore.Entry, fields []zap.Field) error { - c.nr.recordLog(entry, fields) +func (c *NewRelicZapCore) Write(entry zapcore.Entry, fields []zap.Field) error { + allFields := append(fields, c.fields...) + c.nr.recordLog(entry, allFields) return nil } // Sync simply calls zapcore.Sync on the Core object. -func (c NewRelicZapCore) Sync() error { +func (c *NewRelicZapCore) Sync() error { return c.core.Sync() } // Enabled simply calls zapcore.Enabled on the zapcore.Level passed to it. -func (c NewRelicZapCore) Enabled(level zapcore.Level) bool { +func (c *NewRelicZapCore) Enabled(level zapcore.Level) bool { return c.core.Enabled(level) } diff --git a/v3/integrations/logcontext-v2/nrzap/nrzap_test.go b/v3/integrations/logcontext-v2/nrzap/nrzap_test.go index 34100166b..9ceeaa2aa 100644 --- a/v3/integrations/logcontext-v2/nrzap/nrzap_test.go +++ b/v3/integrations/logcontext-v2/nrzap/nrzap_test.go @@ -160,6 +160,10 @@ func TestTransactionLoggerWithFields(t *testing.T) { t.Error(err) } + wrappedCore = wrappedCore.With([]zapcore.Field{ + zap.String("foo", "bar"), + }) + logger := zap.New(wrappedCore) msg := "this is a test info message" @@ -186,6 +190,7 @@ func TestTransactionLoggerWithFields(t *testing.T) { "duration": 1 * time.Second, "int": 123, "bool": true, + "foo": "bar", }, Severity: zap.InfoLevel.String(), Message: msg, diff --git a/v3/integrations/logcontext-v2/nrzerolog/go.mod b/v3/integrations/logcontext-v2/nrzerolog/go.mod index d8b2781d0..fb4593a92 100644 --- a/v3/integrations/logcontext-v2/nrzerolog/go.mod +++ b/v3/integrations/logcontext-v2/nrzerolog/go.mod @@ -3,7 +3,7 @@ module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrzerolog go 1.20 require ( - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/rs/zerolog v1.26.1 ) diff --git a/v3/integrations/logcontext-v2/zerologWriter/go.mod b/v3/integrations/logcontext-v2/zerologWriter/go.mod index dfdd583e6..0087200af 100644 --- a/v3/integrations/logcontext-v2/zerologWriter/go.mod +++ b/v3/integrations/logcontext-v2/zerologWriter/go.mod @@ -3,7 +3,7 @@ module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/zerologWriter go 1.20 require ( - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrwriter v1.0.0 github.com/rs/zerolog v1.27.0 ) diff --git a/v3/integrations/logcontext/nrlogrusplugin/go.mod b/v3/integrations/logcontext/nrlogrusplugin/go.mod index 2e6ec317e..15529e817 100644 --- a/v3/integrations/logcontext/nrlogrusplugin/go.mod +++ b/v3/integrations/logcontext/nrlogrusplugin/go.mod @@ -5,7 +5,7 @@ module github.com/newrelic/go-agent/v3/integrations/logcontext/nrlogrusplugin go 1.20 require ( - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 // v1.4.0 is required for for the log.WithContext. github.com/sirupsen/logrus v1.4.0 ) diff --git a/v3/integrations/nramqp/go.mod b/v3/integrations/nramqp/go.mod index 72015f620..3dcafdde2 100644 --- a/v3/integrations/nramqp/go.mod +++ b/v3/integrations/nramqp/go.mod @@ -3,7 +3,7 @@ module github.com/newrelic/go-agent/v3/integrations/nramqp go 1.20 require ( - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/rabbitmq/amqp091-go v1.9.0 ) replace github.com/newrelic/go-agent/v3 => ../.. diff --git a/v3/integrations/nrawsbedrock/go.mod b/v3/integrations/nrawsbedrock/go.mod index 2b7a3917d..72edb5c9e 100644 --- a/v3/integrations/nrawsbedrock/go.mod +++ b/v3/integrations/nrawsbedrock/go.mod @@ -8,7 +8,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/bedrock v1.7.3 github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.7.1 github.com/google/uuid v1.3.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrawssdk-v1/go.mod b/v3/integrations/nrawssdk-v1/go.mod index 10a481a16..b41c39600 100644 --- a/v3/integrations/nrawssdk-v1/go.mod +++ b/v3/integrations/nrawssdk-v1/go.mod @@ -8,7 +8,7 @@ go 1.20 require ( // v1.15.0 is the first aws-sdk-go version with module support. github.com/aws/aws-sdk-go v1.34.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrawssdk-v2/go.mod b/v3/integrations/nrawssdk-v2/go.mod index d503c908d..2a81df60a 100644 --- a/v3/integrations/nrawssdk-v2/go.mod +++ b/v3/integrations/nrawssdk-v2/go.mod @@ -11,7 +11,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/lambda v1.24.5 github.com/aws/aws-sdk-go-v2/service/s3 v1.27.10 github.com/aws/smithy-go v1.13.3 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrb3/go.mod b/v3/integrations/nrb3/go.mod index 14dd3694d..d69016b42 100644 --- a/v3/integrations/nrb3/go.mod +++ b/v3/integrations/nrb3/go.mod @@ -2,7 +2,7 @@ module github.com/newrelic/go-agent/v3/integrations/nrb3 go 1.20 -require github.com/newrelic/go-agent/v3 v3.32.0 +require github.com/newrelic/go-agent/v3 v3.33.1 replace github.com/newrelic/go-agent/v3 => ../.. diff --git a/v3/integrations/nrecho-v3/go.mod b/v3/integrations/nrecho-v3/go.mod index 53f9ea7ee..a8b06b82b 100644 --- a/v3/integrations/nrecho-v3/go.mod +++ b/v3/integrations/nrecho-v3/go.mod @@ -8,7 +8,7 @@ require ( // v3.1.0 is the earliest v3 version of Echo that works with modules due // to the github.com/rsc/letsencrypt import of v3.0.0. github.com/labstack/echo v3.1.0+incompatible - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrecho-v3/nrecho.go b/v3/integrations/nrecho-v3/nrecho.go index 06de72784..05b8521a1 100644 --- a/v3/integrations/nrecho-v3/nrecho.go +++ b/v3/integrations/nrecho-v3/nrecho.go @@ -51,15 +51,15 @@ func handlerName(router interface{}) string { } } -func transactionName(c echo.Context) string { +func transactionName(c echo.Context) (string, string) { ptr := handlerPointer(c.Handler()) if ptr == handlerPointer(echo.NotFoundHandler) { - return "NotFoundHandler" + return "NotFoundHandler", "" } if ptr == handlerPointer(echo.MethodNotAllowedHandler) { - return "MethodNotAllowedHandler" + return "MethodNotAllowedHandler", "" } - return c.Request().Method + " " + c.Path() + return c.Request().Method + " " + c.Path(), c.Path() } // Middleware creates Echo middleware that instruments requests. @@ -77,9 +77,12 @@ func Middleware(app *newrelic.Application) func(echo.HandlerFunc) echo.HandlerFu return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) (err error) { rw := c.Response().Writer - txn := app.StartTransaction(transactionName(c)) + tName, route := transactionName(c) + txn := app.StartTransaction(tName) defer txn.End() - + if newrelic.IsSecurityAgentPresent() { + txn.SetCsecAttributes(newrelic.AttributeCsecRoute, route) + } txn.SetWebRequestHTTP(c.Request()) c.Response().Writer = txn.SetWebResponse(rw) @@ -112,14 +115,13 @@ func Middleware(app *newrelic.Application) func(echo.HandlerFunc) echo.HandlerFu // which is used to detect application URL mapping(api-endpoints) for provable security. // In this version of the integration, this wrapper is only necessary if you are using the New Relic security agent integration [https://github.com/newrelic/go-agent/tree/master/v3/integrations/nrsecurityagent], // but it may be enhanced to provide additional functionality in future releases. -// e := echo.New() -// .... -// .... -// .... // -// nrecho.WrapRouter(e) +// e := echo.New() +// .... +// .... +// .... // - +// nrecho.WrapRouter(e) func WrapRouter(engine *echo.Echo) { if engine != nil && newrelic.IsSecurityAgentPresent() { router := engine.Routes() diff --git a/v3/integrations/nrecho-v4/go.mod b/v3/integrations/nrecho-v4/go.mod index 709dae0cd..a4ca9897e 100644 --- a/v3/integrations/nrecho-v4/go.mod +++ b/v3/integrations/nrecho-v4/go.mod @@ -6,7 +6,7 @@ go 1.20 require ( github.com/labstack/echo/v4 v4.9.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrecho-v4/nrecho.go b/v3/integrations/nrecho-v4/nrecho.go index d768c7a76..130d04b29 100644 --- a/v3/integrations/nrecho-v4/nrecho.go +++ b/v3/integrations/nrecho-v4/nrecho.go @@ -35,15 +35,15 @@ func handlerPointer(handler echo.HandlerFunc) uintptr { return reflect.ValueOf(handler).Pointer() } -func transactionName(c echo.Context) string { +func transactionName(c echo.Context) (string, string) { ptr := handlerPointer(c.Handler()) if ptr == handlerPointer(echo.NotFoundHandler) { - return "NotFoundHandler" + return "NotFoundHandler", "" } if ptr == handlerPointer(echo.MethodNotAllowedHandler) { - return "MethodNotAllowedHandler" + return "MethodNotAllowedHandler", "" } - return c.Request().Method + " " + c.Path() + return c.Request().Method + " " + c.Path(), c.Path() } // Skipper defines a function to skip middleware. Returning true skips processing @@ -100,9 +100,12 @@ func Middleware(app *newrelic.Application, opts ...ConfigOption) func(echo.Handl } rw := c.Response().Writer - txn := config.App.StartTransaction(transactionName(c)) + tname, path := transactionName(c) + txn := config.App.StartTransaction(tname) defer txn.End() - + if newrelic.IsSecurityAgentPresent() { + txn.SetCsecAttributes(newrelic.AttributeCsecRoute, path) + } txn.SetWebRequestHTTP(c.Request()) c.Response().Writer = txn.SetWebResponse(rw) @@ -135,14 +138,13 @@ func Middleware(app *newrelic.Application, opts ...ConfigOption) func(echo.Handl // which is used to detect application URL mapping(api-endpoints) for provable security. // In this version of the integration, this wrapper is only necessary if you are using the New Relic security agent integration [https://github.com/newrelic/go-agent/tree/master/v3/integrations/nrsecurityagent], // but it may be enhanced to provide additional functionality in future releases. -// e := echo.New() -// .... -// .... -// .... // -// nrecho.WrapRouter(e) +// e := echo.New() +// .... +// .... +// .... // - +// nrecho.WrapRouter(e) func WrapRouter(engine *echo.Echo) { if engine != nil && newrelic.IsSecurityAgentPresent() { router := engine.Routes() diff --git a/v3/integrations/nrelasticsearch-v7/go.mod b/v3/integrations/nrelasticsearch-v7/go.mod index c1a838d43..d8466fb6c 100644 --- a/v3/integrations/nrelasticsearch-v7/go.mod +++ b/v3/integrations/nrelasticsearch-v7/go.mod @@ -6,7 +6,7 @@ go 1.20 require ( github.com/elastic/go-elasticsearch/v7 v7.17.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod b/v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod index 9a1d14830..a0d677eff 100644 --- a/v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod +++ b/v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod @@ -3,7 +3,7 @@ module client-example go 1.20 require ( - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/newrelic/go-agent/v3/integrations/nrfasthttp v1.0.0 github.com/valyala/fasthttp v1.49.0 ) diff --git a/v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod b/v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod index a1f5053a1..13278efc6 100644 --- a/v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod +++ b/v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod @@ -3,7 +3,7 @@ module server-example go 1.20 require ( - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/newrelic/go-agent/v3/integrations/nrfasthttp v1.0.0 github.com/valyala/fasthttp v1.49.0 ) diff --git a/v3/integrations/nrfasthttp/go.mod b/v3/integrations/nrfasthttp/go.mod index deaf31122..f008cedb3 100644 --- a/v3/integrations/nrfasthttp/go.mod +++ b/v3/integrations/nrfasthttp/go.mod @@ -3,7 +3,7 @@ module github.com/newrelic/go-agent/v3/integrations/nrfasthttp go 1.20 require ( - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/valyala/fasthttp v1.49.0 ) diff --git a/v3/integrations/nrfasthttp/instrumentation.go b/v3/integrations/nrfasthttp/instrumentation.go index f05a8b770..ff07a84d9 100644 --- a/v3/integrations/nrfasthttp/instrumentation.go +++ b/v3/integrations/nrfasthttp/instrumentation.go @@ -68,12 +68,16 @@ func WrapHandle(app *newrelic.Application, pattern string, handler fasthttp.Requ fasthttpadaptor.ConvertRequest(ctx, r, true) resp := fasthttpWrapperResponse{ctx: ctx} + if newrelic.IsSecurityAgentPresent() { + txn.SetCsecAttributes(newrelic.AttributeCsecRoute, pattern) + } txn.SetWebResponse(resp) txn.SetWebRequestHTTP(r) handler(ctx) if newrelic.IsSecurityAgentPresent() { newrelic.GetSecurityAgentInterface().SendEvent("INBOUND_WRITE", resp.Body(), resp.Header()) + newrelic.GetSecurityAgentInterface().SendEvent("INBOUND_RESPONSE_CODE", ctx.Response.StatusCode()) } } } diff --git a/v3/integrations/nrgin/go.mod b/v3/integrations/nrgin/go.mod index 5b440be98..b2d9ee716 100644 --- a/v3/integrations/nrgin/go.mod +++ b/v3/integrations/nrgin/go.mod @@ -6,7 +6,7 @@ go 1.20 require ( github.com/gin-gonic/gin v1.9.1 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrgin/nrgin.go b/v3/integrations/nrgin/nrgin.go index e2943466b..03bb266c0 100644 --- a/v3/integrations/nrgin/nrgin.go +++ b/v3/integrations/nrgin/nrgin.go @@ -148,14 +148,13 @@ func MiddlewareHandlerTxnNames(app *newrelic.Application) gin.HandlerFunc { // which is used to detect application URL mapping(api-endpoints) for provable security. // In this version of the integration, this wrapper is only necessary if you are using the New Relic security agent integration [https://github.com/newrelic/go-agent/tree/master/v3/integrations/nrsecurityagent], // but it may be enhanced to provide additional functionality in future releases. -// router := gin.Default() -// .... -// .... -// .... // -// nrgin.WrapRouter(router) +// router := gin.Default() +// .... +// .... +// .... // - +// nrgin.WrapRouter(router) func WrapRouter(engine *gin.Engine) { if engine != nil && newrelic.IsSecurityAgentPresent() { router := engine.Routes() @@ -171,6 +170,9 @@ func middleware(app *newrelic.Application, useNewNames bool) gin.HandlerFunc { w := &headerResponseWriter{w: c.Writer} txn := app.StartTransaction(name, newrelic.WithFunctionLocation(c.Handler())) + if newrelic.IsSecurityAgentPresent() { + txn.SetCsecAttributes(newrelic.AttributeCsecRoute, c.FullPath()) + } txn.SetWebRequestHTTP(c.Request) defer txn.End() diff --git a/v3/integrations/nrgorilla/go.mod b/v3/integrations/nrgorilla/go.mod index d515a54d0..a8d1deef6 100644 --- a/v3/integrations/nrgorilla/go.mod +++ b/v3/integrations/nrgorilla/go.mod @@ -7,7 +7,7 @@ go 1.20 require ( // v1.7.0 is the earliest version of Gorilla using modules. github.com/gorilla/mux v1.7.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrgorilla/nrgorilla.go b/v3/integrations/nrgorilla/nrgorilla.go index a8425d0a3..81a0e2184 100644 --- a/v3/integrations/nrgorilla/nrgorilla.go +++ b/v3/integrations/nrgorilla/nrgorilla.go @@ -64,6 +64,18 @@ func routeName(r *http.Request) string { return r.Method + " " + n } +func handlerName(r *http.Request) string { + route := mux.CurrentRoute(r) + if nil == route { + return r.RequestURI + } + if n, _ := route.GetPathTemplate(); n != "" { + return n + } else { + return r.RequestURI + } +} + // InstrumentRoutes instruments requests through the provided mux.Router. Use // this after the routes have been added to the router. // @@ -104,6 +116,9 @@ func Middleware(app *newrelic.Application) mux.MiddlewareFunc { name := routeName(r) txn := app.StartTransaction(name) defer txn.End() + if newrelic.IsSecurityAgentPresent() { + txn.SetCsecAttributes(newrelic.AttributeCsecRoute, handlerName(r)) + } txn.SetWebRequestHTTP(r) w = txn.SetWebResponse(w) r = newrelic.RequestWithTransactionContext(r, txn) @@ -116,14 +131,13 @@ func Middleware(app *newrelic.Application) mux.MiddlewareFunc { // which is used to detect application URL mapping(api-endpoints) for provable security. // In this version of the integration, this wrapper is only necessary if you are using the New Relic security agent integration [https://github.com/newrelic/go-agent/tree/master/v3/integrations/nrsecurityagent], // but it may be enhanced to provide additional functionality in future releases. -// r := mux.NewRouter() -// .... -// .... -// .... // -// nrgorilla.WrapRouter(router) +// r := mux.NewRouter() +// .... +// .... +// .... // - +// nrgorilla.WrapRouter(router) func WrapRouter(router *mux.Router) { if router != nil && newrelic.IsSecurityAgentPresent() { router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { diff --git a/v3/integrations/nrgraphgophers/go.mod b/v3/integrations/nrgraphgophers/go.mod index ca56c234e..d2e57b713 100644 --- a/v3/integrations/nrgraphgophers/go.mod +++ b/v3/integrations/nrgraphgophers/go.mod @@ -7,7 +7,7 @@ go 1.20 require ( // graphql-go has no tagged releases as of Jan 2020. github.com/graph-gophers/graphql-go v1.3.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrgraphqlgo/example/go.mod b/v3/integrations/nrgraphqlgo/example/go.mod index 2018d0d9d..bee83ed6a 100644 --- a/v3/integrations/nrgraphqlgo/example/go.mod +++ b/v3/integrations/nrgraphqlgo/example/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/graphql-go/graphql v0.8.1 github.com/graphql-go/graphql-go-handler v0.2.3 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/newrelic/go-agent/v3/integrations/nrgraphqlgo v1.0.0 ) diff --git a/v3/integrations/nrgraphqlgo/go.mod b/v3/integrations/nrgraphqlgo/go.mod index c99dff089..9f3a90a80 100644 --- a/v3/integrations/nrgraphqlgo/go.mod +++ b/v3/integrations/nrgraphqlgo/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/graphql-go/graphql v0.8.1 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrgrpc/go.mod b/v3/integrations/nrgrpc/go.mod index aaa6c82a2..2d76c537a 100644 --- a/v3/integrations/nrgrpc/go.mod +++ b/v3/integrations/nrgrpc/go.mod @@ -6,7 +6,7 @@ require ( // protobuf v1.3.0 is the earliest version using modules, we use v1.3.1 // because all dependencies were removed in this version. github.com/golang/protobuf v1.5.3 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/newrelic/go-agent/v3/integrations/nrsecurityagent v1.1.0 // v1.15.0 is the earliest version of grpc using modules. google.golang.org/grpc v1.56.3 diff --git a/v3/integrations/nrgrpc/nrgrpc_server.go b/v3/integrations/nrgrpc/nrgrpc_server.go index 8744f5380..fac1d0201 100644 --- a/v3/integrations/nrgrpc/nrgrpc_server.go +++ b/v3/integrations/nrgrpc/nrgrpc_server.go @@ -79,29 +79,26 @@ func startTransaction(ctx context.Context, app *newrelic.Application, fullMethod ServerName: target, } txn := app.StartTransaction(method) + if newrelic.IsSecurityAgentPresent() { + txn.SetCsecAttributes(newrelic.AttributeCsecRoute, method) + } txn.SetWebRequest(webReq) return txn } -// // ErrorHandler is the type of a gRPC status handler function. // Normally the supplied set of ErrorHandler functions will suffice, but // a custom handler may be crafted by the user and installed as a handler // if needed. -// type ErrorHandler func(context.Context, *newrelic.Transaction, *status.Status) -// // Internal registry of handlers associated with various // status codes. -// type statusHandlerMap map[codes.Code]ErrorHandler -// // interceptorStatusHandlerRegistry is the current default set of handlers // used by each interceptor. -// var interceptorStatusHandlerRegistry = statusHandlerMap{ codes.OK: OKInterceptorStatusHandler, codes.Canceled: InfoInterceptorStatusHandler, @@ -122,13 +119,10 @@ var interceptorStatusHandlerRegistry = statusHandlerMap{ codes.Unauthenticated: InfoInterceptorStatusHandler, } -// // HandlerOption is the type for options passed to the interceptor // functions to specify gRPC status handlers. -// type HandlerOption func(statusHandlerMap) -// // WithStatusHandler indicates a handler function to be used to // report the indicated gRPC status. Zero or more of these may be // given to the Configure, StreamServiceInterceptor, or @@ -136,73 +130,70 @@ type HandlerOption func(statusHandlerMap) // // The ErrorHandler parameter is generally one of the provided standard // reporting functions: -// OKInterceptorStatusHandler // report the operation as successful -// ErrorInterceptorStatusHandler // report the operation as an error -// WarningInterceptorStatusHandler // report the operation as a warning -// InfoInterceptorStatusHandler // report the operation as an informational message +// +// OKInterceptorStatusHandler // report the operation as successful +// ErrorInterceptorStatusHandler // report the operation as an error +// WarningInterceptorStatusHandler // report the operation as a warning +// InfoInterceptorStatusHandler // report the operation as an informational message // // The following reporting function should only be used if you know for sure // you want this. It does not report the error in any way at all, but completely // ignores it. -// IgnoreInterceptorStatusHandler // report the operation as successful +// +// IgnoreInterceptorStatusHandler // report the operation as successful // // Finally, if you have a custom reporting need that isn't covered by the standard // handler functions, you can create your own handler function as -// func myHandler(ctx context.Context, txn *newrelic.Transaction, s *status.Status) { -// ... -// } +// +// func myHandler(ctx context.Context, txn *newrelic.Transaction, s *status.Status) { +// ... +// } +// // Within the function, do whatever you need to do with the txn parameter to report the // gRPC status passed as s. If needed, the context is also passed to your function. // // If you wish to use your custom handler for a code such as codes.NotFound, you would // include the parameter -// WithStatusHandler(codes.NotFound, myHandler) -// to your Configure, StreamServiceInterceptor, or UnaryServiceInterceptor function. // +// WithStatusHandler(codes.NotFound, myHandler) +// +// to your Configure, StreamServiceInterceptor, or UnaryServiceInterceptor function. func WithStatusHandler(c codes.Code, h ErrorHandler) HandlerOption { return func(m statusHandlerMap) { m[c] = h } } -// // Configure takes a list of WithStatusHandler options and sets them // as the new default handlers for the specified gRPC status codes, in the same // way as if WithStatusHandler were given to the StreamServiceInterceptor // or UnaryServiceInterceptor functions (q.v.); however, in this case the new handlers // become the default for any subsequent interceptors created by the above functions. -// func Configure(options ...HandlerOption) { for _, option := range options { option(interceptorStatusHandlerRegistry) } } -// // IgnoreInterceptorStatusHandler is our standard handler for // gRPC statuses which we want to ignore (in terms of any gRPC-specific // reporting on the transaction). -// func IgnoreInterceptorStatusHandler(_ context.Context, _ *newrelic.Transaction, _ *status.Status) {} -// // OKInterceptorStatusHandler is our standard handler for // gRPC statuses which we want to report as being successful, as with the // status code OK. // // This adds no additional attributes on the transaction other than // the fact that it was successful. -// func OKInterceptorStatusHandler(ctx context.Context, txn *newrelic.Transaction, s *status.Status) { txn.SetWebResponse(nil).WriteHeader(int(codes.OK)) } -// // ErrorInterceptorStatusHandler is our standard handler for // gRPC statuses which we want to report as being errors, // with the relevant error messages and // contextual information gleaned from the error value received from the RPC call. -// func ErrorInterceptorStatusHandler(ctx context.Context, txn *newrelic.Transaction, s *status.Status) { txn.SetWebResponse(nil).WriteHeader(int(codes.OK)) txn.NoticeError(&newrelic.Error{ @@ -214,13 +205,11 @@ func ErrorInterceptorStatusHandler(ctx context.Context, txn *newrelic.Transactio txn.AddAttribute("grpcStatusCode", s.Code().String()) } -// // WarningInterceptorStatusHandler is our standard handler for // gRPC statuses which we want to report as warnings. // // Reports the transaction's status with attributes containing information gleaned // from the error value returned, but does not count this as an error. -// func WarningInterceptorStatusHandler(ctx context.Context, txn *newrelic.Transaction, s *status.Status) { txn.SetWebResponse(nil).WriteHeader(int(codes.OK)) txn.AddAttribute("grpcStatusLevel", "warning") @@ -228,13 +217,11 @@ func WarningInterceptorStatusHandler(ctx context.Context, txn *newrelic.Transact txn.AddAttribute("grpcStatusCode", s.Code().String()) } -// // InfoInterceptorStatusHandler is our standard handler for // gRPC statuses which we want to report as informational messages only. // // Reports the transaction's status with attributes containing information gleaned // from the error value returned, but does not count this as an error. -// func InfoInterceptorStatusHandler(ctx context.Context, txn *newrelic.Transaction, s *status.Status) { txn.SetWebResponse(nil).WriteHeader(int(codes.OK)) txn.AddAttribute("grpcStatusLevel", "info") @@ -242,16 +229,12 @@ func InfoInterceptorStatusHandler(ctx context.Context, txn *newrelic.Transaction txn.AddAttribute("grpcStatusCode", s.Code().String()) } -// // DefaultInterceptorStatusHandler indicates which of our standard handlers // will be used for any status code which is not // explicitly assigned a handler. -// var DefaultInterceptorStatusHandler = InfoInterceptorStatusHandler -// // reportInterceptorStatus is the common routine for reporting any kind of interceptor. -// func reportInterceptorStatus(ctx context.Context, txn *newrelic.Transaction, handlers statusHandlerMap, err error) { grpcStatus := status.Convert(err) handler, ok := handlers[grpcStatus.Code()] @@ -296,12 +279,13 @@ func reportInterceptorStatus(ctx context.Context, txn *newrelic.Transaction, han // You can specify a custom set of handlers with each interceptor creation by adding // WithStatusHandler calls at the end of the StreamInterceptor call's parameter list, // like so: -// grpc.UnaryInterceptor(nrgrpc.UnaryServerInterceptor(app, -// nrgrpc.WithStatusHandler(codes.OutOfRange, nrgrpc.WarningInterceptorStatusHandler), -// nrgrpc.WithStatusHandler(codes.Unimplemented, nrgrpc.InfoInterceptorStatusHandler))) +// +// grpc.UnaryInterceptor(nrgrpc.UnaryServerInterceptor(app, +// nrgrpc.WithStatusHandler(codes.OutOfRange, nrgrpc.WarningInterceptorStatusHandler), +// nrgrpc.WithStatusHandler(codes.Unimplemented, nrgrpc.InfoInterceptorStatusHandler))) +// // In this case, those two handlers are used (along with the current defaults for the other status // codes) only for that interceptor. -// func UnaryServerInterceptor(app *newrelic.Application, options ...HandlerOption) grpc.UnaryServerInterceptor { if app == nil { return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { @@ -394,6 +378,29 @@ func StreamServerInterceptor(app *newrelic.Application, options ...HandlerOption } } +// WrapRouter extracts API endpoints from the grpc server instance passed to it +// which is used to detect application URL mapping(api-endpoints) for provable security. +// In this version of the integration, this wrapper is only necessary if you are using the New Relic security agent integration [https://github.com/newrelic/go-agent/tree/master/v3/integrations/nrsecurityagent], +// but it may be enhanced to provide additional functionality in future releases. +// +// grpcServer := grpc.NewServer(...) +// .... +// .... +// .... +// +// nrgrpc.WrapRouter(grpcServer) +func WrapRouter(server *grpc.Server) { + if server != nil && newrelic.IsSecurityAgentPresent() { + for n, info := range server.GetServiceInfo() { + if info.Methods != nil { + for i := range info.Methods { + newrelic.GetSecurityAgentInterface().SendEvent("API_END_POINTS", n+"/"+info.Methods[i].Name, "*", info.Methods[i].Name) + } + } + } + } +} + func getMessageType(req any) (string, string) { messageType := "" version := "v2" diff --git a/v3/integrations/nrhttprouter/go.mod b/v3/integrations/nrhttprouter/go.mod index a1cc04662..8a3ccdb75 100644 --- a/v3/integrations/nrhttprouter/go.mod +++ b/v3/integrations/nrhttprouter/go.mod @@ -7,7 +7,7 @@ go 1.20 require ( // v1.3.0 is the earliest version of httprouter using modules. github.com/julienschmidt/httprouter v1.3.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrhttprouter/nrhttprouter.go b/v3/integrations/nrhttprouter/nrhttprouter.go index d1df356eb..8239dae1a 100644 --- a/v3/integrations/nrhttprouter/nrhttprouter.go +++ b/v3/integrations/nrhttprouter/nrhttprouter.go @@ -74,6 +74,9 @@ func (r *Router) handle(method string, path string, original httprouter.Handle) if nil != r.application { handle = func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { txn := r.application.StartTransaction(txnName(method, path)) + if newrelic.IsSecurityAgentPresent() { + txn.SetCsecAttributes(newrelic.AttributeCsecRoute, path) + } txn.SetWebRequestHTTP(req) w = txn.SetWebResponse(w) defer txn.End() diff --git a/v3/integrations/nrlambda/go.mod b/v3/integrations/nrlambda/go.mod index 534debcf6..9f12b1fac 100644 --- a/v3/integrations/nrlambda/go.mod +++ b/v3/integrations/nrlambda/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/aws/aws-lambda-go v1.41.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrlogrus/go.mod b/v3/integrations/nrlogrus/go.mod index 241faf5c1..6ecb3fd54 100644 --- a/v3/integrations/nrlogrus/go.mod +++ b/v3/integrations/nrlogrus/go.mod @@ -5,7 +5,7 @@ module github.com/newrelic/go-agent/v3/integrations/nrlogrus go 1.20 require ( - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrlogrus v1.0.0 // v1.1.0 is required for the Logger.GetLevel method, and is the earliest // version of logrus using modules. diff --git a/v3/integrations/nrlogxi/go.mod b/v3/integrations/nrlogxi/go.mod index c2632d57b..eab91404f 100644 --- a/v3/integrations/nrlogxi/go.mod +++ b/v3/integrations/nrlogxi/go.mod @@ -7,7 +7,7 @@ go 1.20 require ( // 'v1', at commit aebf8a7d67ab, is the only logxi release. github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrmicro/go.mod b/v3/integrations/nrmicro/go.mod index ee19c0d00..482f339a9 100644 --- a/v3/integrations/nrmicro/go.mod +++ b/v3/integrations/nrmicro/go.mod @@ -4,11 +4,13 @@ module github.com/newrelic/go-agent/v3/integrations/nrmicro // https://github.com/micro/go-micro/blob/master/go.mod go 1.20 +toolchain go1.22.3 + require ( github.com/golang/protobuf v1.5.4 github.com/micro/go-micro v1.8.0 - github.com/newrelic/go-agent/v3 v3.32.0 - google.golang.org/protobuf v1.33.0 + github.com/newrelic/go-agent/v3 v3.33.1 + google.golang.org/protobuf v1.34.1 ) diff --git a/v3/integrations/nrmongo/go.mod b/v3/integrations/nrmongo/go.mod index 078f8f0de..10132f41f 100644 --- a/v3/integrations/nrmongo/go.mod +++ b/v3/integrations/nrmongo/go.mod @@ -5,7 +5,7 @@ module github.com/newrelic/go-agent/v3/integrations/nrmongo go 1.20 require ( - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 // mongo-driver does not support modules as of Nov 2019. go.mongodb.org/mongo-driver v1.10.2 ) diff --git a/v3/integrations/nrmssql/go.mod b/v3/integrations/nrmssql/go.mod index dfd549cbc..f8df4469c 100644 --- a/v3/integrations/nrmssql/go.mod +++ b/v3/integrations/nrmssql/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/microsoft/go-mssqldb v0.19.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrmysql/go.mod b/v3/integrations/nrmysql/go.mod index 44514daa6..7c41cc27e 100644 --- a/v3/integrations/nrmysql/go.mod +++ b/v3/integrations/nrmysql/go.mod @@ -7,7 +7,7 @@ require ( // v1.5.0 is the first mysql version to support gomod github.com/go-sql-driver/mysql v1.6.0 // v3.3.0 includes the new location of ParseQuery - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrnats/go.mod b/v3/integrations/nrnats/go.mod index 56ea68997..909a95903 100644 --- a/v3/integrations/nrnats/go.mod +++ b/v3/integrations/nrnats/go.mod @@ -7,7 +7,7 @@ go 1.20 require ( github.com/nats-io/nats-server v1.4.1 github.com/nats-io/nats.go v1.28.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrnats/test/go.mod b/v3/integrations/nrnats/test/go.mod index 7ae18f9c5..3f6ec494f 100644 --- a/v3/integrations/nrnats/test/go.mod +++ b/v3/integrations/nrnats/test/go.mod @@ -8,7 +8,7 @@ replace github.com/newrelic/go-agent/v3/integrations/nrnats v1.0.0 => ../ require ( github.com/nats-io/nats-server v1.4.1 github.com/nats-io/nats.go v1.17.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/newrelic/go-agent/v3/integrations/nrnats v1.0.0 ) diff --git a/v3/integrations/nropenai/go.mod b/v3/integrations/nropenai/go.mod index ccb8a89cc..5dd7d6899 100644 --- a/v3/integrations/nropenai/go.mod +++ b/v3/integrations/nropenai/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/google/uuid v1.6.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/pkoukk/tiktoken-go v0.1.6 github.com/sashabaranov/go-openai v1.20.2 ) diff --git a/v3/integrations/nropenai/nropenai.go b/v3/integrations/nropenai/nropenai.go index b858aa10b..5febb32b6 100644 --- a/v3/integrations/nropenai/nropenai.go +++ b/v3/integrations/nropenai/nropenai.go @@ -45,6 +45,7 @@ var ( errAIMonitoringDisabled = errors.New("AI Monitoring is set to disabled or High Security Mode is enabled. Please enable AI Monitoring and ensure High Security Mode is disabled") ) +// OpenAIClient is any type that can invoke OpenAI model with a request. type OpenAIClient interface { CreateChatCompletion(ctx context.Context, request openai.ChatCompletionRequest) (response openai.ChatCompletionResponse, err error) CreateChatCompletionStream(ctx context.Context, request openai.ChatCompletionRequest) (stream *openai.ChatCompletionStream, err error) @@ -157,7 +158,10 @@ func GetInput(any interface{}) any { } -// Wrapper for Recv() method that calls the underlying stream's Recv() method +// Wrapper for OpenAI Streaming Recv() method +// Captures the response messages as they are received in the wrapper +// Once the stream is closed, the Close() method is called and sends the captured +// data to New Relic func (w *ChatCompletionStreamWrapper) Recv() (openai.ChatCompletionStreamResponse, error) { response, err := w.stream.Recv() if err != nil { @@ -184,6 +188,7 @@ func (w *ChatCompletionStreamWrapper) Recv() (openai.ChatCompletionStreamRespons } +// Close the stream and send the event to New Relic func (w *ChatCompletionStreamWrapper) Close() { w.StreamingData["response.model"] = w.model NRCreateChatCompletionMessageStream(w.app, uuid.MustParse(w.uuid), w, w.cw, w.sequence) @@ -200,8 +205,11 @@ func (w *ChatCompletionStreamWrapper) Close() { w.stream.Close() } -// NRCreateChatCompletionSummary captures the request and response data for a chat completion request and records a custom event in New Relic. It also captures the completion messages -// With a call to NRCreateChatCompletionMessage +// NRCreateChatCompletionSummary captures the request data for a chat completion request +// A new segment is created for the chat completion request, and the response data is timed and captured +// Custom attributes are added to the event if they exist from client.AddCustomAttributes() +// After closing out the custom event for the chat completion summary, the function then calls +// NRCreateChatCompletionMessageInput/NRCreateChatCompletionMessage to capture the request messages func NRCreateChatCompletionSummary(txn *newrelic.Transaction, app *newrelic.Application, cw *ClientWrapper, req openai.ChatCompletionRequest) ChatCompletionResponseWrapper { // Start span txn.AddAttribute("llm", true) @@ -311,6 +319,9 @@ func NRCreateChatCompletionSummary(txn *newrelic.Transaction, app *newrelic.Appl } // Captures initial request messages and records a custom event in New Relic for each message +// similarly to NRCreateChatCompletionMessage, but only for the request messages +// Returns the sequence of the messages sent in the request +// which is used to calculate the sequence in the response messages func NRCreateChatCompletionMessageInput(txn *newrelic.Transaction, app *newrelic.Application, req openai.ChatCompletionRequest, inputuuid uuid.UUID, cw *ClientWrapper) int { sequence := 0 for i, message := range req.Messages { @@ -358,7 +369,13 @@ func NRCreateChatCompletionMessageInput(txn *newrelic.Transaction, app *newrelic } -// NRCreateChatCompletionMessage captures the completion response messages and records a custom event in New Relic for each message +// NRCreateChatCompletionMessage captures the completion response messages and records a custom event +// in New Relic for each message. The completion response messages are the responses from the model +// after the request messages have been sent and logged in NRCreateChatCompletionMessageInput. +// The sequence of the messages is calculated by logging each of the request messages first, then +// incrementing the sequence for each response message. +// The token count is calculated for each message and added to the custom event if the token count callback is set +// If not, no token count is added to the custom event func NRCreateChatCompletionMessage(txn *newrelic.Transaction, app *newrelic.Application, resp openai.ChatCompletionResponse, uuid uuid.UUID, cw *ClientWrapper, sequence int, req openai.ChatCompletionRequest) { spanID := txn.GetTraceMetadata().SpanID traceID := txn.GetTraceMetadata().TraceID @@ -412,6 +429,8 @@ func NRCreateChatCompletionMessage(txn *newrelic.Transaction, app *newrelic.Appl } } +// NRCreateChatCompletionMessageStream is identical to NRCreateChatCompletionMessage, but for streaming responses. +// Gets invoked only when the stream is closed func NRCreateChatCompletionMessageStream(app *newrelic.Application, uuid uuid.UUID, sw *ChatCompletionStreamWrapper, cw *ClientWrapper, sequence int) { spanID := sw.txn.GetTraceMetadata().SpanID @@ -476,6 +495,32 @@ func TokenCountingHelper(app *newrelic.Application, message openai.ChatCompletio return numTokens, (contentCounted && roleCounted) } +// Similar to NRCreateChatCompletionSummary, but for streaming responses +// Returns a custom wrapper with a stream that can be used to receive messages +// Example Usage: +/* + ctx := context.Background() + stream, err := nropenai.NRCreateChatCompletionStream(client, ctx, req, app) + if err != nil { + panic(err) + } + for { + var response openai.ChatCompletionStreamResponse + response, err = stream.Recv() + if errors.Is(err, io.EOF) { + fmt.Println("\nStream finished") + break + } + if err != nil { + fmt.Printf("\nStream error: %v\n", err) + return + } + fmt.Printf(response.Choices[0].Delta.Content) + } + stream.Close() +*/ +// It is important to call stream.Close() after the stream has been used, as it will close the stream and send the event to New Relic. +// Additionally, custom attributes can be added to the client using client.AddCustomAttributes(map[string]interface{}) just like in NRCreateChatCompletionSummary func NRCreateChatCompletionStream(cw *ClientWrapper, ctx context.Context, req openai.ChatCompletionRequest, app *newrelic.Application) (*ChatCompletionStreamWrapper, error) { txn := app.StartTransaction("OpenAIChatCompletionStream") @@ -546,7 +591,13 @@ func NRCreateChatCompletionStream(cw *ClientWrapper, ctx context.Context, req op } // NRCreateChatCompletion is a wrapper for the OpenAI CreateChatCompletion method. -// If AI Monitoring is disabled, the wrapped function will still call the OpenAI CreateChatCompletion method and return the response with no New Relic instrumentation +// If AI Monitoring is disabled, the wrapped function will still call the OpenAI CreateChatCompletion method +// and return the response with no New Relic instrumentation +// Calls NRCreateChatCompletionSummary to capture the request data and response data +// Returns a ChatCompletionResponseWrapper with the response and the TraceID of the transaction +// The trace ID is used to link the chat response with its feedback, with a call to SendFeedback() +// Otherwise, the response is the same as the OpenAI CreateChatCompletion method. It can be accessed +// by calling resp.ChatCompletionResponse func NRCreateChatCompletion(cw *ClientWrapper, req openai.ChatCompletionRequest, app *newrelic.Application) (ChatCompletionResponseWrapper, error) { config, _ := app.Config() diff --git a/v3/integrations/nrpgx/example/sqlx/go.mod b/v3/integrations/nrpgx/example/sqlx/go.mod index 777934c00..23e247094 100644 --- a/v3/integrations/nrpgx/example/sqlx/go.mod +++ b/v3/integrations/nrpgx/example/sqlx/go.mod @@ -4,7 +4,7 @@ module github.com/newrelic/go-agent/v3/integrations/nrpgx/example/sqlx go 1.20 require ( github.com/jmoiron/sqlx v1.2.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/newrelic/go-agent/v3/integrations/nrpgx v0.0.0 ) replace github.com/newrelic/go-agent/v3/integrations/nrpgx => ../../ diff --git a/v3/integrations/nrpgx/go.mod b/v3/integrations/nrpgx/go.mod index 0b1539aef..0a493f988 100644 --- a/v3/integrations/nrpgx/go.mod +++ b/v3/integrations/nrpgx/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/jackc/pgx v3.6.2+incompatible github.com/jackc/pgx/v4 v4.18.2 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrpgx5/go.mod b/v3/integrations/nrpgx5/go.mod index 3f7cb5026..296c4a427 100644 --- a/v3/integrations/nrpgx5/go.mod +++ b/v3/integrations/nrpgx5/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/egon12/pgsnap v0.0.0-20221022154027-2847f0124ed8 github.com/jackc/pgx/v5 v5.5.4 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/stretchr/testify v1.8.1 ) diff --git a/v3/integrations/nrpkgerrors/go.mod b/v3/integrations/nrpkgerrors/go.mod index b5d3022a3..bbfa68b6f 100644 --- a/v3/integrations/nrpkgerrors/go.mod +++ b/v3/integrations/nrpkgerrors/go.mod @@ -5,7 +5,7 @@ module github.com/newrelic/go-agent/v3/integrations/nrpkgerrors go 1.20 require ( - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 // v0.8.0 was the last release in 2016, and when // major development on pkg/errors stopped. github.com/pkg/errors v0.8.0 diff --git a/v3/integrations/nrpq/example/sqlx/go.mod b/v3/integrations/nrpq/example/sqlx/go.mod index e41cddc77..8cd169014 100644 --- a/v3/integrations/nrpq/example/sqlx/go.mod +++ b/v3/integrations/nrpq/example/sqlx/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/jmoiron/sqlx v1.2.0 github.com/lib/pq v1.1.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/newrelic/go-agent/v3/integrations/nrpq v0.0.0 ) replace github.com/newrelic/go-agent/v3/integrations/nrpq => ../../ diff --git a/v3/integrations/nrpq/go.mod b/v3/integrations/nrpq/go.mod index cf01efa1f..ae0452c72 100644 --- a/v3/integrations/nrpq/go.mod +++ b/v3/integrations/nrpq/go.mod @@ -6,7 +6,7 @@ require ( // NewConnector dsn parsing tests expect v1.1.0 error return behavior. github.com/lib/pq v1.1.0 // v3.3.0 includes the new location of ParseQuery - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrredis-v7/go.mod b/v3/integrations/nrredis-v7/go.mod index 4483f63b0..6eb4e6f12 100644 --- a/v3/integrations/nrredis-v7/go.mod +++ b/v3/integrations/nrredis-v7/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/go-redis/redis/v7 v7.0.0-beta.5 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrredis-v8/go.mod b/v3/integrations/nrredis-v8/go.mod index bcc60d118..7f184a876 100644 --- a/v3/integrations/nrredis-v8/go.mod +++ b/v3/integrations/nrredis-v8/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/go-redis/redis/v8 v8.4.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrredis-v9/go.mod b/v3/integrations/nrredis-v9/go.mod index 7e58a6341..9134cee4b 100644 --- a/v3/integrations/nrredis-v9/go.mod +++ b/v3/integrations/nrredis-v9/go.mod @@ -4,7 +4,7 @@ module github.com/newrelic/go-agent/v3/integrations/nrredis-v9 go 1.20 require ( - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/redis/go-redis/v9 v9.0.2 ) diff --git a/v3/integrations/nrsarama/go.mod b/v3/integrations/nrsarama/go.mod index 627c0e17d..1d2757a13 100644 --- a/v3/integrations/nrsarama/go.mod +++ b/v3/integrations/nrsarama/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/Shopify/sarama v1.38.1 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/stretchr/testify v1.8.1 ) diff --git a/v3/integrations/nrsecurityagent/go.mod b/v3/integrations/nrsecurityagent/go.mod index 78f9814bc..aae7f2977 100644 --- a/v3/integrations/nrsecurityagent/go.mod +++ b/v3/integrations/nrsecurityagent/go.mod @@ -3,8 +3,8 @@ module github.com/newrelic/go-agent/v3/integrations/nrsecurityagent go 1.20 require ( - github.com/newrelic/csec-go-agent v1.2.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/csec-go-agent v1.3.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/newrelic/go-agent/v3/integrations/nrsqlite3 v1.2.0 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/v3/integrations/nrsnowflake/go.mod b/v3/integrations/nrsnowflake/go.mod index b2e7f4b5c..8694527a6 100644 --- a/v3/integrations/nrsnowflake/go.mod +++ b/v3/integrations/nrsnowflake/go.mod @@ -3,7 +3,7 @@ module github.com/newrelic/go-agent/v3/integrations/nrsnowflake go 1.20 require ( - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/snowflakedb/gosnowflake v1.6.19 ) diff --git a/v3/integrations/nrsqlite3/go.mod b/v3/integrations/nrsqlite3/go.mod index 3a6eddb79..def941a1f 100644 --- a/v3/integrations/nrsqlite3/go.mod +++ b/v3/integrations/nrsqlite3/go.mod @@ -7,7 +7,7 @@ go 1.20 require ( github.com/mattn/go-sqlite3 v1.0.0 // v3.3.0 includes the new location of ParseQuery - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrstan/examples/go.mod b/v3/integrations/nrstan/examples/go.mod index f57e435b4..f3b544556 100644 --- a/v3/integrations/nrstan/examples/go.mod +++ b/v3/integrations/nrstan/examples/go.mod @@ -3,7 +3,7 @@ module github.com/newrelic/go-agent/v3/integrations/nrstan/examples go 1.20 require ( github.com/nats-io/stan.go v0.5.0 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/newrelic/go-agent/v3/integrations/nrnats v0.0.0 github.com/newrelic/go-agent/v3/integrations/nrstan v0.0.0 ) diff --git a/v3/integrations/nrstan/go.mod b/v3/integrations/nrstan/go.mod index aa283ea2e..fc7908b79 100644 --- a/v3/integrations/nrstan/go.mod +++ b/v3/integrations/nrstan/go.mod @@ -4,9 +4,11 @@ module github.com/newrelic/go-agent/v3/integrations/nrstan // https://github.com/nats-io/stan.go/blob/master/.travis.yml go 1.20 +toolchain go1.22.3 + require ( github.com/nats-io/stan.go v0.10.4 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 ) diff --git a/v3/integrations/nrstan/test/go.mod b/v3/integrations/nrstan/test/go.mod index 4708789e9..bb0001b15 100644 --- a/v3/integrations/nrstan/test/go.mod +++ b/v3/integrations/nrstan/test/go.mod @@ -4,10 +4,12 @@ module github.com/newrelic/go-agent/v3/integrations/nrstan/test // github.com/nats-io/nats-streaming-server in nrstan. go 1.20 +toolchain go1.22.3 + require ( github.com/nats-io/nats-streaming-server v0.25.6 github.com/nats-io/stan.go v0.10.4 - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/newrelic/go-agent/v3/integrations/nrstan v0.0.0 ) diff --git a/v3/integrations/nrzap/go.mod b/v3/integrations/nrzap/go.mod index f3f0b26d9..34ce78a3b 100644 --- a/v3/integrations/nrzap/go.mod +++ b/v3/integrations/nrzap/go.mod @@ -5,7 +5,7 @@ module github.com/newrelic/go-agent/v3/integrations/nrzap go 1.20 require ( - github.com/newrelic/go-agent/v3 v3.32.0 + github.com/newrelic/go-agent/v3 v3.33.1 // v1.12.0 is the earliest version of zap using modules. go.uber.org/zap v1.12.0 ) diff --git a/v3/internal/limits.go b/v3/internal/limits.go index 3b0b883ac..2ed2f6125 100644 --- a/v3/internal/limits.go +++ b/v3/internal/limits.go @@ -27,5 +27,5 @@ const ( MaxErrorEvents = 100 // MaxSpanEvents is the maximum number of Spans Events that can be captured // per 60-second harvest cycle - MaxSpanEvents = 1000 + MaxSpanEvents = 2000 ) diff --git a/v3/newrelic/app_run.go b/v3/newrelic/app_run.go index f85490dd8..4136fb4ac 100644 --- a/v3/newrelic/app_run.go +++ b/v3/newrelic/app_run.go @@ -99,7 +99,7 @@ func newAppRun(config config, reply *internal.ConnectReply) *appRun { if v := run.Reply.ServerSideConfig.ErrorCollectorExpectStatusCodes; v != nil { run.Config.ErrorCollector.ExpectStatusCodes = v } - if run.Config.ErrorCollector.IgnoreStatusCodes != nil { + if run.Config.ErrorCollector.ExpectStatusCodes != nil { run.mu.Lock() for _, errorCode := range run.Config.ErrorCollector.ExpectStatusCodes { run.expectErrorCodesCache[errorCode] = true diff --git a/v3/newrelic/instrumentation.go b/v3/newrelic/instrumentation.go index b58db6ccc..efc84d22c 100644 --- a/v3/newrelic/instrumentation.go +++ b/v3/newrelic/instrumentation.go @@ -72,7 +72,9 @@ func WrapHandle(app *Application, pattern string, handler http.Handler, options txn := app.StartTransaction(r.Method+" "+pattern, txnOptionList...) defer txn.End() - + if IsSecurityAgentPresent() { + txn.SetCsecAttributes(AttributeCsecRoute, pattern) + } w = txn.SetWebResponse(w) txn.SetWebRequestHTTP(r) diff --git a/v3/newrelic/internal_response_writer.go b/v3/newrelic/internal_response_writer.go index 4271a93f4..ba5427c79 100644 --- a/v3/newrelic/internal_response_writer.go +++ b/v3/newrelic/internal_response_writer.go @@ -44,6 +44,9 @@ func (rw *replacementResponseWriter) WriteHeader(code int) { rw.original.WriteHeader(code) headersJustWritten(rw.thd, code, hdr) + if IsSecurityAgentPresent() { + secureAgent.SendEvent("INBOUND_RESPONSE_CODE", code) + } } func (rw *replacementResponseWriter) CloseNotify() <-chan bool { diff --git a/v3/newrelic/internal_txn.go b/v3/newrelic/internal_txn.go index b06a8c61c..32060485b 100644 --- a/v3/newrelic/internal_txn.go +++ b/v3/newrelic/internal_txn.go @@ -43,7 +43,8 @@ type txn struct { // csecData is used to propagate HTTP request context in async apps, // when NewGoroutine is called. - csecData any + csecData any + csecAttributes map[string]any } type thread struct { @@ -1385,3 +1386,18 @@ func (txn *txn) setCsecData() { txn.csecData = secureAgent.SendEvent("NEW_GOROUTINE", "") } } + +func (txn *txn) getCsecAttributes() any { + txn.Lock() + defer txn.Unlock() + return txn.csecAttributes +} + +func (txn *txn) setCsecAttributes(key, value string) { + txn.Lock() + defer txn.Unlock() + if txn.csecAttributes == nil { + txn.csecAttributes = map[string]any{} + } + txn.csecAttributes[key] = value +} diff --git a/v3/newrelic/secure_agent.go b/v3/newrelic/secure_agent.go index ae0ce723f..40334a53d 100644 --- a/v3/newrelic/secure_agent.go +++ b/v3/newrelic/secure_agent.go @@ -4,6 +4,8 @@ import ( "net/http" ) +const AttributeCsecRoute = "ROUTE" + // secureAgent is a global interface point for the nrsecureagent's hooks into the go agent. // The default value for this is a noOpSecurityAgent value, which has null definitions for // the methods. The Go compiler is expected to optimize away all the securityAgent method diff --git a/v3/newrelic/transaction.go b/v3/newrelic/transaction.go index 001ff36e8..8a17c985b 100644 --- a/v3/newrelic/transaction.go +++ b/v3/newrelic/transaction.go @@ -37,6 +37,10 @@ func (txn *Transaction) End() { // recover must be called in the function directly being deferred, // not any nested call! r = recover() + + if nil != r && IsSecurityAgentPresent() { + secureAgent.SendEvent("RECORD_PANICS", r) + } } if txn.thread.IsWeb && IsSecurityAgentPresent() { secureAgent.SendEvent("INBOUND_END", "") @@ -265,7 +269,7 @@ func (txn *Transaction) SetWebRequest(r WebRequest) { return } if IsSecurityAgentPresent() { - secureAgent.SendEvent("INBOUND", r) + secureAgent.SendEvent("INBOUND", r, txn.GetCsecAttributes()) } txn.thread.logAPIError(txn.thread.SetWebRequest(r), "set web request", nil) } @@ -541,6 +545,21 @@ func (txn *Transaction) IsSampled() bool { return txn.thread.IsSampled() } +func (txn *Transaction) GetCsecAttributes() any { + if txn == nil || txn.thread == nil { + return nil + } + return txn.thread.getCsecAttributes() +} + +func (txn *Transaction) SetCsecAttributes(key, value string) { + if txn == nil || txn.thread == nil { + return + } + txn.thread.setCsecAttributes(key, value) + +} + const ( // DistributedTraceNewRelicHeader is the header used by New Relic agents // for automatic trace payload instrumentation. @@ -604,6 +623,7 @@ type WebRequest struct { ServerName string Type string RemoteAddress string + Router string } func (webrequest WebRequest) GetHeader() http.Header { diff --git a/v3/newrelic/version.go b/v3/newrelic/version.go index 7630ad141..ebc211672 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.33.0" + Version = "3.33.1" ) var (