Skip to content

Commit

Permalink
Merge pull request #1004 from newrelic/develop
Browse files Browse the repository at this point in the history
Release 3.37.0
  • Loading branch information
nr-swilloughby authored Feb 26, 2025
2 parents 5867ad9 + 7043c7a commit c081f78
Show file tree
Hide file tree
Showing 80 changed files with 1,883 additions and 492 deletions.
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

0 comments on commit c081f78

Please sign in to comment.