Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #548 from iamemilio/ioEnricher
nrWriter: an io.Writer that implements logs in context
- Loading branch information
Showing
15 changed files
with
874 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# New Relic Log Writer | ||
|
||
The logWriter library is an `io.Writer` that automatically integrates the latest New Relic Logs in Context features into the go standard library logger. When used as the `io.Writer` for log, this tool will collect log metrics, forward logs, and enrich logs depending on how your New Relic application is configured. This is the most complete and convenient way to to capture log data with New Relic in log. | ||
|
||
## Usage | ||
|
||
Once your New Relic application has been created, create a logWriter instance. It must be passed an io.Writer, which is where the final log content will be written to, and a pointer to New Relic application. | ||
|
||
```go | ||
writer := logWriter.New(os.Stdout, app) | ||
``` | ||
|
||
If any errors occor while trying to decorate your log with New Relic metadata, it will fail silently and print your log message in its original, unedited form. If you want to see the error messages, then enable debug logging. This will print an error message in a new line after the original log message is printed. | ||
|
||
```go | ||
writer.DebugLogging(true) | ||
``` | ||
|
||
To capture log data in the context of a transaction, make a new logWriter with the `WithTransaction` or `WithContext` methods. | ||
|
||
If you have a pointer to a transaction, use the `WithTransaction()` function. | ||
|
||
```go | ||
txn := app.StartTransaction("my transaction") | ||
defer txn.End() | ||
txnWriter := writer.WithTransaction(txn) | ||
``` | ||
|
||
If you have a context with a transaction pointer in it, use the `WithContext()` function. | ||
|
||
```go | ||
func ExampleHandler(w http.ResponseWriter, r *http.Request) { | ||
txnWriter := writer.WithContext(r.Context()) | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package main | ||
|
||
import ( | ||
"log" | ||
"os" | ||
"time" | ||
|
||
"github.com/newrelic/go-agent/v3/integrations/logcontext-v2/logWriter" | ||
"github.com/newrelic/go-agent/v3/newrelic" | ||
) | ||
|
||
func main() { | ||
app, err := newrelic.NewApplication( | ||
newrelic.ConfigAppName("log writer example"), | ||
newrelic.ConfigFromEnvironment(), | ||
newrelic.ConfigInfoLogger(os.Stdout), | ||
newrelic.ConfigAppLogForwardingEnabled(true), | ||
) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
app.WaitForConnection(5 * time.Second) | ||
|
||
// Create a logWriter, then pass it to the log.Logger | ||
writer := logWriter.New(os.Stdout, app) | ||
logger := log.New(&writer, "Background: ", log.Default().Flags()) | ||
|
||
logger.Print("Hello world!") | ||
|
||
txnName := "Example Transaction" | ||
txn := app.StartTransaction(txnName) | ||
|
||
// Always create a new log object in order to avoid changing the context of the logger for | ||
// other threads that may be logging outside of this transaction | ||
txnLogger := log.New(writer.WithTransaction(txn), "Transaction: ", log.Default().Flags()) | ||
txnLogger.Printf("In transaction %s.", txnName) | ||
|
||
// simulate doing something | ||
time.Sleep(time.Microsecond * 100) | ||
|
||
txnLogger.Printf("Ending transaction %s.", txnName) | ||
txn.End() | ||
|
||
logger.Print("Goodbye!") | ||
app.Shutdown(10 * time.Second) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module github.com/newrelic/go-agent/v3/integrations/logcontext-v2/logWriter | ||
|
||
go 1.17 | ||
|
||
require github.com/newrelic/go-agent/v3 v3.19.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package logWriter | ||
|
||
import ( | ||
"context" | ||
"io" | ||
|
||
"github.com/newrelic/go-agent/v3/internal/logcontext/nrwriter" | ||
"github.com/newrelic/go-agent/v3/newrelic" | ||
) | ||
|
||
type LogWriter struct { | ||
w nrwriter.LogWriter | ||
} | ||
|
||
func init() { internal.TrackUsage("integration", "logcontext-v2", "logWriter") } | ||
|
||
// New creates a new LogWriter | ||
// output is the io.Writer destination that you want your log to be written to | ||
// app must be a vaild, non nil new relic Application | ||
func New(output io.Writer, app *newrelic.Application) LogWriter { | ||
return LogWriter{ | ||
w: nrwriter.New(output, app), | ||
} | ||
} | ||
|
||
// DebugLogging toggles whether error information should be printed to console. By default, this service | ||
// will fail silently. Enabling debug logging will print error messages on a new line after your log message. | ||
func (lw *LogWriter) DebugLogging(enabled bool) { lw.w.DebugLogging(enabled) } | ||
|
||
// WithTransaction creates a new LogWriter for a specific transactions | ||
func (lw *LogWriter) WithTransaction(txn *newrelic.Transaction) LogWriter { | ||
return LogWriter{w: lw.w.WithTransaction(txn)} | ||
} | ||
|
||
// WithContext creates a new LogWriter for the transaction inside of a context | ||
func (lw *LogWriter) WithContext(ctx context.Context) LogWriter { | ||
return LogWriter{w: lw.w.WithContext(ctx)} | ||
} | ||
|
||
// Write is a valid io.Writer method that will write the content of an enriched log to the output io.Writer | ||
func (lw LogWriter) Write(p []byte) (n int, err error) { | ||
enrichedLog := lw.w.EnrichLog(newrelic.LogData{Message: string(p)}, p) | ||
return lw.w.Write(enrichedLog) | ||
} |
70 changes: 70 additions & 0 deletions
70
v3/integrations/logcontext-v2/logWriter/log-writer_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package logWriter | ||
|
||
import ( | ||
"bytes" | ||
"log" | ||
"testing" | ||
|
||
"github.com/newrelic/go-agent/v3/internal" | ||
"github.com/newrelic/go-agent/v3/internal/integrationsupport" | ||
"github.com/newrelic/go-agent/v3/internal/logcontext" | ||
"github.com/newrelic/go-agent/v3/internal/sysinfo" | ||
"github.com/newrelic/go-agent/v3/newrelic" | ||
) | ||
|
||
var ( | ||
host, _ = sysinfo.Hostname() | ||
) | ||
|
||
func TestE2E(t *testing.T) { | ||
app := integrationsupport.NewTestApp( | ||
integrationsupport.SampleEverythingReplyFn, | ||
newrelic.ConfigAppLogDecoratingEnabled(true), | ||
newrelic.ConfigAppLogForwardingEnabled(true), | ||
) | ||
|
||
// Capture output in a buffer for testing | ||
buf := bytes.NewBuffer([]byte{}) | ||
|
||
// set up logger | ||
writer := New(buf, app.Application) | ||
logger := log.New(&writer, "My Prefix: ", log.Lshortfile) | ||
|
||
// configure log writer | ||
writer.DebugLogging(true) | ||
|
||
// create a log message | ||
logger.Print("Hello World!") | ||
|
||
logcontext.ValidateDecoratedOutput(t, buf, &logcontext.DecorationExpect{ | ||
EntityGUID: integrationsupport.TestEntityGUID, | ||
Hostname: host, | ||
EntityName: integrationsupport.SampleAppName, | ||
}) | ||
|
||
app.ExpectLogEvents(t, []internal.WantLog{ | ||
{ | ||
Severity: logcontext.LogSeverityUnknown, | ||
Message: "My Prefix: log-writer_test.go:37: Hello World!", | ||
Timestamp: internal.MatchAnyUnixMilli, | ||
}, | ||
}) | ||
} | ||
|
||
func BenchmarkWrite(b *testing.B) { | ||
app := integrationsupport.NewTestApp( | ||
integrationsupport.SampleEverythingReplyFn, | ||
newrelic.ConfigAppLogDecoratingEnabled(true), | ||
newrelic.ConfigAppLogForwardingEnabled(true), | ||
) | ||
buf := bytes.NewBuffer([]byte{}) | ||
a := New(buf, app.Application) | ||
|
||
log := []byte(`{"time":1516134303,"level":"debug","message":"hello world"}`) | ||
|
||
b.ResetTimer() | ||
b.ReportAllocs() | ||
for i := 0; i < b.N; i++ { | ||
a.Write(log) | ||
} | ||
} |
Oops, something went wrong.