diff --git a/v3/integrations/logcontext-v2/nrslog/example/main.go b/v3/integrations/logcontext-v2/nrslog/example/main.go new file mode 100644 index 000000000..57a7d11d1 --- /dev/null +++ b/v3/integrations/logcontext-v2/nrslog/example/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "log/slog" + "os" + "time" + + "github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrslog" + "github.com/newrelic/go-agent/v3/newrelic" +) + +func main() { + app, err := newrelic.NewApplication( + newrelic.ConfigFromEnvironment(), + newrelic.ConfigAppLogEnabled(true), + ) + if err != nil { + panic(err) + } + + app.WaitForConnection(time.Second * 5) + log := slog.New(nrslog.TextHandler(app, os.Stdout, &slog.HandlerOptions{})) + + log.Info("I am a log message") + + txn := app.StartTransaction("example transaction") + txnLogger := nrslog.WithTransaction(txn, log) + txnLogger.Info("I am a log inside a transaction") + + // pretend to do some work + time.Sleep(500 * time.Millisecond) + txnLogger.Warn("Uh oh, something important happened!") + txn.End() + + log.Info("All Done!") + + app.Shutdown(time.Second * 10) +} diff --git a/v3/integrations/logcontext-v2/nrslog/go.mod b/v3/integrations/logcontext-v2/nrslog/go.mod index b129f04a6..202319dcb 100644 --- a/v3/integrations/logcontext-v2/nrslog/go.mod +++ b/v3/integrations/logcontext-v2/nrslog/go.mod @@ -1,19 +1,6 @@ module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrslog -go 1.21.6 +go 1.21 -require ( - github.com/newrelic/go-agent/v3 v3.29.1 - github.com/newrelic/go-agent/v3/integrations/logcontext-v2/logWriter v1.0.1 -) +require github.com/newrelic/go-agent/v3 v3.30.0 -require ( - github.com/golang/protobuf v1.5.3 // indirect - github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrwriter v1.0.0 // indirect - golang.org/x/net v0.9.0 // indirect - golang.org/x/sys v0.7.0 // indirect - golang.org/x/text v0.9.0 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/grpc v1.56.3 // indirect - google.golang.org/protobuf v1.30.0 // indirect -) diff --git a/v3/integrations/logcontext-v2/nrslog/handler.go b/v3/integrations/logcontext-v2/nrslog/handler.go index 5b8b58146..df55abb35 100644 --- a/v3/integrations/logcontext-v2/nrslog/handler.go +++ b/v3/integrations/logcontext-v2/nrslog/handler.go @@ -8,6 +8,7 @@ import ( "github.com/newrelic/go-agent/v3/newrelic" ) +// NRHandler is an Slog handler that includes logic to implement New Relic Logs in Context type NRHandler struct { handler slog.Handler w *LogWriter @@ -15,7 +16,7 @@ type NRHandler struct { txn *newrelic.Transaction } -// TextHandler creates a wrapped slog TextHandler, enabling it to both automatically capture logs +// TextHandler creates a wrapped Slog TextHandler, enabling it to both automatically capture logs // and to enrich logs locally depending on your logs in context configuration in your New Relic // application. func TextHandler(app *newrelic.Application, w io.Writer, opts *slog.HandlerOptions) NRHandler { @@ -26,7 +27,7 @@ func TextHandler(app *newrelic.Application, w io.Writer, opts *slog.HandlerOptio return wrappedHandler } -// JSONHandler creates a wrapped slog JSONHandler, enabling it to both automatically capture logs +// JSONHandler creates a wrapped Slog JSONHandler, enabling it to both automatically capture logs // and to enrich logs locally depending on your logs in context configuration in your New Relic // application. func JSONHandler(app *newrelic.Application, w io.Writer, opts *slog.HandlerOptions) NRHandler { @@ -37,7 +38,7 @@ func JSONHandler(app *newrelic.Application, w io.Writer, opts *slog.HandlerOptio return wrappedHandler } -// WithTransaction creates a new slog Logger object to be used for logging within a given transaction. +// WithTransaction creates a new Slog Logger object to be used for logging within a given transaction. func WithTransaction(txn *newrelic.Transaction, logger *slog.Logger) *slog.Logger { if txn == nil { return logger @@ -53,7 +54,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 +// WithTransaction creates a new Slog Logger object to be used for logging within a given transaction it its found // in a context. func WithContext(ctx context.Context, logger *slog.Logger) *slog.Logger { if ctx == nil { @@ -73,13 +74,14 @@ func WrapHandler(app *newrelic.Application, handler slog.Handler) NRHandler { } } +// addWriter is an internal helper function to append an io.Writer to the NRHandler object func (h *NRHandler) addWriter(w *LogWriter) { h.w = w } // WithTransaction returns a new handler that is configured to capture log data // and attribute it to a specific transaction. -func (h NRHandler) WithTransaction(txn *newrelic.Transaction) NRHandler { +func (h *NRHandler) WithTransaction(txn *newrelic.Transaction) NRHandler { handler := NRHandler{ handler: h.handler, app: h.app, diff --git a/v3/integrations/logcontext-v2/nrslog/handler_test.go b/v3/integrations/logcontext-v2/nrslog/handler_test.go index 55f8e0ba2..ec9c7f2ad 100644 --- a/v3/integrations/logcontext-v2/nrslog/handler_test.go +++ b/v3/integrations/logcontext-v2/nrslog/handler_test.go @@ -2,6 +2,7 @@ package nrslog import ( "bytes" + "context" "log/slog" "testing" @@ -104,6 +105,47 @@ func TestHandlerTransactions(t *testing.T) { }) } +func TestHandlerTransactionCtx(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(handler) + + txn := app.Application.StartTransaction("my txn") + ctx := newrelic.NewContext(context.Background(), txn) + txninfo := txn.GetLinkingMetadata() + + txnLogger := WithContext(ctx, log) + txnLogger.Info(message) + + backgroundMsg := "this is a background message" + log.Debug(backgroundMsg) + 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, + }, + }) +} + func TestHandlerTransactionsAndBackground(t *testing.T) { app := integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn, newrelic.ConfigAppLogDecoratingEnabled(true),