Skip to content

Commit

Permalink
Merge pull request #556 from newrelic/develop
Browse files Browse the repository at this point in the history
Release 3.18.1
  • Loading branch information
nr-swilloughby authored Aug 18, 2022
2 parents b580d7f + 14d45d8 commit 5421539
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 39 deletions.
14 changes: 13 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,24 @@ jobs:
- go-version: 1.12.x
dirs: v3/newrelic,v3/internal,v3/examples
- go-version: 1.15.x
dirs: v3/newrelic,v3/internal,v3/examples,v3/integrations/logcontext
dirs: v3/newrelic,v3/internal,v3/examples
- go-version: 1.16.x
dirs: v3/newrelic,v3/internal,v3/examples
- go-version: 1.17.x
dirs: v3/newrelic,v3/internal,v3/examples
- go-version: 1.18.x
dirs: v3/newrelic,v3/internal,v3/examples

# v3 integrations
- go-version: 1.15.x
dirs: v3/integrations/logcontext/nrlogrusplugin
extratesting: go get -u github.com/sirupsen/logrus@master
- go-version: 1.17.x
dirs: v3/integrations/logcontext-v2/nrlogrus
extratesting: go get -u github.com/sirupsen/logrus@master
- go-version: 1.17.x
dirs: v3/integrations/logcontext-v2/nrzerolog
extratesting: go get -u github.com/rs/zerolog@master
- go-version: 1.15.x
dirs: v3/integrations/nrawssdk-v1
extratesting: go get -u github.com/aws/aws-sdk-go@main
Expand Down
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
## 3.18.1
### Added
* Extended the `IgnoredPrefix` configuration value for Code-Level Metrics so that multiple such prefixes may be given instead of a single one. This deprecates the `IgnoredPrefix` configuration field of `Config.CodeLevelMetrics` in favor of a new slice field `IgnoredPrefixes`. The corresponding configuration option-setting functions `ConfigCodeLevelMetricsIgnoredPrefix` and `WithIgnoredPrefix` now take any number of string parameters to set these values. Since those functions used to take a single string value, this change is backward-compatible with pre-3.18.1 code. Accordingly, the `NEW_RELIC_CODE_LEVEL_METRICS_IGNORED_PREFIX` environment variable is now a comma-separated list of prefixes. Fixes [Issue #551](https://github.com/newrelic/go-agent/issues/551).

### Fixed
* Corrected some small errors in documentation of package features. Fixes [Issue #550](https://github.com/newrelic/go-agent/issues/550)

### Compatibility Notice
As of release 3.18.0, the API was extended by allowing custom options to be added to calls to the `Application.StartTransaction` method and the `WrapHandle` and `WrapHandleFunc` functions. They are implemented as variadic functions such that the new option parameters are optional (i.e., zero or more options may be added to the end of the function calls) to be backward-compatible with pre-3.18.0 usage of those functions. This prevents the changes from breaking existing code for typical usage of the agent. However, it does mean those functions' call signatures have changed:
* `StartTransaction(string)` -> `StartTransaction(string, ...TraceOption)`
* `WrapHandle(*Application, string, http.Handler)` -> `WrapHandle(*Application, string, http.Handler, ...TraceOption)`
* `WrapHandleFunc(*Application, string, func(http.ResponseWriter, *http.Request))` -> `WrapHandleFunc(*Application, string, func(http.ResponseWriter, *http.Request), ...TraceOption)`

If, for example, you created your own custom interface type which includes the `StartTransaction` method or something that depends on these functions' exact call semantics, that code will need to be updated accordingly before using version 3.18.0 (or later) of the Go Agent.

### 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.

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.18.0
### Added
* Code-Level Metrics are now available for instrumented transactions. This is off by default but once enabled via `ConfigCodeLevelMetricsEnabled(true)` transactions will include information about the location in the source code where `StartTransaction` was invoked.
Expand All @@ -13,6 +33,15 @@
### Fixed
* Fixed issue with custom event limits and number of DT Spans to more accurately follow configured limits.

### Compatibility Notice
This release extends the API by allowing custom options to be added to calls to the `Application.StartTransaction` method and the `WrapHandle` and `WrapHandleFunc` functions. They are implemented as variadic functions such that the new option parameters are optional (i.e., zero or more options may be added to the end of the function calls) to be backward-compatible with pre-3.18.0 usage of those functions.
This prevents the changes from breaking existing code for typical usage of the agent. However, it does mean those functions' call signatures have changed:
* `StartTransaction(string)` -> `StartTransaction(string, ...TraceOption)`
* `WrapHandle(*Application, string, http.Handler)` -> `WrapHandle(*Application, string, http.Handler, ...TraceOption)`
* `WrapHandleFunc(*Application, string, func(http.ResponseWriter, *http.Request))` -> `WrapHandleFunc(*Application, string, func(http.ResponseWriter, *http.Request), ...TraceOption)`

If, for example, you created your own custom interface type which includes the `StartTransaction` method or something that depends on these functions' exact call semantics, that code will need to be updated accordingly before using version 3.18.0 of the Go Agent.

### 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.
* Note that the oldest supported version of the Go Agent is 3.6.0.
Expand Down
2 changes: 1 addition & 1 deletion v3/integrations/logcontext-v2/nrzerolog/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrzerolog
go 1.17

require (
github.com/newrelic/go-agent/v3 v3.17.0
github.com/newrelic/go-agent/v3 v3.18.0
github.com/rs/zerolog v1.26.1
)
4 changes: 1 addition & 3 deletions v3/integrations/logcontext-v2/nrzerolog/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ func (h NewRelicHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
}

logLevel := ""
if level == zerolog.NoLevel {
logLevel = newrelic.LogSeverityUnknown
} else {
if level != zerolog.NoLevel {
logLevel = level.String()
}

Expand Down
214 changes: 214 additions & 0 deletions v3/integrations/logcontext-v2/nrzerolog/hook_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package nrzerolog

import (
"bytes"
"context"
"io"
"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/rs/zerolog"
)

func newLogger(out io.Writer, app *newrelic.Application) zerolog.Logger {
logger := zerolog.New(out)
return logger.Hook(NewRelicHook{
App: app,
})
}

func newTxnLogger(out io.Writer, app *newrelic.Application, ctx context.Context) zerolog.Logger {
logger := zerolog.New(out)
return logger.Hook(NewRelicHook{
App: app,
Context: ctx,
})
}

func BenchmarkZerolog(b *testing.B) {
log := zerolog.New(bytes.NewBuffer([]byte("")))

b.ResetTimer()
b.ReportAllocs()

for i := 0; i < b.N; i++ {
log.Info().Msg("This is a test log")
}
}

func BenchmarkZerologLoggingDisabled(b *testing.B) {
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn, newrelic.ConfigAppLogEnabled(false))
log := newLogger(bytes.NewBuffer([]byte("")), app.Application)

b.ResetTimer()
b.ReportAllocs()

for i := 0; i < b.N; i++ {
log.Info().Msg("This is a test log")
}
}

func BenchmarkZerologLogForwarding(b *testing.B) {
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn, newrelic.ConfigAppLogForwardingEnabled(true))
log := newLogger(bytes.NewBuffer([]byte("")), app.Application)

b.ResetTimer()
b.ReportAllocs()

for i := 0; i < b.N; i++ {
log.Info().Msg("This is a test log")
}
}

/*
func BenchmarkFormattingWithOutTransaction(b *testing.B) {
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn, newrelic.ConfigAppLogDecoratingEnabled(true))
log := newLogger(bytes.NewBuffer([]byte("")), app.Application)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
log.Info().Msg("Hello World!")
}
}
func BenchmarkFormattingWithTransaction(b *testing.B) {
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn, newrelic.ConfigAppLogDecoratingEnabled(true))
txn := app.StartTransaction("TestLogDistributedTracingDisabled")
defer txn.End()
out := bytes.NewBuffer([]byte{})
ctx := newrelic.NewContext(context.Background(), txn)
log := newTxnLogger(out, app.Application, ctx)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
log.Info().Msg("Hello World!")
}
}
*/

func TestBackgroundLog(t *testing.T) {
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn,
newrelic.ConfigAppLogDecoratingEnabled(true),
newrelic.ConfigAppLogForwardingEnabled(true),
)
out := bytes.NewBuffer([]byte{})
log := newLogger(out, app.Application)
message := "Hello World!"
log.Info().Msg(message)

// Un-comment when local decorating enabled
/*
logcontext.ValidateDecoratedOutput(t, out, &logcontext.DecorationExpect{
EntityGUID: integrationsupport.TestEntityGUID,
Hostname: host,
EntityName: integrationsupport.SampleAppName,
})
*/

app.ExpectLogEvents(t, []internal.WantLog{
{
Severity: zerolog.InfoLevel.String(),
Message: message,
Timestamp: internal.MatchAnyUnixMilli,
},
})
}

