github.com/newrelic/go-agent@v3.26.0+incompatible/_integrations/logcontext/nrlogrusplugin/nrlogrusplugin.go (about)

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  // Package nrlogrusplugin decorates logs for sending to the New Relic backend.
     5  //
     6  // Use this package if you want to enable the New Relic logging product and see
     7  // your log messages in the New Relic UI.
     8  //
     9  // Since Logrus is completely api-compatible with the stdlib logger, you can
    10  // replace your `"log"` imports with `log "github.com/sirupsen/logrus"` and
    11  // follow the steps below to enable the logging product for use with the stdlib
    12  // Go logger.
    13  //
    14  // Using `logger.WithField`
    15  // (https://godoc.org/github.com/sirupsen/logrus#Logger.WithField) and
    16  // `logger.WithFields`
    17  // (https://godoc.org/github.com/sirupsen/logrus#Logger.WithFields) is
    18  // supported.  However, if the field key collides with one of the keys used by
    19  // the New Relic Formatter, the value will be overwritten.  Reserved keys are
    20  // those found in the `logcontext` package
    21  // (https://godoc.org/github.com/newrelic/go-agent/_integrations/logcontext/#pkg-constants).
    22  //
    23  // Supported types for `logger.WithField` and `logger.WithFields` field values
    24  // are numbers, booleans, strings, and errors.  Func types are dropped and all
    25  // other types are converted to strings.
    26  //
    27  // Requires v1.4.0 of the Logrus package or newer.
    28  //
    29  // Configuration
    30  //
    31  // For the best linking experience be sure to enable Distributed Tracing:
    32  //
    33  //	cfg := NewConfig("Example Application", "__YOUR_NEW_RELIC_LICENSE_KEY__")
    34  //	cfg.DistributedTracer.Enabled = true
    35  //
    36  // To enable log decoration, set your log's formatter to the
    37  // `nrlogrusplugin.ContextFormatter`
    38  //
    39  //	logger := log.New()
    40  //	logger.SetFormatter(nrlogrusplugin.ContextFormatter{})
    41  //
    42  // or if you are using the logrus standard logger
    43  //
    44  //	log.SetFormatter(nrlogrusplugin.ContextFormatter{})
    45  //
    46  // The logger will now look for a newrelic.Transaction inside its context and
    47  // decorate logs accordingly.  Therefore, the Transaction must be added to the
    48  // context and passed to the logger.  For example, this logging call
    49  //
    50  //	logger.Info("Hello New Relic!")
    51  //
    52  // must be transformed to include the context, such as:
    53  //
    54  //	ctx := newrelic.NewContext(context.Background(), txn)
    55  //	logger.WithContext(ctx).Info("Hello New Relic!")
    56  //
    57  // Troubleshooting
    58  //
    59  // When properly configured, your log statements will be in JSON format with
    60  // one message per line:
    61  //
    62  //	{"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"}
    63  //
    64  // If the `trace.id` key is missing, be sure that Distributed Tracing is
    65  // enabled and that the Transaction context has been added to the logger using
    66  // `WithContext` (https://godoc.org/github.com/sirupsen/logrus#Logger.WithContext).
    67  package nrlogrusplugin
    68  
    69  import (
    70  	"bytes"
    71  	"encoding/json"
    72  	"fmt"
    73  
    74  	newrelic "github.com/newrelic/go-agent"
    75  	"github.com/newrelic/go-agent/_integrations/logcontext"
    76  	"github.com/newrelic/go-agent/internal"
    77  	"github.com/newrelic/go-agent/internal/jsonx"
    78  	"github.com/sirupsen/logrus"
    79  )
    80  
    81  func init() { internal.TrackUsage("integration", "logcontext", "logrus") }
    82  
    83  type logFields map[string]interface{}
    84  
    85  // ContextFormatter is a `logrus.Formatter` that will format logs for sending
    86  // to New Relic.
    87  type ContextFormatter struct{}
    88  
    89  // Format renders a single log entry.
    90  func (f ContextFormatter) Format(e *logrus.Entry) ([]byte, error) {
    91  	// 12 = 6 from GetLinkingMetadata + 6 more below
    92  	data := make(logFields, len(e.Data)+12)
    93  	for k, v := range e.Data {
    94  		data[k] = v
    95  	}
    96  
    97  	if ctx := e.Context; nil != ctx {
    98  		if txn := newrelic.FromContext(ctx); nil != txn {
    99  			logcontext.AddLinkingMetadata(data, txn.GetLinkingMetadata())
   100  		}
   101  	}
   102  
   103  	data[logcontext.KeyTimestamp] = uint64(e.Time.UnixNano()) / uint64(1000*1000)
   104  	data[logcontext.KeyMessage] = e.Message
   105  	data[logcontext.KeyLevel] = e.Level
   106  
   107  	if e.HasCaller() {
   108  		data[logcontext.KeyFile] = e.Caller.File
   109  		data[logcontext.KeyLine] = e.Caller.Line
   110  		data[logcontext.KeyMethod] = e.Caller.Function
   111  	}
   112  
   113  	var b *bytes.Buffer
   114  	if e.Buffer != nil {
   115  		b = e.Buffer
   116  	} else {
   117  		b = &bytes.Buffer{}
   118  	}
   119  	writeDataJSON(b, data)
   120  	return b.Bytes(), nil
   121  }
   122  
   123  func writeDataJSON(buf *bytes.Buffer, data logFields) {
   124  	buf.WriteByte('{')
   125  	var needsComma bool
   126  	for k, v := range data {
   127  		if needsComma {
   128  			buf.WriteByte(',')
   129  		} else {
   130  			needsComma = true
   131  		}
   132  		jsonx.AppendString(buf, k)
   133  		buf.WriteByte(':')
   134  		writeValue(buf, v)
   135  	}
   136  	buf.WriteByte('}')
   137  	buf.WriteByte('\n')
   138  }
   139  
   140  func writeValue(buf *bytes.Buffer, val interface{}) {
   141  	switch v := val.(type) {
   142  	case string:
   143  		jsonx.AppendString(buf, v)
   144  	case bool:
   145  		if v {
   146  			buf.WriteString("true")
   147  		} else {
   148  			buf.WriteString("false")
   149  		}
   150  	case uint8:
   151  		jsonx.AppendInt(buf, int64(v))
   152  	case uint16:
   153  		jsonx.AppendInt(buf, int64(v))
   154  	case uint32:
   155  		jsonx.AppendInt(buf, int64(v))
   156  	case uint64:
   157  		jsonx.AppendInt(buf, int64(v))
   158  	case uint:
   159  		jsonx.AppendInt(buf, int64(v))
   160  	case uintptr:
   161  		jsonx.AppendInt(buf, int64(v))
   162  	case int8:
   163  		jsonx.AppendInt(buf, int64(v))
   164  	case int16:
   165  		jsonx.AppendInt(buf, int64(v))
   166  	case int32:
   167  		jsonx.AppendInt(buf, int64(v))
   168  	case int:
   169  		jsonx.AppendInt(buf, int64(v))
   170  	case int64:
   171  		jsonx.AppendInt(buf, v)
   172  	case float32:
   173  		jsonx.AppendFloat(buf, float64(v))
   174  	case float64:
   175  		jsonx.AppendFloat(buf, v)
   176  	case logrus.Level:
   177  		jsonx.AppendString(buf, v.String())
   178  	case error:
   179  		jsonx.AppendString(buf, v.Error())
   180  	default:
   181  		if m, ok := v.(json.Marshaler); ok {
   182  			if js, err := m.MarshalJSON(); nil == err {
   183  				buf.Write(js)
   184  				return
   185  			}
   186  		}
   187  		jsonx.AppendString(buf, fmt.Sprintf("%#v", v))
   188  	}
   189  }