Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 3.37.0 #1004

Merged
merged 18 commits into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
## 3.37.0
### Enhanced
- 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:
- Wrapping `slog.Handler` objects with errors to allow users to handle invalid use cases
- 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.
- Complete support for `With()`, `WithGroup()`, and attributes for automatic instrumentation.
- Performance operations.
- Robust testing (close to 90% coverage).
- **This updates logcontext-v2/nrslog to v1.4.0.**
- Now custom application tags (labels) may be added to all forwarded log events.
- Enabled if `ConfigAppLogForwardingLabelsEnabled(true)` or `NEW_RELIC_APPLICATION_LOGGING_FORWARDING_LABELS_ENABLED=TRUE`
- May exclude labels named in `ConfigAppLogForwardingLabelsExclude("label1","label2",...)` or `NEW_RELIC_APPLICATION_LOGGING_FORWARDING_LABELS_EXCLUDE="label1,label2,..."`
- Labels are defined via `ConfigLabels(...)` or `NEW_RELIC_LABELS`
- 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.

### Fixed
- Added protection around transaction methods to gracefully return when the transaction object is `nil`.

### 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.36.0
### Enhanced
- 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:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Go is a compiled language, and doesn’t use a virtual machine. This means that

### Compatibility and Requirements

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

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

Expand Down
56 changes: 56 additions & 0 deletions v3/examples/oom/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2020 New Relic Corporation. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package main

import (
"fmt"
"os"
"runtime"
"time"

"github.com/newrelic/go-agent/v3/newrelic"
)

const MB = 1024 * 1024

func main() {
app, err := newrelic.NewApplication(
newrelic.ConfigAppName("OOM Response High Water Mark App"),
newrelic.ConfigFromEnvironment(),
newrelic.ConfigDebugLogger(os.Stdout),
)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

// Wait for the application to connect.
if err := app.WaitForConnection(5 * time.Second); err != nil {
fmt.Println(err)
}

app.HeapHighWaterMarkAlarmSet(1*MB, megabyte)
app.HeapHighWaterMarkAlarmSet(10*MB, tenMegabyte)
app.HeapHighWaterMarkAlarmSet(100*MB, hundredMegabyte)
app.HeapHighWaterMarkAlarmEnable(2 * time.Second)

var a [][]byte
for _ = range 100 {
a = append(a, make([]byte, MB, MB))
time.Sleep(1 * time.Second)
}

// Shut down the application to flush data to New Relic.
app.Shutdown(10 * time.Second)
}

func megabyte(limit uint64, stats *runtime.MemStats) {
fmt.Printf("*** 1M *** threshold %v alloc %v (%v)\n", limit, stats.Alloc, stats.TotalAlloc)
}
func tenMegabyte(limit uint64, stats *runtime.MemStats) {
fmt.Printf("*** 10M *** threshold %v alloc %v (%v)\n", limit, stats.Alloc, stats.TotalAlloc)
}
func hundredMegabyte(limit uint64, stats *runtime.MemStats) {
fmt.Printf("*** 100M *** threshold %v alloc %v (%v)\n", limit, stats.Alloc, stats.TotalAlloc)
}
2 changes: 1 addition & 1 deletion v3/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/newrelic/go-agent/v3

go 1.21
go 1.22

