Skip to content

Commit

Permalink
Merge pull request #659 from newrelic/develop
Browse files Browse the repository at this point in the history
Release 3.21.0
  • Loading branch information
iamemilio committed Mar 30, 2023
2 parents 0a31403 + 5a9be4c commit 92283bc
Show file tree
Hide file tree
Showing 21 changed files with 699 additions and 47 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
## 3.21.0

### Added
* New Errors inbox features:
* User tracking: You can now see the number of users impacted by an error group. Identify the end user with the setUser method.
* Error fingerprint: Are your error occurrences grouped poorly? Set your own error fingerprint via a callback function.
* Ability to disable reporting parameterized query in nrpgx-5

### Fixed
* Improved test coverage for gRPC integration, nrgrpc

### Support Statement
New Relic recommends that you upgrade the agent regularly to ensure that you’re getting the latest features and performance benefits. Additionally, older releases will no longer be supported when they reach end-of-life.

We also recommend using the latest version of the Go language. At minimum, you should at least be using no version of Go older than what is supported by the Go team themselves.

See the [Go Agent EOL Policy](https://docs.newrelic.com/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.20.4

### Fixed
Expand Down
103 changes: 103 additions & 0 deletions v3/integrations/nrgrpc/nrgrpc_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"testing"

"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/test/bufconn"

Expand Down Expand Up @@ -53,6 +54,107 @@ func newTestServerAndConn(t *testing.T, app *newrelic.Application) (*grpc.Server
return s, conn
}

func TestWithCustomStatusHandler(t *testing.T) {
app := testApp()
Configure(WithStatusHandler(codes.OK, WarningInterceptorStatusHandler))

s, conn := newTestServerAndConn(t, app.Application)
defer s.Stop()
defer conn.Close()

client := testapp.NewTestApplicationClient(conn)
txn := app.StartTransaction("client")
ctx := newrelic.NewContext(context.Background(), txn)
_, err := client.DoUnaryUnary(ctx, &testapp.Message{})
if err != nil {
t.Fatal("unable to call client DoUnaryUnary", err)
}

app.ExpectMetrics(t, []internal.WantMetric{
{Name: "Apdex", Scope: "", Forced: true, Data: nil},
{Name: "Apdex/Go/TestApplication/DoUnaryUnary", Scope: "", Forced: false, Data: nil},
{Name: "Custom/DoUnaryUnary", Scope: "", Forced: false, Data: nil},
{Name: "Custom/DoUnaryUnary", Scope: "WebTransaction/Go/TestApplication/DoUnaryUnary", Forced: false, Data: nil},
{Name: "DurationByCaller/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil},
{Name: "DurationByCaller/App/123/456/HTTP/allWeb", Scope: "", Forced: false, Data: nil},
{Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil},
{Name: "Supportability/TraceContext/Accept/Success", Scope: "", Forced: true, Data: nil},
{Name: "TransportDuration/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil},
{Name: "TransportDuration/App/123/456/HTTP/allWeb", Scope: "", Forced: false, Data: nil},
{Name: "WebTransaction", Scope: "", Forced: true, Data: nil},
{Name: "WebTransaction/Go/TestApplication/DoUnaryUnary", Scope: "", Forced: true, Data: nil},
{Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil},
{Name: "WebTransactionTotalTime/Go/TestApplication/DoUnaryUnary", Scope: "", Forced: false, Data: nil},
})
app.ExpectTxnEvents(t, []internal.WantEvent{{
Intrinsics: map[string]interface{}{
"guid": internal.MatchAnything,
"name": "WebTransaction/Go/TestApplication/DoUnaryUnary",
"nr.apdexPerfZone": internal.MatchAnything,
"parent.account": 123,
"parent.app": 456,
"parent.transportDuration": internal.MatchAnything,
"parent.transportType": "HTTP",
"parent.type": "App",
"parentId": internal.MatchAnything,
"parentSpanId": internal.MatchAnything,
"priority": internal.MatchAnything,
"sampled": internal.MatchAnything,
"traceId": internal.MatchAnything,
},
UserAttributes: map[string]interface{}{
"grpcStatusLevel": "warning",
"grpcStatusCode": "OK",
"grpcStatusMessage": internal.MatchAnything,
},
AgentAttributes: map[string]interface{}{
"httpResponseCode": 0,
"http.statusCode": 0,
"request.headers.contentType": "application/grpc",
"request.method": "TestApplication/DoUnaryUnary",
"request.uri": "grpc://bufnet/TestApplication/DoUnaryUnary",
},
}})
app.ExpectSpanEvents(t, []internal.WantEvent{
{
Intrinsics: map[string]interface{}{
"category": "generic",
"name": "Custom/DoUnaryUnary",
"parentId": internal.MatchAnything,
},
UserAttributes: map[string]interface{}{},
AgentAttributes: map[string]interface{}{},
},
{
Intrinsics: map[string]interface{}{
"category": "generic",
"name": "WebTransaction/Go/TestApplication/DoUnaryUnary",
"transaction.name": "WebTransaction/Go/TestApplication/DoUnaryUnary",
"nr.entryPoint": true,
"parentId": internal.MatchAnything,
"trustedParentId": internal.MatchAnything,
},
UserAttributes: map[string]interface{}{
"grpcStatusLevel": "warning",
"grpcStatusCode": "OK",
},
AgentAttributes: map[string]interface{}{
"httpResponseCode": 0,
"http.statusCode": 0,
"parent.account": "123",
"parent.app": "456",
"parent.transportDuration": internal.MatchAnything,
"parent.transportType": "HTTP",
"parent.type": "App",
"request.headers.contentType": "application/grpc",
"request.method": "TestApplication/DoUnaryUnary",
"request.uri": "grpc://bufnet/TestApplication/DoUnaryUnary",
},
},
})
Configure(WithStatusHandler(codes.OK, OKInterceptorStatusHandler))
}

func TestUnaryServerInterceptor(t *testing.T) {
app := testApp()

Expand Down Expand Up @@ -143,6 +245,7 @@ func TestUnaryServerInterceptor(t *testing.T) {
},
},
})

}

