Skip to content

Commit 5421539

Browse files
Merge pull request #556 from newrelic/develop
Release 3.18.1
2 parents b580d7f + 14d45d8 commit 5421539

File tree

11 files changed

+319
-39
lines changed

11 files changed

+319
-39
lines changed

.github/workflows/ci.yaml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,24 @@ jobs:
100100
- go-version: 1.12.x
101101
dirs: v3/newrelic,v3/internal,v3/examples
102102
- go-version: 1.15.x
103-
dirs: v3/newrelic,v3/internal,v3/examples,v3/integrations/logcontext
103+
dirs: v3/newrelic,v3/internal,v3/examples
104+
- go-version: 1.16.x
105+
dirs: v3/newrelic,v3/internal,v3/examples
106+
- go-version: 1.17.x
107+
dirs: v3/newrelic,v3/internal,v3/examples
108+
- go-version: 1.18.x
109+
dirs: v3/newrelic,v3/internal,v3/examples
104110

105111
# v3 integrations
106112
- go-version: 1.15.x
107113
dirs: v3/integrations/logcontext/nrlogrusplugin
108114
extratesting: go get -u github.com/sirupsen/logrus@master
115+
- go-version: 1.17.x
116+
dirs: v3/integrations/logcontext-v2/nrlogrus
117+
extratesting: go get -u github.com/sirupsen/logrus@master
118+
- go-version: 1.17.x
119+
dirs: v3/integrations/logcontext-v2/nrzerolog
120+
extratesting: go get -u github.com/rs/zerolog@master
109121
- go-version: 1.15.x
110122
dirs: v3/integrations/nrawssdk-v1
111123
extratesting: go get -u github.com/aws/aws-sdk-go@main

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
## 3.18.1
2+
### Added
3+
* 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).
4+
5+
### Fixed
6+
* Corrected some small errors in documentation of package features. Fixes [Issue #550](https://github.com/newrelic/go-agent/issues/550)
7+
8+
### Compatibility Notice
9+
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:
10+
* `StartTransaction(string)` -> `StartTransaction(string, ...TraceOption)`
11+
* `WrapHandle(*Application, string, http.Handler)` -> `WrapHandle(*Application, string, http.Handler, ...TraceOption)`
12+
* `WrapHandleFunc(*Application, string, func(http.ResponseWriter, *http.Request))` -> `WrapHandleFunc(*Application, string, func(http.ResponseWriter, *http.Request), ...TraceOption)`
13+
14+
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.
15+
16+
### Support Statement
17+
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.
18+
19+
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.
20+
121
## 3.18.0
222
### Added
323
* 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.
@@ -13,6 +33,15 @@
1333
### Fixed
1434
* Fixed issue with custom event limits and number of DT Spans to more accurately follow configured limits.
1535

36+
### Compatibility Notice
37+
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.
38+
This prevents the changes from breaking existing code for typical usage of the agent. However, it does mean those functions' call signatures have changed:
39+
* `StartTransaction(string)` -> `StartTransaction(string, ...TraceOption)`
40+
* `WrapHandle(*Application, string, http.Handler)` -> `WrapHandle(*Application, string, http.Handler, ...TraceOption)`
41+
* `WrapHandleFunc(*Application, string, func(http.ResponseWriter, *http.Request))` -> `WrapHandleFunc(*Application, string, func(http.ResponseWriter, *http.Request), ...TraceOption)`
42+
43+
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.
44+
1645
### Support Statement
1746
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.
1847
* Note that the oldest supported version of the Go Agent is 3.6.0.

v3/integrations/logcontext-v2/nrzerolog/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrzerolog
33
go 1.17
44

55
require (
6-
github.com/newrelic/go-agent/v3 v3.17.0
6+
github.com/newrelic/go-agent/v3 v3.18.0
77
github.com/rs/zerolog v1.26.1
88
)

v3/integrations/logcontext-v2/nrzerolog/hook.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ func (h NewRelicHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
2222
}
2323

2424
logLevel := ""
25-
if level == zerolog.NoLevel {
26-
logLevel = newrelic.LogSeverityUnknown
27-
} else {
25+
if level != zerolog.NoLevel {
2826
logLevel = level.String()
2927
}
3028

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
package nrzerolog
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"io"
7+
"testing"
8+
9+
"github.com/newrelic/go-agent/v3/internal"
10+
"github.com/newrelic/go-agent/v3/internal/integrationsupport"
11+
"github.com/newrelic/go-agent/v3/newrelic"
12+
13+
"github.com/rs/zerolog"
14+
)
15+
16+
func newLogger(out io.Writer, app *newrelic.Application) zerolog.Logger {
17+
logger := zerolog.New(out)
18+
return logger.Hook(NewRelicHook{
19+
App: app,
20+
})
21+
}
22+
23+
func newTxnLogger(out io.Writer, app *newrelic.Application, ctx context.Context) zerolog.Logger {
24+
logger := zerolog.New(out)
25+
return logger.Hook(NewRelicHook{
26+
App: app,
27+
Context: ctx,
28+
})
29+
}
30+
31+
func BenchmarkZerolog(b *testing.B) {
32+
log := zerolog.New(bytes.NewBuffer([]byte("")))
33+
34+
b.ResetTimer()
35+
b.ReportAllocs()
36+
37+
for i := 0; i < b.N; i++ {
38+
log.Info().Msg("This is a test log")
39+
}
40+
}
41+
42+
func BenchmarkZerologLoggingDisabled(b *testing.B) {
43+
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn, newrelic.ConfigAppLogEnabled(false))
44+
log := newLogger(bytes.NewBuffer([]byte("")), app.Application)
45+
46+
b.ResetTimer()
47+
b.ReportAllocs()
48+
49+
for i := 0; i < b.N; i++ {
50+
log.Info().Msg("This is a test log")
51+
}
52+
}
53+
54+
func BenchmarkZerologLogForwarding(b *testing.B) {
55+
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn, newrelic.ConfigAppLogForwardingEnabled(true))
56+
log := newLogger(bytes.NewBuffer([]byte("")), app.Application)
57+
58+
b.ResetTimer()
59+
b.ReportAllocs()
60+
61+
for i := 0; i < b.N; i++ {
62+
log.Info().Msg("This is a test log")
63+
}
64+
}
65+
66+
/*
67+
68+
func BenchmarkFormattingWithOutTransaction(b *testing.B) {
69+
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn, newrelic.ConfigAppLogDecoratingEnabled(true))
70+
log := newLogger(bytes.NewBuffer([]byte("")), app.Application)
71+
72+
b.ResetTimer()
73+
b.ReportAllocs()
74+
75+
for i := 0; i < b.N; i++ {
76+
log.Info().Msg("Hello World!")
77+
}
78+
}
79+
80+
func BenchmarkFormattingWithTransaction(b *testing.B) {
81+
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn, newrelic.ConfigAppLogDecoratingEnabled(true))
82+
txn := app.StartTransaction("TestLogDistributedTracingDisabled")
83+
defer txn.End()
84+
out := bytes.NewBuffer([]byte{})
85+
ctx := newrelic.NewContext(context.Background(), txn)
86+
log := newTxnLogger(out, app.Application, ctx)
87+
88+
89+
b.ResetTimer()
90+
b.ReportAllocs()
91+
92+
for i := 0; i < b.N; i++ {
93+
log.Info().Msg("Hello World!")
94+
}
95+
}
96+
*/
97+
98+
func TestBackgroundLog(t *testing.T) {
99+
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn,
100+
newrelic.ConfigAppLogDecoratingEnabled(true),
101+
newrelic.ConfigAppLogForwardingEnabled(true),
102+
)
103+
out := bytes.NewBuffer([]byte{})
104+
log := newLogger(out, app.Application)
105+
message := "Hello World!"
106+
log.Info().Msg(message)
107+
108+
// Un-comment when local decorating enabled
109+
/*
110+
logcontext.ValidateDecoratedOutput(t, out, &logcontext.DecorationExpect{
111+
EntityGUID: integrationsupport.TestEntityGUID,
112+
Hostname: host,
113+
EntityName: integrationsupport.SampleAppName,
114+
})
115+
*/
116+
117+
app.ExpectLogEvents(t, []internal.WantLog{
118+
{
119+
Severity: zerolog.InfoLevel.String(),
120+
Message: message,
121+
Timestamp: internal.MatchAnyUnixMilli,
122+
},
123+
})
124+
}
125+
126+
func TestLogEmptyContext(t *testing.T) {
127+
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn,
128+
newrelic.ConfigAppLogDecoratingEnabled(true),
129+
newrelic.ConfigAppLogForwardingEnabled(true),
130+
)
131+
out := bytes.NewBuffer([]byte{})
132+
log := newTxnLogger(out, app.Application, context.Background())
133+
message := "Hello World!"
134+
log.Info().Msg(message)
135+
136+
// Un-comment when local decorating enabled
137+
/*
138+
logcontext.ValidateDecoratedOutput(t, out, &logcontext.DecorationExpect{
139+
EntityGUID: integrationsupport.TestEntityGUID,
140+
Hostname: host,
141+
EntityName: integrationsupport.SampleAppName,
142+
}) */
143+
144+
app.ExpectLogEvents(t, []internal.WantLog{
145+
{
146+
Severity: zerolog.InfoLevel.String(),
147+
Message: message,
148+
Timestamp: internal.MatchAnyUnixMilli,
149+
},
150+
})
151+
}
152+
153+
func TestLogDebugLevel(t *testing.T) {
154+
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn,
155+
newrelic.ConfigAppLogDecoratingEnabled(true),
156+
newrelic.ConfigAppLogForwardingEnabled(true),
157+
)
158+
out := bytes.NewBuffer([]byte{})
159+
log := newTxnLogger(out, app.Application, context.Background())
160+
message := "Hello World!"
161+
log.Print(message)
162+
163+
// Un-comment when local decorating enabled
164+
/*
165+
logcontext.ValidateDecoratedOutput(t, out, &logcontext.DecorationExpect{
166+
EntityGUID: integrationsupport.TestEntityGUID,
167+
Hostname: host,
168+
EntityName: integrationsupport.SampleAppName,
169+
}) */
170+
171+
app.ExpectLogEvents(t, []internal.WantLog{
172+
{
173+
Severity: zerolog.DebugLevel.String(),
174+
Message: message,
175+
Timestamp: internal.MatchAnyUnixMilli,
176+
},
177+
})
178+
}
179+
180+
func TestLogInContext(t *testing.T) {
181+
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn,
182+
newrelic.ConfigAppLogDecoratingEnabled(true),
183+
newrelic.ConfigAppLogForwardingEnabled(true),
184+
)
185+
out := bytes.NewBuffer([]byte{})
186+
txn := app.StartTransaction("test txn")
187+
ctx := newrelic.NewContext(context.Background(), txn)
188+
log := newTxnLogger(out, app.Application, ctx)
189+
message := "Hello World!"
190+
log.Info().Msg(message)
191+
192+
// Un-comment when local decorating enabled
193+
/*
194+
logcontext.ValidateDecoratedOutput(t, out, &logcontext.DecorationExpect{
195+
EntityGUID: integrationsupport.TestEntityGUID,
196+
Hostname: host,
197+
EntityName: integrationsupport.SampleAppName,
198+
TraceID: txn.GetLinkingMetadata().TraceID,
199+
SpanID: txn.GetLinkingMetadata().SpanID,
200+
})
201+
*/
202+
203+
txn.ExpectLogEvents(t, []internal.WantLog{
204+
{
205+
Severity: zerolog.InfoLevel.String(),
206+
Message: message,
207+
Timestamp: internal.MatchAnyUnixMilli,
208+
SpanID: txn.GetLinkingMetadata().SpanID,
209+
TraceID: txn.GetLinkingMetadata().TraceID,
210+
},
211+
})
212+
213+
txn.End()
214+
}