func TestLogEmptyContext(t *testing.T) {
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn,
newrelic.ConfigAppLogDecoratingEnabled(true),
newrelic.ConfigAppLogForwardingEnabled(true),
)
out := bytes.NewBuffer([]byte{})
log := newTxnLogger(out, app.Application, context.Background())
message := "Hello World!"
log.Info().Msg(message)

// Un-comment when local decorating enabled
/*
logcontext.ValidateDecoratedOutput(t, out, &logcontext.DecorationExpect{
EntityGUID: integrationsupport.TestEntityGUID,
Hostname: host,
EntityName: integrationsupport.SampleAppName,
}) */

app.ExpectLogEvents(t, []internal.WantLog{
{
Severity: zerolog.InfoLevel.String(),
Message: message,
Timestamp: internal.MatchAnyUnixMilli,
},
})
}

func TestLogDebugLevel(t *testing.T) {
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn,
newrelic.ConfigAppLogDecoratingEnabled(true),
newrelic.ConfigAppLogForwardingEnabled(true),
)
out := bytes.NewBuffer([]byte{})
log := newTxnLogger(out, app.Application, context.Background())
message := "Hello World!"
log.Print(message)

// Un-comment when local decorating enabled
/*
logcontext.ValidateDecoratedOutput(t, out, &logcontext.DecorationExpect{
EntityGUID: integrationsupport.TestEntityGUID,
Hostname: host,
EntityName: integrationsupport.SampleAppName,
}) */

