Skip to content

Commit

Permalink
Merge pull request #548 from iamemilio/ioEnricher
Browse files Browse the repository at this point in the history
nrWriter: an io.Writer that implements logs in context
  • Loading branch information
iamemilio committed Sep 15, 2022
2 parents 870b44d + c0e9558 commit dd07840
Show file tree
Hide file tree
Showing 15 changed files with 874 additions and 52 deletions.
80 changes: 39 additions & 41 deletions .github/workflows/ci.yaml
Expand Up @@ -87,16 +87,6 @@ jobs:
dirs: _integrations/nrmongo

# v3 agent
- go-version: 1.10.x
dirs: v3/newrelic,v3/internal,v3/examples
- go-version: 1.11.x
dirs: v3/newrelic,v3/internal,v3/examples
- go-version: 1.12.x
dirs: v3/newrelic,v3/internal,v3/examples
- go-version: 1.15.x
dirs: v3/newrelic,v3/internal,v3/examples
- go-version: 1.16.x
dirs: v3/newrelic,v3/internal,v3/examples
- go-version: 1.17.x
dirs: v3/newrelic,v3/internal,v3/examples
- go-version: 1.18.x
Expand All @@ -105,7 +95,7 @@ jobs:
dirs: v3/newrelic,v3/internal,v3/examples

# v3 integrations
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/logcontext/nrlogrusplugin
extratesting: go get -u github.com/sirupsen/logrus@master
- go-version: 1.17.x
Expand All @@ -114,62 +104,69 @@ jobs:
- go-version: 1.17.x
dirs: v3/integrations/logcontext-v2/nrzerolog
extratesting: go get -u github.com/rs/zerolog@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/internal/logcontext/nrWriter
- go-version: 1.17.x
dirs: v3/integrations/logcontext-v2/zerologWriter
extratesting: go get -u github.com/rs/zerolog@master
- go-version: 1.17.x
dirs: v3/integrations/logcontext-v2/logWriter
- go-version: 1.17.x
dirs: v3/integrations/nrawssdk-v1
extratesting: go get -u github.com/aws/aws-sdk-go@main
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrawssdk-v2
extratesting: go get -u github.com/aws/aws-sdk-go-v2@main
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrecho-v3
# Test against the latest v3 Echo:
extratesting: go get -u github.com/labstack/echo@v3
# go/new/http no longer stable under go 1.17.x
- go-version: 1.17.x
dirs: v3/integrations/nrecho-v4
extratesting: go get -u github.com/labstack/echo/v4@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrelasticsearch-v7
extratesting: go get -u github.com/elastic/go-elasticsearch/v7@7.x
- go-version: 1.18.x
dirs: v3/integrations/nrgin
extratesting: go get -u github.com/gin-gonic/gin@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrgorilla
extratesting: go get -u github.com/gorilla/mux@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrgraphgophers
extratesting: go get -u github.com/graph-gophers/graphql-go@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrlogrus
extratesting: go get -u github.com/sirupsen/logrus@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrlogxi
extratesting: go get -u github.com/mgutz/logxi@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrpkgerrors
extratesting: go get -u github.com/pkg/errors@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrlambda
extratesting: go get -u github.com/aws/aws-lambda-go@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrmysql
extratesting: go get -u github.com/go-sql-driver/mysql@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrpq
extratesting: go get -u github.com/lib/pq@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrpq/example/sqlx
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrredis-v7
extratesting: go get -u github.com/go-redis/redis/v7@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrsqlite3
extratesting: go get -u github.com/mattn/go-sqlite3@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrsnowflake
extratesting: go get -u github.com/snowflakedb/gosnowflake@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrgrpc
extratesting: go get -u google.golang.org/grpc@master
- go-version: 1.17.x
Expand All @@ -181,40 +178,40 @@ jobs:
# As of June 2020, confirmed errors still result
# extratesting: go get -u github.com/micro/go-micro@latest
# If we are using the latest released version to test, we need to use a newer version of go
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrnats
extratesting: go get -u github.com/nats-io/nats.go/@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrnats/test
extratesting: go get -u github.com/nats-io/nats.go/@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrstan
extratesting: go get -u github.com/nats-io/stan.go/@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrstan/test
extratesting: go get -u github.com/nats-io/stan.go/@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrstan/examples
extratesting: go get -u github.com/nats-io/stan.go/@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/logcontext
extratesting: go get -u github.com/sirupsen/logrus@master
# nrzap only supports the two most recent minor go releases
- go-version: 1.17.x
dirs: v3/integrations/nrzap
extratesting: go get -u go.uber.org/zap@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrhttprouter
extratesting: go get -u github.com/julienschmidt/httprouter@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrb3
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrmongo
extratesting: go get -u go.mongodb.org/mongo-driver@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrgraphqlgo,v3/integrations/nrgraphqlgo/example
extratesting: go get -u github.com/graphql-go/graphql@master
- go-version: 1.15.x
- go-version: 1.17.x
dirs: v3/integrations/nrmssql
extratesting: go get -u github.com/denisenkom/go-mssqldb@master

Expand Down Expand Up @@ -251,8 +248,9 @@ jobs:
fail-fast: false
matrix:
include:
- go-version: 1.17.1
- go-version: 1.16.8
- go-version: 1.17.10
- go-version: 1.18.6
- go-version: 1.19.1
steps:
- uses: actions/checkout@v1
with:
Expand Down
35 changes: 35 additions & 0 deletions v3/integrations/logcontext-v2/logWriter/Readme.md
@@ -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())
}
```
47 changes: 47 additions & 0 deletions v3/integrations/logcontext-v2/logWriter/example/main.go
@@ -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)
}
5 changes: 5 additions & 0 deletions v3/integrations/logcontext-v2/logWriter/go.mod
@@ -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
44 changes: 44 additions & 0 deletions v3/integrations/logcontext-v2/logWriter/log-writer.go
@@ -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 v3/integrations/logcontext-v2/logWriter/log-writer_test.go
@@ -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)
}
}

0 comments on commit dd07840

Please sign in to comment.