Skip to content

Commit c081f78

Browse files
Merge pull request #1004 from newrelic/develop
Release 3.37.0
2 parents 5867ad9 + 7043c7a commit c081f78

File tree

80 files changed

+1883
-492
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+1883
-492
lines changed

CHANGELOG.md

+23
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1+
## 3.37.0
2+
### Enhanced
3+
- Implemented a new approach to integrating New Relic with SLOG that is more lightweight, out of the way, and collects richer data. These changes have been constructed to be completely backwards-compatible with v1 of nrslog. Changes include:
4+
- Wrapping `slog.Handler` objects with errors to allow users to handle invalid use cases
5+
- A complete rework of log enrichment so that New Relic linking metadata does not invalidate JSON, BSON, or YAML scanners. This new approach will instead inject the linking metadata as a key-value pair.
6+
- Complete support for `With()`, `WithGroup()`, and attributes for automatic instrumentation.
7+
- Performance operations.
8+
- Robust testing (close to 90% coverage).
9+
- **This updates logcontext-v2/nrslog to v1.4.0.**
10+
- Now custom application tags (labels) may be added to all forwarded log events.
11+
- Enabled if `ConfigAppLogForwardingLabelsEnabled(true)` or `NEW_RELIC_APPLICATION_LOGGING_FORWARDING_LABELS_ENABLED=TRUE`
12+
- May exclude labels named in `ConfigAppLogForwardingLabelsExclude("label1","label2",...)` or `NEW_RELIC_APPLICATION_LOGGING_FORWARDING_LABELS_EXCLUDE="label1,label2,..."`
13+
- Labels are defined via `ConfigLabels(...)` or `NEW_RELIC_LABELS`
14+
- Added memory allocation limit detection/response mechanism to facilitate calling custom functions to perform application-specific resource management functionality, report custom metrics or events, or take other appropriate actions, in response to rising heap memory size.
15+
16+
### Fixed
17+
- Added protection around transaction methods to gracefully return when the transaction object is `nil`.
18+
19+
### Support statement
20+
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.
21+
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.
22+
23+
124
## 3.36.0
225
### Enhanced
326
- Internal improvements to securityagent integration to better support trace handling and other support for security analysis of applications under test, now v1.3.4; affects the following other integrations:

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Go is a compiled language, and doesn’t use a virtual machine. This means that
1313

1414
### Compatibility and Requirements
1515

16-
For the latest version of the agent, Go 1.18+ is required.
16+
For the latest version of the agent, Go 1.22+ is required.
1717

1818
Linux, OS X, and Windows (Vista, Server 2008 and later) are supported.
1919

v3/examples/oom/main.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2020 New Relic Corporation. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
import (
7+
"fmt"
8+
"os"
9+
"runtime"
10+
"time"
11+
12+
"github.com/newrelic/go-agent/v3/newrelic"
13+
)
14+
15+
const MB = 1024 * 1024
16+
17+
func main() {
18+
app, err := newrelic.NewApplication(
19+
newrelic.ConfigAppName("OOM Response High Water Mark App"),
20+
newrelic.ConfigFromEnvironment(),
21+
newrelic.ConfigDebugLogger(os.Stdout),
22+
)
23+
if err != nil {
24+
fmt.Println(err)
25+
os.Exit(1)
26+
}
27+
28+
// Wait for the application to connect.
29+
if err := app.WaitForConnection(5 * time.Second); err != nil {
30+
fmt.Println(err)
31+
}
32+
33+
app.HeapHighWaterMarkAlarmSet(1*MB, megabyte)
34+
app.HeapHighWaterMarkAlarmSet(10*MB, tenMegabyte)
35+
app.HeapHighWaterMarkAlarmSet(100*MB, hundredMegabyte)
36+
app.HeapHighWaterMarkAlarmEnable(2 * time.Second)
37+
38+
var a [][]byte
39+
for _ = range 100 {
40+
a = append(a, make([]byte, MB, MB))
41+
time.Sleep(1 * time.Second)
42+
}
43+
44+
// Shut down the application to flush data to New Relic.
45+
app.Shutdown(10 * time.Second)
46+
}
47+
48+
func megabyte(limit uint64, stats *runtime.MemStats) {
49+
fmt.Printf("*** 1M *** threshold %v alloc %v (%v)\n", limit, stats.Alloc, stats.TotalAlloc)
50+
}
51+
func tenMegabyte(limit uint64, stats *runtime.MemStats) {
52+
fmt.Printf("*** 10M *** threshold %v alloc %v (%v)\n", limit, stats.Alloc, stats.TotalAlloc)
53+
}
54+
func hundredMegabyte(limit uint64, stats *runtime.MemStats) {
55+
fmt.Printf("*** 100M *** threshold %v alloc %v (%v)\n", limit, stats.Alloc, stats.TotalAlloc)
56+
}

