Skip to content

Commit

Permalink
Add back v2 of agent.
Browse files Browse the repository at this point in the history
  • Loading branch information
purple4reina authored and [email protected] committed Nov 26, 2019
1 parent 11b016f commit 6332353
Show file tree
Hide file tree
Showing 314 changed files with 52,553 additions and 0 deletions.
8 changes: 8 additions & 0 deletions _integrations/logcontext/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# _integrations/logcontext [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/logcontext?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/logcontext)

Logs in Context. Each directory represents a different logging plugin.
Plugins allow you to add the context required to your log messages so you can
see linking in the APM UI.

For more information, see
[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/logcontext).
38 changes: 38 additions & 0 deletions _integrations/logcontext/logcontext.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package logcontext

import newrelic "github.com/newrelic/go-agent"

// Keys used for logging context JSON.
const (
KeyFile = "file.name"
KeyLevel = "log.level"
KeyLine = "line.number"
KeyMessage = "message"
KeyMethod = "method.name"
KeyTimestamp = "timestamp"
KeyTraceID = "trace.id"
KeySpanID = "span.id"
KeyEntityName = "entity.name"
KeyEntityType = "entity.type"
KeyEntityGUID = "entity.guid"
KeyHostname = "hostname"
)

func metadataMapField(m map[string]interface{}, key, val string) {
if val != "" {
m[key] = val
}
}

// AddLinkingMetadata adds the LinkingMetadata into a map. Only non-empty
// string fields are included in the map. The specific key names facilitate
// agent logs in context. These keys are: "trace.id", "span.id",
// "entity.name", "entity.type", "entity.guid", and "hostname".
func AddLinkingMetadata(m map[string]interface{}, md newrelic.LinkingMetadata) {
metadataMapField(m, KeyTraceID, md.TraceID)
metadataMapField(m, KeySpanID, md.SpanID)
metadataMapField(m, KeyEntityName, md.EntityName)
metadataMapField(m, KeyEntityType, md.EntityType)
metadataMapField(m, KeyEntityGUID, md.EntityGUID)
metadataMapField(m, KeyHostname, md.Hostname)
}
10 changes: 10 additions & 0 deletions _integrations/logcontext/nrlogrusplugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# _integrations/logcontext/nrlogrusplugin [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin)

Package `nrlogrusplugin` decorates logs for sending to the New Relic backend.

```go
import "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin"
```

For more information, see
[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin).
70 changes: 70 additions & 0 deletions _integrations/logcontext/nrlogrusplugin/example/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package main

import (
"context"
"fmt"
"os"
"time"

newrelic "github.com/newrelic/go-agent"
"github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin"
"github.com/sirupsen/logrus"
)

func mustGetEnv(key string) string {
if val := os.Getenv(key); "" != val {
return val
}
panic(fmt.Sprintf("environment variable %s unset", key))
}

func doFunction2(txn newrelic.Transaction, e *logrus.Entry) {
defer newrelic.StartSegment(txn, "doFunction2").End()
e.Error("In doFunction2")
}

func doFunction1(txn newrelic.Transaction, e *logrus.Entry) {
defer newrelic.StartSegment(txn, "doFunction1").End()
e.Trace("In doFunction1")
doFunction2(txn, e)
}

func main() {
log := logrus.New()
// To enable New Relic log decoration, use the
// nrlogrusplugin.ContextFormatter{}
log.SetFormatter(nrlogrusplugin.ContextFormatter{})
log.SetLevel(logrus.TraceLevel)

log.Debug("Logger created")

cfg := newrelic.NewConfig("Logrus Log Decoration", mustGetEnv("NEW_RELIC_LICENSE_KEY"))
cfg.DistributedTracer.Enabled = true
cfg.CrossApplicationTracer.Enabled = false

app, err := newrelic.NewApplication(cfg)
if nil != err {
log.Panic("Failed to create application", err)
}

log.Debug("Application created, waiting for connection")

err = app.WaitForConnection(10 * time.Second)
if nil != err {
log.Panic("Failed to connect application", err)
}
log.Info("Application connected")
defer app.Shutdown(10 * time.Second)

log.Debug("Starting transaction now")
txn := app.StartTransaction("main", nil, nil)

// Add the transaction context to the logger. Only once this happens will
// the logs be properly decorated with all required fields.
e := log.WithContext(newrelic.NewContext(context.Background(), txn))

doFunction1(txn, e)

e.Info("Ending transaction")
txn.End()
}
186 changes: 186 additions & 0 deletions _integrations/logcontext/nrlogrusplugin/nrlogrusplugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Package nrlogrusplugin decorates logs for sending to the New Relic backend.
//
// Use this package if you want to enable the New Relic logging product and see
// your log messages in the New Relic UI.
//
// Since Logrus is completely api-compatible with the stdlib logger, you can
// replace your `"log"` imports with `log "github.com/sirupsen/logrus"` and
// follow the steps below to enable the logging product for use with the stdlib
// Go logger.
//
// Using `logger.WithField`
// (https://godoc.org/github.com/sirupsen/logrus#Logger.WithField) and
// `logger.WithFields`
// (https://godoc.org/github.com/sirupsen/logrus#Logger.WithFields) is
// supported. However, if the field key collides with one of the keys used by
// the New Relic Formatter, the value will be overwritten. Reserved keys are
// those found in the `logcontext` package
// (https://godoc.org/github.com/newrelic/go-agent/_integrations/logcontext/#pkg-constants).
//
// Supported types for `logger.WithField` and `logger.WithFields` field values
// are numbers, booleans, strings, and errors. Func types are dropped and all
// other types are converted to strings.
//
// Requires v1.4.0 of the Logrus package or newer.
//
// Configuration
//
// For the best linking experience be sure to enable Distributed Tracing:
//
// cfg := NewConfig("Example Application", "__YOUR_NEW_RELIC_LICENSE_KEY__")
// cfg.DistributedTracer.Enabled = true
//
// To enable log decoration, set your log's formatter to the
// `nrlogrusplugin.ContextFormatter`
//
// logger := log.New()
// logger.SetFormatter(nrlogrusplugin.ContextFormatter{})
//
// or if you are using the logrus standard logger
//
// log.SetFormatter(nrlogrusplugin.ContextFormatter{})
//
// The logger will now look for a newrelic.Transaction inside its context and
// decorate logs accordingly. Therefore, the Transaction must be added to the
// context and passed to the logger. For example, this logging call
//
// logger.Info("Hello New Relic!")
//
// must be transformed to include the context, such as:
//
// ctx := newrelic.NewContext(context.Background(), txn)
// logger.WithContext(ctx).Info("Hello New Relic!")
//
// Troubleshooting
//
// When properly configured, your log statements will be in JSON format with
// one message per line:
//
// {"message":"Hello New Relic!","log.level":"info","trace.id":"469a04f6c1278593","span.id":"9f365c71f0f04a98","entity.type":"SERVICE","entity.guid":"MTE3ODUwMHxBUE18QVBQTElDQVRJT058Mjc3MDU2Njc1","hostname":"my.hostname","timestamp":1568917432034,"entity.name":"Example Application"}
//
// If the `trace.id` key is missing, be sure that Distributed Tracing is
// enabled and that the Transaction context has been added to the logger using
// `WithContext` (https://godoc.org/github.com/sirupsen/logrus#Logger.WithContext).
package nrlogrusplugin

