mirror of https://github.com/sirupsen/logrus.git
First cut at adding calling method
If log.SetReportMethod(true) then method=PACKAGE.FUNCTION will be added as a field to log lines. eg: time="2016-11-25T19:04:43-08:00" level=info method=main msg="log testing" TODO: documentation, examples
This commit is contained in:
parent
a437dfd246
commit
93af604ba7
|
@ -45,6 +45,13 @@ time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca s
|
||||||
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
||||||
exit status 1
|
exit status 1
|
||||||
```
|
```
|
||||||
|
To ensure this behaviour even if a TTY is attached, set your formatter as follows:
|
||||||
|
```
|
||||||
|
log.SetFormatter(&log.TextFormatter{
|
||||||
|
DisableColors: true,
|
||||||
|
FullTimestamp: true,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ package logrus
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -17,6 +19,9 @@ func TestRegister(t *testing.T) {
|
||||||
|
|
||||||
func TestHandler(t *testing.T) {
|
func TestHandler(t *testing.T) {
|
||||||
gofile := "/tmp/testprog.go"
|
gofile := "/tmp/testprog.go"
|
||||||
|
testprog := testprogleader
|
||||||
|
testprog = append(testprog, getPackage()...)
|
||||||
|
testprog = append(testprog, testprogtrailer...)
|
||||||
if err := ioutil.WriteFile(gofile, testprog, 0666); err != nil {
|
if err := ioutil.WriteFile(gofile, testprog, 0666); err != nil {
|
||||||
t.Fatalf("can't create go file")
|
t.Fatalf("can't create go file")
|
||||||
}
|
}
|
||||||
|
@ -38,13 +43,24 @@ func TestHandler(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var testprog = []byte(`
|
// getPackage returns the name of the current package, which makes running this
|
||||||
|
// test in a fork simpler
|
||||||
|
func getPackage() []byte {
|
||||||
|
pc, _, _, _ := runtime.Caller(0)
|
||||||
|
fullFuncName := runtime.FuncForPC(pc).Name()
|
||||||
|
idx := strings.LastIndex(fullFuncName, ".")
|
||||||
|
return []byte(fullFuncName[:idx]) // trim off function details
|
||||||
|
}
|
||||||
|
|
||||||
|
var testprogleader = []byte(`
|
||||||
// Test program for atexit, gets output file and data as arguments and writes
|
// Test program for atexit, gets output file and data as arguments and writes
|
||||||
// data to output file in atexit handler.
|
// data to output file in atexit handler.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Sirupsen/logrus"
|
"`)
|
||||||
|
var testprogtrailer = []byte(
|
||||||
|
`"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
47
entry.go
47
entry.go
|
@ -4,6 +4,9 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -37,6 +40,9 @@ type Entry struct {
|
||||||
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
||||||
Level Level
|
Level Level
|
||||||
|
|
||||||
|
// Calling method, with package name
|
||||||
|
Method string
|
||||||
|
|
||||||
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
||||||
Message string
|
Message string
|
||||||
|
|
||||||
|
@ -47,8 +53,8 @@ type Entry struct {
|
||||||
func NewEntry(logger *Logger) *Entry {
|
func NewEntry(logger *Logger) *Entry {
|
||||||
return &Entry{
|
return &Entry{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
// Default is three fields, give a little extra room
|
// Default is three fields, plus one optional. Give a little extra room.
|
||||||
Data: make(Fields, 5),
|
Data: make(Fields, 6),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +91,39 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||||
return &Entry{Logger: entry.Logger, Data: data}
|
return &Entry{Logger: entry.Logger, Data: data}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getCaller retrieves the name of the first non-logrus calling function
|
||||||
|
func getCaller() (method string) {
|
||||||
|
// Restrict the lookback to 25 frames - if it's further than that, report UNKNOWN
|
||||||
|
pcs := make([]uintptr, 25)
|
||||||
|
|
||||||
|
// the first non-logrus caller is at least three frames away
|
||||||
|
depth := runtime.Callers(3, pcs)
|
||||||
|
for i := 0; i < depth; i++ {
|
||||||
|
fullFuncName := runtime.FuncForPC(pcs[i]).Name()
|
||||||
|
idx := strings.LastIndex(fullFuncName, "/") + 1
|
||||||
|
if idx > 0 {
|
||||||
|
fullFuncName = fullFuncName[idx:]
|
||||||
|
}
|
||||||
|
|
||||||
|
matched, err := regexp.MatchString("logrus.*", fullFuncName)
|
||||||
|
if err != nil {
|
||||||
|
return "CALLER_LOOKUP_FAILED"
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the caller isn't part of logrus, we're done
|
||||||
|
if !matched {
|
||||||
|
if fullFuncName == "main.main" {
|
||||||
|
return "main"
|
||||||
|
} else {
|
||||||
|
return fullFuncName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we got here, we failed to find the caller's context
|
||||||
|
return "UNKNOWN_CALLER"
|
||||||
|
}
|
||||||
|
|
||||||
// This function is not declared with a pointer value because otherwise
|
// This function is not declared with a pointer value because otherwise
|
||||||
// race conditions will occur when using multiple goroutines
|
// race conditions will occur when using multiple goroutines
|
||||||
func (entry Entry) log(level Level, msg string) {
|
func (entry Entry) log(level Level, msg string) {
|
||||||
|
@ -92,7 +131,9 @@ func (entry Entry) log(level Level, msg string) {
|
||||||
entry.Time = time.Now()
|
entry.Time = time.Now()
|
||||||
entry.Level = level
|
entry.Level = level
|
||||||
entry.Message = msg
|
entry.Message = msg
|
||||||
|
if ReportMethod() {
|
||||||
|
entry.Method = getCaller()
|
||||||
|
}
|
||||||
if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
|
if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
|
||||||
entry.Logger.mu.Lock()
|
entry.Logger.mu.Lock()
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||||
|
|
16
exported.go
16
exported.go
|
@ -27,6 +27,22 @@ func SetFormatter(formatter Formatter) {
|
||||||
std.Formatter = formatter
|
std.Formatter = formatter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetReportMethod sets whether to include calling method and line as
|
||||||
|
// fields
|
||||||
|
func SetReportMethod(include bool) {
|
||||||
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
std.ReportMethod = include
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportMethod sets whether to include calling method and line as
|
||||||
|
// fields
|
||||||
|
func ReportMethod() bool {
|
||||||
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
return std.ReportMethod
|
||||||
|
}
|
||||||
|
|
||||||
// SetLevel sets the standard logger level.
|
// SetLevel sets the standard logger level.
|
||||||
func SetLevel(level Level) {
|
func SetLevel(level Level) {
|
||||||
std.mu.Lock()
|
std.mu.Lock()
|
||||||
|
|
10
formatter.go
10
formatter.go
|
@ -2,6 +2,7 @@ package logrus
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
// DefaultTimestampFormat is YYYY-mm-DDTHH:MM:SS-TZ
|
||||||
const DefaultTimestampFormat = time.RFC3339
|
const DefaultTimestampFormat = time.RFC3339
|
||||||
|
|
||||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||||
|
@ -18,7 +19,7 @@ type Formatter interface {
|
||||||
Format(*Entry) ([]byte, error)
|
Format(*Entry) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is to not silently overwrite `time`, `msg` and `level` fields when
|
// This is to not silently overwrite `time`, `msg`, `method` and `level` fields when
|
||||||
// dumping it. If this code wasn't there doing:
|
// dumping it. If this code wasn't there doing:
|
||||||
//
|
//
|
||||||
// logrus.WithField("level", 1).Info("hello")
|
// logrus.WithField("level", 1).Info("hello")
|
||||||
|
@ -42,4 +43,11 @@ func prefixFieldClashes(data Fields) {
|
||||||
if l, ok := data["level"]; ok {
|
if l, ok := data["level"]; ok {
|
||||||
data["fields.level"] = l
|
data["fields.level"] = l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If ReportMethod is not set, 'method' will not conflict.
|
||||||
|
if ReportMethod() {
|
||||||
|
if l, ok := data["method"]; ok {
|
||||||
|
data["fields.method"] = l
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,10 @@ type fieldKey string
|
||||||
type FieldMap map[fieldKey]string
|
type FieldMap map[fieldKey]string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FieldKeyMsg = "msg"
|
FieldKeyMsg = "msg"
|
||||||
FieldKeyLevel = "level"
|
FieldKeyLevel = "level"
|
||||||
FieldKeyTime = "time"
|
FieldKeyTime = "time"
|
||||||
|
FieldKeyMethod = "method"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (f FieldMap) resolve(key fieldKey) string {
|
func (f FieldMap) resolve(key fieldKey) string {
|
||||||
|
@ -30,16 +31,17 @@ type JSONFormatter struct {
|
||||||
// As an example:
|
// As an example:
|
||||||
// formatter := &JSONFormatter{
|
// formatter := &JSONFormatter{
|
||||||
// FieldMap: FieldMap{
|
// FieldMap: FieldMap{
|
||||||
// FieldKeyTime: "@timestamp",
|
// FieldKeyTime: "@timestamp",
|
||||||
// FieldKeyLevel: "@level",
|
// FieldKeyLevel: "@level",
|
||||||
// FieldKeyLevel: "@message",
|
// FieldKeyMsg: "@message",
|
||||||
|
// FieldKeyMethod: "@caller",
|
||||||
// },
|
// },
|
||||||
// }
|
// }
|
||||||
FieldMap FieldMap
|
FieldMap FieldMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
data := make(Fields, len(entry.Data)+3)
|
data := make(Fields, len(entry.Data)+4)
|
||||||
for k, v := range entry.Data {
|
for k, v := range entry.Data {
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case error:
|
case error:
|
||||||
|
@ -60,7 +62,9 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
|
data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
|
||||||
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
|
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
|
||||||
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
|
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
|
||||||
|
if ReportMethod() {
|
||||||
|
data[f.FieldMap.resolve(FieldKeyMethod)] = entry.Method
|
||||||
|
}
|
||||||
serialized, err := json.Marshal(data)
|
serialized, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||||
|
|
|
@ -169,3 +169,51 @@ func TestJSONTimeKey(t *testing.T) {
|
||||||
t.Fatal("Expected JSON to format time key")
|
t.Fatal("Expected JSON to format time key")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFieldDoesNotClashWithMethod(t *testing.T) {
|
||||||
|
SetReportMethod(false)
|
||||||
|
formatter := &JSONFormatter{}
|
||||||
|
|
||||||
|
b, err := formatter.Format(WithField("method", "howdy pardner"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to format entry: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := make(map[string]interface{})
|
||||||
|
err = json.Unmarshal(b, &entry)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry["method"] != "howdy pardner" {
|
||||||
|
t.Fatal("method field replaced when ReportMethod=false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFieldClashWithMethod(t *testing.T) {
|
||||||
|
SetReportMethod(true)
|
||||||
|
formatter := &JSONFormatter{}
|
||||||
|
|
||||||
|
b, err := formatter.Format(WithField("method", "howdy pardner"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to format entry: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := make(map[string]interface{})
|
||||||
|
err = json.Unmarshal(b, &entry)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry["fields.method"] != "howdy pardner" {
|
||||||
|
t.Fatalf("fields.method not set to original method field when ReportMethod=true (got '%s')",
|
||||||
|
entry["fields.method"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry["method"] != "" { // since we didn't actually log, it's set to the empty string
|
||||||
|
t.Fatalf("method not set as expected when ReportMethod=true (got '%s')",
|
||||||
|
entry["method"])
|
||||||
|
}
|
||||||
|
|
||||||
|
SetReportMethod(false) // return to default value
|
||||||
|
}
|
||||||
|
|
13
logger.go
13
logger.go
|
@ -22,6 +22,10 @@ type Logger struct {
|
||||||
// own that implements the `Formatter` interface, see the `README` or included
|
// own that implements the `Formatter` interface, see the `README` or included
|
||||||
// formatters for examples.
|
// formatters for examples.
|
||||||
Formatter Formatter
|
Formatter Formatter
|
||||||
|
|
||||||
|
//Flag for whether to log caller info (off by default)
|
||||||
|
ReportMethod bool
|
||||||
|
|
||||||
// The logging level the logger should log at. This is typically (and defaults
|
// The logging level the logger should log at. This is typically (and defaults
|
||||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||||
// logged. `logrus.Debug` is useful in
|
// logged. `logrus.Debug` is useful in
|
||||||
|
@ -67,10 +71,11 @@ func (mw *MutexWrap) Disable() {
|
||||||
// It's recommended to make this a global instance called `log`.
|
// It's recommended to make this a global instance called `log`.
|
||||||
func New() *Logger {
|
func New() *Logger {
|
||||||
return &Logger{
|
return &Logger{
|
||||||
Out: os.Stderr,
|
Out: os.Stderr,
|
||||||
Formatter: new(TextFormatter),
|
Formatter: new(TextFormatter),
|
||||||
Hooks: make(LevelHooks),
|
Hooks: make(LevelHooks),
|
||||||
Level: InfoLevel,
|
Level: InfoLevel,
|
||||||
|
ReportMethod: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,30 @@ func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields ma
|
||||||
assertions(fields)
|
assertions(fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestReportMethod verifies that when ReportMethod is set, the 'method' field
|
||||||
|
// is added, and when it is unset it is not set or modified
|
||||||
|
func TestReportMethod(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
SetReportMethod(false)
|
||||||
|
log.Print("testNoCaller")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "testNoCaller")
|
||||||
|
assert.Equal(t, fields["level"], "info")
|
||||||
|
assert.Equal(t, fields["method"], nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
SetReportMethod(true)
|
||||||
|
log.Print("testWithCaller")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "testWithCaller")
|
||||||
|
assert.Equal(t, fields["level"], "info")
|
||||||
|
assert.Equal(t, fields["method"], "testing.tRunner")
|
||||||
|
})
|
||||||
|
|
||||||
|
SetReportMethod(false) // return to default value
|
||||||
|
}
|
||||||
|
|
||||||
func TestPrint(t *testing.T) {
|
func TestPrint(t *testing.T) {
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
log.Print("test")
|
log.Print("test")
|
||||||
|
@ -241,6 +265,55 @@ func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNestedLoggingReportsCorrectCaller(t *testing.T) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
var fields Fields
|
||||||
|
|
||||||
|
SetReportMethod(true)
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &buffer
|
||||||
|
logger.Formatter = new(JSONFormatter)
|
||||||
|
|
||||||
|
llog := logger.WithField("context", "eating raw fish")
|
||||||
|
|
||||||
|
llog.Info("looks delicious")
|
||||||
|
|
||||||
|
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||||
|
assert.NoError(t, err, "should have decoded first message")
|
||||||
|
assert.Equal(t, len(fields), 5, "should have msg/time/level/method/context fields")
|
||||||
|
assert.Equal(t, fields["msg"], "looks delicious")
|
||||||
|
assert.Equal(t, fields["context"], "eating raw fish")
|
||||||
|
assert.Equal(t, fields["method"], "testing.tRunner")
|
||||||
|
|
||||||
|
buffer.Reset()
|
||||||
|
|
||||||
|
logger.WithFields(Fields{
|
||||||
|
"foo": "a",
|
||||||
|
}).WithFields(Fields{
|
||||||
|
"bar": "b",
|
||||||
|
}).WithFields(Fields{
|
||||||
|
"baz": "c",
|
||||||
|
}).WithFields(Fields{
|
||||||
|
"method": "man",
|
||||||
|
}).WithFields(Fields{
|
||||||
|
"clan": "Wu Tang",
|
||||||
|
}).Print("omg it is!")
|
||||||
|
|
||||||
|
err = json.Unmarshal(buffer.Bytes(), &fields)
|
||||||
|
assert.NoError(t, err, "should have decoded second message")
|
||||||
|
assert.Equal(t, 10, len(fields), "should have all builtin fields plus foo,bar,baz")
|
||||||
|
assert.Equal(t, "omg it is!", fields["msg"])
|
||||||
|
assert.Equal(t, "a", fields["foo"])
|
||||||
|
assert.Equal(t, "b", fields["bar"])
|
||||||
|
assert.Equal(t, "c", fields["baz"])
|
||||||
|
assert.Equal(t, "man", fields["fields.method"])
|
||||||
|
assert.Equal(t, "Wu Tang", fields["clan"])
|
||||||
|
assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry")
|
||||||
|
assert.Equal(t, "testing.tRunner", fields["method"])
|
||||||
|
|
||||||
|
SetReportMethod(false) // return to default value
|
||||||
|
}
|
||||||
|
|
||||||
func TestConvertLevelToString(t *testing.T) {
|
func TestConvertLevelToString(t *testing.T) {
|
||||||
assert.Equal(t, "debug", DebugLevel.String())
|
assert.Equal(t, "debug", DebugLevel.String())
|
||||||
assert.Equal(t, "info", InfoLevel.String())
|
assert.Equal(t, "info", InfoLevel.String())
|
||||||
|
|
|
@ -88,6 +88,9 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
|
f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
|
||||||
}
|
}
|
||||||
f.appendKeyValue(b, "level", entry.Level.String())
|
f.appendKeyValue(b, "level", entry.Level.String())
|
||||||
|
if ReportMethod() {
|
||||||
|
f.appendKeyValue(b, "method", entry.Method)
|
||||||
|
}
|
||||||
if entry.Message != "" {
|
if entry.Message != "" {
|
||||||
f.appendKeyValue(b, "msg", entry.Message)
|
f.appendKeyValue(b, "msg", entry.Message)
|
||||||
}
|
}
|
||||||
|
@ -115,10 +118,17 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
|
||||||
|
|
||||||
levelText := strings.ToUpper(entry.Level.String())[0:4]
|
levelText := strings.ToUpper(entry.Level.String())[0:4]
|
||||||
|
|
||||||
|
caller := ""
|
||||||
|
if ReportMethod() {
|
||||||
|
caller = fmt.Sprintf(" %s()", entry.Method)
|
||||||
|
}
|
||||||
|
|
||||||
if !f.FullTimestamp {
|
if !f.FullTimestamp {
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
|
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText,
|
||||||
|
miniTS(), caller, entry.Message)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
|
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText,
|
||||||
|
entry.Time.Format(timestampFormat), caller, entry.Message)
|
||||||
}
|
}
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
v := entry.Data[k]
|
v := entry.Data[k]
|
||||||
|
|
Loading…
Reference in New Issue