require (
google.golang.org/grpc v1.65.0
Expand Down
4 changes: 2 additions & 2 deletions v3/integrations/logcontext-v2/logWriter/go.mod
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/logWriter

go 1.21
go 1.22

require (
github.com/newrelic/go-agent/v3 v3.36.0
github.com/newrelic/go-agent/v3 v3.37.0
github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrwriter v1.0.0
)

Expand Down
4 changes: 2 additions & 2 deletions v3/integrations/logcontext-v2/nrlogrus/go.mod
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrlogrus

go 1.21
go 1.22

require (
github.com/newrelic/go-agent/v3 v3.36.0
github.com/newrelic/go-agent/v3 v3.37.0
github.com/sirupsen/logrus v1.8.1
)

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

import (
"log/slog"
"maps"
"strings"
)

type attributeCache struct {
preCompiledAttributes map[string]interface{}
prefix string
}

func newAttributeCache() *attributeCache {
return &attributeCache{
preCompiledAttributes: make(map[string]interface{}),
prefix: "",
}
}

func (c *attributeCache) clone() *attributeCache {
return &attributeCache{
preCompiledAttributes: maps.Clone(c.preCompiledAttributes),
prefix: c.prefix,
}
}

func (c *attributeCache) copyPreCompiledAttributes() map[string]interface{} {
return maps.Clone(c.preCompiledAttributes)
}

func (c *attributeCache) getPrefix() string {
return c.prefix
}

// precompileGroup sets the group prefix for the cache created by a handler
// precompileGroup call. This is used to avoid re-computing the group prefix
// and should only ever be called on newly created caches and handlers.
func (c *attributeCache) precompileGroup(group string) {
if c.prefix != "" {
c.prefix += "."
}
c.prefix += group
}

// precompileAttributes appends attributes to the cache created by a handler
// WithAttrs call. This is used to avoid re-computing the with Attrs attributes
// and should only ever be called on newly created caches and handlers.
func (c *attributeCache) precompileAttributes(attrs []slog.Attr) {
if len(attrs) == 0 {
return
}

for _, a := range attrs {
c.appendAttr(c.preCompiledAttributes, a, c.prefix)
}
}

func (c *attributeCache) appendAttr(nrAttrs map[string]interface{}, a slog.Attr, groupPrefix string) {
// Resolve the Attr's value before doing anything else.
a.Value = a.Value.Resolve()
// Ignore empty Attrs.
if a.Equal(slog.Attr{}) {
return
}

// majority of runtime spent allocating and copying strings
group := strings.Builder{}
group.Grow(len(groupPrefix) + len(a.Key) + 1)
group.WriteString(groupPrefix)

if a.Key != "" {
if group.Len() > 0 {
group.WriteByte('.')
}
group.WriteString(a.Key)
}

key := group.String()

// If the Attr is a group, append its attributes
if a.Value.Kind() == slog.KindGroup {
attrs := a.Value.Group()
// Ignore empty groups.
if len(attrs) == 0 {
return
}

for _, ga := range attrs {
c.appendAttr(nrAttrs, ga, key)
}
return
}

// attr is an attribute
nrAttrs[key] = a.Value.Any()
}
80 changes: 80 additions & 0 deletions v3/integrations/logcontext-v2/nrslog/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package nrslog

import (
"time"

"github.com/newrelic/go-agent/v3/newrelic"
)

const updateFrequency = 1 * time.Minute // check infrequently because the go agent config is not expected to change --> cost 50-100 uS

// 44% faster than checking the config on every log message
type configCache struct {
lastCheck time.Time

// true if we have successfully gotten the config at least once to verify the agent is connected
gotStartupConfig bool
// true if the logs in context feature is enabled as well as either local decorating or forwarding
enabled bool
enrichLogs bool
forwardLogs bool
}

func newConfigCache() *configCache {
return &configCache{}
}

func (c *configCache) clone() *configCache {
return &configCache{
lastCheck: c.lastCheck,
gotStartupConfig: c.gotStartupConfig,
enabled: c.enabled,
enrichLogs: c.enrichLogs,
forwardLogs: c.forwardLogs,
}
}

func (c *configCache) shouldEnrichLog(app *newrelic.Application) bool {
c.update(app)
return c.enrichLogs
}

func (c *configCache) shouldForwardLogs(app *newrelic.Application) bool {
c.update(app)
return c.forwardLogs
}

// isEnabled returns true if the logs in context feature is enabled
// as well as either local decorating or forwarding.
func (c *configCache) isEnabled(app *newrelic.Application) bool {
c.update(app)
return c.enabled
}

// Note: this has a data race in async use cases, but it does not
// cause logical errors, only cache misses. This is acceptable in
// comparison to the cost of synchronization.
func (c *configCache) update(app *newrelic.Application) {
// do not get the config from agent if we have successfully gotten it before
// and it has been less than updateFrequency since the last check. This is
// because on startup, the agent will return a dummy config until it has
// connected and received the real config.
if c.gotStartupConfig && time.Since(c.lastCheck) < updateFrequency {
return
}

config, ok := app.Config()
if !ok {
c.enrichLogs = false
c.forwardLogs = false
c.enabled = false
return
}

c.gotStartupConfig = true
c.enrichLogs = config.ApplicationLogging.LocalDecorating.Enabled && config.ApplicationLogging.Enabled
c.forwardLogs = config.ApplicationLogging.Forwarding.Enabled && config.ApplicationLogging.Enabled
c.enabled = config.ApplicationLogging.Enabled && (c.enrichLogs || c.forwardLogs)

c.lastCheck = time.Now()
}
4 changes: 2 additions & 2 deletions v3/integrations/logcontext-v2/nrslog/go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrslog

go 1.21
go 1.22

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


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