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 }