v3/newrelic/code_level_metrics.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ type CodeLocation struct {
3636
type traceOptSet struct {
3737
LocationOverride *CodeLocation
3838
SuppressCLM bool
39-
IgnoredPrefix string
39+
IgnoredPrefixes []string
4040
}
4141

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

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

194-
if tOpts.IgnoredPrefix == "" {
195-
tOpts.IgnoredPrefix = run.Config.CodeLevelMetrics.IgnoredPrefix
196-
if tOpts.IgnoredPrefix == "" {
197-
tOpts.IgnoredPrefix = defaultAgentProjectRoot
194+
if tOpts.IgnoredPrefixes == nil {
195+
tOpts.IgnoredPrefixes = run.Config.CodeLevelMetrics.IgnoredPrefixes
196+
// for backward compatibility, add the singleton IgnoredPrefix if there is one
197+
if run.Config.CodeLevelMetrics.IgnoredPrefix != "" {
198+
tOpts.IgnoredPrefixes = append(tOpts.IgnoredPrefixes, run.Config.CodeLevelMetrics.IgnoredPrefix)
199+
}
200+
if tOpts.IgnoredPrefixes == nil {
201+
tOpts.IgnoredPrefixes = append(tOpts.IgnoredPrefixes, defaultAgentProjectRoot)
198202
}
199203
}
200204

201205
// skip out to first non-agent frame, unless that IS the top-most frame
202206
for moreToRead {
203207
frame, moreToRead = frames.Next()
204-
if !strings.HasPrefix(frame.Function, tOpts.IgnoredPrefix) {
208+
if func() bool {
209+
for _, eachPrefix := range tOpts.IgnoredPrefixes {
210+
if strings.HasPrefix(frame.Function, eachPrefix) {
211+
return false
212+
}
213+
}
214+
return true
215+
}() {
205216
break
206217
}
207218
}

0 commit comments

Comments
 (0)