Skip to content

Commit

Permalink
caching elements to increase speed
Browse files Browse the repository at this point in the history
  • Loading branch information
iamemilio committed Feb 6, 2025
1 parent ee64b76 commit 07f6f78
Show file tree
Hide file tree
Showing 5 changed files with 460 additions and 213 deletions.
97 changes: 97 additions & 0 deletions v3/integrations/logcontext-v2/nrslog/attribute_cache.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 (c *attributeCache) getPreCompiledAttributes() map[string]interface{} {
if c.preCompiledAttributes == nil {
return make(map[string]interface{})
}
return maps.Clone(c.preCompiledAttributes)
}

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

func (c *attributeCache) computePrecompiledAttributes(goas []groupOrAttrs) {
if len(goas) == 0 {
return
}

// if just one element, we can avoid allocation for the sting builder
if len(goas) == 1 {
if goas[0].group != "" {
c.prefix = goas[0].group
} else {
attrs := make(map[string]interface{})
for _, a := range goas[0].attrs {
c.appendAttr(attrs, a, "")
}
}
return
}

// string builder worth the pre-allocation cost
groupPrefix := strings.Builder{}
attrs := make(map[string]interface{})

for _, goa := range goas {
if goa.group != "" {
if len(groupPrefix.String()) > 0 {
groupPrefix.WriteByte('.')
}
groupPrefix.WriteString(goa.group)
} else {
for _, a := range goa.attrs {
c.appendAttr(attrs, a, groupPrefix.String())
}
}
}

c.preCompiledAttributes = attrs
c.prefix = groupPrefix.String()
}

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
}

group := strings.Builder{}
group.WriteString(groupPrefix)

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()
}
66 changes: 66 additions & 0 deletions v3/integrations/logcontext-v2/nrslog/config_cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
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 (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()
}
Loading

0 comments on commit 07f6f78

Please sign in to comment.