Skip to content

Commit

Permalink
Merge pull request newrelic#881 from adomaskizogian/develop
Browse files Browse the repository at this point in the history
feat: slog: automatically reference transaction from context
  • Loading branch information
nr-swilloughby authored Apr 4, 2024
2 parents 81c6d4c + d8cab45 commit 4134348
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ jobs:
- dirs: v3/integrations/logcontext-v2/nrlogrus
- dirs: v3/integrations/logcontext-v2/nrzerolog
- dirs: v3/integrations/logcontext-v2/nrzap
- dirs: v3/integrations/logcontext-v2/nrslog
- dirs: v3/integrations/logcontext-v2/nrwriter
- dirs: v3/integrations/logcontext-v2/zerologWriter
- dirs: v3/integrations/logcontext-v2/logWriter
Expand Down Expand Up @@ -138,4 +139,4 @@ jobs:
else
echo "Directory /app/$dir does not exist."
fi
done
done
40 changes: 40 additions & 0 deletions v3/integrations/logcontext-v2/nrslog/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func JSONHandler(app *newrelic.Application, w io.Writer, opts *slog.HandlerOptio
}

// WithTransaction creates a new Slog Logger object to be used for logging within a given transaction.
// Calling this function with a logger having underlying TransactionFromContextHandler handler is a no-op.
func WithTransaction(txn *newrelic.Transaction, logger *slog.Logger) *slog.Logger {
if txn == nil || logger == nil {
return logger
Expand All @@ -56,6 +57,7 @@ func WithTransaction(txn *newrelic.Transaction, logger *slog.Logger) *slog.Logge

// WithTransaction creates a new Slog Logger object to be used for logging within a given transaction it its found
// in a context.
// Calling this function with a logger having underlying TransactionFromContextHandler handler is a no-op.
func WithContext(ctx context.Context, logger *slog.Logger) *slog.Logger {
if ctx == nil {
return logger
Expand Down Expand Up @@ -181,3 +183,41 @@ func (h NRHandler) WithGroup(name string) slog.Handler {
txn: h.txn,
}
}

// NRHandler is an Slog handler that includes logic to implement New Relic Logs in Context.
// New Relic transaction value is taken from context. It cannot be set directly.
// This serves as a quality of life improvement for cases where slog.Default global instance is
// referenced, allowing to use slog methods directly and maintaining New Relic instrumentation.
type TransactionFromContextHandler struct {
NRHandler
}

// WithTransactionFromContext creates a wrapped NRHandler, enabling it to automatically reference New Relic
// transaction from context.
func WithTransactionFromContext(handler NRHandler) TransactionFromContextHandler {
return TransactionFromContextHandler{handler}
}

// Handle handles the Record.
// It will only be called when Enabled returns true.
// The Context argument is as for Enabled and NewRelic transaction.
// Canceling the context should not affect record processing.
// (Among other things, log messages may be necessary to debug a
// cancellation-related problem.)
//
// Handle methods that produce output should observe the following rules:
// - If r.Time is the zero time, ignore the time.
// - If r.PC is zero, ignore it.
// - Attr's values should be resolved.
// - If an Attr's key and value are both the zero value, ignore the Attr.
// This can be tested with attr.Equal(Attr{}).
// - If a group's key is empty, inline the group's Attrs.
// - If a group has no Attrs (even if it has a non-empty key),
// ignore it.
func (h TransactionFromContextHandler) Handle(ctx context.Context, record slog.Record) error {
if txn := newrelic.FromContext(ctx); txn != nil {
return h.NRHandler.WithTransaction(txn).Handle(ctx, record)
}

return h.NRHandler.Handle(ctx, record)
}
37 changes: 37 additions & 0 deletions v3/integrations/logcontext-v2/nrslog/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,40 @@ func TestWithGroup(t *testing.T) {
}

}

func TestTransactionFromContextHandler(t *testing.T) {
app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn,
newrelic.ConfigAppLogDecoratingEnabled(true),
newrelic.ConfigAppLogForwardingEnabled(true),
)

out := bytes.NewBuffer([]byte{})
message := "Hello World!"

handler := TextHandler(app.Application, out, &slog.HandlerOptions{})
log := slog.New(WithTransactionFromContext(handler))

txn := app.Application.StartTransaction("my txn")
ctx := newrelic.NewContext(context.Background(), txn)
txninfo := txn.GetLinkingMetadata()

log.InfoContext(ctx, message)

txn.End()

logcontext.ValidateDecoratedOutput(t, out, &logcontext.DecorationExpect{
EntityGUID: integrationsupport.TestEntityGUID,
Hostname: host,
EntityName: integrationsupport.SampleAppName,
})

app.ExpectLogEvents(t, []internal.WantLog{
{
Severity: slog.LevelInfo.String(),
Message: message,
Timestamp: internal.MatchAnyUnixMilli,
SpanID: txninfo.SpanID,
TraceID: txninfo.TraceID,
},
})
}

0 comments on commit 4134348

Please sign in to comment.