v3/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/newrelic/go-agent/v3
22

3-
go 1.21
3+
go 1.22
44

55
require (
66
google.golang.org/grpc v1.65.0

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/logWriter
22

3-
go 1.21
3+
go 1.22
44

55
require (
6-
github.com/newrelic/go-agent/v3 v3.36.0
6+
github.com/newrelic/go-agent/v3 v3.37.0
77
github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrwriter v1.0.0
88
)
99

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrlogrus
22

3-
go 1.21
3+
go 1.22
44

55
require (
6-
github.com/newrelic/go-agent/v3 v3.36.0
6+
github.com/newrelic/go-agent/v3 v3.37.0
77
github.com/sirupsen/logrus v1.8.1
88
)
99

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package nrslog
2+
3+
import (
4+
"log/slog"
5+
"maps"
6+
"strings"
7+
)
8+
9+
type attributeCache struct {
10+
preCompiledAttributes map[string]interface{}
11+
prefix string
12+
}
13+
14+
func newAttributeCache() *attributeCache {
15+
return &attributeCache{
16+
preCompiledAttributes: make(map[string]interface{}),
17+
prefix: "",
18+
}
19+
}
20+
21+
func (c *attributeCache) clone() *attributeCache {
22+
return &attributeCache{
23+
preCompiledAttributes: maps.Clone(c.preCompiledAttributes),
24+
prefix: c.prefix,
25+
}
26+
}
27+
28+
func (c *attributeCache) copyPreCompiledAttributes() map[string]interface{} {
29+
return maps.Clone(c.preCompiledAttributes)
30+
}
31+
32+
func (c *attributeCache) getPrefix() string {
33+
return c.prefix
34+
}
35+
36+
// precompileGroup sets the group prefix for the cache created by a handler
37+
// precompileGroup call. This is used to avoid re-computing the group prefix
38+
// and should only ever be called on newly created caches and handlers.
39+
func (c *attributeCache) precompileGroup(group string) {
40+
if c.prefix != "" {
41+
c.prefix += "."
42+
}
43+
c.prefix += group
44+
}
45+
46+
// precompileAttributes appends attributes to the cache created by a handler
47+
// WithAttrs call. This is used to avoid re-computing the with Attrs attributes
48+
// and should only ever be called on newly created caches and handlers.
49+
func (c *attributeCache) precompileAttributes(attrs []slog.Attr) {
50+
if len(attrs) == 0 {
51+
return
52+
}
53+
54+
for _, a := range attrs {
55+
c.appendAttr(c.preCompiledAttributes, a, c.prefix)
56+
}
57+
}
58+
59+
func (c *attributeCache) appendAttr(nrAttrs map[string]interface{}, a slog.Attr, groupPrefix string) {
60+
// Resolve the Attr's value before doing anything else.
61+
a.Value = a.Value.Resolve()
62+
// Ignore empty Attrs.
63+
if a.Equal(slog.Attr{}) {
64+
return
65+
}
66+
67+
// majority of runtime spent allocating and copying strings
68+
group := strings.Builder{}
69+
group.Grow(len(groupPrefix) + len(a.Key) + 1)
70+
group.WriteString(groupPrefix)
71+
72+
if a.Key != "" {
73+
if group.Len() > 0 {
74+
group.WriteByte('.')
75+
}
76+
group.WriteString(a.Key)
77+
}
78+
79+
key := group.String()
80+
81+
// If the Attr is a group, append its attributes
82+
if a.Value.Kind() == slog.KindGroup {
83+
attrs := a.Value.Group()
84+
// Ignore empty groups.
85+
if len(attrs) == 0 {
86+
return
87+
}
88+
89+
for _, ga := range attrs {
90+
c.appendAttr(nrAttrs, ga, key)
91+
}
92+
return
93+
}
94+
95+
// attr is an attribute
96+
nrAttrs[key] = a.Value.Any()
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package nrslog
2+
3+
import (
4+
"time"
5+
6+
"github.com/newrelic/go-agent/v3/newrelic"
7+
)
8+
9+
const updateFrequency = 1 * time.Minute // check infrequently because the go agent config is not expected to change --> cost 50-100 uS
10+
11+
// 44% faster than checking the config on every log message
12+
type configCache struct {
13+
lastCheck time.Time
14+
15+
// true if we have successfully gotten the config at least once to verify the agent is connected
16+
gotStartupConfig bool
17+
// true if the logs in context feature is enabled as well as either local decorating or forwarding
18+
enabled bool
19+
enrichLogs bool
20+
forwardLogs bool
21+
}
22+
23+
func newConfigCache() *configCache {
24+
return &configCache{}
25+
}
26+
27+
func (c *configCache) clone() *configCache {
28+
return &configCache{
29+
lastCheck: c.lastCheck,
30+
gotStartupConfig: c.gotStartupConfig,
31+
enabled: c.enabled,
32+
enrichLogs: c.enrichLogs,
33+
forwardLogs: c.forwardLogs,
34+
}
35+
}
36+
37+
func (c *configCache) shouldEnrichLog(app *newrelic.Application) bool {
38+
c.update(app)
39+
return c.enrichLogs
40+
}
41+
42+
func (c *configCache) shouldForwardLogs(app *newrelic.Application) bool {
43+
c.update(app)
44+
return c.forwardLogs
45+
}
46+
47+
// isEnabled returns true if the logs in context feature is enabled
48+
// as well as either local decorating or forwarding.
49+
func (c *configCache) isEnabled(app *newrelic.Application) bool {
50+
c.update(app)
51+
return c.enabled
52+
}
53+
54+
// Note: this has a data race in async use cases, but it does not
55+
// cause logical errors, only cache misses. This is acceptable in
56+
// comparison to the cost of synchronization.
57+
func (c *configCache) update(app *newrelic.Application) {
58+
// do not get the config from agent if we have successfully gotten it before
59+
// and it has been less than updateFrequency since the last check. This is
60+
// because on startup, the agent will return a dummy config until it has
61+
// connected and received the real config.
62+
if c.gotStartupConfig && time.Since(c.lastCheck) < updateFrequency {
63+
return
64+
}
65+
66+
config, ok := app.Config()
67+
if !ok {
68+
c.enrichLogs = false
69+
c.forwardLogs = false
70+
c.enabled = false
71+
return
72+
}
73+
74+
c.gotStartupConfig = true
75+
c.enrichLogs = config.ApplicationLogging.LocalDecorating.Enabled && config.ApplicationLogging.Enabled
76+
c.forwardLogs = config.ApplicationLogging.Forwarding.Enabled && config.ApplicationLogging.Enabled
77+
c.enabled = config.ApplicationLogging.Enabled && (c.enrichLogs || c.forwardLogs)
78+
79+
c.lastCheck = time.Now()
80+
}
+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrslog
22

3-
go 1.21
3+
go 1.22
44

5-
require github.com/newrelic/go-agent/v3 v3.36.0
5+
require github.com/newrelic/go-agent/v3 v3.37.0
66

77

88
replace github.com/newrelic/go-agent/v3 => ../../..

0 commit comments

Comments
 (0)