import (
"bytes"
"encoding/json"
"fmt"

newrelic "github.com/newrelic/go-agent"
"github.com/newrelic/go-agent/_integrations/logcontext"
"github.com/newrelic/go-agent/internal"
"github.com/newrelic/go-agent/internal/jsonx"
"github.com/sirupsen/logrus"
)

func init() { internal.TrackUsage("integration", "logcontext", "logrus") }

type logFields map[string]interface{}

// ContextFormatter is a `logrus.Formatter` that will format logs for sending
// to New Relic.
type ContextFormatter struct{}

// Format renders a single log entry.
func (f ContextFormatter) Format(e *logrus.Entry) ([]byte, error) {
// 12 = 6 from GetLinkingMetadata + 6 more below
data := make(logFields, len(e.Data)+12)
for k, v := range e.Data {
data[k] = v
}

if ctx := e.Context; nil != ctx {
if txn := newrelic.FromContext(ctx); nil != txn {
logcontext.AddLinkingMetadata(data, txn.GetLinkingMetadata())
}
}

data[logcontext.KeyTimestamp] = uint64(e.Time.UnixNano()) / uint64(1000*1000)
data[logcontext.KeyMessage] = e.Message
data[logcontext.KeyLevel] = e.Level

if e.HasCaller() {
data[logcontext.KeyFile] = e.Caller.File
data[logcontext.KeyLine] = e.Caller.Line
data[logcontext.KeyMethod] = e.Caller.Function
}

var b *bytes.Buffer
if e.Buffer != nil {
b = e.Buffer
} else {
b = &bytes.Buffer{}
}
writeDataJSON(b, data)
return b.Bytes(), nil
}

func writeDataJSON(buf *bytes.Buffer, data logFields) {
buf.WriteByte('{')
var needsComma bool
for k, v := range data {
if needsComma {
buf.WriteByte(',')
} else {
needsComma = true
}
jsonx.AppendString(buf, k)
buf.WriteByte(':')
writeValue(buf, v)
}
buf.WriteByte('}')
buf.WriteByte('\n')
}

func writeValue(buf *bytes.Buffer, val interface{}) {
switch v := val.(type) {
case string:
jsonx.AppendString(buf, v)
case bool:
if v {
buf.WriteString("true")
} else {
buf.WriteString("false")
}
case uint8:
jsonx.AppendInt(buf, int64(v))
case uint16:
jsonx.AppendInt(buf, int64(v))
case uint32:
jsonx.AppendInt(buf, int64(v))
case uint64:
jsonx.AppendInt(buf, int64(v))
case uint:
jsonx.AppendInt(buf, int64(v))
case uintptr:
jsonx.AppendInt(buf, int64(v))
case int8:
jsonx.AppendInt(buf, int64(v))
case int16:
jsonx.AppendInt(buf, int64(v))
case int32:
jsonx.AppendInt(buf, int64(v))
case int:
jsonx.AppendInt(buf, int64(v))
case int64:
jsonx.AppendInt(buf, v)
case float32:
jsonx.AppendFloat(buf, float64(v))
case float64:
jsonx.AppendFloat(buf, v)
case logrus.Level:
jsonx.AppendString(buf, v.String())
case error:
jsonx.AppendString(buf, v.Error())
default:
if m, ok := v.(json.Marshaler); ok {
if js, err := m.MarshalJSON(); nil == err {
buf.Write(js)
return
}
}
jsonx.AppendString(buf, fmt.Sprintf("%#v", v))
}
}
Loading

0 comments on commit 6332353

Please sign in to comment.