func TestUnaryServerInterceptorError(t *testing.T) {
Expand Down
12 changes: 8 additions & 4 deletions v3/integrations/nrpgx5/nrpgx5.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ func init() {

type (
Tracer struct {
BaseSegment newrelic.DatastoreSegment
ParseQuery func(segment *newrelic.DatastoreSegment, query string)
BaseSegment newrelic.DatastoreSegment
ParseQuery func(segment *newrelic.DatastoreSegment, query string)
SendQueryParameters bool
}

nrPgxSegmentType string
Expand All @@ -83,7 +84,8 @@ const (

func NewTracer() *Tracer {
return &Tracer{
ParseQuery: sqlparse.ParseQuery,
ParseQuery: sqlparse.ParseQuery,
SendQueryParameters: true,
}
}

Expand All @@ -109,7 +111,9 @@ func (t *Tracer) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.T
segment := t.BaseSegment
segment.StartTime = newrelic.FromContext(ctx).StartSegmentNow()
segment.ParameterizedQuery = data.SQL
segment.QueryParameters = t.getQueryParameters(data.Args)
if t.SendQueryParameters {
segment.QueryParameters = t.getQueryParameters(data.Args)
}

// fill Operation and Collection
t.ParseQuery(&segment, data.SQL)
Expand Down
1 change: 0 additions & 1 deletion v3/newrelic/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ func (app *Application) RecordLog(logEvent LogData) {
// as needed in the background (and will continue attempting to connect
// if it wasn't immediately successful, all while allowing your application
// to proceed with its primary function).
//
func (app *Application) WaitForConnection(timeout time.Duration) error {
if nil == app {
return nil
Expand Down
4 changes: 4 additions & 0 deletions v3/newrelic/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ const (
AttributeCodeFilepath = "code.filepath"
// AttributeCodeLineno contains the Code Level Metrics source file line number name.
AttributeCodeLineno = "code.lineno"
// AttributeErrorGroupName contains the error group name set by the user defined callback function.
AttributeErrorGroupName = "error.group.name"
// AttributeUserID tracks the user a transaction and its child events are impacting
AttributeUserID = "enduser.id"
)

// Attributes destined for Errors and Transaction Traces:
Expand Down
13 changes: 11 additions & 2 deletions v3/newrelic/attributes_from_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ var (
AttributeCodeNamespace: usualDests,
AttributeCodeFilepath: usualDests,
AttributeCodeLineno: usualDests,
AttributeUserID: usualDests,

// Span specific attributes
SpanAttributeDBStatement: usualDests,
Expand Down Expand Up @@ -457,11 +458,12 @@ func writeAttributeValueJSON(w *jsonFieldsWriter, key string, val interface{}) {
}
}

func agentAttributesJSON(a *attributes, buf *bytes.Buffer, d destinationSet) {
func agentAttributesJSON(a *attributes, buf *bytes.Buffer, d destinationSet, additionalAttributes ...map[string]string) {
if a == nil {
buf.WriteString("{}")
return
}

w := jsonFieldsWriter{buf: buf}
buf.WriteByte('{')
for id, val := range a.Agent {
Expand All @@ -473,8 +475,15 @@ func agentAttributesJSON(a *attributes, buf *bytes.Buffer, d destinationSet) {
}
}
}
buf.WriteByte('}')

// Add additional agent attributes to json
for _, additionalAttribute := range additionalAttributes {
for id, val := range additionalAttribute {
w.stringField(id, val)
}
}

buf.WriteByte('}')
}

func userAttributesJSON(a *attributes, buf *bytes.Buffer, d destinationSet, extraAttributes map[string]interface{}) {
Expand Down
18 changes: 18 additions & 0 deletions v3/newrelic/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,24 @@ type Config struct {
// as errors, and then re-panic them. By default, this is
// set to false.
RecordPanics bool
// ErrorGroupCallback is a user defined callback function that takes an error as an input
// and returns a string that will be applied to an error to put it in an error group.
//
// If no error group is identified for a given error, this function should return an empty string.
// If an ErrorGroupCallbeck is defined, it will be executed against every error the go agent notices that
// is not ignored.
//
// example function:
//
// func ErrorGroupCallback(errorInfo newrelic.ErrorInfo) string {
// if errorInfo.Class == "403" && errorInfo.GetUserId() == "example user" {
// return "customer X payment issue"
// }
//
// // returning empty string causes default error grouping behavior
// return ""
// }
ErrorGroupCallback `json:"-"`
}

// TransactionTracer controls the capture of transaction traces.
Expand Down
11 changes: 11 additions & 0 deletions v3/newrelic/config_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,17 @@ func ConfigModuleDependencyMetricsIgnoredPrefixes(prefix ...string) ConfigOption
}
}

// ConfigSetErrorGroupCallbackFunction set a callback function of type ErrorGroupCallback that will
// be invoked against errors at harvest time. This function overrides the default grouping behavior
// of errors into a custom, user defined group when set. Setting this may have performance implications
// for your application depending on the contents of the callback function. Do not set this if you want
// the default error grouping behavior to be executed.
func ConfigSetErrorGroupCallbackFunction(callback ErrorGroupCallback) ConfigOption {
return func(cfg *Config) {
cfg.ErrorCollector.ErrorGroupCallback = callback
}
}

// ConfigModuleDependencyMetricsRedactIgnoredPrefixes controls whether the names
// of ignored module path prefixes should be redacted from the agent configuration data
// reported and visible in the New Relic UI. Since one of the reasons these
Expand Down
6 changes: 3 additions & 3 deletions v3/newrelic/environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ func TestModuleDependency(t *testing.T) {
// of modules to at least check that the various options work.
expectedModules := make(map[string]*debug.Module)
mockedModuleList := []*debug.Module{
&debug.Module{Path: "example/path/to/module", Version: "v1.2.3"},
&debug.Module{Path: "github.com/another/module", Version: "v0.1.2"},
&debug.Module{Path: "some/development/module", Version: "(develop)"},
{Path: "example/path/to/module", Version: "v1.2.3"},
{Path: "github.com/another/module", Version: "v0.1.2"},
{Path: "some/development/module", Version: "(develop)"},
}
for _, module := range mockedModuleList {
expectedModules[module.Path] = module
Expand Down
8 changes: 7 additions & 1 deletion v3/newrelic/error_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@ func (e *errorEvent) WriteJSON(buf *bytes.Buffer) {
buf.WriteByte(',')
userAttributesJSON(e.Attrs, buf, destError, e.errorData.ExtraAttributes)
buf.WriteByte(',')
agentAttributesJSON(e.Attrs, buf, destError)

if e.ErrorGroup != "" {
agentAttributesJSON(e.Attrs, buf, destError, map[string]string{AttributeErrorGroupName: e.ErrorGroup})
} else {
agentAttributesJSON(e.Attrs, buf, destError)
}

buf.WriteByte(']')
}

Expand Down
Loading

0 comments on commit 92283bc

Please sign in to comment.