app.ExpectLogEvents(t, []internal.WantLog{
{
Severity: zerolog.DebugLevel.String(),
Message: message,
Timestamp: internal.MatchAnyUnixMilli,
},
})
}

func TestLogInContext(t *testing.T) {
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn,
newrelic.ConfigAppLogDecoratingEnabled(true),
newrelic.ConfigAppLogForwardingEnabled(true),
)
out := bytes.NewBuffer([]byte{})
txn := app.StartTransaction("test txn")
ctx := newrelic.NewContext(context.Background(), txn)
log := newTxnLogger(out, app.Application, ctx)
message := "Hello World!"
log.Info().Msg(message)

// Un-comment when local decorating enabled
/*
logcontext.ValidateDecoratedOutput(t, out, &logcontext.DecorationExpect{
EntityGUID: integrationsupport.TestEntityGUID,
Hostname: host,
EntityName: integrationsupport.SampleAppName,
TraceID: txn.GetLinkingMetadata().TraceID,
SpanID: txn.GetLinkingMetadata().SpanID,
})
*/

txn.ExpectLogEvents(t, []internal.WantLog{
{
Severity: zerolog.InfoLevel.String(),
Message: message,
Timestamp: internal.MatchAnyUnixMilli,
SpanID: txn.GetLinkingMetadata().SpanID,
TraceID: txn.GetLinkingMetadata().TraceID,
},
})

txn.End()
}
31 changes: 21 additions & 10 deletions v3/newrelic/code_level_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type CodeLocation struct {
type traceOptSet struct {
LocationOverride *CodeLocation
SuppressCLM bool
IgnoredPrefix string
IgnoredPrefixes []string
}

//
Expand All @@ -59,7 +59,7 @@ func WithCodeLocation(loc *CodeLocation) TraceOption {
//
// WithIgnoredPrefix indicates that the code location reported
// for Code Level Metrics should be the first function in the
// call stack that does not begin with the given string. This
// call stack that does not begin with the given string (or any of the given strings if more than one are given). This
// string is matched against the entire fully-qualified function
// name, which includes the name of the package the function
// comes from. By default, the Go Agent tries to take the first
Expand All @@ -68,12 +68,12 @@ func WithCodeLocation(loc *CodeLocation) TraceOption {
// this option.
//
// If all functions in the call stack begin with this prefix,
// the outermos one will be used anyway, since we didn't find
// the outermost one will be used anyway, since we didn't find
// anything better on the way to the bottom of the stack.
//
func WithIgnoredPrefix(prefix string) TraceOption {
func WithIgnoredPrefix(prefix ...string) TraceOption {
return func(o *traceOptSet) {
o.IgnoredPrefix = prefix
o.IgnoredPrefixes = prefix
}
}

Expand Down Expand Up @@ -191,17 +191,28 @@ func reportCodeLevelMetrics(tOpts traceOptSet, run *appRun, setAttr func(string,
moreToRead := true
var frame runtime.Frame

if tOpts.IgnoredPrefix == "" {
tOpts.IgnoredPrefix = run.Config.CodeLevelMetrics.IgnoredPrefix
if tOpts.IgnoredPrefix == "" {
tOpts.IgnoredPrefix = defaultAgentProjectRoot
if tOpts.IgnoredPrefixes == nil {
tOpts.IgnoredPrefixes = run.Config.CodeLevelMetrics.IgnoredPrefixes
// for backward compatibility, add the singleton IgnoredPrefix if there is one
if run.Config.CodeLevelMetrics.IgnoredPrefix != "" {
tOpts.IgnoredPrefixes = append(tOpts.IgnoredPrefixes, run.Config.CodeLevelMetrics.IgnoredPrefix)
}
if tOpts.IgnoredPrefixes == nil {
tOpts.IgnoredPrefixes = append(tOpts.IgnoredPrefixes, defaultAgentProjectRoot)
}
}

// skip out to first non-agent frame, unless that IS the top-most frame
for moreToRead {
frame, moreToRead = frames.Next()
if !strings.HasPrefix(frame.Function, tOpts.IgnoredPrefix) {
if func() bool {
for _, eachPrefix := range tOpts.IgnoredPrefixes {
if strings.HasPrefix(frame.Function, eachPrefix) {
return false
}
}
return true
}() {
break
}
}
Expand Down
Loading

0 comments on commit 5421539

Please sign in to comment.