From 93af604ba7774ca16474f3485ab6fcf084606fe0 Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Fri, 25 Nov 2016 19:02:56 -0800 Subject: [PATCH 01/99] 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 --- README.md | 7 ++++ alt_exit_test.go | 20 ++++++++++-- entry.go | 47 +++++++++++++++++++++++++-- exported.go | 16 +++++++++ formatter.go | 10 +++++- json_formatter.go | 20 +++++++----- json_formatter_test.go | 48 +++++++++++++++++++++++++++ logger.go | 13 +++++--- logrus_test.go | 73 ++++++++++++++++++++++++++++++++++++++++++ text_formatter.go | 14 ++++++-- 10 files changed, 248 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index f9cfb0a..275c113 100644 --- a/README.md +++ b/README.md @@ -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 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 diff --git a/alt_exit_test.go b/alt_exit_test.go index 022b778..5763c60 100644 --- a/alt_exit_test.go +++ b/alt_exit_test.go @@ -3,6 +3,8 @@ package logrus import ( "io/ioutil" "os/exec" + "runtime" + "strings" "testing" "time" ) @@ -17,6 +19,9 @@ func TestRegister(t *testing.T) { func TestHandler(t *testing.T) { gofile := "/tmp/testprog.go" + testprog := testprogleader + testprog = append(testprog, getPackage()...) + testprog = append(testprog, testprogtrailer...) if err := ioutil.WriteFile(gofile, testprog, 0666); err != nil { 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 // data to output file in atexit handler. package main import ( - "github.com/Sirupsen/logrus" + "`) +var testprogtrailer = []byte( + `" "flag" "fmt" "io/ioutil" diff --git a/entry.go b/entry.go index 4edbe7a..1e721ce 100644 --- a/entry.go +++ b/entry.go @@ -4,6 +4,9 @@ import ( "bytes" "fmt" "os" + "regexp" + "runtime" + "strings" "sync" "time" ) @@ -37,6 +40,9 @@ type Entry struct { // Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic Level Level + // Calling method, with package name + Method string + // Message passed to Debug, Info, Warn, Error, Fatal or Panic Message string @@ -47,8 +53,8 @@ type Entry struct { func NewEntry(logger *Logger) *Entry { return &Entry{ Logger: logger, - // Default is three fields, give a little extra room - Data: make(Fields, 5), + // Default is three fields, plus one optional. Give a little extra room. + Data: make(Fields, 6), } } @@ -85,6 +91,39 @@ func (entry *Entry) WithFields(fields Fields) *Entry { 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 // race conditions will occur when using multiple goroutines 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.Level = level entry.Message = msg - + if ReportMethod() { + entry.Method = getCaller() + } if err := entry.Logger.Hooks.Fire(level, &entry); err != nil { entry.Logger.mu.Lock() fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) diff --git a/exported.go b/exported.go index 9a0120a..7a02d7c 100644 --- a/exported.go +++ b/exported.go @@ -27,6 +27,22 @@ func SetFormatter(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. func SetLevel(level Level) { std.mu.Lock() diff --git a/formatter.go b/formatter.go index b5fbe93..c48e02f 100644 --- a/formatter.go +++ b/formatter.go @@ -2,6 +2,7 @@ package logrus import "time" +// DefaultTimestampFormat is YYYY-mm-DDTHH:MM:SS-TZ const DefaultTimestampFormat = time.RFC3339 // The Formatter interface is used to implement a custom Formatter. It takes an @@ -18,7 +19,7 @@ type Formatter interface { 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: // // logrus.WithField("level", 1).Info("hello") @@ -42,4 +43,11 @@ func prefixFieldClashes(data Fields) { if l, ok := data["level"]; ok { 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 + } + } } diff --git a/json_formatter.go b/json_formatter.go index f3729bf..a84cc15 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -9,9 +9,10 @@ type fieldKey string type FieldMap map[fieldKey]string const ( - FieldKeyMsg = "msg" - FieldKeyLevel = "level" - FieldKeyTime = "time" + FieldKeyMsg = "msg" + FieldKeyLevel = "level" + FieldKeyTime = "time" + FieldKeyMethod = "method" ) func (f FieldMap) resolve(key fieldKey) string { @@ -30,16 +31,17 @@ type JSONFormatter struct { // As an example: // formatter := &JSONFormatter{ // FieldMap: FieldMap{ - // FieldKeyTime: "@timestamp", - // FieldKeyLevel: "@level", - // FieldKeyLevel: "@message", + // FieldKeyTime: "@timestamp", + // FieldKeyLevel: "@level", + // FieldKeyMsg: "@message", + // FieldKeyMethod: "@caller", // }, // } FieldMap FieldMap } 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 { switch v := v.(type) { 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(FieldKeyMsg)] = entry.Message data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String() - + if ReportMethod() { + data[f.FieldMap.resolve(FieldKeyMethod)] = entry.Method + } serialized, err := json.Marshal(data) if err != nil { return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) diff --git a/json_formatter_test.go b/json_formatter_test.go index 5baa93e..dc09bfe 100644 --- a/json_formatter_test.go +++ b/json_formatter_test.go @@ -169,3 +169,51 @@ func TestJSONTimeKey(t *testing.T) { 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 +} diff --git a/logger.go b/logger.go index b769f3d..64967c4 100644 --- a/logger.go +++ b/logger.go @@ -22,6 +22,10 @@ type Logger struct { // own that implements the `Formatter` interface, see the `README` or included // formatters for examples. 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 // to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be // 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`. func New() *Logger { return &Logger{ - Out: os.Stderr, - Formatter: new(TextFormatter), - Hooks: make(LevelHooks), - Level: InfoLevel, + Out: os.Stderr, + Formatter: new(TextFormatter), + Hooks: make(LevelHooks), + Level: InfoLevel, + ReportMethod: false, } } diff --git a/logrus_test.go b/logrus_test.go index bfc4780..2ba154e 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -56,6 +56,30 @@ func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields ma 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) { LogAndAssertJSON(t, func(log *Logger) { 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) { assert.Equal(t, "debug", DebugLevel.String()) assert.Equal(t, "info", InfoLevel.String()) diff --git a/text_formatter.go b/text_formatter.go index 9114b3c..4b3cc62 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -88,6 +88,9 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat)) } f.appendKeyValue(b, "level", entry.Level.String()) + if ReportMethod() { + f.appendKeyValue(b, "method", entry.Method) + } if 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] + caller := "" + if ReportMethod() { + caller = fmt.Sprintf(" %s()", entry.Method) + } + 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 { - 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 { v := entry.Data[k] From 473c3448abc37edc827ade029c8ee302b207b2a6 Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Mon, 28 Nov 2016 13:43:38 -0800 Subject: [PATCH 02/99] Add README notes and CHANGELOG entries documentation for usage of the new optional calling-method logging --- CHANGELOG.md | 4 ++++ README.md | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2c2bc2..0d84a2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.11.0 + +* feature: Add optional logging of caller method + # 0.10.0 * feature: Add a test hook (#180) diff --git a/README.md b/README.md index 275c113..57f15e4 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,9 @@ time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4 time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009 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 ``` To ensure this behaviour even if a TTY is attached, set your formatter as follows: + ``` log.SetFormatter(&log.TextFormatter{ DisableColors: true, @@ -53,6 +53,22 @@ To ensure this behaviour even if a TTY is attached, set your formatter as follow }) ``` +If you wish to add the calling method as a field, instruct the logger via: +``` +log.SetReportMethod(true) +``` +This adds the caller as 'method' like so: + +```json +{"animal":"penguin","level":"fatal","method":"arcticcreatures.migrate","msg":"a penguin swims by", +"time":"2014-03-10 19:57:38.562543129 -0400 EDT"} + +``` + +```text +time="2015-03-26T01:27:38-04:00" level=fatal method=arcticcreatures.migrate msg="a penguin swims by" animal=penguin + +``` #### Example The simplest way to use Logrus is simply the package-level exported logger: From 8161d932a1c23cea2db63cc7cccbc00e78de48f2 Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Mon, 28 Nov 2016 14:47:38 -0800 Subject: [PATCH 03/99] performance: precompile regex before iterating --- entry.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/entry.go b/entry.go index 1e721ce..290232a 100644 --- a/entry.go +++ b/entry.go @@ -96,6 +96,11 @@ func getCaller() (method string) { // Restrict the lookback to 25 frames - if it's further than that, report UNKNOWN pcs := make([]uintptr, 25) + matchesLogrus, err := regexp.Compile("logrus.*") + if err != nil { + return "CALLER_LOOKUP_FAILED" + } + // the first non-logrus caller is at least three frames away depth := runtime.Callers(3, pcs) for i := 0; i < depth; i++ { @@ -105,10 +110,7 @@ func getCaller() (method string) { fullFuncName = fullFuncName[idx:] } - matched, err := regexp.MatchString("logrus.*", fullFuncName) - if err != nil { - return "CALLER_LOOKUP_FAILED" - } + matched := matchesLogrus.MatchString(fullFuncName) // If the caller isn't part of logrus, we're done if !matched { From 1e214504085248b77a41d97e90cc9903cc524005 Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Mon, 28 Nov 2016 16:22:33 -0800 Subject: [PATCH 04/99] push compilation even higher, to reduce to one call --- entry.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/entry.go b/entry.go index 290232a..b7f4e9c 100644 --- a/entry.go +++ b/entry.go @@ -13,12 +13,17 @@ import ( var bufferPool *sync.Pool +// regex for validation is external, to reduce compilation overhead +var matchesLogrus *regexp.Regexp + func init() { bufferPool = &sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, } + + matchesLogrus, _ = regexp.Compile("logrus.*") } // Defines the key when adding errors using WithError. @@ -51,11 +56,13 @@ type Entry struct { } func NewEntry(logger *Logger) *Entry { - return &Entry{ + entry := &Entry{ Logger: logger, // Default is three fields, plus one optional. Give a little extra room. Data: make(Fields, 6), } + + return entry } // Returns the string representation from the reader and ultimately the @@ -96,11 +103,6 @@ func getCaller() (method string) { // Restrict the lookback to 25 frames - if it's further than that, report UNKNOWN pcs := make([]uintptr, 25) - matchesLogrus, err := regexp.Compile("logrus.*") - if err != nil { - return "CALLER_LOOKUP_FAILED" - } - // the first non-logrus caller is at least three frames away depth := runtime.Callers(3, pcs) for i := 0; i < depth; i++ { From 348bace2692cddf1f24a90f452348b1eecda6400 Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Tue, 29 Nov 2016 09:35:34 -0800 Subject: [PATCH 05/99] doc updates, and relabel ReportMethod in the Logrus context it's the caller, so use that internally. Label stays as 'method' since in the context of the log event that seems more correct. --- README.md | 2 +- entry.go | 6 +++--- exported.go | 14 ++++++-------- formatter.go | 4 ++-- json_formatter.go | 8 ++++---- json_formatter_test.go | 16 ++++++++-------- logger.go | 4 ++-- logrus_test.go | 14 +++++++------- text_formatter.go | 8 ++++---- 9 files changed, 37 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 57f15e4..25cc2c5 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ To ensure this behaviour even if a TTY is attached, set your formatter as follow If you wish to add the calling method as a field, instruct the logger via: ``` -log.SetReportMethod(true) +log.SetReportCaller(true) ``` This adds the caller as 'method' like so: diff --git a/entry.go b/entry.go index b7f4e9c..7949e1f 100644 --- a/entry.go +++ b/entry.go @@ -46,7 +46,7 @@ type Entry struct { Level Level // Calling method, with package name - Method string + Caller string // Message passed to Debug, Info, Warn, Error, Fatal or Panic Message string @@ -135,8 +135,8 @@ func (entry Entry) log(level Level, msg string) { entry.Time = time.Now() entry.Level = level entry.Message = msg - if ReportMethod() { - entry.Method = getCaller() + if ReportCaller() { + entry.Caller = getCaller() } if err := entry.Logger.Hooks.Fire(level, &entry); err != nil { entry.Logger.mu.Lock() diff --git a/exported.go b/exported.go index 7a02d7c..356d4a9 100644 --- a/exported.go +++ b/exported.go @@ -27,20 +27,18 @@ func SetFormatter(formatter Formatter) { std.Formatter = formatter } -// SetReportMethod sets whether to include calling method and line as -// fields -func SetReportMethod(include bool) { +// SetReportCaller sets whether to include the calling method as a field +func SetReportCaller(include bool) { std.mu.Lock() defer std.mu.Unlock() - std.ReportMethod = include + std.ReportCaller = include } -// ReportMethod sets whether to include calling method and line as -// fields -func ReportMethod() bool { +// ReportCaller returns the 'include calling method' state +func ReportCaller() bool { std.mu.Lock() defer std.mu.Unlock() - return std.ReportMethod + return std.ReportCaller } // SetLevel sets the standard logger level. diff --git a/formatter.go b/formatter.go index c48e02f..9e94689 100644 --- a/formatter.go +++ b/formatter.go @@ -44,8 +44,8 @@ func prefixFieldClashes(data Fields) { data["fields.level"] = l } - // If ReportMethod is not set, 'method' will not conflict. - if ReportMethod() { + // If Reportmethod is not set, 'method' will not conflict. + if ReportCaller() { if l, ok := data["method"]; ok { data["fields.method"] = l } diff --git a/json_formatter.go b/json_formatter.go index a84cc15..2bb3a6a 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -12,7 +12,7 @@ const ( FieldKeyMsg = "msg" FieldKeyLevel = "level" FieldKeyTime = "time" - FieldKeyMethod = "method" + FieldKeyCaller = "method" ) func (f FieldMap) resolve(key fieldKey) string { @@ -34,7 +34,7 @@ type JSONFormatter struct { // FieldKeyTime: "@timestamp", // FieldKeyLevel: "@level", // FieldKeyMsg: "@message", - // FieldKeyMethod: "@caller", + // FieldKeyCaller: "@caller", // }, // } FieldMap FieldMap @@ -62,8 +62,8 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat) data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String() - if ReportMethod() { - data[f.FieldMap.resolve(FieldKeyMethod)] = entry.Method + if ReportCaller() { + data[f.FieldMap.resolve(FieldKeyCaller)] = entry.Caller } serialized, err := json.Marshal(data) if err != nil { diff --git a/json_formatter_test.go b/json_formatter_test.go index dc09bfe..ab52095 100644 --- a/json_formatter_test.go +++ b/json_formatter_test.go @@ -170,8 +170,8 @@ func TestJSONTimeKey(t *testing.T) { } } -func TestFieldDoesNotClashWithMethod(t *testing.T) { - SetReportMethod(false) +func TestFieldDoesNotClashWithCaller(t *testing.T) { + SetReportCaller(false) formatter := &JSONFormatter{} b, err := formatter.Format(WithField("method", "howdy pardner")) @@ -186,12 +186,12 @@ func TestFieldDoesNotClashWithMethod(t *testing.T) { } if entry["method"] != "howdy pardner" { - t.Fatal("method field replaced when ReportMethod=false") + t.Fatal("method field replaced when ReportCaller=false") } } -func TestFieldClashWithMethod(t *testing.T) { - SetReportMethod(true) +func TestFieldClashWithCaller(t *testing.T) { + SetReportCaller(true) formatter := &JSONFormatter{} b, err := formatter.Format(WithField("method", "howdy pardner")) @@ -206,14 +206,14 @@ func TestFieldClashWithMethod(t *testing.T) { } if entry["fields.method"] != "howdy pardner" { - t.Fatalf("fields.method not set to original method field when ReportMethod=true (got '%s')", + t.Fatalf("fields.method not set to original method field when ReportCaller=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')", + t.Fatalf("method not set as expected when ReportCaller=true (got '%s')", entry["method"]) } - SetReportMethod(false) // return to default value + SetReportCaller(false) // return to default value } diff --git a/logger.go b/logger.go index 64967c4..ddab68a 100644 --- a/logger.go +++ b/logger.go @@ -24,7 +24,7 @@ type Logger struct { Formatter Formatter //Flag for whether to log caller info (off by default) - ReportMethod bool + ReportCaller bool // 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 @@ -75,7 +75,7 @@ func New() *Logger { Formatter: new(TextFormatter), Hooks: make(LevelHooks), Level: InfoLevel, - ReportMethod: false, + ReportCaller: false, } } diff --git a/logrus_test.go b/logrus_test.go index 2ba154e..760a24b 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -56,11 +56,11 @@ func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields ma assertions(fields) } -// TestReportMethod verifies that when ReportMethod is set, the 'method' field +// TestReportCaller verifies that when ReportCaller is set, the 'method' field // is added, and when it is unset it is not set or modified -func TestReportMethod(t *testing.T) { +func TestReportCaller(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { - SetReportMethod(false) + SetReportCaller(false) log.Print("testNoCaller") }, func(fields Fields) { assert.Equal(t, fields["msg"], "testNoCaller") @@ -69,7 +69,7 @@ func TestReportMethod(t *testing.T) { }) LogAndAssertJSON(t, func(log *Logger) { - SetReportMethod(true) + SetReportCaller(true) log.Print("testWithCaller") }, func(fields Fields) { assert.Equal(t, fields["msg"], "testWithCaller") @@ -77,7 +77,7 @@ func TestReportMethod(t *testing.T) { assert.Equal(t, fields["method"], "testing.tRunner") }) - SetReportMethod(false) // return to default value + SetReportCaller(false) // return to default value } func TestPrint(t *testing.T) { @@ -269,7 +269,7 @@ func TestNestedLoggingReportsCorrectCaller(t *testing.T) { var buffer bytes.Buffer var fields Fields - SetReportMethod(true) + SetReportCaller(true) logger := New() logger.Out = &buffer logger.Formatter = new(JSONFormatter) @@ -311,7 +311,7 @@ func TestNestedLoggingReportsCorrectCaller(t *testing.T) { 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 + SetReportCaller(false) // return to default value } func TestConvertLevelToString(t *testing.T) { diff --git a/text_formatter.go b/text_formatter.go index 4b3cc62..b003451 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -88,8 +88,8 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat)) } f.appendKeyValue(b, "level", entry.Level.String()) - if ReportMethod() { - f.appendKeyValue(b, "method", entry.Method) + if ReportCaller() { + f.appendKeyValue(b, "method", entry.Caller) } if entry.Message != "" { f.appendKeyValue(b, "msg", entry.Message) @@ -119,8 +119,8 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin levelText := strings.ToUpper(entry.Level.String())[0:4] caller := "" - if ReportMethod() { - caller = fmt.Sprintf(" %s()", entry.Method) + if ReportCaller() { + caller = fmt.Sprintf(" %s()", entry.Caller) } if !f.FullTimestamp { From 05a8f4db95032b1209e9badae15584fae216e59d Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Wed, 30 Nov 2016 10:47:03 -0800 Subject: [PATCH 06/99] fix test description --- logrus_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logrus_test.go b/logrus_test.go index 760a24b..ec31850 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -301,7 +301,7 @@ func TestNestedLoggingReportsCorrectCaller(t *testing.T) { 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, 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"]) From 4575b7a64d0ff75887a6433464fb476d8725ac71 Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Wed, 30 Nov 2016 11:36:48 -0800 Subject: [PATCH 07/99] revert slight added complexity in NewEntry() --- entry.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/entry.go b/entry.go index 7949e1f..850b958 100644 --- a/entry.go +++ b/entry.go @@ -56,13 +56,11 @@ type Entry struct { } func NewEntry(logger *Logger) *Entry { - entry := &Entry{ + return &Entry{ Logger: logger, // Default is three fields, plus one optional. Give a little extra room. Data: make(Fields, 6), } - - return entry } // Returns the string representation from the reader and ultimately the From a5c845c2247a1798ddfb903dd3af2d5e062f6c7e Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Wed, 30 Nov 2016 14:07:10 -0800 Subject: [PATCH 08/99] responses to review comments - empty string as marker for failure to discover calling function - tighten up logger usage - don't rely on std logger internally Also fix ordering of expected/got in logrus_test.go to ensure correct output form test failures. --- entry.go | 4 +-- exported.go | 10 ++----- formatter.go | 6 ++-- json_formatter.go | 10 +++++-- json_formatter_test.go | 1 + logrus_test.go | 68 ++++++++++++++++++++---------------------- text_formatter.go | 17 +++++++++-- 7 files changed, 63 insertions(+), 53 deletions(-) diff --git a/entry.go b/entry.go index 850b958..9e86687 100644 --- a/entry.go +++ b/entry.go @@ -123,7 +123,7 @@ func getCaller() (method string) { } // if we got here, we failed to find the caller's context - return "UNKNOWN_CALLER" + return "" } // This function is not declared with a pointer value because otherwise @@ -133,7 +133,7 @@ func (entry Entry) log(level Level, msg string) { entry.Time = time.Now() entry.Level = level entry.Message = msg - if ReportCaller() { + if entry.Logger.ReportCaller { entry.Caller = getCaller() } if err := entry.Logger.Hooks.Fire(level, &entry); err != nil { diff --git a/exported.go b/exported.go index 356d4a9..3585f94 100644 --- a/exported.go +++ b/exported.go @@ -27,20 +27,14 @@ func SetFormatter(formatter Formatter) { std.Formatter = formatter } -// SetReportCaller sets whether to include the calling method as a field +// SetReportCaller sets whether the standard logger will include the calling +// method as a field. func SetReportCaller(include bool) { std.mu.Lock() defer std.mu.Unlock() std.ReportCaller = include } -// ReportCaller returns the 'include calling method' state -func ReportCaller() bool { - std.mu.Lock() - defer std.mu.Unlock() - return std.ReportCaller -} - // SetLevel sets the standard logger level. func SetLevel(level Level) { std.mu.Lock() diff --git a/formatter.go b/formatter.go index 9e94689..773b359 100644 --- a/formatter.go +++ b/formatter.go @@ -31,7 +31,7 @@ type Formatter interface { // // It's not exported because it's still using Data in an opinionated way. It's to // avoid code duplication between the two default formatters. -func prefixFieldClashes(data Fields) { +func prefixFieldClashes(data Fields, reportCaller bool) { if t, ok := data["time"]; ok { data["fields.time"] = t } @@ -44,8 +44,8 @@ func prefixFieldClashes(data Fields) { data["fields.level"] = l } - // If Reportmethod is not set, 'method' will not conflict. - if ReportCaller() { + // If reportCaller is not set, 'method' will not conflict. + if reportCaller { if l, ok := data["method"]; ok { data["fields.method"] = l } diff --git a/json_formatter.go b/json_formatter.go index 2bb3a6a..46498da 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -52,7 +52,13 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { data[k] = v } } - prefixFieldClashes(data) + + reportCaller := false + if entry.Logger != nil { + reportCaller = entry.Logger.ReportCaller + } + + prefixFieldClashes(data, reportCaller) timestampFormat := f.TimestampFormat if timestampFormat == "" { @@ -62,7 +68,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat) data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String() - if ReportCaller() { + if reportCaller { data[f.FieldMap.resolve(FieldKeyCaller)] = entry.Caller } serialized, err := json.Marshal(data) diff --git a/json_formatter_test.go b/json_formatter_test.go index ab52095..8235224 100644 --- a/json_formatter_test.go +++ b/json_formatter_test.go @@ -193,6 +193,7 @@ func TestFieldDoesNotClashWithCaller(t *testing.T) { func TestFieldClashWithCaller(t *testing.T) { SetReportCaller(true) formatter := &JSONFormatter{} + std.ReportCaller = true b, err := formatter.Format(WithField("method", "howdy pardner")) if err != nil { diff --git a/logrus_test.go b/logrus_test.go index ec31850..40cbd64 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -60,32 +60,30 @@ func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields ma // is added, and when it is unset it is not set or modified func TestReportCaller(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { - SetReportCaller(false) + log.ReportCaller = 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) + assert.Equal(t, "testNoCaller", fields["msg"]) + assert.Equal(t, "info", fields["level"]) + assert.Equal(t, nil, fields["method"]) }) LogAndAssertJSON(t, func(log *Logger) { - SetReportCaller(true) + log.ReportCaller = 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") + assert.Equal(t, "testWithCaller", fields["msg"]) + assert.Equal(t, "info", fields["level"]) + assert.Equal(t, "testing.tRunner", fields["method"]) }) - - SetReportCaller(false) // return to default value } func TestPrint(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.Print("test") }, func(fields Fields) { - assert.Equal(t, fields["msg"], "test") - assert.Equal(t, fields["level"], "info") + assert.Equal(t, "test", fields["msg"]) + assert.Equal(t, "info", fields["level"]) }) } @@ -93,8 +91,8 @@ func TestInfo(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.Info("test") }, func(fields Fields) { - assert.Equal(t, fields["msg"], "test") - assert.Equal(t, fields["level"], "info") + assert.Equal(t, "test", fields["msg"]) + assert.Equal(t, "info", fields["level"]) }) } @@ -102,8 +100,8 @@ func TestWarn(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.Warn("test") }, func(fields Fields) { - assert.Equal(t, fields["msg"], "test") - assert.Equal(t, fields["level"], "warning") + assert.Equal(t, "test", fields["msg"]) + assert.Equal(t, "warning", fields["level"]) }) } @@ -111,7 +109,7 @@ func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.Infoln("test", "test") }, func(fields Fields) { - assert.Equal(t, fields["msg"], "test test") + assert.Equal(t, "test test", fields["msg"]) }) } @@ -119,7 +117,7 @@ func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.Infoln("test", 10) }, func(fields Fields) { - assert.Equal(t, fields["msg"], "test 10") + assert.Equal(t, "test 10", fields["msg"]) }) } @@ -127,7 +125,7 @@ func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.Infoln(10, 10) }, func(fields Fields) { - assert.Equal(t, fields["msg"], "10 10") + assert.Equal(t, "10 10", fields["msg"]) }) } @@ -135,7 +133,7 @@ func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.Infoln(10, 10) }, func(fields Fields) { - assert.Equal(t, fields["msg"], "10 10") + assert.Equal(t, "10 10", fields["msg"]) }) } @@ -143,7 +141,7 @@ func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.Info("test", 10) }, func(fields Fields) { - assert.Equal(t, fields["msg"], "test10") + assert.Equal(t, "test10", fields["msg"]) }) } @@ -151,7 +149,7 @@ func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.Info("test", "test") }, func(fields Fields) { - assert.Equal(t, fields["msg"], "testtest") + assert.Equal(t, "testtest", fields["msg"]) }) } @@ -189,7 +187,7 @@ func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.WithField("msg", "hello").Info("test") }, func(fields Fields) { - assert.Equal(t, fields["msg"], "test") + assert.Equal(t, "test", fields["msg"]) }) } @@ -197,8 +195,8 @@ func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.WithField("msg", "hello").Info("test") }, func(fields Fields) { - assert.Equal(t, fields["msg"], "test") - assert.Equal(t, fields["fields.msg"], "hello") + assert.Equal(t, "test", fields["msg"]) + assert.Equal(t, "hello", fields["fields.msg"]) }) } @@ -206,7 +204,7 @@ func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.WithField("time", "hello").Info("test") }, func(fields Fields) { - assert.Equal(t, fields["fields.time"], "hello") + assert.Equal(t, "hello", fields["fields.time"]) }) } @@ -214,8 +212,8 @@ func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.WithField("level", 1).Info("test") }, func(fields Fields) { - assert.Equal(t, fields["level"], "info") - assert.Equal(t, fields["fields.level"], 1.0) // JSON has floats only + assert.Equal(t, "info", fields["level"]) + assert.Equal(t, 1.0, fields["fields.level"]) // JSON has floats only }) } @@ -259,8 +257,8 @@ func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) { err = json.Unmarshal(buffer.Bytes(), &fields) assert.NoError(t, err, "should have decoded second message") assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields") - assert.Equal(t, fields["msg"], "omg it is!") - assert.Equal(t, fields["context"], "eating raw fish") + assert.Equal(t, "omg it is!", fields["msg"]) + assert.Equal(t, "eating raw fish", fields["context"]) assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry") } @@ -269,10 +267,10 @@ func TestNestedLoggingReportsCorrectCaller(t *testing.T) { var buffer bytes.Buffer var fields Fields - SetReportCaller(true) logger := New() logger.Out = &buffer logger.Formatter = new(JSONFormatter) + logger.ReportCaller = true llog := logger.WithField("context", "eating raw fish") @@ -281,9 +279,9 @@ func TestNestedLoggingReportsCorrectCaller(t *testing.T) { 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") + assert.Equal(t, "looks delicious", fields["msg"]) + assert.Equal(t, "eating raw fish", fields["context"]) + assert.Equal(t, "testing.tRunner", fields["method"]) buffer.Reset() @@ -311,7 +309,7 @@ func TestNestedLoggingReportsCorrectCaller(t *testing.T) { assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry") assert.Equal(t, "testing.tRunner", fields["method"]) - SetReportCaller(false) // return to default value + logger.ReportCaller = false // return to default value } func TestConvertLevelToString(t *testing.T) { diff --git a/text_formatter.go b/text_formatter.go index b003451..91715f8 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -72,7 +72,12 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { b = &bytes.Buffer{} } - prefixFieldClashes(entry.Data) + reportCaller := false + if entry.Logger != nil { + reportCaller = entry.Logger.ReportCaller + } + + prefixFieldClashes(entry.Data, reportCaller) isColorTerminal := isTerminal && (runtime.GOOS != "windows") isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors @@ -88,7 +93,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat)) } f.appendKeyValue(b, "level", entry.Level.String()) - if ReportCaller() { + if reportCaller { f.appendKeyValue(b, "method", entry.Caller) } if entry.Message != "" { @@ -119,7 +124,13 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin levelText := strings.ToUpper(entry.Level.String())[0:4] caller := "" - if ReportCaller() { + + reportCaller := false + if entry.Logger != nil { + reportCaller = entry.Logger.ReportCaller + } + + if reportCaller { caller = fmt.Sprintf(" %s()", entry.Caller) } From 65f3af38f74b4b71d4d75b72cc7e1d207bdd5cf2 Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Wed, 30 Nov 2016 15:15:38 -0800 Subject: [PATCH 09/99] simplify hasCaller check --- entry.go | 6 ++++++ json_formatter.go | 9 ++------- json_formatter_test.go | 8 ++++---- text_formatter.go | 16 +++------------- 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/entry.go b/entry.go index 9e86687..d3b1aa9 100644 --- a/entry.go +++ b/entry.go @@ -126,6 +126,12 @@ func getCaller() (method string) { return "" } +func (entry Entry) HasCaller() (has bool) { + return entry.Logger != nil && + entry.Logger.ReportCaller && + entry.Caller != "" +} + // This function is not declared with a pointer value because otherwise // race conditions will occur when using multiple goroutines func (entry Entry) log(level Level, msg string) { diff --git a/json_formatter.go b/json_formatter.go index 46498da..efb9a9a 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -53,12 +53,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { } } - reportCaller := false - if entry.Logger != nil { - reportCaller = entry.Logger.ReportCaller - } - - prefixFieldClashes(data, reportCaller) + prefixFieldClashes(data, entry.HasCaller()) timestampFormat := f.TimestampFormat if timestampFormat == "" { @@ -68,7 +63,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat) data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String() - if reportCaller { + if entry.HasCaller() { data[f.FieldMap.resolve(FieldKeyCaller)] = entry.Caller } serialized, err := json.Marshal(data) diff --git a/json_formatter_test.go b/json_formatter_test.go index 8235224..05cd51e 100644 --- a/json_formatter_test.go +++ b/json_formatter_test.go @@ -193,9 +193,9 @@ func TestFieldDoesNotClashWithCaller(t *testing.T) { func TestFieldClashWithCaller(t *testing.T) { SetReportCaller(true) formatter := &JSONFormatter{} - std.ReportCaller = true - - b, err := formatter.Format(WithField("method", "howdy pardner")) + e := WithField("method", "howdy pardner") + e.Caller = "somefunc" + b, err := formatter.Format(e) if err != nil { t.Fatal("Unable to format entry: ", err) } @@ -211,7 +211,7 @@ func TestFieldClashWithCaller(t *testing.T) { entry["fields.method"]) } - if entry["method"] != "" { // since we didn't actually log, it's set to the empty string + if entry["method"] != "somefunc" { t.Fatalf("method not set as expected when ReportCaller=true (got '%s')", entry["method"]) } diff --git a/text_formatter.go b/text_formatter.go index 91715f8..b0fa789 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -72,12 +72,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { b = &bytes.Buffer{} } - reportCaller := false - if entry.Logger != nil { - reportCaller = entry.Logger.ReportCaller - } - - prefixFieldClashes(entry.Data, reportCaller) + prefixFieldClashes(entry.Data, entry.HasCaller()) isColorTerminal := isTerminal && (runtime.GOOS != "windows") isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors @@ -93,7 +88,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat)) } f.appendKeyValue(b, "level", entry.Level.String()) - if reportCaller { + if entry.HasCaller() { f.appendKeyValue(b, "method", entry.Caller) } if entry.Message != "" { @@ -125,12 +120,7 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin caller := "" - reportCaller := false - if entry.Logger != nil { - reportCaller = entry.Logger.ReportCaller - } - - if reportCaller { + if entry.HasCaller() { caller = fmt.Sprintf(" %s()", entry.Caller) } From 306956c385dcd90137fcf7c3cac8bbe938f6e768 Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Thu, 1 Dec 2016 11:12:44 -0800 Subject: [PATCH 10/99] tweak timing tests to handle slower VMs and older GoLang --- logrus_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logrus_test.go b/logrus_test.go index 6765acf..58d9981 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -328,7 +328,7 @@ func logLoop(iterations int, reportCaller bool) { // Assertions for upper bounds to reporting overhead func TestCallerReportingOverhead(t *testing.T) { - iterations := 10000 + iterations := 5000 before := time.Now() logLoop(iterations, false) during := time.Now() From 802fba19a4dcbcabe24af1b67e0db5d5de175f04 Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Fri, 2 Dec 2016 09:52:11 -0800 Subject: [PATCH 11/99] add note on caller-reporting overhead to README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 30402b6..083fcdc 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,12 @@ This adds the caller as 'method' like so: time="2015-03-26T01:27:38-04:00" level=fatal method=arcticcreatures.migrate msg="a penguin swims by" animal=penguin ``` +Note that this does add measurable overhead - the cost will depend on the of Go, but is between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your environment via benchmarks: +``` +go test -bench=.*CallerTracing +``` + + #### Example The simplest way to use Logrus is simply the package-level exported logger: From 2e7c40ede0b32845514d43c5d9f6e77d7eb8c548 Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Fri, 2 Dec 2016 09:57:30 -0800 Subject: [PATCH 12/99] README formatting tweak --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 083fcdc..32d145b 100644 --- a/README.md +++ b/README.md @@ -68,17 +68,14 @@ This adds the caller as 'method' like so: ```json {"animal":"penguin","level":"fatal","method":"arcticcreatures.migrate","msg":"a penguin swims by", "time":"2014-03-10 19:57:38.562543129 -0400 EDT"} - ``` ```text time="2015-03-26T01:27:38-04:00" level=fatal method=arcticcreatures.migrate msg="a penguin swims by" animal=penguin - -``` -Note that this does add measurable overhead - the cost will depend on the of Go, but is between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your environment via benchmarks: -``` -go test -bench=.*CallerTracing ``` +Note that this does add measurable overhead - the cost will depend on the of Go, but is +between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your +environment via benchmarks: `go test -bench=.*CallerTracing` #### Example From d8fd23467cdc8d7bdbb1ededd0cd859164aba652 Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Fri, 2 Dec 2016 10:09:30 -0800 Subject: [PATCH 13/99] add syntax hilighting to new example blocks --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 32d145b..bb71050 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x20822 ``` To ensure this behaviour even if a TTY is attached, set your formatter as follows: -``` +```go log.SetFormatter(&log.TextFormatter{ DisableColors: true, FullTimestamp: true, @@ -60,7 +60,7 @@ To ensure this behaviour even if a TTY is attached, set your formatter as follow ``` If you wish to add the calling method as a field, instruct the logger via: -``` +```go log.SetReportCaller(true) ``` This adds the caller as 'method' like so: @@ -75,7 +75,7 @@ time="2015-03-26T01:27:38-04:00" level=fatal method=arcticcreatures.migrate msg= ``` Note that this does add measurable overhead - the cost will depend on the of Go, but is between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your -environment via benchmarks: `go test -bench=.*CallerTracing` +environment via benchmarks: ```go test -bench=.*CallerTracing``` #### Example From 88dd8df1f89474ff523f6fd18fcbe35a83534512 Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Tue, 6 Dec 2016 12:53:21 -0800 Subject: [PATCH 14/99] responses to code review - field rename to be more properly generic - drop rewrite of main.main --- README.md | 5 ++++- entry.go | 6 +----- formatter.go | 12 +++++++----- json_formatter.go | 18 +++++++++--------- json_formatter_test.go | 20 ++++++++++---------- logrus_test.go | 36 ++++++++++++++++++------------------ text_formatter.go | 2 +- 7 files changed, 50 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index bb71050..329e769 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,10 @@ time="2015-03-26T01:27:38-04:00" level=fatal method=arcticcreatures.migrate msg= ``` Note that this does add measurable overhead - the cost will depend on the of Go, but is between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your -environment via benchmarks: ```go test -bench=.*CallerTracing``` +environment via benchmarks: +``` +go test -bench=.*CallerTracing +``` #### Example diff --git a/entry.go b/entry.go index a96f11d..50ca795 100644 --- a/entry.go +++ b/entry.go @@ -138,11 +138,7 @@ func getCaller() (method string) { // If the caller isn't part of this package, we're done if pkg != logrusPackage { - if fullFuncName == "main.main" { - return "main" - } else { - return fullFuncName - } + return fullFuncName } } diff --git a/formatter.go b/formatter.go index 773b359..ae319c3 100644 --- a/formatter.go +++ b/formatter.go @@ -1,6 +1,8 @@ package logrus -import "time" +import ( + "time" +) // DefaultTimestampFormat is YYYY-mm-DDTHH:MM:SS-TZ const DefaultTimestampFormat = time.RFC3339 @@ -19,7 +21,7 @@ type Formatter interface { Format(*Entry) ([]byte, error) } -// This is to not silently overwrite `time`, `msg`, `method` and `level` fields when +// This is to not silently overwrite `time`, `msg`, `func` and `level` fields when // dumping it. If this code wasn't there doing: // // logrus.WithField("level", 1).Info("hello") @@ -44,10 +46,10 @@ func prefixFieldClashes(data Fields, reportCaller bool) { data["fields.level"] = l } - // If reportCaller is not set, 'method' will not conflict. + // If reportCaller is not set, 'func' will not conflict. if reportCaller { - if l, ok := data["method"]; ok { - data["fields.method"] = l + if l, ok := data["func"]; ok { + data["fields.func"] = l } } } diff --git a/json_formatter.go b/json_formatter.go index 110b91d..8bdac89 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -9,10 +9,10 @@ type fieldKey string type FieldMap map[fieldKey]string const ( - FieldKeyMsg = "msg" - FieldKeyLevel = "level" - FieldKeyTime = "time" - FieldKeyCaller = "method" + FieldKeyMsg = "msg" + FieldKeyLevel = "level" + FieldKeyTime = "time" + FieldKeyFunc = "func" ) func (f FieldMap) resolve(key fieldKey) string { @@ -34,10 +34,10 @@ type JSONFormatter struct { // As an example: // formatter := &JSONFormatter{ // FieldMap: FieldMap{ - // FieldKeyTime: "@timestamp", - // FieldKeyLevel: "@level", - // FieldKeyMsg: "@message", - // FieldKeyCaller: "@caller", + // FieldKeyTime: "@timestamp", + // FieldKeyLevel: "@level", + // FieldKeyMsg: "@message", + // FieldKeyFunc: "@caller", // }, // } FieldMap FieldMap @@ -69,7 +69,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String() if entry.HasCaller() { - data[f.FieldMap.resolve(FieldKeyCaller)] = entry.Caller + data[f.FieldMap.resolve(FieldKeyFunc)] = entry.Caller } serialized, err := json.Marshal(data) if err != nil { diff --git a/json_formatter_test.go b/json_formatter_test.go index 4a7e5e7..84c7882 100644 --- a/json_formatter_test.go +++ b/json_formatter_test.go @@ -174,7 +174,7 @@ func TestFieldDoesNotClashWithCaller(t *testing.T) { SetReportCaller(false) formatter := &JSONFormatter{} - b, err := formatter.Format(WithField("method", "howdy pardner")) + b, err := formatter.Format(WithField("func", "howdy pardner")) if err != nil { t.Fatal("Unable to format entry: ", err) } @@ -185,15 +185,15 @@ func TestFieldDoesNotClashWithCaller(t *testing.T) { t.Fatal("Unable to unmarshal formatted entry: ", err) } - if entry["method"] != "howdy pardner" { - t.Fatal("method field replaced when ReportCaller=false") + if entry["func"] != "howdy pardner" { + t.Fatal("func field replaced when ReportCaller=false") } } func TestFieldClashWithCaller(t *testing.T) { SetReportCaller(true) formatter := &JSONFormatter{} - e := WithField("method", "howdy pardner") + e := WithField("func", "howdy pardner") e.Caller = "somefunc" b, err := formatter.Format(e) if err != nil { @@ -206,14 +206,14 @@ func TestFieldClashWithCaller(t *testing.T) { 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 ReportCaller=true (got '%s')", - entry["fields.method"]) + if entry["fields.func"] != "howdy pardner" { + t.Fatalf("fields.func not set to original func field when ReportCaller=true (got '%s')", + entry["fields.func"]) } - if entry["method"] != "somefunc" { - t.Fatalf("method not set as expected when ReportCaller=true (got '%s')", - entry["method"]) + if entry["func"] != "somefunc" { + t.Fatalf("func not set as expected when ReportCaller=true (got '%s')", + entry["func"]) } SetReportCaller(false) // return to default value diff --git a/logrus_test.go b/logrus_test.go index 58d9981..7b5c2c4 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -57,7 +57,7 @@ func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields ma assertions(fields) } -// TestReportCaller verifies that when ReportCaller is set, the 'method' field +// TestReportCaller verifies that when ReportCaller is set, the 'func' field // is added, and when it is unset it is not set or modified func TestReportCaller(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { @@ -66,7 +66,7 @@ func TestReportCaller(t *testing.T) { }, func(fields Fields) { assert.Equal(t, "testNoCaller", fields["msg"]) assert.Equal(t, "info", fields["level"]) - assert.Equal(t, nil, fields["method"]) + assert.Equal(t, nil, fields["func"]) }) LogAndAssertJSON(t, func(log *Logger) { @@ -75,7 +75,7 @@ func TestReportCaller(t *testing.T) { }, func(fields Fields) { assert.Equal(t, "testWithCaller", fields["msg"]) assert.Equal(t, "info", fields["level"]) - assert.Equal(t, "testing.tRunner", fields["method"]) + assert.Equal(t, "testing.tRunner", fields["func"]) }) } @@ -279,36 +279,36 @@ func TestNestedLoggingReportsCorrectCaller(t *testing.T) { 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, len(fields), 5, "should have msg/time/level/func/context fields") assert.Equal(t, "looks delicious", fields["msg"]) assert.Equal(t, "eating raw fish", fields["context"]) - assert.Equal(t, "testing.tRunner", fields["method"]) + assert.Equal(t, "testing.tRunner", fields["func"]) buffer.Reset() logger.WithFields(Fields{ - "foo": "a", + "Clyde": "Stubblefield", }).WithFields(Fields{ - "bar": "b", + "Jab'o": "Starks", }).WithFields(Fields{ - "baz": "c", + "uri": "https://www.youtube.com/watch?v=V5DTznu-9v0", }).WithFields(Fields{ - "method": "man", + "func": "y drummer", }).WithFields(Fields{ - "clan": "Wu Tang", - }).Print("omg it is!") + "James": "Brown", + }).Print("The hardest workin' man in show business") 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.Equal(t, "Stubblefield", fields["Clyde"]) + assert.Equal(t, "Starks", fields["Jab'o"]) + assert.Equal(t, "https://www.youtube.com/watch?v=V5DTznu-9v0", fields["uri"]) + assert.Equal(t, "y drummer", fields["fields.func"]) + assert.Equal(t, "Brown", fields["James"]) + assert.Equal(t, "The hardest workin' man in show business", fields["msg"]) assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry") - assert.Equal(t, "testing.tRunner", fields["method"]) + assert.Equal(t, "testing.tRunner", fields["func"]) logger.ReportCaller = false // return to default value } diff --git a/text_formatter.go b/text_formatter.go index b0fa789..b4abd50 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -89,7 +89,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { } f.appendKeyValue(b, "level", entry.Level.String()) if entry.HasCaller() { - f.appendKeyValue(b, "method", entry.Caller) + f.appendKeyValue(b, "func", entry.Caller) } if entry.Message != "" { f.appendKeyValue(b, "msg", entry.Message) From bc6d984670dab2f0fca29bd13aebb15969766fd2 Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Fri, 3 Feb 2017 15:08:53 -0800 Subject: [PATCH 15/99] add caller logic to DisableTimestamp case --- README.md | 2 +- text_formatter.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b6e2afd..720f85a 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ This adds the caller as 'method' like so: ```text time="2015-03-26T01:27:38-04:00" level=fatal method=arcticcreatures.migrate msg="a penguin swims by" animal=penguin ``` -Note that this does add measurable overhead - the cost will depend on the of Go, but is +Note that this does add measurable overhead - the cost will depend on the version of Go, but is between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your environment via benchmarks: ``` diff --git a/text_formatter.go b/text_formatter.go index 6f9c72d..7c63ac3 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -121,7 +121,7 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin } if f.DisableTimestamp { - fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s ", levelColor, levelText, entry.Message) + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message) } else if !f.FullTimestamp { fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message) } else { From 3cb9e18ef925540b009fdb6f1289ae64a3be12d7 Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Wed, 2 Aug 2017 17:21:18 -0700 Subject: [PATCH 16/99] test updates - add tests for callers accessed directly or via function pointer (results are unchanged) - undo unwanted capitalization/export in previous commit --- entry.go | 8 ++++---- formatter.go | 4 ++-- logrus_test.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/entry.go b/entry.go index 11f79c9..1d0a603 100644 --- a/entry.go +++ b/entry.go @@ -13,7 +13,7 @@ import ( var bufferPool *sync.Pool // qualified package name, cached at first use -var logrusPackage string +var LogrusPackage string // Positions in the call stack when tracing to report the calling method var minimumCallerDepth int @@ -126,8 +126,8 @@ func getCaller() (method string) { depth := runtime.Callers(minimumCallerDepth, pcs) // cache this package's fully-qualified name - if logrusPackage == "" { - logrusPackage = getPackageName(runtime.FuncForPC(pcs[0]).Name()) + if LogrusPackage == "" { + LogrusPackage = getPackageName(runtime.FuncForPC(pcs[0]).Name()) // now that we have the cache, we can skip a minimum count of known-logrus functions minimumCallerDepth = knownLogrusFrames @@ -138,7 +138,7 @@ func getCaller() (method string) { pkg := getPackageName(fullFuncName) // If the caller isn't part of this package, we're done - if pkg != logrusPackage { + if pkg != LogrusPackage { return fullFuncName } } diff --git a/formatter.go b/formatter.go index dc6631d..ba7b509 100644 --- a/formatter.go +++ b/formatter.go @@ -2,8 +2,8 @@ package logrus import "time" -// DefaultTimestampFormat is YYYY-mm-DDTHH:MM:SS-TZ -const DefaultTimestampFormat = time.RFC3339 +// defaultTimestampFormat is YYYY-mm-DDTHH:MM:SS-TZ +const defaultTimestampFormat = time.RFC3339 // The Formatter interface is used to implement a custom Formatter. It takes an // `Entry`. It exposes all the fields, including the default ones: diff --git a/logrus_test.go b/logrus_test.go index e724cc8..66c863d 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -59,7 +59,9 @@ func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields ma // TestReportCaller verifies that when ReportCaller is set, the 'func' field // is added, and when it is unset it is not set or modified -func TestReportCaller(t *testing.T) { +// Verify that functions within the Logrus package aren't considered when +// discovering the caller. +func TestReportCallerWhenConfigured(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.ReportCaller = false log.Print("testNoCaller") @@ -79,6 +81,51 @@ func TestReportCaller(t *testing.T) { }) } +func logSomething(t *testing.T, message string) Fields { + var buffer bytes.Buffer + var fields Fields + + logger := New() + logger.Out = &buffer + logger.Formatter = new(JSONFormatter) + logger.ReportCaller = true + + // override the filter to allow reporting of functions within the logrus package + LogrusPackage = "bogusForTesting" + + entry := logger.WithFields(Fields{ + "foo": "bar", + }) + + entry.Info(message) + + err := json.Unmarshal(buffer.Bytes(), &fields) + assert.Nil(t, err) + + // now clear the override so as not to mess with other usage + LogrusPackage = "" + return fields +} + +// TestReportCallerHelperDirect - verify reference when logging from a regular function +func TestReportCallerHelperDirect(t *testing.T) { + fields := logSomething(t, "direct") + + assert.Equal(t, "direct", fields["msg"]) + assert.Equal(t, "info", fields["level"]) + assert.Equal(t, "github.com/dclendenan/logrus.logSomething", fields["func"]) +} + +// TestReportCallerHelperDirect - verify reference when logging from a function called via pointer +func TestReportCallerHelperViaPointer(t *testing.T) { + fptr := logSomething + fields := fptr(t, "via pointer") + + assert.Equal(t, "via pointer", fields["msg"]) + assert.Equal(t, "info", fields["level"]) + assert.Equal(t, "github.com/dclendenan/logrus.logSomething", fields["func"]) +} + func TestPrint(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.Print("test") From b1db1b9c673ac4bc6e3d7fc64b7fb11f6ec187ca Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Wed, 2 Aug 2017 17:28:13 -0700 Subject: [PATCH 17/99] regex assertion rather than literal, for github path --- logrus_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logrus_test.go b/logrus_test.go index 66c863d..3fc68c9 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -113,7 +113,7 @@ func TestReportCallerHelperDirect(t *testing.T) { assert.Equal(t, "direct", fields["msg"]) assert.Equal(t, "info", fields["level"]) - assert.Equal(t, "github.com/dclendenan/logrus.logSomething", fields["func"]) + assert.Regexp(t, "github.com/.*/logrus.logSomething", fields["func"]) } // TestReportCallerHelperDirect - verify reference when logging from a function called via pointer @@ -123,7 +123,7 @@ func TestReportCallerHelperViaPointer(t *testing.T) { assert.Equal(t, "via pointer", fields["msg"]) assert.Equal(t, "info", fields["level"]) - assert.Equal(t, "github.com/dclendenan/logrus.logSomething", fields["func"]) + assert.Regexp(t, "github.com/.*/logrus.logSomething", fields["func"]) } func TestPrint(t *testing.T) { From 9ce1c9e3b52084b3cb71a1fd9a5791542e6674c1 Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Wed, 2 Aug 2017 17:33:13 -0700 Subject: [PATCH 18/99] add github path to log message in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3c22360..7a78d98 100644 --- a/README.md +++ b/README.md @@ -73,12 +73,12 @@ log.SetReportCaller(true) This adds the caller as 'method' like so: ```json -{"animal":"penguin","level":"fatal","method":"arcticcreatures.migrate","msg":"a penguin swims by", +{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by", "time":"2014-03-10 19:57:38.562543129 -0400 EDT"} ``` ```text -time="2015-03-26T01:27:38-04:00" level=fatal method=arcticcreatures.migrate msg="a penguin swims by" animal=penguin +time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin ``` Note that this does add measurable overhead - the cost will depend on the version of Go, but is between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your From e3d17767d1dbd87b98f0a835a618bc8e26daaac8 Mon Sep 17 00:00:00 2001 From: Dave Clendenan Date: Wed, 2 Aug 2017 17:35:14 -0700 Subject: [PATCH 19/99] MD formatting --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7a78d98..df7c0da 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,8 @@ To ensure this behaviour even if a TTY is attached, set your formatter as follow }) ``` +#### Logging Method Name + If you wish to add the calling method as a field, instruct the logger via: ```go log.SetReportCaller(true) From c44d5246287d92a71847107c7907dd52ce55142a Mon Sep 17 00:00:00 2001 From: Aditya Mukerjee Date: Mon, 9 Oct 2017 11:18:43 -0400 Subject: [PATCH 20/99] Fix typo in docstring --- logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logger.go b/logger.go index fdaf8a6..03a5de2 100644 --- a/logger.go +++ b/logger.go @@ -10,7 +10,7 @@ import ( type Logger struct { // The logs are `io.Copy`'d to this in a mutex. It's common to set this to a // file, or leave it default which is `os.Stderr`. You can also set this to - // something more adventorous, such as logging to Kafka. + // something more adventurous, such as logging to Kafka. Out io.Writer // Hooks for the logger instance. These allow firing events based on logging // levels and log entries. For example, to send errors to an error tracking From 639325f81ab8f8610fc69be7b810769c1d99f500 Mon Sep 17 00:00:00 2001 From: Marianne Feng Date: Mon, 6 Nov 2017 16:19:47 -0800 Subject: [PATCH 21/99] added pretty print option for json logs --- README.md | 2 ++ json_formatter.go | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f656c3..f13335e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Logrus :walrus: [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus) +**this is an exact clone of the Logrus repo [here](https://github.com/sirupsen/logrus), the only change that has been made is json formatter takes in a setting to pretty print json logs for easier debugging** + Logrus is a structured logger for Go (golang), completely API compatible with the standard library logger. diff --git a/json_formatter.go b/json_formatter.go index fb01c1b..e52ab17 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -43,6 +43,9 @@ type JSONFormatter struct { // }, // } FieldMap FieldMap + + // PrettyPrint will indent all json logs + PrettyPrint bool } // Format renders a single log entry @@ -71,7 +74,15 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String() - serialized, err := json.Marshal(data) + var serialized []byte + var err error + + if f.PrettyPrint { + serialized, err = json.MarshalIndent(data, "", "\t") + } else { + serialized, err = json.Marshal(data) + } + if err != nil { return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) } From 10d6a5b4272fa06109decc537a040d6a89f9a42f Mon Sep 17 00:00:00 2001 From: Marianne Feng Date: Mon, 6 Nov 2017 16:28:37 -0800 Subject: [PATCH 22/99] removed useless line from readme --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index f13335e..5f656c3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Logrus :walrus: [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus) -**this is an exact clone of the Logrus repo [here](https://github.com/sirupsen/logrus), the only change that has been made is json formatter takes in a setting to pretty print json logs for easier debugging** - Logrus is a structured logger for Go (golang), completely API compatible with the standard library logger. From 0c03a05a0e896c23dcd631acafd2f35add6a3fec Mon Sep 17 00:00:00 2001 From: conor Date: Thu, 21 Dec 2017 14:04:49 -0500 Subject: [PATCH 23/99] mirror and wrap Logger instance methods in exported.go --- .gitignore | 1 + exported.go | 16 +++------------- logger.go | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 66be63a..c722487 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ logrus +.idea \ No newline at end of file diff --git a/exported.go b/exported.go index 013183e..141e951 100644 --- a/exported.go +++ b/exported.go @@ -15,36 +15,26 @@ func StandardLogger() *Logger { // SetOutput sets the standard logger output. func SetOutput(out io.Writer) { - std.mu.Lock() - defer std.mu.Unlock() - std.Out = out + std.SetOutput(out) } // SetFormatter sets the standard logger formatter. func SetFormatter(formatter Formatter) { - std.mu.Lock() - defer std.mu.Unlock() - std.Formatter = formatter + std.SetFormatter(formatter) } // SetLevel sets the standard logger level. func SetLevel(level Level) { - std.mu.Lock() - defer std.mu.Unlock() std.SetLevel(level) } // GetLevel returns the standard logger level. func GetLevel() Level { - std.mu.Lock() - defer std.mu.Unlock() - return std.level() + return std.GetLevel() } // AddHook adds a hook to the standard logger hooks. func AddHook(hook Hook) { - std.mu.Lock() - defer std.mu.Unlock() std.Hooks.Add(hook) } diff --git a/logger.go b/logger.go index fdaf8a6..91617fc 100644 --- a/logger.go +++ b/logger.go @@ -312,12 +312,33 @@ func (logger *Logger) level() Level { return Level(atomic.LoadUint32((*uint32)(&logger.Level))) } +// SetLevel sets the logger level. func (logger *Logger) SetLevel(level Level) { atomic.StoreUint32((*uint32)(&logger.Level), uint32(level)) } +// GetLevel returns the logger level. +func (logger *Logger) GetLevel() Level { + return logger.level() +} + +// AddHook adds a hook to the logger hooks. func (logger *Logger) AddHook(hook Hook) { logger.mu.Lock() defer logger.mu.Unlock() logger.Hooks.Add(hook) } + +// SetFormatter sets the logger formatter. +func (logger *Logger) SetFormatter(formatter Formatter) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.Formatter = formatter +} + +// SetOutput sets the logger output. +func (logger *Logger) SetOutput(output io.Writer) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.Out = output +} From 20cc8e2bc33863de7191b9bfb55d27804e05e90e Mon Sep 17 00:00:00 2001 From: conor Date: Thu, 21 Dec 2017 14:10:48 -0500 Subject: [PATCH 24/99] remove .gitignore changes --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c722487..bb7e111 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -logrus -.idea \ No newline at end of file +logrus \ No newline at end of file From eb156905d7c48f0f2191a3f805e59849c9c018ed Mon Sep 17 00:00:00 2001 From: conor Date: Thu, 21 Dec 2017 14:16:49 -0500 Subject: [PATCH 25/99] remove .gitignore changes and update AddHook --- .gitignore | 2 +- exported.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index bb7e111..66be63a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -logrus \ No newline at end of file +logrus diff --git a/exported.go b/exported.go index 141e951..e5e484f 100644 --- a/exported.go +++ b/exported.go @@ -35,7 +35,7 @@ func GetLevel() Level { // AddHook adds a hook to the standard logger hooks. func AddHook(hook Hook) { - std.Hooks.Add(hook) + std.AddHook(hook) } // WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key. From 92aece568bf7ac7d2008928efa09da14b63395fe Mon Sep 17 00:00:00 2001 From: Dennis de Reus Date: Fri, 29 Dec 2017 20:26:35 +0100 Subject: [PATCH 26/99] TextFormatter behaviour aligned with stdlib log (fixes #167) stdlib `log` adds a newline at the end of a message if none is present, otherwise does not. Before this change logrus would always add a newline, resulting in inconsistent behaviour if stdlib log was replaced with logrus, and a user would e.g. use 'log.printf("test\n")' --- text_formatter.go | 4 ++++ text_formatter_test.go | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/text_formatter.go b/text_formatter.go index 61b21ca..9f7dc35 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -126,6 +126,10 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin levelText := strings.ToUpper(entry.Level.String())[0:4] + // Remove a single newline if it already exists in the message to keep + // the behavior of logrus text_formatter the same as the stdlib log package + entry.Message = strings.TrimSuffix(entry.Message, "\n") + if f.DisableTimestamp { fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s ", levelColor, levelText, entry.Message) } else if !f.FullTimestamp { diff --git a/text_formatter_test.go b/text_formatter_test.go index d93b931..aea8727 100644 --- a/text_formatter_test.go +++ b/text_formatter_test.go @@ -137,5 +137,28 @@ func TestDisableTimestampWithColoredOutput(t *testing.T) { } } +func TestNewlineBehavior(t *testing.T) { + tf := &TextFormatter{ForceColors: true} + + // Ensure a single new line is removed as per stdlib log + e := NewEntry(StandardLogger()) + e.Message = "test message\n" + b, _ := tf.Format(e) + if bytes.Contains(b, []byte("test message\n")) { + t.Error("first newline at end of Entry.Message resulted in unexpected 2 newlines in output. Expected newline to be removed.") + } + + // Ensure a double new line is reduced to a single new line + e = NewEntry(StandardLogger()) + e.Message = "test message\n\n" + b, _ = tf.Format(e) + if bytes.Contains(b, []byte("test message\n\n")) { + t.Error("Double newline at end of Entry.Message resulted in unexpected 2 newlines in output. Expected single newline") + } + if !bytes.Contains(b, []byte("test message\n")) { + t.Error("Double newline at end of Entry.Message did not result in a single newline after formatting") + } +} + // TODO add tests for sorting etc., this requires a parser for the text // formatter output. From efbfdb5f09fe8f6efee1efd21ae690156304cf36 Mon Sep 17 00:00:00 2001 From: Michael Haines Date: Mon, 5 Feb 2018 12:42:00 -0700 Subject: [PATCH 27/99] Add failing test for using a FieldLogger with hooks inside goroutines --- logrus_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/logrus_test.go b/logrus_test.go index 78cbc28..1585709 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -343,6 +343,24 @@ func TestLoggingRace(t *testing.T) { wg.Wait() } +func TestLoggingRaceWithHooksOnFieldLogger(t *testing.T) { + logger := New() + hook := new(ModifyHook) + logger.AddHook(hook) + fieldLogger := logger.WithField("context", "clue") + + var wg sync.WaitGroup + wg.Add(100) + + for i := 0; i < 100; i++ { + go func() { + fieldLogger.Info("info") + wg.Done() + }() + } + wg.Wait() +} + // Compile test func TestLogrusInterface(t *testing.T) { var buffer bytes.Buffer From eeb653535cb49f0aee7aefce8583b2593d4466fd Mon Sep 17 00:00:00 2001 From: Michael Haines Date: Mon, 5 Feb 2018 12:44:11 -0700 Subject: [PATCH 28/99] Lock mutex before formatting to avoid race --- entry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entry.go b/entry.go index df6f92d..24ded45 100644 --- a/entry.go +++ b/entry.go @@ -123,9 +123,9 @@ func (entry *Entry) fireHooks() { } func (entry *Entry) write() { - serialized, err := entry.Logger.Formatter.Format(entry) entry.Logger.mu.Lock() defer entry.Logger.mu.Unlock() + serialized, err := entry.Logger.Formatter.Format(entry) if err != nil { fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err) } else { From 828a649ef2d3660936a7182dadbf5573de38433d Mon Sep 17 00:00:00 2001 From: Michael Haines Date: Mon, 5 Feb 2018 12:52:11 -0700 Subject: [PATCH 29/99] rename fieldLogger to entry --- logrus_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/logrus_test.go b/logrus_test.go index 1585709..cd17602 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -343,18 +343,18 @@ func TestLoggingRace(t *testing.T) { wg.Wait() } -func TestLoggingRaceWithHooksOnFieldLogger(t *testing.T) { +func TestLoggingRaceWithHooksOnEntry(t *testing.T) { logger := New() hook := new(ModifyHook) logger.AddHook(hook) - fieldLogger := logger.WithField("context", "clue") + entry := logger.WithField("context", "clue") var wg sync.WaitGroup wg.Add(100) for i := 0; i < 100; i++ { go func() { - fieldLogger.Info("info") + entry.Info("info") wg.Done() }() } From 723dd3cd1f7a69421a05118059a9ed644bdd3d12 Mon Sep 17 00:00:00 2001 From: Marianne Feng Date: Tue, 20 Mar 2018 18:20:51 -0700 Subject: [PATCH 30/99] changed prettyprinting to use spaces as opposed to /t --- json_formatter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/json_formatter.go b/json_formatter.go index e52ab17..dab5a10 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -78,7 +78,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { var err error if f.PrettyPrint { - serialized, err = json.MarshalIndent(data, "", "\t") + serialized, err = json.MarshalIndent(data, "", " ") } else { serialized, err = json.Marshal(data) } From bb487e068c121ad7129d3d4b6628507038d85d7a Mon Sep 17 00:00:00 2001 From: Felix Kollmann Date: Tue, 3 Apr 2018 00:25:30 +0200 Subject: [PATCH 31/99] Added support for text coloring on Windows 10 --- text_formatter.go | 2 ++ text_formatter_linux.go | 6 ++++++ text_formatter_windows.go | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 text_formatter_linux.go create mode 100644 text_formatter_windows.go diff --git a/text_formatter.go b/text_formatter.go index ae91edd..afd9ffb 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -67,6 +67,8 @@ type TextFormatter struct { func (f *TextFormatter) init(entry *Entry) { if entry.Logger != nil { f.isTerminal = checkIfTerminal(entry.Logger.Out) + + f.initTerminal(entry) } } diff --git a/text_formatter_linux.go b/text_formatter_linux.go new file mode 100644 index 0000000..e5fa6a9 --- /dev/null +++ b/text_formatter_linux.go @@ -0,0 +1,6 @@ +// +build !appengine,!gopherjs + +package logrus + +func (f *TextFormatter) initTerminal(entry *Entry) { +} diff --git a/text_formatter_windows.go b/text_formatter_windows.go new file mode 100644 index 0000000..552c5f3 --- /dev/null +++ b/text_formatter_windows.go @@ -0,0 +1,19 @@ +// +build !appengine,!gopherjs + +package logrus + +import ( + "os" + "syscall" + + sequences "github.com/konsorten/go-windows-terminal-sequences" +) + +func (f *TextFormatter) initTerminal(entry *Entry) { + switch v := entry.Logger.Out.(type) { + case *os.File: + handle := syscall.Handle(v.Fd()) + + sequences.EnableVirtualTerminalProcessing(handle, true) + } +} From f142d8145bc6c072eb15937ab37cb6ede92d579e Mon Sep 17 00:00:00 2001 From: Felix Kollmann Date: Tue, 3 Apr 2018 01:15:45 +0200 Subject: [PATCH 32/99] Improved building of non-windows code --- text_formatter_linux.go => text_formatter_nonwindows.go | 2 +- text_formatter_windows.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename text_formatter_linux.go => text_formatter_nonwindows.go (64%) diff --git a/text_formatter_linux.go b/text_formatter_nonwindows.go similarity index 64% rename from text_formatter_linux.go rename to text_formatter_nonwindows.go index e5fa6a9..f732b8b 100644 --- a/text_formatter_linux.go +++ b/text_formatter_nonwindows.go @@ -1,4 +1,4 @@ -// +build !appengine,!gopherjs +// +build !appengine,!gopherjs,!windows package logrus diff --git a/text_formatter_windows.go b/text_formatter_windows.go index 552c5f3..c9045a0 100644 --- a/text_formatter_windows.go +++ b/text_formatter_windows.go @@ -1,4 +1,4 @@ -// +build !appengine,!gopherjs +// +build !appengine,!gopherjs,windows package logrus From 7d2a5214bf851c9c13289e475a486b1761375216 Mon Sep 17 00:00:00 2001 From: Felix Kollmann Date: Tue, 3 Apr 2018 01:23:44 +0200 Subject: [PATCH 33/99] Extended conditions to include non-native builds --- text_formatter_nonwindows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text_formatter_nonwindows.go b/text_formatter_nonwindows.go index f732b8b..0c0802a 100644 --- a/text_formatter_nonwindows.go +++ b/text_formatter_nonwindows.go @@ -1,4 +1,4 @@ -// +build !appengine,!gopherjs,!windows +// +build appengine gopherjs !windows package logrus From c9a46a1e7c34ec07a1ffd66147e654d85ac94e36 Mon Sep 17 00:00:00 2001 From: Felix Kollmann Date: Tue, 3 Apr 2018 04:40:58 +0200 Subject: [PATCH 34/99] Added terminal check on Windows --- terminal_check_notappengine.go | 2 +- terminal_check_windows.go | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 terminal_check_windows.go diff --git a/terminal_check_notappengine.go b/terminal_check_notappengine.go index 067047a..e5a5186 100644 --- a/terminal_check_notappengine.go +++ b/terminal_check_notappengine.go @@ -1,4 +1,4 @@ -// +build !appengine,!gopherjs +// +build !appengine,!gopherjs,!windows package logrus diff --git a/terminal_check_windows.go b/terminal_check_windows.go new file mode 100644 index 0000000..17ebe80 --- /dev/null +++ b/terminal_check_windows.go @@ -0,0 +1,20 @@ +// +build !appengine,!gopherjs,windows + +package logrus + +import ( + "io" + "os" + "syscall" +) + +func checkIfTerminal(w io.Writer) bool { + switch v := w.(type) { + case *os.File: + var mode uint32 + err := syscall.GetConsoleMode(syscall.Handle(v.Fd()), &mode) + return err == nil + default: + return false + } +} From cf5eba7dfdefae2d33eb00af1e805bc31f6c4151 Mon Sep 17 00:00:00 2001 From: Felix Kollmann Date: Tue, 3 Apr 2018 04:47:29 +0200 Subject: [PATCH 35/99] Simplified file structure --- terminal_bsd.go | 3 +++ terminal_linux.go | 3 +++ text_formatter_windows.go => terminal_windows.go | 0 text_formatter_nonwindows.go | 6 ------ 4 files changed, 6 insertions(+), 6 deletions(-) rename text_formatter_windows.go => terminal_windows.go (100%) delete mode 100644 text_formatter_nonwindows.go diff --git a/terminal_bsd.go b/terminal_bsd.go index 4880d13..3b20604 100644 --- a/terminal_bsd.go +++ b/terminal_bsd.go @@ -8,3 +8,6 @@ import "golang.org/x/sys/unix" const ioctlReadTermios = unix.TIOCGETA type Termios unix.Termios + +func (f *TextFormatter) initTerminal(entry *Entry) { +} diff --git a/terminal_linux.go b/terminal_linux.go index f29a009..78c4298 100644 --- a/terminal_linux.go +++ b/terminal_linux.go @@ -12,3 +12,6 @@ import "golang.org/x/sys/unix" const ioctlReadTermios = unix.TCGETS type Termios unix.Termios + +func (f *TextFormatter) initTerminal(entry *Entry) { +} diff --git a/text_formatter_windows.go b/terminal_windows.go similarity index 100% rename from text_formatter_windows.go rename to terminal_windows.go diff --git a/text_formatter_nonwindows.go b/text_formatter_nonwindows.go deleted file mode 100644 index 0c0802a..0000000 --- a/text_formatter_nonwindows.go +++ /dev/null @@ -1,6 +0,0 @@ -// +build appengine gopherjs !windows - -package logrus - -func (f *TextFormatter) initTerminal(entry *Entry) { -} From 9bc59a596929f70e795b6a59bdf95b8c90cb9a7f Mon Sep 17 00:00:00 2001 From: Felix Kollmann Date: Tue, 3 Apr 2018 04:50:50 +0200 Subject: [PATCH 36/99] Fixed initTerminal() was run for non-terminals --- text_formatter.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/text_formatter.go b/text_formatter.go index afd9ffb..c62c412 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -51,7 +51,6 @@ type TextFormatter struct { // be desired. DisableSorting bool - // Disables the truncation of the level text to 4 characters. DisableLevelTruncation bool @@ -68,7 +67,9 @@ func (f *TextFormatter) init(entry *Entry) { if entry.Logger != nil { f.isTerminal = checkIfTerminal(entry.Logger.Out) - f.initTerminal(entry) + if f.isTerminal { + f.initTerminal(entry) + } } } From 2f58bc83cb1aaa88d642d561af062b7a04d15129 Mon Sep 17 00:00:00 2001 From: Felix Kollmann Date: Tue, 3 Apr 2018 04:55:52 +0200 Subject: [PATCH 37/99] Unified terminal initialization code handling --- terminal_bsd.go | 8 ++++++-- terminal_linux.go | 8 ++++++-- terminal_windows.go | 9 ++++----- text_formatter.go | 2 +- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/terminal_bsd.go b/terminal_bsd.go index 3b20604..c498bcd 100644 --- a/terminal_bsd.go +++ b/terminal_bsd.go @@ -3,11 +3,15 @@ package logrus -import "golang.org/x/sys/unix" +import ( + "io" + + "golang.org/x/sys/unix" +) const ioctlReadTermios = unix.TIOCGETA type Termios unix.Termios -func (f *TextFormatter) initTerminal(entry *Entry) { +func initTerminal(w io.Writer) { } diff --git a/terminal_linux.go b/terminal_linux.go index 78c4298..6eb013e 100644 --- a/terminal_linux.go +++ b/terminal_linux.go @@ -7,11 +7,15 @@ package logrus -import "golang.org/x/sys/unix" +import ( + "io" + + "golang.org/x/sys/unix" +) const ioctlReadTermios = unix.TCGETS type Termios unix.Termios -func (f *TextFormatter) initTerminal(entry *Entry) { +func initTerminal(w io.Writer) { } diff --git a/terminal_windows.go b/terminal_windows.go index c9045a0..2494950 100644 --- a/terminal_windows.go +++ b/terminal_windows.go @@ -3,17 +3,16 @@ package logrus import ( + "io" "os" "syscall" sequences "github.com/konsorten/go-windows-terminal-sequences" ) -func (f *TextFormatter) initTerminal(entry *Entry) { - switch v := entry.Logger.Out.(type) { +func initTerminal(w io.Writer) { + switch v := w.(type) { case *os.File: - handle := syscall.Handle(v.Fd()) - - sequences.EnableVirtualTerminalProcessing(handle, true) + sequences.EnableVirtualTerminalProcessing(syscall.Handle(v.Fd()), true) } } diff --git a/text_formatter.go b/text_formatter.go index c62c412..b4e1f0a 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -68,7 +68,7 @@ func (f *TextFormatter) init(entry *Entry) { f.isTerminal = checkIfTerminal(entry.Logger.Out) if f.isTerminal { - f.initTerminal(entry) + initTerminal(entry.Logger.Out) } } } From aa6766adfe97f5f5d05f9bd694986d171db9b4ff Mon Sep 17 00:00:00 2001 From: taylorchu Date: Tue, 15 May 2018 10:07:01 -0700 Subject: [PATCH 38/99] PERF: use buffer pool in json formatter benchmark old ns/op new ns/op delta BenchmarkLogrus-8 4163 4369 +4.95% benchmark old allocs new allocs delta BenchmarkLogrus-8 36 31 -13.89% benchmark old bytes new bytes delta BenchmarkLogrus-8 3027 2163 -28.54% --- json_formatter.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/json_formatter.go b/json_formatter.go index 7064947..82a1da8 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -1,6 +1,7 @@ package logrus import ( + "bytes" "encoding/json" "fmt" ) @@ -71,9 +72,15 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String() - serialized, err := json.Marshal(data) + var b *bytes.Buffer + if entry.Buffer != nil { + b = entry.Buffer + } else { + b = &bytes.Buffer{} + } + err := json.NewEncoder(b).Encode(data) if err != nil { return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) } - return append(serialized, '\n'), nil + return b.Bytes(), nil } From 070c81def33f6362a8267b6a4e56fb7bf23fc6b5 Mon Sep 17 00:00:00 2001 From: Moriyoshi Koizumi Date: Wed, 30 May 2018 09:50:59 +0000 Subject: [PATCH 39/99] Revert the change introduced in #707 and do the proper fix. Fixes #729 --- entry.go | 6 ++---- hooks/test/test.go | 15 ++++++--------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/entry.go b/entry.go index d075d72..1be48ab 100644 --- a/entry.go +++ b/entry.go @@ -113,12 +113,10 @@ func (entry Entry) log(level Level, msg string) { } } -// This function is not declared with a pointer value because otherwise -// race conditions will occur when using multiple goroutines -func (entry Entry) fireHooks() { +func (entry *Entry) fireHooks() { entry.Logger.mu.Lock() defer entry.Logger.mu.Unlock() - err := entry.Logger.Hooks.Fire(entry.Level, &entry) + err := entry.Logger.Hooks.Fire(entry.Level, entry) if err != nil { fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) } diff --git a/hooks/test/test.go b/hooks/test/test.go index 62c4845..234a17d 100644 --- a/hooks/test/test.go +++ b/hooks/test/test.go @@ -15,7 +15,7 @@ type Hook struct { // Entries is an array of all entries that have been received by this hook. // For safe access, use the AllEntries() method, rather than reading this // value directly. - Entries []*logrus.Entry + Entries []logrus.Entry mu sync.RWMutex } @@ -52,7 +52,7 @@ func NewNullLogger() (*logrus.Logger, *Hook) { func (t *Hook) Fire(e *logrus.Entry) error { t.mu.Lock() defer t.mu.Unlock() - t.Entries = append(t.Entries, e) + t.Entries = append(t.Entries, *e) return nil } @@ -68,9 +68,7 @@ func (t *Hook) LastEntry() *logrus.Entry { if i < 0 { return nil } - // Make a copy, for safety - e := *t.Entries[i] - return &e + return &t.Entries[i] } // AllEntries returns all entries that were logged. @@ -79,10 +77,9 @@ func (t *Hook) AllEntries() []*logrus.Entry { defer t.mu.RUnlock() // Make a copy so the returned value won't race with future log requests entries := make([]*logrus.Entry, len(t.Entries)) - for i, entry := range t.Entries { + for i := 0; i < len(t.Entries); i++ { // Make a copy, for safety - e := *entry - entries[i] = &e + entries[i] = &t.Entries[i] } return entries } @@ -91,5 +88,5 @@ func (t *Hook) AllEntries() []*logrus.Entry { func (t *Hook) Reset() { t.mu.Lock() defer t.mu.Unlock() - t.Entries = make([]*logrus.Entry, 0) + t.Entries = make([]logrus.Entry, 0) } From 4225d694ba95bda1ce8bcbd67c5701beef4491d0 Mon Sep 17 00:00:00 2001 From: Logan HAUSPIE Date: Wed, 30 May 2018 01:47:39 +0200 Subject: [PATCH 40/99] feat: new methods to check enabled log level Adding 6 methods on 'exported', 'logger' and 'entry': - IsDebugEnabled() bool - IsInfoEnabled() bool - IsWarnEnabled() bool - IsErrorEnabled() bool - IsFatalEnabled() bool - IsPanicEnabled() bool Replace duplicated 'if logger.level() >= XxxxLevel' by a call to the new methods in 'logger' and 'entry' Closes #761 --- entry.go | 60 +++++++++++++++++++++++++++++++-------------- exported.go | 24 ++++++++++++++++++ logger.go | 66 ++++++++++++++++++++++++++++++++++---------------- logrus.go | 7 ++++++ logrus_test.go | 51 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 169 insertions(+), 39 deletions(-) diff --git a/entry.go b/entry.go index d075d72..29bcae6 100644 --- a/entry.go +++ b/entry.go @@ -139,7 +139,7 @@ func (entry *Entry) write() { } func (entry *Entry) Debug(args ...interface{}) { - if entry.Logger.level() >= DebugLevel { + if entry.IsDebugEnabled() { entry.log(DebugLevel, fmt.Sprint(args...)) } } @@ -149,13 +149,13 @@ func (entry *Entry) Print(args ...interface{}) { } func (entry *Entry) Info(args ...interface{}) { - if entry.Logger.level() >= InfoLevel { + if entry.IsInfoEnabled() { entry.log(InfoLevel, fmt.Sprint(args...)) } } func (entry *Entry) Warn(args ...interface{}) { - if entry.Logger.level() >= WarnLevel { + if entry.IsWarnEnabled() { entry.log(WarnLevel, fmt.Sprint(args...)) } } @@ -165,20 +165,20 @@ func (entry *Entry) Warning(args ...interface{}) { } func (entry *Entry) Error(args ...interface{}) { - if entry.Logger.level() >= ErrorLevel { + if entry.IsErrorEnabled() { entry.log(ErrorLevel, fmt.Sprint(args...)) } } func (entry *Entry) Fatal(args ...interface{}) { - if entry.Logger.level() >= FatalLevel { + if entry.IsFatalEnabled() { entry.log(FatalLevel, fmt.Sprint(args...)) } Exit(1) } func (entry *Entry) Panic(args ...interface{}) { - if entry.Logger.level() >= PanicLevel { + if entry.IsPanicEnabled() { entry.log(PanicLevel, fmt.Sprint(args...)) } panic(fmt.Sprint(args...)) @@ -187,13 +187,13 @@ func (entry *Entry) Panic(args ...interface{}) { // Entry Printf family functions func (entry *Entry) Debugf(format string, args ...interface{}) { - if entry.Logger.level() >= DebugLevel { + if entry.IsDebugEnabled() { entry.Debug(fmt.Sprintf(format, args...)) } } func (entry *Entry) Infof(format string, args ...interface{}) { - if entry.Logger.level() >= InfoLevel { + if entry.IsInfoEnabled() { entry.Info(fmt.Sprintf(format, args...)) } } @@ -203,7 +203,7 @@ func (entry *Entry) Printf(format string, args ...interface{}) { } func (entry *Entry) Warnf(format string, args ...interface{}) { - if entry.Logger.level() >= WarnLevel { + if entry.IsWarnEnabled() { entry.Warn(fmt.Sprintf(format, args...)) } } @@ -213,20 +213,20 @@ func (entry *Entry) Warningf(format string, args ...interface{}) { } func (entry *Entry) Errorf(format string, args ...interface{}) { - if entry.Logger.level() >= ErrorLevel { + if entry.IsErrorEnabled() { entry.Error(fmt.Sprintf(format, args...)) } } func (entry *Entry) Fatalf(format string, args ...interface{}) { - if entry.Logger.level() >= FatalLevel { + if entry.IsFatalEnabled() { entry.Fatal(fmt.Sprintf(format, args...)) } Exit(1) } func (entry *Entry) Panicf(format string, args ...interface{}) { - if entry.Logger.level() >= PanicLevel { + if entry.IsPanicEnabled() { entry.Panic(fmt.Sprintf(format, args...)) } } @@ -234,13 +234,13 @@ func (entry *Entry) Panicf(format string, args ...interface{}) { // Entry Println family functions func (entry *Entry) Debugln(args ...interface{}) { - if entry.Logger.level() >= DebugLevel { + if entry.IsDebugEnabled() { entry.Debug(entry.sprintlnn(args...)) } } func (entry *Entry) Infoln(args ...interface{}) { - if entry.Logger.level() >= InfoLevel { + if entry.IsInfoEnabled() { entry.Info(entry.sprintlnn(args...)) } } @@ -250,7 +250,7 @@ func (entry *Entry) Println(args ...interface{}) { } func (entry *Entry) Warnln(args ...interface{}) { - if entry.Logger.level() >= WarnLevel { + if entry.IsWarnEnabled() { entry.Warn(entry.sprintlnn(args...)) } } @@ -260,24 +260,48 @@ func (entry *Entry) Warningln(args ...interface{}) { } func (entry *Entry) Errorln(args ...interface{}) { - if entry.Logger.level() >= ErrorLevel { + if entry.IsErrorEnabled() { entry.Error(entry.sprintlnn(args...)) } } func (entry *Entry) Fatalln(args ...interface{}) { - if entry.Logger.level() >= FatalLevel { + if entry.IsFatalEnabled() { entry.Fatal(entry.sprintlnn(args...)) } Exit(1) } func (entry *Entry) Panicln(args ...interface{}) { - if entry.Logger.level() >= PanicLevel { + if entry.IsPanicEnabled() { entry.Panic(entry.sprintlnn(args...)) } } +func (entry *Entry) IsDebugEnabled() bool { + return entry.Logger.IsDebugEnabled() +} + +func (entry *Entry) IsInfoEnabled() bool { + return entry.Logger.IsInfoEnabled() +} + +func (entry *Entry) IsWarnEnabled() bool { + return entry.Logger.IsWarnEnabled() +} + +func (entry *Entry) IsErrorEnabled() bool { + return entry.Logger.IsErrorEnabled() +} + +func (entry *Entry) IsFatalEnabled() bool { + return entry.Logger.IsFatalEnabled() +} + +func (entry *Entry) IsPanicEnabled() bool { + return entry.Logger.IsPanicEnabled() +} + // Sprintlnn => Sprint no newline. This is to get the behavior of how // fmt.Sprintln where spaces are always added between operands, regardless of // their type. Instead of vendoring the Sprintln implementation to spare a diff --git a/exported.go b/exported.go index 013183e..f2df9e3 100644 --- a/exported.go +++ b/exported.go @@ -41,6 +41,30 @@ func GetLevel() Level { return std.level() } +func IsDebugEnabled() bool { + return std.IsDebugEnabled() +} + +func IsInfoEnabled() bool { + return std.IsInfoEnabled() +} + +func IsWarnEnabled() bool { + return std.IsWarnEnabled() +} + +func IsErrorEnabled() bool { + return std.IsErrorEnabled() +} + +func IsFatalEnabled() bool { + return std.IsFatalEnabled() +} + +func IsPanicEnabled() bool { + return std.IsPanicEnabled() +} + // AddHook adds a hook to the standard logger hooks. func AddHook(hook Hook) { std.mu.Lock() diff --git a/logger.go b/logger.go index fdaf8a6..034fb18 100644 --- a/logger.go +++ b/logger.go @@ -113,7 +113,7 @@ func (logger *Logger) WithError(err error) *Entry { } func (logger *Logger) Debugf(format string, args ...interface{}) { - if logger.level() >= DebugLevel { + if logger.IsDebugEnabled() { entry := logger.newEntry() entry.Debugf(format, args...) logger.releaseEntry(entry) @@ -121,7 +121,7 @@ func (logger *Logger) Debugf(format string, args ...interface{}) { } func (logger *Logger) Infof(format string, args ...interface{}) { - if logger.level() >= InfoLevel { + if logger.IsInfoEnabled() { entry := logger.newEntry() entry.Infof(format, args...) logger.releaseEntry(entry) @@ -135,7 +135,7 @@ func (logger *Logger) Printf(format string, args ...interface{}) { } func (logger *Logger) Warnf(format string, args ...interface{}) { - if logger.level() >= WarnLevel { + if logger.IsWarnEnabled() { entry := logger.newEntry() entry.Warnf(format, args...) logger.releaseEntry(entry) @@ -143,7 +143,7 @@ func (logger *Logger) Warnf(format string, args ...interface{}) { } func (logger *Logger) Warningf(format string, args ...interface{}) { - if logger.level() >= WarnLevel { + if logger.IsWarnEnabled() { entry := logger.newEntry() entry.Warnf(format, args...) logger.releaseEntry(entry) @@ -151,7 +151,7 @@ func (logger *Logger) Warningf(format string, args ...interface{}) { } func (logger *Logger) Errorf(format string, args ...interface{}) { - if logger.level() >= ErrorLevel { + if logger.IsErrorEnabled() { entry := logger.newEntry() entry.Errorf(format, args...) logger.releaseEntry(entry) @@ -159,7 +159,7 @@ func (logger *Logger) Errorf(format string, args ...interface{}) { } func (logger *Logger) Fatalf(format string, args ...interface{}) { - if logger.level() >= FatalLevel { + if logger.IsFatalEnabled() { entry := logger.newEntry() entry.Fatalf(format, args...) logger.releaseEntry(entry) @@ -168,7 +168,7 @@ func (logger *Logger) Fatalf(format string, args ...interface{}) { } func (logger *Logger) Panicf(format string, args ...interface{}) { - if logger.level() >= PanicLevel { + if logger.IsPanicEnabled() { entry := logger.newEntry() entry.Panicf(format, args...) logger.releaseEntry(entry) @@ -176,7 +176,7 @@ func (logger *Logger) Panicf(format string, args ...interface{}) { } func (logger *Logger) Debug(args ...interface{}) { - if logger.level() >= DebugLevel { + if logger.IsDebugEnabled() { entry := logger.newEntry() entry.Debug(args...) logger.releaseEntry(entry) @@ -184,7 +184,7 @@ func (logger *Logger) Debug(args ...interface{}) { } func (logger *Logger) Info(args ...interface{}) { - if logger.level() >= InfoLevel { + if logger.IsInfoEnabled() { entry := logger.newEntry() entry.Info(args...) logger.releaseEntry(entry) @@ -198,7 +198,7 @@ func (logger *Logger) Print(args ...interface{}) { } func (logger *Logger) Warn(args ...interface{}) { - if logger.level() >= WarnLevel { + if logger.IsWarnEnabled() { entry := logger.newEntry() entry.Warn(args...) logger.releaseEntry(entry) @@ -206,7 +206,7 @@ func (logger *Logger) Warn(args ...interface{}) { } func (logger *Logger) Warning(args ...interface{}) { - if logger.level() >= WarnLevel { + if logger.IsWarnEnabled() { entry := logger.newEntry() entry.Warn(args...) logger.releaseEntry(entry) @@ -214,7 +214,7 @@ func (logger *Logger) Warning(args ...interface{}) { } func (logger *Logger) Error(args ...interface{}) { - if logger.level() >= ErrorLevel { + if logger.IsErrorEnabled() { entry := logger.newEntry() entry.Error(args...) logger.releaseEntry(entry) @@ -222,7 +222,7 @@ func (logger *Logger) Error(args ...interface{}) { } func (logger *Logger) Fatal(args ...interface{}) { - if logger.level() >= FatalLevel { + if logger.IsFatalEnabled() { entry := logger.newEntry() entry.Fatal(args...) logger.releaseEntry(entry) @@ -231,7 +231,7 @@ func (logger *Logger) Fatal(args ...interface{}) { } func (logger *Logger) Panic(args ...interface{}) { - if logger.level() >= PanicLevel { + if logger.IsPanicEnabled() { entry := logger.newEntry() entry.Panic(args...) logger.releaseEntry(entry) @@ -239,7 +239,7 @@ func (logger *Logger) Panic(args ...interface{}) { } func (logger *Logger) Debugln(args ...interface{}) { - if logger.level() >= DebugLevel { + if logger.IsDebugEnabled() { entry := logger.newEntry() entry.Debugln(args...) logger.releaseEntry(entry) @@ -247,7 +247,7 @@ func (logger *Logger) Debugln(args ...interface{}) { } func (logger *Logger) Infoln(args ...interface{}) { - if logger.level() >= InfoLevel { + if logger.IsInfoEnabled() { entry := logger.newEntry() entry.Infoln(args...) logger.releaseEntry(entry) @@ -261,7 +261,7 @@ func (logger *Logger) Println(args ...interface{}) { } func (logger *Logger) Warnln(args ...interface{}) { - if logger.level() >= WarnLevel { + if logger.IsWarnEnabled() { entry := logger.newEntry() entry.Warnln(args...) logger.releaseEntry(entry) @@ -269,7 +269,7 @@ func (logger *Logger) Warnln(args ...interface{}) { } func (logger *Logger) Warningln(args ...interface{}) { - if logger.level() >= WarnLevel { + if logger.IsWarnEnabled() { entry := logger.newEntry() entry.Warnln(args...) logger.releaseEntry(entry) @@ -277,7 +277,7 @@ func (logger *Logger) Warningln(args ...interface{}) { } func (logger *Logger) Errorln(args ...interface{}) { - if logger.level() >= ErrorLevel { + if logger.IsErrorEnabled() { entry := logger.newEntry() entry.Errorln(args...) logger.releaseEntry(entry) @@ -285,7 +285,7 @@ func (logger *Logger) Errorln(args ...interface{}) { } func (logger *Logger) Fatalln(args ...interface{}) { - if logger.level() >= FatalLevel { + if logger.IsFatalEnabled() { entry := logger.newEntry() entry.Fatalln(args...) logger.releaseEntry(entry) @@ -294,7 +294,7 @@ func (logger *Logger) Fatalln(args ...interface{}) { } func (logger *Logger) Panicln(args ...interface{}) { - if logger.level() >= PanicLevel { + if logger.IsPanicEnabled() { entry := logger.newEntry() entry.Panicln(args...) logger.releaseEntry(entry) @@ -321,3 +321,27 @@ func (logger *Logger) AddHook(hook Hook) { defer logger.mu.Unlock() logger.Hooks.Add(hook) } + +func (logger *Logger) IsDebugEnabled() bool { + return logger.level() >= DebugLevel +} + +func (logger *Logger) IsInfoEnabled() bool { + return logger.level() >= InfoLevel +} + +func (logger *Logger) IsWarnEnabled() bool { + return logger.level() >= WarnLevel +} + +func (logger *Logger) IsErrorEnabled() bool { + return logger.level() >= ErrorLevel +} + +func (logger *Logger) IsFatalEnabled() bool { + return logger.level() >= FatalLevel +} + +func (logger *Logger) IsPanicEnabled() bool { + return logger.level() >= PanicLevel +} diff --git a/logrus.go b/logrus.go index dd38999..33e07fa 100644 --- a/logrus.go +++ b/logrus.go @@ -140,4 +140,11 @@ type FieldLogger interface { Errorln(args ...interface{}) Fatalln(args ...interface{}) Panicln(args ...interface{}) + + IsDebugEnabled() bool + IsInfoEnabled() bool + IsWarnEnabled() bool + IsErrorEnabled() bool + IsFatalEnabled() bool + IsPanicEnabled() bool } diff --git a/logrus_test.go b/logrus_test.go index 78cbc28..2c59dc9 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -384,3 +384,54 @@ func TestEntryWriter(t *testing.T) { assert.Equal(t, fields["foo"], "bar") assert.Equal(t, fields["level"], "warning") } + +func TestLogLevelEnabled(t *testing.T) { + log := New() + log.SetLevel(PanicLevel) + assert.Equal(t, true, log.IsPanicEnabled()) + assert.Equal(t, false, log.IsFatalEnabled()) + assert.Equal(t, false, log.IsErrorEnabled()) + assert.Equal(t, false, log.IsWarnEnabled()) + assert.Equal(t, false, log.IsInfoEnabled()) + assert.Equal(t, false, log.IsDebugEnabled()) + + log.SetLevel(FatalLevel) + assert.Equal(t, true, log.IsPanicEnabled()) + assert.Equal(t, true, log.IsFatalEnabled()) + assert.Equal(t, false, log.IsErrorEnabled()) + assert.Equal(t, false, log.IsWarnEnabled()) + assert.Equal(t, false, log.IsInfoEnabled()) + assert.Equal(t, false, log.IsDebugEnabled()) + + log.SetLevel(ErrorLevel) + assert.Equal(t, true, log.IsPanicEnabled()) + assert.Equal(t, true, log.IsFatalEnabled()) + assert.Equal(t, true, log.IsErrorEnabled()) + assert.Equal(t, false, log.IsWarnEnabled()) + assert.Equal(t, false, log.IsInfoEnabled()) + assert.Equal(t, false, log.IsDebugEnabled()) + + log.SetLevel(WarnLevel) + assert.Equal(t, true, log.IsPanicEnabled()) + assert.Equal(t, true, log.IsFatalEnabled()) + assert.Equal(t, true, log.IsErrorEnabled()) + assert.Equal(t, true, log.IsWarnEnabled()) + assert.Equal(t, false, log.IsInfoEnabled()) + assert.Equal(t, false, log.IsDebugEnabled()) + + log.SetLevel(InfoLevel) + assert.Equal(t, true, log.IsPanicEnabled()) + assert.Equal(t, true, log.IsFatalEnabled()) + assert.Equal(t, true, log.IsErrorEnabled()) + assert.Equal(t, true, log.IsWarnEnabled()) + assert.Equal(t, true, log.IsInfoEnabled()) + assert.Equal(t, false, log.IsDebugEnabled()) + + log.SetLevel(DebugLevel) + assert.Equal(t, true, log.IsPanicEnabled()) + assert.Equal(t, true, log.IsFatalEnabled()) + assert.Equal(t, true, log.IsErrorEnabled()) + assert.Equal(t, true, log.IsWarnEnabled()) + assert.Equal(t, true, log.IsInfoEnabled()) + assert.Equal(t, true, log.IsDebugEnabled()) +} From eed1c0f832603c63deae9d5ffa2c4a6e611ed31f Mon Sep 17 00:00:00 2001 From: Christian Stewart Date: Wed, 20 Jun 2018 21:39:23 -0700 Subject: [PATCH 41/99] Fix GopherJS build tags The GopherJS build tag is "js" not "gopherjs" Signed-off-by: Christian Stewart --- terminal_bsd.go | 2 +- terminal_check_appengine.go | 2 +- terminal_check_notappengine.go | 2 +- terminal_linux.go | 2 +- text_formatter_js.go | 11 +++++++++++ text_formatter_other.go | 3 +++ 6 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 text_formatter_js.go create mode 100644 text_formatter_other.go diff --git a/terminal_bsd.go b/terminal_bsd.go index 4880d13..5b6212d 100644 --- a/terminal_bsd.go +++ b/terminal_bsd.go @@ -1,5 +1,5 @@ // +build darwin freebsd openbsd netbsd dragonfly -// +build !appengine,!gopherjs +// +build !appengine,!js package logrus diff --git a/terminal_check_appengine.go b/terminal_check_appengine.go index 3de08e8..26a2867 100644 --- a/terminal_check_appengine.go +++ b/terminal_check_appengine.go @@ -1,4 +1,4 @@ -// +build appengine gopherjs +// +build appengine js package logrus diff --git a/terminal_check_notappengine.go b/terminal_check_notappengine.go index 067047a..87f0b80 100644 --- a/terminal_check_notappengine.go +++ b/terminal_check_notappengine.go @@ -1,4 +1,4 @@ -// +build !appengine,!gopherjs +// +build !appengine,!js package logrus diff --git a/terminal_linux.go b/terminal_linux.go index f29a009..634e39b 100644 --- a/terminal_linux.go +++ b/terminal_linux.go @@ -3,7 +3,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !appengine,!gopherjs +// +build !appengine,!js package logrus diff --git a/text_formatter_js.go b/text_formatter_js.go new file mode 100644 index 0000000..d52803a --- /dev/null +++ b/text_formatter_js.go @@ -0,0 +1,11 @@ +// +build js + +package logrus + +import ( + "io" +) + +func (f *TextFormatter) checkIfTerminal(w io.Writer) bool { + return false +} diff --git a/text_formatter_other.go b/text_formatter_other.go new file mode 100644 index 0000000..0d9704f --- /dev/null +++ b/text_formatter_other.go @@ -0,0 +1,3 @@ +// +build !js + +package logrus From 52b92f5b89ba5a81f021ad845bcd3a0d3ba1b2ac Mon Sep 17 00:00:00 2001 From: Simon Brisson Date: Thu, 28 Jun 2018 16:33:52 -0400 Subject: [PATCH 42/99] Allows overriding Entry.Time. --- entry.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/entry.go b/entry.go index d075d72..c3adf01 100644 --- a/entry.go +++ b/entry.go @@ -90,7 +90,16 @@ func (entry *Entry) WithFields(fields Fields) *Entry { // race conditions will occur when using multiple goroutines func (entry Entry) log(level Level, msg string) { var buffer *bytes.Buffer - entry.Time = time.Now() + + // Default to now, but allow users to override if they want. + // + // We don't have to worry about polluting future calls to Entry#log() + // with this assignment because this function is declared with a + // non-pointer receiver. + if entry.Time.IsZero() { + entry.Time = time.Now() + } + entry.Level = level entry.Message = msg From 725f3be1995f9bb46c7aa065ff200bef3a346cee Mon Sep 17 00:00:00 2001 From: Simon Brisson Date: Fri, 29 Jun 2018 10:53:51 -0400 Subject: [PATCH 43/99] Adds WithTime to Logger and Entry types, as well as a pure module-level function. --- entry.go | 7 +++++- exported.go | 10 +++++++++ logger.go | 8 +++++++ logrus_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 1 deletion(-) diff --git a/entry.go b/entry.go index c3adf01..14f0a26 100644 --- a/entry.go +++ b/entry.go @@ -83,7 +83,12 @@ func (entry *Entry) WithFields(fields Fields) *Entry { for k, v := range fields { data[k] = v } - return &Entry{Logger: entry.Logger, Data: data} + return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time} +} + +// Overrides the time of the Entry. +func (entry *Entry) WithTime(t time.Time) *Entry { + return &Entry{Logger: entry.Logger, Data: entry.Data, Time: t} } // This function is not declared with a pointer value because otherwise diff --git a/exported.go b/exported.go index 013183e..ec1a417 100644 --- a/exported.go +++ b/exported.go @@ -2,6 +2,7 @@ package logrus import ( "io" + "time" ) var ( @@ -72,6 +73,15 @@ func WithFields(fields Fields) *Entry { return std.WithFields(fields) } +// WithTime creats an entry from the standard logger and overrides the time of +// logs generated with it. +// +// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal +// or Panic on the Entry it returns. +func WithTime(t time.Time) *Entry { + return std.WithTime(t) +} + // Debug logs a message at level Debug on the standard logger. func Debug(args ...interface{}) { std.Debug(args...) diff --git a/logger.go b/logger.go index 0ac8ce2..52b942d 100644 --- a/logger.go +++ b/logger.go @@ -5,6 +5,7 @@ import ( "os" "sync" "sync/atomic" + "time" ) type Logger struct { @@ -112,6 +113,13 @@ func (logger *Logger) WithError(err error) *Entry { return entry.WithError(err) } +// Overrides the time of the log entry. +func (logger *Logger) WithTime(t time.Time) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithTime(t) +} + func (logger *Logger) Debugf(format string, args ...interface{}) { if logger.level() >= DebugLevel { entry := logger.newEntry() diff --git a/logrus_test.go b/logrus_test.go index 78cbc28..78e1301 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -7,6 +7,7 @@ import ( "strings" "sync" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -209,6 +210,65 @@ func TestDefaultFieldsAreNotPrefixed(t *testing.T) { }) } +func TestWithTimeShouldOverrideTime(t *testing.T) { + now := time.Now().Add(24 * time.Hour) + + LogAndAssertJSON(t, func(log *Logger) { + log.WithTime(now).Info("foobar") + }, func(fields Fields) { + assert.Equal(t, fields["time"], now.Format(defaultTimestampFormat)) + }) +} + +func TestWithTimeShouldNotOverrideFields(t *testing.T) { + now := time.Now().Add(24 * time.Hour) + + LogAndAssertJSON(t, func(log *Logger) { + log.WithField("herp", "derp").WithTime(now).Info("blah") + }, func(fields Fields) { + assert.Equal(t, fields["time"], now.Format(defaultTimestampFormat)) + assert.Equal(t, fields["herp"], "derp") + }) +} + +func TestWithFieldShouldNotOverrideTime(t *testing.T) { + now := time.Now().Add(24 * time.Hour) + + LogAndAssertJSON(t, func(log *Logger) { + log.WithTime(now).WithField("herp", "derp").Info("blah") + }, func(fields Fields) { + assert.Equal(t, fields["time"], now.Format(defaultTimestampFormat)) + assert.Equal(t, fields["herp"], "derp") + }) +} + +func TestTimeOverrideMultipleLogs(t *testing.T) { + var buffer bytes.Buffer + var firstFields, secondFields Fields + + logger := New() + logger.Out = &buffer + formatter := new(JSONFormatter) + formatter.TimestampFormat = time.StampMilli + logger.Formatter = formatter + + llog := logger.WithField("herp", "derp") + llog.Info("foo") + + err := json.Unmarshal(buffer.Bytes(), &firstFields) + assert.NoError(t, err, "should have decoded first message") + + buffer.Reset() + + time.Sleep(10 * time.Millisecond) + llog.Info("bar") + + err = json.Unmarshal(buffer.Bytes(), &secondFields) + assert.NoError(t, err, "should have decoded second message") + + assert.NotEqual(t, firstFields["time"], secondFields["time"], "timestamps should not be equal") +} + func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) { var buffer bytes.Buffer From 6999e59e73b0b94716d9cef60c51e5adb5e5b4c3 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Fri, 20 Jul 2018 13:16:19 +0200 Subject: [PATCH 44/99] properly fix the hooks race test --- hooks/test/test_test.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/hooks/test/test_test.go b/hooks/test/test_test.go index 742be55..d6f6d30 100644 --- a/hooks/test/test_test.go +++ b/hooks/test/test_test.go @@ -1,8 +1,10 @@ package test import ( + "math/rand" "sync" "testing" + "time" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" @@ -38,24 +40,34 @@ func TestAllHooks(t *testing.T) { } func TestLoggingWithHooksRace(t *testing.T) { + + rand.Seed(time.Now().Unix()) + unlocker := rand.Int() % 100 + assert := assert.New(t) logger, hook := NewNullLogger() - var wg sync.WaitGroup - wg.Add(100) + var wgOne, wgAll sync.WaitGroup + wgOne.Add(1) + wgAll.Add(100) for i := 0; i < 100; i++ { - go func() { + go func(i int) { logger.Info("info") - wg.Done() - }() + wgAll.Done() + if i == unlocker { + wgOne.Done() + } + }(i) } - wg.Wait() + wgOne.Wait() assert.Equal(logrus.InfoLevel, hook.LastEntry().Level) assert.Equal("info", hook.LastEntry().Message) + wgAll.Wait() + entries := hook.AllEntries() assert.Equal(100, len(entries)) } From 54db2bb29af499574a7b8f7f86dcf1dc11297823 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Fri, 20 Jul 2018 13:34:26 +0200 Subject: [PATCH 45/99] limit the build/test matrix to the two latest stable version --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index aebdc35..2f19b4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,7 @@ language: go go: - - 1.8.x - 1.9.x - 1.10.x - - tip env: - GOMAXPROCS=4 GORACE=halt_on_error=1 install: From d3162770a8b8e496c83f63327ede96304f57ddee Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sat, 28 Jul 2018 17:21:06 +0200 Subject: [PATCH 46/99] Add logger benchmark --- logger_bench_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/logger_bench_test.go b/logger_bench_test.go index dd23a35..f0a7684 100644 --- a/logger_bench_test.go +++ b/logger_bench_test.go @@ -1,6 +1,7 @@ package logrus import ( + "io/ioutil" "os" "testing" ) @@ -59,3 +60,26 @@ func doLoggerBenchmarkNoLock(b *testing.B, out *os.File, formatter Formatter, fi } }) } + +func BenchmarkLoggerJSONFormatter(b *testing.B) { + doLoggerBenchmarkWithFormatter(b, &JSONFormatter{}) +} + +func BenchmarkLoggerTextFormatter(b *testing.B) { + doLoggerBenchmarkWithFormatter(b, &TextFormatter{}) +} + +func doLoggerBenchmarkWithFormatter(b *testing.B, f Formatter) { + b.SetParallelism(100) + log := New() + log.Formatter = f + log.Out = ioutil.Discard + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + log. + WithField("foo1", "bar1"). + WithField("foo2", "bar2"). + Info("this is a dummy log") + } + }) +} From 179037fcd41cd279507e65aeebb32b0af35958fc Mon Sep 17 00:00:00 2001 From: David Bariod Date: Tue, 31 Jul 2018 18:08:27 +0200 Subject: [PATCH 47/99] Ensure a new entry data fields are empty Fixes #795 --- hook_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ logger.go | 1 + 2 files changed, 44 insertions(+) diff --git a/hook_test.go b/hook_test.go index 4fea751..80b93b8 100644 --- a/hook_test.go +++ b/hook_test.go @@ -1,10 +1,13 @@ package logrus import ( + "bytes" + "encoding/json" "sync" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type TestHook struct { @@ -85,6 +88,46 @@ func TestCanFireMultipleHooks(t *testing.T) { }) } +type SingleLevelModifyHook struct { + ModifyHook +} + +func (h *SingleLevelModifyHook) Levels() []Level { + return []Level{InfoLevel} +} + +func TestHookEntryIsPristine(t *testing.T) { + l := New() + b := &bytes.Buffer{} + l.Formatter = &JSONFormatter{} + l.Out = b + l.AddHook(&SingleLevelModifyHook{}) + + l.Error("error message") + data := map[string]string{} + err := json.Unmarshal(b.Bytes(), &data) + require.NoError(t, err) + _, ok := data["wow"] + require.False(t, ok) + b.Reset() + + l.Info("error message") + data = map[string]string{} + err = json.Unmarshal(b.Bytes(), &data) + require.NoError(t, err) + _, ok = data["wow"] + require.True(t, ok) + b.Reset() + + l.Error("error message") + data = map[string]string{} + err = json.Unmarshal(b.Bytes(), &data) + require.NoError(t, err) + _, ok = data["wow"] + require.False(t, ok) + b.Reset() +} + type ErrorHook struct { Fired bool } diff --git a/logger.go b/logger.go index 342f797..7fa8d7d 100644 --- a/logger.go +++ b/logger.go @@ -85,6 +85,7 @@ func (logger *Logger) newEntry() *Entry { } func (logger *Logger) releaseEntry(entry *Entry) { + entry.Data = map[string]interface{}{} logger.entryPool.Put(entry) } From 37d651c1f2847d8514b79d1dd7389be06ec60447 Mon Sep 17 00:00:00 2001 From: Alessio Caiazza Date: Fri, 13 Jul 2018 17:33:25 +0200 Subject: [PATCH 48/99] Add CLICOLOR support This implement CLICOLOR and CLICOLOR_FORCE check on terminal coloring as defined in https://bixense.com/clicolors/ --- text_formatter.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/text_formatter.go b/text_formatter.go index 3e55040..cdf3185 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -3,6 +3,7 @@ package logrus import ( "bytes" "fmt" + "os" "sort" "strings" "sync" @@ -35,6 +36,9 @@ type TextFormatter struct { // Force disabling colors. DisableColors bool + // Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/ + OverrideColors bool + // Disable timestamp logging. useful when output is redirected to logging // system that already adds timestamps. DisableTimestamp bool @@ -78,6 +82,22 @@ func (f *TextFormatter) init(entry *Entry) { } } +func (f *TextFormatter) isColored() bool { + isColored := f.ForceColors || f.isTerminal + + if f.OverrideColors { + if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" { + isColored = true + } + + if os.Getenv("CLICOLOR") == "0" { + isColored = false + } + } + + return isColored && !f.DisableColors +} + // Format renders a single log entry func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { prefixFieldClashes(entry.Data, f.FieldMap) @@ -100,13 +120,11 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { f.Do(func() { f.init(entry) }) - isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors - timestampFormat := f.TimestampFormat if timestampFormat == "" { timestampFormat = defaultTimestampFormat } - if isColored { + if f.isColored() { f.printColored(b, entry, keys, timestampFormat) } else { if !f.DisableTimestamp { From da39da23485a153e5c9b7902e4b5cefbd924ef78 Mon Sep 17 00:00:00 2001 From: Kwok-kuen Cheung Date: Mon, 6 Aug 2018 00:43:49 +0800 Subject: [PATCH 49/99] Keep terminal check naming convention --- terminal_check_appengine.go | 2 +- text_formatter_js.go => terminal_check_js.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename text_formatter_js.go => terminal_check_js.go (51%) diff --git a/terminal_check_appengine.go b/terminal_check_appengine.go index 26a2867..2403de9 100644 --- a/terminal_check_appengine.go +++ b/terminal_check_appengine.go @@ -1,4 +1,4 @@ -// +build appengine js +// +build appengine package logrus diff --git a/text_formatter_js.go b/terminal_check_js.go similarity index 51% rename from text_formatter_js.go rename to terminal_check_js.go index d52803a..0c20975 100644 --- a/text_formatter_js.go +++ b/terminal_check_js.go @@ -6,6 +6,6 @@ import ( "io" ) -func (f *TextFormatter) checkIfTerminal(w io.Writer) bool { +func checkIfTerminal(w io.Writer) bool { return false } From d950ecd55bc2e1ddaf9af20cb8c6910a5633b031 Mon Sep 17 00:00:00 2001 From: Kwok-kuen Cheung Date: Mon, 6 Aug 2018 00:43:58 +0800 Subject: [PATCH 50/99] Remove unnecessary text_formatter file --- text_formatter_other.go | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 text_formatter_other.go diff --git a/text_formatter_other.go b/text_formatter_other.go deleted file mode 100644 index 0d9704f..0000000 --- a/text_formatter_other.go +++ /dev/null @@ -1,3 +0,0 @@ -// +build !js - -package logrus From 8a6a17c00343eaa23f978b454d878ae304992ef2 Mon Sep 17 00:00:00 2001 From: Dennis Date: Sun, 5 Aug 2018 22:40:58 +0200 Subject: [PATCH 51/99] Fixed missing brace after wrong merge --- text_formatter_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/text_formatter_test.go b/text_formatter_test.go index 72adda1..092b19d 100644 --- a/text_formatter_test.go +++ b/text_formatter_test.go @@ -198,6 +198,7 @@ func TestNewlineBehavior(t *testing.T) { if !bytes.Contains(b, []byte("test message\n")) { t.Error("Double newline at end of Entry.Message did not result in a single newline after formatting") } +} func TestTextFormatterFieldMap(t *testing.T) { formatter := &TextFormatter{ From eb968b65069b7b415d83ba5a6451718d3f93763e Mon Sep 17 00:00:00 2001 From: David Bariod Date: Thu, 9 Aug 2018 15:00:46 +0200 Subject: [PATCH 52/99] Fix for CLICOLOR_FORCE handling --- text_formatter.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/text_formatter.go b/text_formatter.go index cdf3185..beb628f 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -37,7 +37,7 @@ type TextFormatter struct { DisableColors bool // Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/ - OverrideColors bool + EnvironmentOverrideColors bool // Disable timestamp logging. useful when output is redirected to logging // system that already adds timestamps. @@ -85,12 +85,12 @@ func (f *TextFormatter) init(entry *Entry) { func (f *TextFormatter) isColored() bool { isColored := f.ForceColors || f.isTerminal - if f.OverrideColors { + if f.EnvironmentOverrideColors { if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" { isColored = true - } - - if os.Getenv("CLICOLOR") == "0" { + } else if ok && force == "0" { + isColored = false + } else if os.Getenv("CLICOLOR") == "0" { isColored = false } } From cadf2ceaf8580dddc21b34895f5fed8d2c3b2e60 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Thu, 9 Aug 2018 15:01:49 +0200 Subject: [PATCH 53/99] Add unit test for TextFormatter.isColored --- text_formatter_test.go | 214 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) diff --git a/text_formatter_test.go b/text_formatter_test.go index 921d052..023f346 100644 --- a/text_formatter_test.go +++ b/text_formatter_test.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "os" "strings" "testing" "time" @@ -216,5 +217,218 @@ func TestTextFormatterFieldMap(t *testing.T) { "Formatted output doesn't respect FieldMap") } +func TestTextFormatterIsColored(t *testing.T) { + params := []struct { + name string + expectedResult bool + isTerminal bool + disableColor bool + forceColor bool + envColor bool + clicolorIsSet bool + clicolorForceIsSet bool + clicolorVal string + clicolorForceVal string + }{ + // Default values + { + name: "testcase1", + expectedResult: false, + isTerminal: false, + disableColor: false, + forceColor: false, + envColor: false, + clicolorIsSet: false, + clicolorForceIsSet: false, + }, + // Output on terminal + { + name: "testcase2", + expectedResult: true, + isTerminal: true, + disableColor: false, + forceColor: false, + envColor: false, + clicolorIsSet: false, + clicolorForceIsSet: false, + }, + // Output on terminal with color disabled + { + name: "testcase3", + expectedResult: false, + isTerminal: true, + disableColor: true, + forceColor: false, + envColor: false, + clicolorIsSet: false, + clicolorForceIsSet: false, + }, + // Output not on terminal with color disabled + { + name: "testcase4", + expectedResult: false, + isTerminal: false, + disableColor: true, + forceColor: false, + envColor: false, + clicolorIsSet: false, + clicolorForceIsSet: false, + }, + // Output not on terminal with color forced + { + name: "testcase5", + expectedResult: true, + isTerminal: false, + disableColor: false, + forceColor: true, + envColor: false, + clicolorIsSet: false, + clicolorForceIsSet: false, + }, + // Output on terminal with clicolor set to "0" + { + name: "testcase6", + expectedResult: false, + isTerminal: true, + disableColor: false, + forceColor: false, + envColor: true, + clicolorIsSet: true, + clicolorForceIsSet: false, + clicolorVal: "0", + }, + // Output on terminal with clicolor set to "1" + { + name: "testcase7", + expectedResult: true, + isTerminal: true, + disableColor: false, + forceColor: false, + envColor: true, + clicolorIsSet: true, + clicolorForceIsSet: false, + clicolorVal: "1", + }, + // Output not on terminal with clicolor set to "0" + { + name: "testcase8", + expectedResult: false, + isTerminal: false, + disableColor: false, + forceColor: false, + envColor: true, + clicolorIsSet: true, + clicolorForceIsSet: false, + clicolorVal: "0", + }, + // Output not on terminal with clicolor set to "1" + { + name: "testcase9", + expectedResult: false, + isTerminal: false, + disableColor: false, + forceColor: false, + envColor: true, + clicolorIsSet: true, + clicolorForceIsSet: false, + clicolorVal: "1", + }, + // Output not on terminal with clicolor set to "1" and force color + { + name: "testcase10", + expectedResult: true, + isTerminal: false, + disableColor: false, + forceColor: true, + envColor: true, + clicolorIsSet: true, + clicolorForceIsSet: false, + clicolorVal: "1", + }, + // Output not on terminal with clicolor set to "0" and force color + { + name: "testcase11", + expectedResult: false, + isTerminal: false, + disableColor: false, + forceColor: true, + envColor: true, + clicolorIsSet: true, + clicolorForceIsSet: false, + clicolorVal: "0", + }, + // Output not on terminal with clicolor set to "1" + { + name: "testcase12", + expectedResult: false, + isTerminal: false, + disableColor: false, + forceColor: false, + envColor: true, + clicolorIsSet: true, + clicolorForceIsSet: false, + clicolorVal: "1", + }, + // Output not on terminal with clicolor_force set to "1" + { + name: "testcase13", + expectedResult: true, + isTerminal: false, + disableColor: false, + forceColor: false, + envColor: true, + clicolorIsSet: false, + clicolorForceIsSet: true, + clicolorForceVal: "1", + }, + // Output not on terminal with clicolor_force set to "0" + { + name: "testcase14", + expectedResult: false, + isTerminal: false, + disableColor: false, + forceColor: false, + envColor: true, + clicolorIsSet: false, + clicolorForceIsSet: true, + clicolorForceVal: "0", + }, + // Output on terminal with clicolor_force set to "0" + { + name: "testcase14", + expectedResult: false, + isTerminal: true, + disableColor: false, + forceColor: false, + envColor: true, + clicolorIsSet: false, + clicolorForceIsSet: true, + clicolorForceVal: "0", + }, + } + + defer os.Clearenv() + + for _, val := range params { + t.Run("textformatter_"+val.name, func(subT *testing.T) { + tf := TextFormatter{ + isTerminal: val.isTerminal, + DisableColors: val.disableColor, + ForceColors: val.forceColor, + EnvironmentOverrideColors: val.envColor, + } + os.Clearenv() + if val.clicolorIsSet { + os.Setenv("CLICOLOR", val.clicolorVal) + } + if val.clicolorForceIsSet { + os.Setenv("CLICOLOR_FORCE", val.clicolorForceVal) + } + res := tf.isColored() + assert.Equal(subT, val.expectedResult, res) + }) + } +} + // TODO add tests for sorting etc., this requires a parser for the text // formatter output. From b5e6fae4fba49f90e609d6379cc6409fa8f85e2e Mon Sep 17 00:00:00 2001 From: David Bariod Date: Mon, 13 Aug 2018 17:27:32 +0200 Subject: [PATCH 54/99] Cleanup on unit test on isColored --- text_formatter_test.go | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/text_formatter_test.go b/text_formatter_test.go index 023f346..652102d 100644 --- a/text_formatter_test.go +++ b/text_formatter_test.go @@ -357,21 +357,9 @@ func TestTextFormatterIsColored(t *testing.T) { clicolorForceIsSet: false, clicolorVal: "0", }, - // Output not on terminal with clicolor set to "1" - { - name: "testcase12", - expectedResult: false, - isTerminal: false, - disableColor: false, - forceColor: false, - envColor: true, - clicolorIsSet: true, - clicolorForceIsSet: false, - clicolorVal: "1", - }, // Output not on terminal with clicolor_force set to "1" { - name: "testcase13", + name: "testcase12", expectedResult: true, isTerminal: false, disableColor: false, @@ -383,7 +371,7 @@ func TestTextFormatterIsColored(t *testing.T) { }, // Output not on terminal with clicolor_force set to "0" { - name: "testcase14", + name: "testcase13", expectedResult: false, isTerminal: false, disableColor: false, @@ -407,7 +395,12 @@ func TestTextFormatterIsColored(t *testing.T) { }, } - defer os.Clearenv() + cleanenv := func() { + os.Unsetenv("CLICOLOR") + os.Unsetenv("CLICOLOR_FORCE") + } + + defer cleanenv() for _, val := range params { t.Run("textformatter_"+val.name, func(subT *testing.T) { @@ -417,7 +410,7 @@ func TestTextFormatterIsColored(t *testing.T) { ForceColors: val.forceColor, EnvironmentOverrideColors: val.envColor, } - os.Clearenv() + cleanenv() if val.clicolorIsSet { os.Setenv("CLICOLOR", val.clicolorVal) } From 7a0120e2c67ac3a748674a84fbb6ca4fe6231897 Mon Sep 17 00:00:00 2001 From: betrok Date: Wed, 22 Aug 2018 12:10:05 +0300 Subject: [PATCH 55/99] logger.ReplaceHooks --- logger.go | 6 ++++++ logrus_test.go | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/logger.go b/logger.go index 7fa8d7d..53bbf95 100644 --- a/logger.go +++ b/logger.go @@ -336,3 +336,9 @@ func (logger *Logger) AddHook(hook Hook) { defer logger.mu.Unlock() logger.Hooks.Add(hook) } + +func (logger *Logger) ReplaceHooks(hooks LevelHooks) { + logger.mu.Lock() + logger.Hooks = hooks + logger.mu.Unlock() +} diff --git a/logrus_test.go b/logrus_test.go index 57fb8d1..7a96686 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -421,6 +421,22 @@ func TestLoggingRaceWithHooksOnEntry(t *testing.T) { wg.Wait() } +func TestHooksReplace(t *testing.T) { + old, cur := &TestHook{}, &TestHook{} + + logger := New() + logger.AddHook(old) + + hooks := make(LevelHooks) + hooks.Add(cur) + logger.ReplaceHooks(hooks) + + logger.Info("test") + + assert.Equal(t, old.Fired, false) + assert.Equal(t, cur.Fired, true) +} + // Compile test func TestLogrusInterface(t *testing.T) { var buffer bytes.Buffer From 13d10d8d89db071ade54fb0b1667817dd48dc53e Mon Sep 17 00:00:00 2001 From: betrok Date: Sun, 26 Aug 2018 14:40:51 +0300 Subject: [PATCH 56/99] return old hooks from RelplaceHooks --- logger.go | 5 ++++- logrus_test.go | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/logger.go b/logger.go index 53bbf95..885f150 100644 --- a/logger.go +++ b/logger.go @@ -337,8 +337,11 @@ func (logger *Logger) AddHook(hook Hook) { logger.Hooks.Add(hook) } -func (logger *Logger) ReplaceHooks(hooks LevelHooks) { +// ReplaceHooks replaces the logger hooks and returns the old ones +func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks { logger.mu.Lock() + oldHooks := logger.Hooks logger.Hooks = hooks logger.mu.Unlock() + return oldHooks } diff --git a/logrus_test.go b/logrus_test.go index 7a96686..f6db6e9 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -3,6 +3,7 @@ package logrus import ( "bytes" "encoding/json" + "io/ioutil" "strconv" "strings" "sync" @@ -421,20 +422,25 @@ func TestLoggingRaceWithHooksOnEntry(t *testing.T) { wg.Wait() } -func TestHooksReplace(t *testing.T) { +func TestReplaceHooks(t *testing.T) { old, cur := &TestHook{}, &TestHook{} logger := New() + logger.SetOutput(ioutil.Discard) logger.AddHook(old) hooks := make(LevelHooks) hooks.Add(cur) - logger.ReplaceHooks(hooks) + replaced := logger.ReplaceHooks(hooks) logger.Info("test") assert.Equal(t, old.Fired, false) assert.Equal(t, cur.Fired, true) + + logger.ReplaceHooks(replaced) + logger.Info("test") + assert.Equal(t, old.Fired, true) } // Compile test From 90bf2e7f391a08ecb7d505856924ed3bf1bc771f Mon Sep 17 00:00:00 2001 From: Logan HAUSPIE Date: Sun, 26 Aug 2018 23:51:09 +0200 Subject: [PATCH 57/99] feat(LogLevel): taking in account code review from David Bariod --- entry.go | 60 +++++++++++++---------------------------- exported.go | 25 +++--------------- logger.go | 65 +++++++++++++++++---------------------------- logrus.go | 12 ++++----- logrus_test.go | 72 +++++++++++++++++++++++++------------------------- 5 files changed, 87 insertions(+), 147 deletions(-) diff --git a/entry.go b/entry.go index 82ef93c..8c6a389 100644 --- a/entry.go +++ b/entry.go @@ -151,7 +151,7 @@ func (entry *Entry) write() { } func (entry *Entry) Debug(args ...interface{}) { - if entry.IsDebugEnabled() { + if entry.Logger.IsLevelEnabled(DebugLevel) { entry.log(DebugLevel, fmt.Sprint(args...)) } } @@ -161,13 +161,13 @@ func (entry *Entry) Print(args ...interface{}) { } func (entry *Entry) Info(args ...interface{}) { - if entry.IsInfoEnabled() { + if entry.Logger.IsLevelEnabled(InfoLevel) { entry.log(InfoLevel, fmt.Sprint(args...)) } } func (entry *Entry) Warn(args ...interface{}) { - if entry.IsWarnEnabled() { + if entry.Logger.IsLevelEnabled(WarnLevel) { entry.log(WarnLevel, fmt.Sprint(args...)) } } @@ -177,20 +177,20 @@ func (entry *Entry) Warning(args ...interface{}) { } func (entry *Entry) Error(args ...interface{}) { - if entry.IsErrorEnabled() { + if entry.Logger.IsLevelEnabled(ErrorLevel) { entry.log(ErrorLevel, fmt.Sprint(args...)) } } func (entry *Entry) Fatal(args ...interface{}) { - if entry.IsFatalEnabled() { + if entry.Logger.IsLevelEnabled(FatalLevel) { entry.log(FatalLevel, fmt.Sprint(args...)) } Exit(1) } func (entry *Entry) Panic(args ...interface{}) { - if entry.IsPanicEnabled() { + if entry.Logger.IsLevelEnabled(PanicLevel) { entry.log(PanicLevel, fmt.Sprint(args...)) } panic(fmt.Sprint(args...)) @@ -199,13 +199,13 @@ func (entry *Entry) Panic(args ...interface{}) { // Entry Printf family functions func (entry *Entry) Debugf(format string, args ...interface{}) { - if entry.IsDebugEnabled() { + if entry.Logger.IsLevelEnabled(DebugLevel) { entry.Debug(fmt.Sprintf(format, args...)) } } func (entry *Entry) Infof(format string, args ...interface{}) { - if entry.IsInfoEnabled() { + if entry.Logger.IsLevelEnabled(InfoLevel) { entry.Info(fmt.Sprintf(format, args...)) } } @@ -215,7 +215,7 @@ func (entry *Entry) Printf(format string, args ...interface{}) { } func (entry *Entry) Warnf(format string, args ...interface{}) { - if entry.IsWarnEnabled() { + if entry.Logger.IsLevelEnabled(WarnLevel) { entry.Warn(fmt.Sprintf(format, args...)) } } @@ -225,20 +225,20 @@ func (entry *Entry) Warningf(format string, args ...interface{}) { } func (entry *Entry) Errorf(format string, args ...interface{}) { - if entry.IsErrorEnabled() { + if entry.Logger.IsLevelEnabled(ErrorLevel) { entry.Error(fmt.Sprintf(format, args...)) } } func (entry *Entry) Fatalf(format string, args ...interface{}) { - if entry.IsFatalEnabled() { + if entry.Logger.IsLevelEnabled(FatalLevel) { entry.Fatal(fmt.Sprintf(format, args...)) } Exit(1) } func (entry *Entry) Panicf(format string, args ...interface{}) { - if entry.IsPanicEnabled() { + if entry.Logger.IsLevelEnabled(PanicLevel) { entry.Panic(fmt.Sprintf(format, args...)) } } @@ -246,13 +246,13 @@ func (entry *Entry) Panicf(format string, args ...interface{}) { // Entry Println family functions func (entry *Entry) Debugln(args ...interface{}) { - if entry.IsDebugEnabled() { + if entry.Logger.IsLevelEnabled(DebugLevel) { entry.Debug(entry.sprintlnn(args...)) } } func (entry *Entry) Infoln(args ...interface{}) { - if entry.IsInfoEnabled() { + if entry.Logger.IsLevelEnabled(InfoLevel) { entry.Info(entry.sprintlnn(args...)) } } @@ -262,7 +262,7 @@ func (entry *Entry) Println(args ...interface{}) { } func (entry *Entry) Warnln(args ...interface{}) { - if entry.IsWarnEnabled() { + if entry.Logger.IsLevelEnabled(WarnLevel) { entry.Warn(entry.sprintlnn(args...)) } } @@ -272,48 +272,24 @@ func (entry *Entry) Warningln(args ...interface{}) { } func (entry *Entry) Errorln(args ...interface{}) { - if entry.IsErrorEnabled() { + if entry.Logger.IsLevelEnabled(ErrorLevel) { entry.Error(entry.sprintlnn(args...)) } } func (entry *Entry) Fatalln(args ...interface{}) { - if entry.IsFatalEnabled() { + if entry.Logger.IsLevelEnabled(FatalLevel) { entry.Fatal(entry.sprintlnn(args...)) } Exit(1) } func (entry *Entry) Panicln(args ...interface{}) { - if entry.IsPanicEnabled() { + if entry.Logger.IsLevelEnabled(PanicLevel) { entry.Panic(entry.sprintlnn(args...)) } } -func (entry *Entry) IsDebugEnabled() bool { - return entry.Logger.IsDebugEnabled() -} - -func (entry *Entry) IsInfoEnabled() bool { - return entry.Logger.IsInfoEnabled() -} - -func (entry *Entry) IsWarnEnabled() bool { - return entry.Logger.IsWarnEnabled() -} - -func (entry *Entry) IsErrorEnabled() bool { - return entry.Logger.IsErrorEnabled() -} - -func (entry *Entry) IsFatalEnabled() bool { - return entry.Logger.IsFatalEnabled() -} - -func (entry *Entry) IsPanicEnabled() bool { - return entry.Logger.IsPanicEnabled() -} - // Sprintlnn => Sprint no newline. This is to get the behavior of how // fmt.Sprintln where spaces are always added between operands, regardless of // their type. Instead of vendoring the Sprintln implementation to spare a diff --git a/exported.go b/exported.go index 7643671..fb2a7a1 100644 --- a/exported.go +++ b/exported.go @@ -34,28 +34,9 @@ func GetLevel() Level { return std.GetLevel() } -func IsDebugEnabled() bool { - return std.IsDebugEnabled() -} - -func IsInfoEnabled() bool { - return std.IsInfoEnabled() -} - -func IsWarnEnabled() bool { - return std.IsWarnEnabled() -} - -func IsErrorEnabled() bool { - return std.IsErrorEnabled() -} - -func IsFatalEnabled() bool { - return std.IsFatalEnabled() -} - -func IsPanicEnabled() bool { - return std.IsPanicEnabled() +// IsLevelEnabled checks if the log level of the standard logger is greater than the level param +func IsLevelEnabled(level Level) bool { + return std.IsLevelEnabled(level) } // AddHook adds a hook to the standard logger hooks. diff --git a/logger.go b/logger.go index 61bc1cc..b67bfcb 100644 --- a/logger.go +++ b/logger.go @@ -122,7 +122,7 @@ func (logger *Logger) WithTime(t time.Time) *Entry { } func (logger *Logger) Debugf(format string, args ...interface{}) { - if logger.IsDebugEnabled() { + if logger.IsLevelEnabled(DebugLevel) { entry := logger.newEntry() entry.Debugf(format, args...) logger.releaseEntry(entry) @@ -130,7 +130,7 @@ func (logger *Logger) Debugf(format string, args ...interface{}) { } func (logger *Logger) Infof(format string, args ...interface{}) { - if logger.IsInfoEnabled() { + if logger.IsLevelEnabled(InfoLevel) { entry := logger.newEntry() entry.Infof(format, args...) logger.releaseEntry(entry) @@ -144,7 +144,7 @@ func (logger *Logger) Printf(format string, args ...interface{}) { } func (logger *Logger) Warnf(format string, args ...interface{}) { - if logger.IsWarnEnabled() { + if logger.IsLevelEnabled(WarnLevel) { entry := logger.newEntry() entry.Warnf(format, args...) logger.releaseEntry(entry) @@ -152,7 +152,7 @@ func (logger *Logger) Warnf(format string, args ...interface{}) { } func (logger *Logger) Warningf(format string, args ...interface{}) { - if logger.IsWarnEnabled() { + if logger.IsLevelEnabled(WarnLevel) { entry := logger.newEntry() entry.Warnf(format, args...) logger.releaseEntry(entry) @@ -160,7 +160,7 @@ func (logger *Logger) Warningf(format string, args ...interface{}) { } func (logger *Logger) Errorf(format string, args ...interface{}) { - if logger.IsErrorEnabled() { + if logger.IsLevelEnabled(ErrorLevel) { entry := logger.newEntry() entry.Errorf(format, args...) logger.releaseEntry(entry) @@ -168,7 +168,7 @@ func (logger *Logger) Errorf(format string, args ...interface{}) { } func (logger *Logger) Fatalf(format string, args ...interface{}) { - if logger.IsFatalEnabled() { + if logger.IsLevelEnabled(FatalLevel) { entry := logger.newEntry() entry.Fatalf(format, args...) logger.releaseEntry(entry) @@ -177,7 +177,7 @@ func (logger *Logger) Fatalf(format string, args ...interface{}) { } func (logger *Logger) Panicf(format string, args ...interface{}) { - if logger.IsPanicEnabled() { + if logger.IsLevelEnabled(PanicLevel) { entry := logger.newEntry() entry.Panicf(format, args...) logger.releaseEntry(entry) @@ -185,7 +185,7 @@ func (logger *Logger) Panicf(format string, args ...interface{}) { } func (logger *Logger) Debug(args ...interface{}) { - if logger.IsDebugEnabled() { + if logger.IsLevelEnabled(DebugLevel) { entry := logger.newEntry() entry.Debug(args...) logger.releaseEntry(entry) @@ -193,7 +193,7 @@ func (logger *Logger) Debug(args ...interface{}) { } func (logger *Logger) Info(args ...interface{}) { - if logger.IsInfoEnabled() { + if logger.IsLevelEnabled(InfoLevel) { entry := logger.newEntry() entry.Info(args...) logger.releaseEntry(entry) @@ -207,7 +207,7 @@ func (logger *Logger) Print(args ...interface{}) { } func (logger *Logger) Warn(args ...interface{}) { - if logger.IsWarnEnabled() { + if logger.IsLevelEnabled(WarnLevel) { entry := logger.newEntry() entry.Warn(args...) logger.releaseEntry(entry) @@ -215,7 +215,7 @@ func (logger *Logger) Warn(args ...interface{}) { } func (logger *Logger) Warning(args ...interface{}) { - if logger.IsWarnEnabled() { + if logger.IsLevelEnabled(WarnLevel) { entry := logger.newEntry() entry.Warn(args...) logger.releaseEntry(entry) @@ -223,7 +223,7 @@ func (logger *Logger) Warning(args ...interface{}) { } func (logger *Logger) Error(args ...interface{}) { - if logger.IsErrorEnabled() { + if logger.IsLevelEnabled(ErrorLevel) { entry := logger.newEntry() entry.Error(args...) logger.releaseEntry(entry) @@ -231,7 +231,7 @@ func (logger *Logger) Error(args ...interface{}) { } func (logger *Logger) Fatal(args ...interface{}) { - if logger.IsFatalEnabled() { + if logger.IsLevelEnabled(FatalLevel) { entry := logger.newEntry() entry.Fatal(args...) logger.releaseEntry(entry) @@ -240,7 +240,7 @@ func (logger *Logger) Fatal(args ...interface{}) { } func (logger *Logger) Panic(args ...interface{}) { - if logger.IsPanicEnabled() { + if logger.IsLevelEnabled(PanicLevel) { entry := logger.newEntry() entry.Panic(args...) logger.releaseEntry(entry) @@ -248,7 +248,7 @@ func (logger *Logger) Panic(args ...interface{}) { } func (logger *Logger) Debugln(args ...interface{}) { - if logger.IsDebugEnabled() { + if logger.IsLevelEnabled(DebugLevel) { entry := logger.newEntry() entry.Debugln(args...) logger.releaseEntry(entry) @@ -256,7 +256,7 @@ func (logger *Logger) Debugln(args ...interface{}) { } func (logger *Logger) Infoln(args ...interface{}) { - if logger.IsInfoEnabled() { + if logger.IsLevelEnabled(InfoLevel) { entry := logger.newEntry() entry.Infoln(args...) logger.releaseEntry(entry) @@ -270,7 +270,7 @@ func (logger *Logger) Println(args ...interface{}) { } func (logger *Logger) Warnln(args ...interface{}) { - if logger.IsWarnEnabled() { + if logger.IsLevelEnabled(WarnLevel) { entry := logger.newEntry() entry.Warnln(args...) logger.releaseEntry(entry) @@ -278,7 +278,7 @@ func (logger *Logger) Warnln(args ...interface{}) { } func (logger *Logger) Warningln(args ...interface{}) { - if logger.IsWarnEnabled() { + if logger.IsLevelEnabled(WarnLevel) { entry := logger.newEntry() entry.Warnln(args...) logger.releaseEntry(entry) @@ -286,7 +286,7 @@ func (logger *Logger) Warningln(args ...interface{}) { } func (logger *Logger) Errorln(args ...interface{}) { - if logger.IsErrorEnabled() { + if logger.IsLevelEnabled(ErrorLevel) { entry := logger.newEntry() entry.Errorln(args...) logger.releaseEntry(entry) @@ -294,7 +294,7 @@ func (logger *Logger) Errorln(args ...interface{}) { } func (logger *Logger) Fatalln(args ...interface{}) { - if logger.IsFatalEnabled() { + if logger.IsLevelEnabled(FatalLevel) { entry := logger.newEntry() entry.Fatalln(args...) logger.releaseEntry(entry) @@ -303,7 +303,7 @@ func (logger *Logger) Fatalln(args ...interface{}) { } func (logger *Logger) Panicln(args ...interface{}) { - if logger.IsPanicEnabled() { + if logger.IsLevelEnabled(PanicLevel) { entry := logger.newEntry() entry.Panicln(args...) logger.releaseEntry(entry) @@ -338,28 +338,11 @@ func (logger *Logger) AddHook(hook Hook) { logger.Hooks.Add(hook) } -func (logger *Logger) IsDebugEnabled() bool { - return logger.level() >= DebugLevel +// IsLevelEnabled checks if the log level of the logger is greater than the level param +func (logger *Logger) IsLevelEnabled(level Level) bool { + return logger.level() >= level } -func (logger *Logger) IsInfoEnabled() bool { - return logger.level() >= InfoLevel -} - -func (logger *Logger) IsWarnEnabled() bool { - return logger.level() >= WarnLevel -} - -func (logger *Logger) IsErrorEnabled() bool { - return logger.level() >= ErrorLevel -} - -func (logger *Logger) IsFatalEnabled() bool { - return logger.level() >= FatalLevel -} - -func (logger *Logger) IsPanicEnabled() bool { - return logger.level() >= PanicLevel // SetFormatter sets the logger formatter. func (logger *Logger) SetFormatter(formatter Formatter) { logger.mu.Lock() diff --git a/logrus.go b/logrus.go index 33e07fa..fa0b9de 100644 --- a/logrus.go +++ b/logrus.go @@ -141,10 +141,10 @@ type FieldLogger interface { Fatalln(args ...interface{}) Panicln(args ...interface{}) - IsDebugEnabled() bool - IsInfoEnabled() bool - IsWarnEnabled() bool - IsErrorEnabled() bool - IsFatalEnabled() bool - IsPanicEnabled() bool + // IsDebugEnabled() bool + // IsInfoEnabled() bool + // IsWarnEnabled() bool + // IsErrorEnabled() bool + // IsFatalEnabled() bool + // IsPanicEnabled() bool } diff --git a/logrus_test.go b/logrus_test.go index 4820e33..97d15d7 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -488,50 +488,50 @@ func TestEntryWriter(t *testing.T) { func TestLogLevelEnabled(t *testing.T) { log := New() log.SetLevel(PanicLevel) - assert.Equal(t, true, log.IsPanicEnabled()) - assert.Equal(t, false, log.IsFatalEnabled()) - assert.Equal(t, false, log.IsErrorEnabled()) - assert.Equal(t, false, log.IsWarnEnabled()) - assert.Equal(t, false, log.IsInfoEnabled()) - assert.Equal(t, false, log.IsDebugEnabled()) + assert.Equal(t, true, log.IsLevelEnabled(PanicLevel)) + assert.Equal(t, false, log.IsLevelEnabled(FatalLevel)) + assert.Equal(t, false, log.IsLevelEnabled(ErrorLevel)) + assert.Equal(t, false, log.IsLevelEnabled(WarnLevel)) + assert.Equal(t, false, log.IsLevelEnabled(InfoLevel)) + assert.Equal(t, false, log.IsLevelEnabled(DebugLevel)) log.SetLevel(FatalLevel) - assert.Equal(t, true, log.IsPanicEnabled()) - assert.Equal(t, true, log.IsFatalEnabled()) - assert.Equal(t, false, log.IsErrorEnabled()) - assert.Equal(t, false, log.IsWarnEnabled()) - assert.Equal(t, false, log.IsInfoEnabled()) - assert.Equal(t, false, log.IsDebugEnabled()) + assert.Equal(t, true, log.IsLevelEnabled(PanicLevel)) + assert.Equal(t, true, log.IsLevelEnabled(FatalLevel)) + assert.Equal(t, false, log.IsLevelEnabled(ErrorLevel)) + assert.Equal(t, false, log.IsLevelEnabled(WarnLevel)) + assert.Equal(t, false, log.IsLevelEnabled(InfoLevel)) + assert.Equal(t, false, log.IsLevelEnabled(DebugLevel)) log.SetLevel(ErrorLevel) - assert.Equal(t, true, log.IsPanicEnabled()) - assert.Equal(t, true, log.IsFatalEnabled()) - assert.Equal(t, true, log.IsErrorEnabled()) - assert.Equal(t, false, log.IsWarnEnabled()) - assert.Equal(t, false, log.IsInfoEnabled()) - assert.Equal(t, false, log.IsDebugEnabled()) + assert.Equal(t, true, log.IsLevelEnabled(PanicLevel)) + assert.Equal(t, true, log.IsLevelEnabled(FatalLevel)) + assert.Equal(t, true, log.IsLevelEnabled(ErrorLevel)) + assert.Equal(t, false, log.IsLevelEnabled(WarnLevel)) + assert.Equal(t, false, log.IsLevelEnabled(InfoLevel)) + assert.Equal(t, false, log.IsLevelEnabled(DebugLevel)) log.SetLevel(WarnLevel) - assert.Equal(t, true, log.IsPanicEnabled()) - assert.Equal(t, true, log.IsFatalEnabled()) - assert.Equal(t, true, log.IsErrorEnabled()) - assert.Equal(t, true, log.IsWarnEnabled()) - assert.Equal(t, false, log.IsInfoEnabled()) - assert.Equal(t, false, log.IsDebugEnabled()) + assert.Equal(t, true, log.IsLevelEnabled(PanicLevel)) + assert.Equal(t, true, log.IsLevelEnabled(FatalLevel)) + assert.Equal(t, true, log.IsLevelEnabled(ErrorLevel)) + assert.Equal(t, true, log.IsLevelEnabled(WarnLevel)) + assert.Equal(t, false, log.IsLevelEnabled(InfoLevel)) + assert.Equal(t, false, log.IsLevelEnabled(DebugLevel)) log.SetLevel(InfoLevel) - assert.Equal(t, true, log.IsPanicEnabled()) - assert.Equal(t, true, log.IsFatalEnabled()) - assert.Equal(t, true, log.IsErrorEnabled()) - assert.Equal(t, true, log.IsWarnEnabled()) - assert.Equal(t, true, log.IsInfoEnabled()) - assert.Equal(t, false, log.IsDebugEnabled()) + assert.Equal(t, true, log.IsLevelEnabled(PanicLevel)) + assert.Equal(t, true, log.IsLevelEnabled(FatalLevel)) + assert.Equal(t, true, log.IsLevelEnabled(ErrorLevel)) + assert.Equal(t, true, log.IsLevelEnabled(WarnLevel)) + assert.Equal(t, true, log.IsLevelEnabled(InfoLevel)) + assert.Equal(t, false, log.IsLevelEnabled(DebugLevel)) log.SetLevel(DebugLevel) - assert.Equal(t, true, log.IsPanicEnabled()) - assert.Equal(t, true, log.IsFatalEnabled()) - assert.Equal(t, true, log.IsErrorEnabled()) - assert.Equal(t, true, log.IsWarnEnabled()) - assert.Equal(t, true, log.IsInfoEnabled()) - assert.Equal(t, true, log.IsDebugEnabled()) + assert.Equal(t, true, log.IsLevelEnabled(PanicLevel)) + assert.Equal(t, true, log.IsLevelEnabled(FatalLevel)) + assert.Equal(t, true, log.IsLevelEnabled(ErrorLevel)) + assert.Equal(t, true, log.IsLevelEnabled(WarnLevel)) + assert.Equal(t, true, log.IsLevelEnabled(InfoLevel)) + assert.Equal(t, true, log.IsLevelEnabled(DebugLevel)) } From 98d0f313feb5dd384db2b29e2c12481d382f8c3c Mon Sep 17 00:00:00 2001 From: David Bariod Date: Thu, 30 Aug 2018 07:17:54 +0200 Subject: [PATCH 58/99] Add previously forgotten v1.0.6 description in changelog fixes #802 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bd1deb..ff40b2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# 1.0.6 + +This new release introduces: + * a new api WithTime which allows to easily force the time of the log entry + which is mostly useful for logger wrapper + * a fix reverting the immutability of the entry given as parameter to the hooks + a new configuration field of the json formatter in order to put all the fields + in a nested dictionnary + * a new SetOutput method in the Logger + * a new configuration of the textformatter to configure the name of the default keys + * a new configuration of the text formatter to disable the level truncation + # 1.0.5 * Fix hooks race (#707) From e58aa84bc1ffea0f9a7fa46891bd659a0b414e97 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Thu, 30 Aug 2018 22:06:53 +0200 Subject: [PATCH 59/99] bump go toolchain version in travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2f19b4a..2bd3496 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: go go: - - 1.9.x - 1.10.x + - 1.11.x env: - GOMAXPROCS=4 GORACE=halt_on_error=1 install: From 7556e245e22e7d23e075791afabadce5cfc4516a Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sun, 2 Sep 2018 20:22:06 +0200 Subject: [PATCH 60/99] Use syslog instead of airbrake as syslog example The purpose is to reduce package dependencies, fixes #809. --- example_hook_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/example_hook_test.go b/example_hook_test.go index d4ddffc..b997c56 100644 --- a/example_hook_test.go +++ b/example_hook_test.go @@ -2,7 +2,8 @@ package logrus_test import ( "github.com/sirupsen/logrus" - "gopkg.in/gemnasium/logrus-airbrake-hook.v2" + slhooks "github.com/sirupsen/logrus/hooks/syslog" + "log/syslog" "os" ) @@ -10,7 +11,9 @@ func Example_hook() { var log = logrus.New() log.Formatter = new(logrus.TextFormatter) // default log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output - log.Hooks.Add(airbrake.NewHook(123, "xyz", "development")) + if sl, err := slhooks.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, ""); err != nil { + log.Hooks.Add(sl) + } log.Out = os.Stdout log.WithFields(logrus.Fields{ From 8b120431f3f374c0365f24021322dda147714c24 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Mon, 3 Sep 2018 21:58:50 +0200 Subject: [PATCH 61/99] Fix example build on windows --- example_hook_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/example_hook_test.go b/example_hook_test.go index b997c56..4e4ea1d 100644 --- a/example_hook_test.go +++ b/example_hook_test.go @@ -1,3 +1,5 @@ +// +build !windows + package logrus_test import ( From 4bcb47b846362274026619052b2dcec92328ab62 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Tue, 4 Sep 2018 22:15:13 +0200 Subject: [PATCH 62/99] commit to trigger appveyor build --- example_hook_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/example_hook_test.go b/example_hook_test.go index 4e4ea1d..28bd8ea 100644 --- a/example_hook_test.go +++ b/example_hook_test.go @@ -9,6 +9,7 @@ import ( "os" ) +// An example on how to use a hook func Example_hook() { var log = logrus.New() log.Formatter = new(logrus.TextFormatter) // default From f75951b604e3ef800fc3e6a3e58486a5323210e6 Mon Sep 17 00:00:00 2001 From: Dave Goddard Date: Thu, 6 Sep 2018 09:49:06 -0300 Subject: [PATCH 63/99] Add go module support Travis will only run the "go get" install lines if using go 1.10.x --- .travis.yml | 17 +++++++++-------- go.mod | 23 +++++++++++++++++++++++ go.sum | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 go.mod create mode 100644 go.sum diff --git a/.travis.yml b/.travis.yml index 2bd3496..de38feb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,14 @@ language: go -go: - - 1.10.x - - 1.11.x env: - GOMAXPROCS=4 GORACE=halt_on_error=1 -install: - - go get github.com/stretchr/testify/assert - - go get gopkg.in/gemnasium/logrus-airbrake-hook.v2 - - go get golang.org/x/sys/unix - - go get golang.org/x/sys/windows +matrix: + include: + - go: 1.10.x + install: + - go get github.com/stretchr/testify/assert + - go get gopkg.in/gemnasium/logrus-airbrake-hook.v2 + - go get golang.org/x/sys/unix + - go get golang.org/x/sys/windows + - go: 1.11.x script: - go test -race -v ./... diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7946fe9 --- /dev/null +++ b/go.mod @@ -0,0 +1,23 @@ +module github.com/dgodd/logrus + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fsnotify/fsnotify v1.4.7 // indirect + github.com/golang/protobuf v1.2.0 // indirect + github.com/hpcloud/tail v1.0.0 // indirect + github.com/onsi/ginkgo v1.6.0 // indirect + github.com/onsi/gomega v1.4.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sirupsen/logrus v1.0.6 + github.com/stretchr/testify v1.2.2 + golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 + golang.org/x/net v0.0.0-20180826012351-8a410e7b638d // indirect + golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect + golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 + golang.org/x/text v0.3.0 // indirect + gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect + gopkg.in/fsnotify.v1 v1.4.7 // indirect + gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/yaml.v2 v2.2.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..24aaefe --- /dev/null +++ b/go.sum @@ -0,0 +1,40 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.1 h1:PZSj/UFNaVp3KxrzHOcS7oyuWA7LoOY/77yCTEFu21U= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 88eb166d31087867eb651f4434839644d8c59a6c Mon Sep 17 00:00:00 2001 From: William Huang Date: Thu, 6 Sep 2018 21:11:16 -0500 Subject: [PATCH 64/99] Fix spelling in Entry.Buffer comment --- entry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entry.go b/entry.go index 8c6a389..4efeddd 100644 --- a/entry.go +++ b/entry.go @@ -41,7 +41,7 @@ type Entry struct { // Message passed to Debug, Info, Warn, Error, Fatal or Panic Message string - // When formatter is called in entry.log(), an Buffer may be set to entry + // When formatter is called in entry.log(), a Buffer may be set to entry Buffer *bytes.Buffer } From 66895ce1655f9b7c12ae5a9df6efe672a041f15d Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sat, 8 Sep 2018 08:27:05 +0200 Subject: [PATCH 65/99] Fix module name and remove unused dependencies --- .travis.yml | 2 ++ go.mod | 16 +--------------- go.sum | 30 ------------------------------ 3 files changed, 3 insertions(+), 45 deletions(-) diff --git a/.travis.yml b/.travis.yml index de38feb..001842f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,5 +10,7 @@ matrix: - go get golang.org/x/sys/unix - go get golang.org/x/sys/windows - go: 1.11.x + install: + - go mod download script: - go test -race -v ./... diff --git a/go.mod b/go.mod index 7946fe9..faee392 100644 --- a/go.mod +++ b/go.mod @@ -1,23 +1,9 @@ -module github.com/dgodd/logrus +module github.com/sirupsen/logrus require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fsnotify/fsnotify v1.4.7 // indirect - github.com/golang/protobuf v1.2.0 // indirect - github.com/hpcloud/tail v1.0.0 // indirect - github.com/onsi/ginkgo v1.6.0 // indirect - github.com/onsi/gomega v1.4.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sirupsen/logrus v1.0.6 github.com/stretchr/testify v1.2.2 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 - golang.org/x/net v0.0.0-20180826012351-8a410e7b638d // indirect - golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 - golang.org/x/text v0.3.0 // indirect - gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect - gopkg.in/fsnotify.v1 v1.4.7 // indirect - gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect - gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect - gopkg.in/yaml.v2 v2.2.1 // indirect ) diff --git a/go.sum b/go.sum index 24aaefe..7fd44a1 100644 --- a/go.sum +++ b/go.sum @@ -1,40 +1,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.4.1 h1:PZSj/UFNaVp3KxrzHOcS7oyuWA7LoOY/77yCTEFu21U= -github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From eed7c223745826b148628edb742f682d5aaf5e49 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sat, 8 Sep 2018 09:44:05 +0200 Subject: [PATCH 66/99] Fix travis build for go 1.11 with modules --- .travis.yml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 001842f..dd1218c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,11 +6,23 @@ matrix: - go: 1.10.x install: - go get github.com/stretchr/testify/assert - - go get gopkg.in/gemnasium/logrus-airbrake-hook.v2 + - go get golang.org/x/crypto/ssh/terminal - go get golang.org/x/sys/unix - go get golang.org/x/sys/windows + script: + - go test -race -v ./... - go: 1.11.x + env: GO111MODULE=on install: - go mod download -script: - - go test -race -v ./... + script: + - go test -race -v ./... + - go: 1.11.x + env: GO111MODULE=off + install: + - go get github.com/stretchr/testify/assert + - go get golang.org/x/crypto/ssh/terminal + - go get golang.org/x/sys/unix + - go get golang.org/x/sys/windows + script: + - go test -race -v ./... From 98c898cc2d6cfa5b8589c1266b9e56cf207981a5 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sun, 16 Sep 2018 10:14:59 +0200 Subject: [PATCH 67/99] Fix gopherjs build constraint name --- terminal_check_windows.go | 2 +- terminal_windows.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/terminal_check_windows.go b/terminal_check_windows.go index 17ebe80..3b9d286 100644 --- a/terminal_check_windows.go +++ b/terminal_check_windows.go @@ -1,4 +1,4 @@ -// +build !appengine,!gopherjs,windows +// +build !appengine,!js,windows package logrus diff --git a/terminal_windows.go b/terminal_windows.go index 2494950..b4ef528 100644 --- a/terminal_windows.go +++ b/terminal_windows.go @@ -1,4 +1,4 @@ -// +build !appengine,!gopherjs,windows +// +build !appengine,!js,windows package logrus From 90501cfcc53d8ff36297203d6e8e0653da98f7d1 Mon Sep 17 00:00:00 2001 From: Mark Dittmer Date: Mon, 17 Sep 2018 13:50:48 -0400 Subject: [PATCH 68/99] Fix AppEngine builds `go build -tags appengine [...]` currently yields: ``` .../github.com/sirupsen/logrus/text_formatter.go:84:4: undefined: initTerminal ``` This change implements `initTerminal` for AppEngine builds. --- terminal_appengine.go | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 terminal_appengine.go diff --git a/terminal_appengine.go b/terminal_appengine.go new file mode 100644 index 0000000..2f34402 --- /dev/null +++ b/terminal_appengine.go @@ -0,0 +1,11 @@ +// Based on ssh/terminal: +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build appengine + +package logrus + +func initTerminal(w io.Writer) { +} From f1ce1baf5623907d7056fc71a6e4232447f01cc5 Mon Sep 17 00:00:00 2001 From: Mark Dittmer Date: Mon, 17 Sep 2018 13:52:43 -0400 Subject: [PATCH 69/99] Fix copypasta --- terminal_appengine.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/terminal_appengine.go b/terminal_appengine.go index 2f34402..72f679c 100644 --- a/terminal_appengine.go +++ b/terminal_appengine.go @@ -1,5 +1,5 @@ // Based on ssh/terminal: -// Copyright 2013 The Go Authors. All rights reserved. +// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -7,5 +7,7 @@ package logrus +import "io" + func initTerminal(w io.Writer) { } From 0a8fc8d77cebf6bb58f90fe8a934a3b516b9e5bd Mon Sep 17 00:00:00 2001 From: Mark Dittmer Date: Mon, 17 Sep 2018 13:56:18 -0400 Subject: [PATCH 70/99] Add AppEngine test configurations to travis to a void regression --- .travis.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.travis.yml b/.travis.yml index dd1218c..1f953be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,3 +26,26 @@ matrix: - go get golang.org/x/sys/windows script: - go test -race -v ./... + - go: 1.10.x + install: + - go get github.com/stretchr/testify/assert + - go get golang.org/x/crypto/ssh/terminal + - go get golang.org/x/sys/unix + - go get golang.org/x/sys/windows + script: + - go test -race -v -tags appengine ./... + - go: 1.11.x + env: GO111MODULE=on + install: + - go mod download + script: + - go test -race -v -tags appengine ./... + - go: 1.11.x + env: GO111MODULE=off + install: + - go get github.com/stretchr/testify/assert + - go get golang.org/x/crypto/ssh/terminal + - go get golang.org/x/sys/unix + - go get golang.org/x/sys/windows + script: + - go test -race -v -tags appengine ./... From 629982b4957da4679c12227a2b3d09dd42f659d2 Mon Sep 17 00:00:00 2001 From: Mark Dittmer Date: Tue, 18 Sep 2018 13:54:23 -0400 Subject: [PATCH 71/99] DisableColors in two tests to fix AppEngine configuration --- example_basic_test.go | 4 +++- example_hook_test.go | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/example_basic_test.go b/example_basic_test.go index a2acf55..5f3849b 100644 --- a/example_basic_test.go +++ b/example_basic_test.go @@ -1,14 +1,16 @@ package logrus_test import ( - "github.com/sirupsen/logrus" "os" + + "github.com/sirupsen/logrus" ) func Example_basic() { var log = logrus.New() log.Formatter = new(logrus.JSONFormatter) log.Formatter = new(logrus.TextFormatter) //default + log.Formatter.(*logrus.TextFormatter).DisableColors = true // remove colors log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output log.Level = logrus.DebugLevel log.Out = os.Stdout diff --git a/example_hook_test.go b/example_hook_test.go index 28bd8ea..15118d2 100644 --- a/example_hook_test.go +++ b/example_hook_test.go @@ -3,16 +3,18 @@ package logrus_test import ( - "github.com/sirupsen/logrus" - slhooks "github.com/sirupsen/logrus/hooks/syslog" "log/syslog" "os" + + "github.com/sirupsen/logrus" + slhooks "github.com/sirupsen/logrus/hooks/syslog" ) // An example on how to use a hook func Example_hook() { var log = logrus.New() log.Formatter = new(logrus.TextFormatter) // default + log.Formatter.(*logrus.TextFormatter).DisableColors = true // remove colors log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output if sl, err := slhooks.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, ""); err != nil { log.Hooks.Add(sl) From 5a88d3c21dcdb8a18079b3f7d6f4113bbc9044c8 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Wed, 19 Sep 2018 13:46:16 +0200 Subject: [PATCH 72/99] Add missing module dependency for windows build --- go.mod | 1 + go.sum | 2 ++ hooks/syslog/syslog_test.go | 2 ++ 3 files changed, 5 insertions(+) diff --git a/go.mod b/go.mod index faee392..f4fed02 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,7 @@ module github.com/sirupsen/logrus require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.2.2 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 diff --git a/go.sum b/go.sum index 7fd44a1..1f0d719 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= diff --git a/hooks/syslog/syslog_test.go b/hooks/syslog/syslog_test.go index 5ec3a44..bec6efd 100644 --- a/hooks/syslog/syslog_test.go +++ b/hooks/syslog/syslog_test.go @@ -1,3 +1,5 @@ +// +build !windows,!nacl,!plan9 + package syslog import ( From 73bc94e60c753099e8bae902f81fbd6e7dd95f26 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Tue, 25 Sep 2018 13:45:23 +0200 Subject: [PATCH 73/99] Add custom sorting function in text formatter --- text_formatter.go | 52 ++++++++++++++++++++++++++++++++---------- text_formatter_test.go | 34 +++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 14 deletions(-) diff --git a/text_formatter.go b/text_formatter.go index 6aee14f..67fb686 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -55,6 +55,9 @@ type TextFormatter struct { // be desired. DisableSorting bool + // The keys sorting function, when uninitialized it uses sort.Strings. + SortingFunc func([]string) + // Disables the truncation of the level text to 4 characters. DisableLevelTruncation bool @@ -73,7 +76,7 @@ type TextFormatter struct { // FieldKeyMsg: "@message"}} FieldMap FieldMap - sync.Once + terminalInitOnce sync.Once } func (f *TextFormatter) init(entry *Entry) { @@ -111,8 +114,29 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { keys = append(keys, k) } + fixedKeys := make([]string, 0, 3+len(entry.Data)) + if !f.DisableTimestamp { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime)) + } + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel)) + if entry.Message != "" { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg)) + } + if !f.DisableSorting { - sort.Strings(keys) + if f.SortingFunc == nil { + sort.Strings(keys) + fixedKeys = append(fixedKeys, keys...) + } else { + if !f.isColored() { + fixedKeys = append(fixedKeys, keys...) + f.SortingFunc(fixedKeys) + } else { + f.SortingFunc(keys) + } + } + } else { + fixedKeys = append(fixedKeys, keys...) } var b *bytes.Buffer @@ -122,7 +146,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { b = &bytes.Buffer{} } - f.Do(func() { f.init(entry) }) + f.terminalInitOnce.Do(func() { f.init(entry) }) timestampFormat := f.TimestampFormat if timestampFormat == "" { @@ -131,15 +155,19 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { if f.isColored() { f.printColored(b, entry, keys, timestampFormat) } else { - if !f.DisableTimestamp { - f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyTime), entry.Time.Format(timestampFormat)) - } - f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyLevel), entry.Level.String()) - if entry.Message != "" { - f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyMsg), entry.Message) - } - for _, key := range keys { - f.appendKeyValue(b, key, entry.Data[key]) + for _, key := range fixedKeys { + var value interface{} + switch key { + case f.FieldMap.resolve(FieldKeyTime): + value = entry.Time.Format(timestampFormat) + case f.FieldMap.resolve(FieldKeyLevel): + value = entry.Level.String() + case f.FieldMap.resolve(FieldKeyMsg): + value = entry.Message + default: + value = entry.Data[key] + } + f.appendKeyValue(b, key, value) } } diff --git a/text_formatter_test.go b/text_formatter_test.go index b57ef7c..b0d3a91 100644 --- a/text_formatter_test.go +++ b/text_formatter_test.go @@ -5,11 +5,13 @@ import ( "errors" "fmt" "os" + "sort" "strings" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestFormatting(t *testing.T) { @@ -446,5 +448,33 @@ func TestTextFormatterIsColored(t *testing.T) { } } -// TODO add tests for sorting etc., this requires a parser for the text -// formatter output. +func TestCustomSorting(t *testing.T) { + formatter := &TextFormatter{ + DisableColors: true, + SortingFunc: func(keys []string) { + sort.Slice(keys, func(i, j int) bool { + if keys[j] == "prefix" { + return false + } + if keys[i] == "prefix" { + return true + } + return strings.Compare(keys[i], keys[j]) == -1 + }) + }, + } + + entry := &Entry{ + Message: "Testing custom sort function", + Time: time.Now(), + Level: InfoLevel, + Data: Fields{ + "test": "testvalue", + "prefix": "the application prefix", + "blablabla": "blablabla", + }, + } + b, err := formatter.Format(entry) + require.NoError(t, err) + require.True(t, strings.HasPrefix(string(b), "prefix="), "format output is %q", string(b)) +} From a67f783a3814b8729bd2dac5780b5f78f8dbd64d Mon Sep 17 00:00:00 2001 From: David Bariod Date: Tue, 25 Sep 2018 21:35:18 +0200 Subject: [PATCH 74/99] Update changelog for v1.1.0 release --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff40b2a..1702696 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +# 1.1.0 +This new release introduces: + * several fixes: + * a fix for a race condition on entry formatting + * proper cleanup of previously used entries before putting them back in the pool + * the extra new line at the end of message in text formatter has been removed + * a new global public API to check if a level is activated: IsLevelEnabled + * the following methods have been added to the Logger object + * IsLevelEnabled + * SetFormatter + * SetOutput + * ReplaceHooks + * introduction of go module + * an indent configuration for the json formatter + * output colour support for windows + * the field sort function is now configurable for text formatter + * the CLICOLOR and CLICOLOR\_FORCE environment variable support in text formater + # 1.0.6 This new release introduces: From 7b467df6975c8092bbc1aea2184c13ee73b8ee8a Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sun, 30 Sep 2018 22:51:02 +0200 Subject: [PATCH 75/99] Skip func type value in fields. We skip those unprintable fields and an error field instead of dropping the whole trace. Fixes #642 --- entry.go | 16 ++++++++++++++-- formatter.go | 15 ++++++++++++++- json_formatter.go | 10 +++------- logger_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ text_formatter.go | 7 ++++++- 5 files changed, 79 insertions(+), 11 deletions(-) create mode 100644 logger_test.go diff --git a/entry.go b/entry.go index 4efeddd..ca634a6 100644 --- a/entry.go +++ b/entry.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "os" + "reflect" "sync" "time" ) @@ -43,6 +44,9 @@ type Entry struct { // When formatter is called in entry.log(), a Buffer may be set to entry Buffer *bytes.Buffer + + // err may contain a field formatting error + err string } func NewEntry(logger *Logger) *Entry { @@ -80,10 +84,18 @@ func (entry *Entry) WithFields(fields Fields) *Entry { for k, v := range entry.Data { data[k] = v } + var field_err string for k, v := range fields { - data[k] = v + if t := reflect.TypeOf(v); t != nil && t.Kind() == reflect.Func { + field_err = fmt.Sprintf("can not add field %q", k) + if entry.err != "" { + field_err = entry.err + ", " + field_err + } + } else { + data[k] = v + } } - return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time} + return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: field_err} } // Overrides the time of the Entry. diff --git a/formatter.go b/formatter.go index 83c7494..be2f3fc 100644 --- a/formatter.go +++ b/formatter.go @@ -2,7 +2,14 @@ package logrus import "time" -const defaultTimestampFormat = time.RFC3339 +// Default key names for the default fields +const ( + defaultTimestampFormat = time.RFC3339 + FieldKeyMsg = "msg" + FieldKeyLevel = "level" + FieldKeyTime = "time" + FieldKeyLogrusError = "logrus_error" +) // The Formatter interface is used to implement a custom Formatter. It takes an // `Entry`. It exposes all the fields, including the default ones: @@ -48,4 +55,10 @@ func prefixFieldClashes(data Fields, fieldMap FieldMap) { data["fields."+levelKey] = l delete(data, levelKey) } + + logrusErrKey := fieldMap.resolve(FieldKeyLogrusError) + if l, ok := data[logrusErrKey]; ok { + data["fields."+logrusErrKey] = l + delete(data, logrusErrKey) + } } diff --git a/json_formatter.go b/json_formatter.go index d3dadef..ef8d074 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -11,13 +11,6 @@ type fieldKey string // FieldMap allows customization of the key names for default fields. type FieldMap map[fieldKey]string -// Default key names for the default fields -const ( - FieldKeyMsg = "msg" - FieldKeyLevel = "level" - FieldKeyTime = "time" -) - func (f FieldMap) resolve(key fieldKey) string { if k, ok := f[key]; ok { return k @@ -79,6 +72,9 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { timestampFormat = defaultTimestampFormat } + if entry.err != "" { + data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err + } if !f.DisableTimestamp { data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat) } diff --git a/logger_test.go b/logger_test.go new file mode 100644 index 0000000..73ba450 --- /dev/null +++ b/logger_test.go @@ -0,0 +1,42 @@ +package logrus + +import ( + "bytes" + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFieldValueError(t *testing.T) { + buf := &bytes.Buffer{} + l := &Logger{ + Out: buf, + Formatter: new(JSONFormatter), + Hooks: make(LevelHooks), + Level: DebugLevel, + } + l.WithField("func", func() {}).Info("test") + fmt.Println(buf.String()) + var data map[string]interface{} + json.Unmarshal(buf.Bytes(), &data) + _, ok := data[FieldKeyLogrusError] + require.True(t, ok) +} + +func TestNoFieldValueError(t *testing.T) { + buf := &bytes.Buffer{} + l := &Logger{ + Out: buf, + Formatter: new(JSONFormatter), + Hooks: make(LevelHooks), + Level: DebugLevel, + } + l.WithField("str", "str").Info("test") + fmt.Println(buf.String()) + var data map[string]interface{} + json.Unmarshal(buf.Bytes(), &data) + _, ok := data[FieldKeyLogrusError] + require.False(t, ok) +} diff --git a/text_formatter.go b/text_formatter.go index 67fb686..d4663b8 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -114,7 +114,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { keys = append(keys, k) } - fixedKeys := make([]string, 0, 3+len(entry.Data)) + fixedKeys := make([]string, 0, 4+len(entry.Data)) if !f.DisableTimestamp { fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime)) } @@ -122,6 +122,9 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { if entry.Message != "" { fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg)) } + if entry.err != "" { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError)) + } if !f.DisableSorting { if f.SortingFunc == nil { @@ -164,6 +167,8 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { value = entry.Level.String() case f.FieldMap.resolve(FieldKeyMsg): value = entry.Message + case f.FieldMap.resolve(FieldKeyLogrusError): + value = entry.err default: value = entry.Data[key] } From 2be620216affd84620a08ee082d6e074d1bb5ca4 Mon Sep 17 00:00:00 2001 From: Albert Salim Date: Sat, 6 Oct 2018 18:08:19 +0800 Subject: [PATCH 76/99] Add option to panic in `test.NewNullLogger` to allow testing of calls to `Fatal*` See #813 --- alt_exit.go | 2 ++ entry.go | 6 +++--- hooks/test/test.go | 14 +++++++++++++- hooks/test/test_test.go | 11 +++++++++++ logger.go | 9 ++++++--- 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/alt_exit.go b/alt_exit.go index 8af9063..f1bb44c 100644 --- a/alt_exit.go +++ b/alt_exit.go @@ -45,6 +45,8 @@ func runHandlers() { } } +type exitFunc func(int) + // Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code) func Exit(code int) { runHandlers() diff --git a/entry.go b/entry.go index ca634a6..225bdb6 100644 --- a/entry.go +++ b/entry.go @@ -198,7 +198,7 @@ func (entry *Entry) Fatal(args ...interface{}) { if entry.Logger.IsLevelEnabled(FatalLevel) { entry.log(FatalLevel, fmt.Sprint(args...)) } - Exit(1) + entry.Logger.Exit(1) } func (entry *Entry) Panic(args ...interface{}) { @@ -246,7 +246,7 @@ func (entry *Entry) Fatalf(format string, args ...interface{}) { if entry.Logger.IsLevelEnabled(FatalLevel) { entry.Fatal(fmt.Sprintf(format, args...)) } - Exit(1) + entry.Logger.Exit(1) } func (entry *Entry) Panicf(format string, args ...interface{}) { @@ -293,7 +293,7 @@ func (entry *Entry) Fatalln(args ...interface{}) { if entry.Logger.IsLevelEnabled(FatalLevel) { entry.Fatal(entry.sprintlnn(args...)) } - Exit(1) + entry.Logger.Exit(1) } func (entry *Entry) Panicln(args ...interface{}) { diff --git a/hooks/test/test.go b/hooks/test/test.go index 234a17d..f84fe80 100644 --- a/hooks/test/test.go +++ b/hooks/test/test.go @@ -39,12 +39,24 @@ func NewLocal(logger *logrus.Logger) *Hook { } +type TestOption func(logger *logrus.Logger) + +func FatalPanics(logger *logrus.Logger) { + logger.Exit = func(code int) { + panic(code) + } +} + // NewNullLogger creates a discarding logger and installs the test hook. -func NewNullLogger() (*logrus.Logger, *Hook) { +func NewNullLogger(options ...TestOption) (*logrus.Logger, *Hook) { logger := logrus.New() logger.Out = ioutil.Discard + for _, option := range options { + option(logger) + } + return logger, NewLocal(logger) } diff --git a/hooks/test/test_test.go b/hooks/test/test_test.go index d6f6d30..692d36a 100644 --- a/hooks/test/test_test.go +++ b/hooks/test/test_test.go @@ -71,3 +71,14 @@ func TestLoggingWithHooksRace(t *testing.T) { entries := hook.AllEntries() assert.Equal(100, len(entries)) } + +func TestFatalWithPanic(t *testing.T) { + assert := assert.New(t) + + logger, hook := NewNullLogger(FatalPanics) + + assert.Nil(hook.LastEntry()) + assert.Equal(0, len(hook.Entries)) + + assert.Panics(func() { logger.Fatal("something went wrong") }) +} diff --git a/logger.go b/logger.go index b67bfcb..188c600 100644 --- a/logger.go +++ b/logger.go @@ -32,6 +32,8 @@ type Logger struct { mu MutexWrap // Reusable empty entry entryPool sync.Pool + // Function to exit the application, defaults to `Exit()` + Exit exitFunc } type MutexWrap struct { @@ -73,6 +75,7 @@ func New() *Logger { Formatter: new(TextFormatter), Hooks: make(LevelHooks), Level: InfoLevel, + Exit: Exit, } } @@ -173,7 +176,7 @@ func (logger *Logger) Fatalf(format string, args ...interface{}) { entry.Fatalf(format, args...) logger.releaseEntry(entry) } - Exit(1) + logger.Exit(1) } func (logger *Logger) Panicf(format string, args ...interface{}) { @@ -236,7 +239,7 @@ func (logger *Logger) Fatal(args ...interface{}) { entry.Fatal(args...) logger.releaseEntry(entry) } - Exit(1) + logger.Exit(1) } func (logger *Logger) Panic(args ...interface{}) { @@ -299,7 +302,7 @@ func (logger *Logger) Fatalln(args ...interface{}) { entry.Fatalln(args...) logger.releaseEntry(entry) } - Exit(1) + logger.Exit(1) } func (logger *Logger) Panicln(args ...interface{}) { From 3f90cee1e41a38ba27c831844c002952512997c0 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sat, 6 Oct 2018 15:21:56 +0200 Subject: [PATCH 77/99] Rationalize os specific build constraints --- terminal_appengine.go | 13 ------------- terminal_bsd.go | 17 ----------------- terminal_linux.go | 21 --------------------- terminal_notwindows.go | 8 ++++++++ 4 files changed, 8 insertions(+), 51 deletions(-) delete mode 100644 terminal_appengine.go delete mode 100644 terminal_bsd.go delete mode 100644 terminal_linux.go create mode 100644 terminal_notwindows.go diff --git a/terminal_appengine.go b/terminal_appengine.go deleted file mode 100644 index 72f679c..0000000 --- a/terminal_appengine.go +++ /dev/null @@ -1,13 +0,0 @@ -// Based on ssh/terminal: -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build appengine - -package logrus - -import "io" - -func initTerminal(w io.Writer) { -} diff --git a/terminal_bsd.go b/terminal_bsd.go deleted file mode 100644 index 62ca252..0000000 --- a/terminal_bsd.go +++ /dev/null @@ -1,17 +0,0 @@ -// +build darwin freebsd openbsd netbsd dragonfly -// +build !appengine,!js - -package logrus - -import ( - "io" - - "golang.org/x/sys/unix" -) - -const ioctlReadTermios = unix.TIOCGETA - -type Termios unix.Termios - -func initTerminal(w io.Writer) { -} diff --git a/terminal_linux.go b/terminal_linux.go deleted file mode 100644 index 18066f0..0000000 --- a/terminal_linux.go +++ /dev/null @@ -1,21 +0,0 @@ -// Based on ssh/terminal: -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !appengine,!js - -package logrus - -import ( - "io" - - "golang.org/x/sys/unix" -) - -const ioctlReadTermios = unix.TCGETS - -type Termios unix.Termios - -func initTerminal(w io.Writer) { -} diff --git a/terminal_notwindows.go b/terminal_notwindows.go new file mode 100644 index 0000000..3dbd237 --- /dev/null +++ b/terminal_notwindows.go @@ -0,0 +1,8 @@ +// +build !windows + +package logrus + +import "io" + +func initTerminal(w io.Writer) { +} From ad15b42461921f1fb3529b058c6786c6a45d5162 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Mon, 8 Oct 2018 22:30:39 +0200 Subject: [PATCH 78/99] Update changelog for v1.1.1 release --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1702696..ff04718 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.1.1 +This is a bug fix release. + * fix the build break on Solaris + * don't drop a whole trace in JSONFormatter when a field param is a function pointer which can not be serialized + # 1.1.0 This new release introduces: * several fixes: From 99bc300c8d60c2f531a4690533f63273e4fbaaf7 Mon Sep 17 00:00:00 2001 From: Albert Salim Date: Wed, 10 Oct 2018 21:54:15 +0800 Subject: [PATCH 79/99] Add a method Exit on Logger that calls `os.Exit` or alternate exit function. This keeps backward compatibility for static declaration of logger that does not specify `ExitFunc` field. --- alt_exit.go | 8 ++++++-- hooks/test/test.go | 14 +------------- hooks/test/test_test.go | 13 +++++++------ logger.go | 14 +++++++++++--- logrus.go | 2 +- 5 files changed, 26 insertions(+), 25 deletions(-) diff --git a/alt_exit.go b/alt_exit.go index f1bb44c..183db77 100644 --- a/alt_exit.go +++ b/alt_exit.go @@ -45,11 +45,15 @@ func runHandlers() { } } -type exitFunc func(int) - // Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code) func Exit(code int) { runHandlers() + osExit(code) +} + +type exitFunc func(int) + +func osExit(code int) { os.Exit(code) } diff --git a/hooks/test/test.go b/hooks/test/test.go index f84fe80..234a17d 100644 --- a/hooks/test/test.go +++ b/hooks/test/test.go @@ -39,24 +39,12 @@ func NewLocal(logger *logrus.Logger) *Hook { } -type TestOption func(logger *logrus.Logger) - -func FatalPanics(logger *logrus.Logger) { - logger.Exit = func(code int) { - panic(code) - } -} - // NewNullLogger creates a discarding logger and installs the test hook. -func NewNullLogger(options ...TestOption) (*logrus.Logger, *Hook) { +func NewNullLogger() (*logrus.Logger, *Hook) { logger := logrus.New() logger.Out = ioutil.Discard - for _, option := range options { - option(logger) - } - return logger, NewLocal(logger) } diff --git a/hooks/test/test_test.go b/hooks/test/test_test.go index 692d36a..636bad5 100644 --- a/hooks/test/test_test.go +++ b/hooks/test/test_test.go @@ -72,13 +72,14 @@ func TestLoggingWithHooksRace(t *testing.T) { assert.Equal(100, len(entries)) } -func TestFatalWithPanic(t *testing.T) { +func TestFatalWithAlternateExit(t *testing.T) { assert := assert.New(t) - logger, hook := NewNullLogger(FatalPanics) + logger, hook := NewNullLogger() + logger.ExitFunc = func(code int) {} - assert.Nil(hook.LastEntry()) - assert.Equal(0, len(hook.Entries)) - - assert.Panics(func() { logger.Fatal("something went wrong") }) + logger.Fatal("something went very wrong") + assert.Equal(logrus.FatalLevel, hook.LastEntry().Level) + assert.Equal("something went very wrong", hook.LastEntry().Message) + assert.Equal(1, len(hook.Entries)) } diff --git a/logger.go b/logger.go index 188c600..364819e 100644 --- a/logger.go +++ b/logger.go @@ -32,8 +32,8 @@ type Logger struct { mu MutexWrap // Reusable empty entry entryPool sync.Pool - // Function to exit the application, defaults to `Exit()` - Exit exitFunc + // Function to exit the application, defaults to `osExit()` + ExitFunc exitFunc } type MutexWrap struct { @@ -75,7 +75,7 @@ func New() *Logger { Formatter: new(TextFormatter), Hooks: make(LevelHooks), Level: InfoLevel, - Exit: Exit, + ExitFunc: osExit, } } @@ -313,6 +313,14 @@ func (logger *Logger) Panicln(args ...interface{}) { } } +func (logger *Logger) Exit(code int) { + runHandlers() + if logger.ExitFunc == nil { + logger.ExitFunc = osExit + } + logger.ExitFunc(code) +} + //When file is opened with appending mode, it's safe to //write concurrently to a file (within 4k message on Linux). //In these cases user can choose to disable the lock. diff --git a/logrus.go b/logrus.go index fa0b9de..6fff506 100644 --- a/logrus.go +++ b/logrus.go @@ -69,7 +69,7 @@ const ( // PanicLevel level, highest level of severity. Logs and then calls panic with the // message passed to Debug, Info, ... PanicLevel Level = iota - // FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the + // FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the // logging level is set to Panic. FatalLevel // ErrorLevel level. Logs. Used for errors that should definitely be noted. From 4346c76f26ec031258a1d8f5f08cfb829f74611e Mon Sep 17 00:00:00 2001 From: Albert Salim Date: Wed, 10 Oct 2018 21:57:58 +0800 Subject: [PATCH 80/99] Remove unnecessary wrapper function on `os.Exit` --- alt_exit.go | 6 ------ logger.go | 6 ++++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/alt_exit.go b/alt_exit.go index 183db77..8af9063 100644 --- a/alt_exit.go +++ b/alt_exit.go @@ -48,12 +48,6 @@ func runHandlers() { // Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code) func Exit(code int) { runHandlers() - osExit(code) -} - -type exitFunc func(int) - -func osExit(code int) { os.Exit(code) } diff --git a/logger.go b/logger.go index 364819e..21f49ec 100644 --- a/logger.go +++ b/logger.go @@ -36,6 +36,8 @@ type Logger struct { ExitFunc exitFunc } +type exitFunc func(int) + type MutexWrap struct { lock sync.Mutex disabled bool @@ -75,7 +77,7 @@ func New() *Logger { Formatter: new(TextFormatter), Hooks: make(LevelHooks), Level: InfoLevel, - ExitFunc: osExit, + ExitFunc: os.Exit, } } @@ -316,7 +318,7 @@ func (logger *Logger) Panicln(args ...interface{}) { func (logger *Logger) Exit(code int) { runHandlers() if logger.ExitFunc == nil { - logger.ExitFunc = osExit + logger.ExitFunc = os.Exit } logger.ExitFunc(code) } From a13c5db57c07b6f81ed4ad333c219966151ec30a Mon Sep 17 00:00:00 2001 From: Albert Salim Date: Wed, 10 Oct 2018 21:59:03 +0800 Subject: [PATCH 81/99] Fix typo in comment --- logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logger.go b/logger.go index 21f49ec..1c934ed 100644 --- a/logger.go +++ b/logger.go @@ -32,7 +32,7 @@ type Logger struct { mu MutexWrap // Reusable empty entry entryPool sync.Pool - // Function to exit the application, defaults to `osExit()` + // Function to exit the application, defaults to `os.Exit()` ExitFunc exitFunc } From f2ab87f230e2c652a48c789c24add4e17dce84e9 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Mon, 15 Oct 2018 21:20:03 +0200 Subject: [PATCH 82/99] Add an example for tracing global variable with hook --- example_global_hook_test.go | 37 +++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 ++ 3 files changed, 40 insertions(+) create mode 100644 example_global_hook_test.go diff --git a/example_global_hook_test.go b/example_global_hook_test.go new file mode 100644 index 0000000..df1584c --- /dev/null +++ b/example_global_hook_test.go @@ -0,0 +1,37 @@ +package logrus_test + +import ( + "github.com/sirupsen/logrus" + "os" +) + +var ( + mystring string +) + +type GlobalHook struct { +} + +func (h *GlobalHook) Levels() []logrus.Level { + return logrus.AllLevels +} + +func (h *GlobalHook) Fire(e *logrus.Entry) error { + e.Data["mystring"] = mystring + return nil +} + +func Example() { + l := logrus.New() + l.Out = os.Stdout + l.Formatter = &logrus.TextFormatter{DisableTimestamp: true} + l.Formatter.(*logrus.TextFormatter).DisableTimestamp = true + l.AddHook(&GlobalHook{}) + mystring = "first value" + l.Info("first log") + mystring = "another value" + l.Info("second log") + // Output: + // level=info msg="first log" mystring="first value" + // level=info msg="second log" mystring="another value" +} diff --git a/go.mod b/go.mod index f4fed02..79f11b7 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.1.1 // indirect github.com/stretchr/testify v1.2.2 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 diff --git a/go.sum b/go.sum index 1f0d719..16cab75 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f26 github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= From 9c7692ccff2dca67e6db9b6eaef21a2c37ad82e7 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Mon, 15 Oct 2018 21:32:20 +0200 Subject: [PATCH 83/99] disable colors on hook example --- example_global_hook_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/example_global_hook_test.go b/example_global_hook_test.go index df1584c..c81e448 100644 --- a/example_global_hook_test.go +++ b/example_global_hook_test.go @@ -24,8 +24,7 @@ func (h *GlobalHook) Fire(e *logrus.Entry) error { func Example() { l := logrus.New() l.Out = os.Stdout - l.Formatter = &logrus.TextFormatter{DisableTimestamp: true} - l.Formatter.(*logrus.TextFormatter).DisableTimestamp = true + l.Formatter = &logrus.TextFormatter{DisableTimestamp: true, DisableColors: true} l.AddHook(&GlobalHook{}) mystring = "first value" l.Info("first log") From 4981d8161c238636b2ca8cf6b4e0d717716452ee Mon Sep 17 00:00:00 2001 From: drampull Date: Fri, 20 Oct 2017 08:40:54 -0400 Subject: [PATCH 84/99] Added TRACE level logging. --- .gitignore | 1 + entry.go | 18 ++++++++++++++++++ example_basic_test.go | 8 +++++++- exported.go | 15 +++++++++++++++ hook_test.go | 2 ++ hooks/syslog/syslog.go | 2 +- logger.go | 24 ++++++++++++++++++++++++ logrus.go | 10 ++++++++++ text_formatter.go | 2 +- writer.go | 2 ++ 10 files changed, 81 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 66be63a..6b7d7d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ logrus +vendor diff --git a/entry.go b/entry.go index 225bdb6..eb4ae5c 100644 --- a/entry.go +++ b/entry.go @@ -168,6 +168,12 @@ func (entry *Entry) Debug(args ...interface{}) { } } +func (entry *Entry) Trace(args ...interface{}) { + if entry.Logger.IsLevelEnabled(TraceLevel) { + entry.log(TraceLevel, fmt.Sprint(args...)) + } +} + func (entry *Entry) Print(args ...interface{}) { entry.Info(args...) } @@ -216,6 +222,12 @@ func (entry *Entry) Debugf(format string, args ...interface{}) { } } +func (entry *Entry) Tracef(format string, args ...interface{}) { + if entry.Logger.IsLevelEnabled(TraceLevel) { + entry.Trace(fmt.Sprintf(format, args...)) + } +} + func (entry *Entry) Infof(format string, args ...interface{}) { if entry.Logger.IsLevelEnabled(InfoLevel) { entry.Info(fmt.Sprintf(format, args...)) @@ -257,6 +269,12 @@ func (entry *Entry) Panicf(format string, args ...interface{}) { // Entry Println family functions +func (entry *Entry) Traceln(args ...interface{}) { + if entry.Logger.IsLevelEnabled(TraceLevel) { + entry.Trace(entry.sprintlnn(args...)) + } +} + func (entry *Entry) Debugln(args ...interface{}) { if entry.Logger.IsLevelEnabled(DebugLevel) { entry.Debug(entry.sprintlnn(args...)) diff --git a/example_basic_test.go b/example_basic_test.go index 5f3849b..9ff5655 100644 --- a/example_basic_test.go +++ b/example_basic_test.go @@ -12,7 +12,7 @@ func Example_basic() { log.Formatter = new(logrus.TextFormatter) //default log.Formatter.(*logrus.TextFormatter).DisableColors = true // remove colors log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output - log.Level = logrus.DebugLevel + log.Level = logrus.TraceLevel log.Out = os.Stdout // file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666) @@ -37,6 +37,11 @@ func Example_basic() { } }() + log.WithFields(logrus.Fields{ + "animal": "walrus", + "number": 0, + }).Trace("Went to the beach") + log.WithFields(logrus.Fields{ "animal": "walrus", "number": 8, @@ -62,6 +67,7 @@ func Example_basic() { }).Panic("It's over 9000!") // Output: + // level=trace msg="Went to the beach" animal=walrus number=0 // level=debug msg="Started observing beach" animal=walrus number=8 // level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10 // level=warning msg="The group's number increased tremendously!" number=122 omg=true diff --git a/exported.go b/exported.go index fb2a7a1..fb261aa 100644 --- a/exported.go +++ b/exported.go @@ -77,6 +77,11 @@ func WithTime(t time.Time) *Entry { return std.WithTime(t) } +// Trace logs a message at level Trace on the standard logger. +func Trace(args ...interface{}) { + std.Trace(args...) +} + // Debug logs a message at level Debug on the standard logger. func Debug(args ...interface{}) { std.Debug(args...) @@ -117,6 +122,11 @@ func Fatal(args ...interface{}) { std.Fatal(args...) } +// Tracef logs a message at level Debug on the standard logger. +func Tracef(format string, args ...interface{}) { + std.Tracef(format, args...) +} + // Debugf logs a message at level Debug on the standard logger. func Debugf(format string, args ...interface{}) { std.Debugf(format, args...) @@ -157,6 +167,11 @@ func Fatalf(format string, args ...interface{}) { std.Fatalf(format, args...) } +// Traceln logs a message at level Debug on the standard logger. +func Traceln(args ...interface{}) { + std.Traceln(args...) +} + // Debugln logs a message at level Debug on the standard logger. func Debugln(args ...interface{}) { std.Debugln(args...) diff --git a/hook_test.go b/hook_test.go index 80b93b8..7196e99 100644 --- a/hook_test.go +++ b/hook_test.go @@ -21,6 +21,7 @@ func (hook *TestHook) Fire(entry *Entry) error { func (hook *TestHook) Levels() []Level { return []Level{ + TraceLevel, DebugLevel, InfoLevel, WarnLevel, @@ -53,6 +54,7 @@ func (hook *ModifyHook) Fire(entry *Entry) error { func (hook *ModifyHook) Levels() []Level { return []Level{ + TraceLevel, DebugLevel, InfoLevel, WarnLevel, diff --git a/hooks/syslog/syslog.go b/hooks/syslog/syslog.go index 329ce0d..02b8df3 100644 --- a/hooks/syslog/syslog.go +++ b/hooks/syslog/syslog.go @@ -43,7 +43,7 @@ func (hook *SyslogHook) Fire(entry *logrus.Entry) error { return hook.Writer.Warning(line) case logrus.InfoLevel: return hook.Writer.Info(line) - case logrus.DebugLevel: + case logrus.DebugLevel, logrus.TraceLevel: return hook.Writer.Debug(line) default: return nil diff --git a/logger.go b/logger.go index 1c934ed..52f1761 100644 --- a/logger.go +++ b/logger.go @@ -126,6 +126,14 @@ func (logger *Logger) WithTime(t time.Time) *Entry { return entry.WithTime(t) } +func (logger *Logger) Tracef(format string, args ...interface{}) { + if logger.IsLevelEnabled(TraceLevel) { + entry := logger.newEntry() + entry.Tracef(format, args...) + logger.releaseEntry(entry) + } +} + func (logger *Logger) Debugf(format string, args ...interface{}) { if logger.IsLevelEnabled(DebugLevel) { entry := logger.newEntry() @@ -189,6 +197,14 @@ func (logger *Logger) Panicf(format string, args ...interface{}) { } } +func (logger *Logger) Trace(args ...interface{}) { + if logger.IsLevelEnabled(TraceLevel) { + entry := logger.newEntry() + entry.Trace(args...) + logger.releaseEntry(entry) + } +} + func (logger *Logger) Debug(args ...interface{}) { if logger.IsLevelEnabled(DebugLevel) { entry := logger.newEntry() @@ -252,6 +268,14 @@ func (logger *Logger) Panic(args ...interface{}) { } } +func (logger *Logger) Traceln(args ...interface{}) { + if logger.IsLevelEnabled(TraceLevel) { + entry := logger.newEntry() + entry.Traceln(args...) + logger.releaseEntry(entry) + } +} + func (logger *Logger) Debugln(args ...interface{}) { if logger.IsLevelEnabled(DebugLevel) { entry := logger.newEntry() diff --git a/logrus.go b/logrus.go index 6fff506..601d5e0 100644 --- a/logrus.go +++ b/logrus.go @@ -15,6 +15,8 @@ type Level uint32 // Convert the Level to a string. E.g. PanicLevel becomes "panic". func (level Level) String() string { switch level { + case TraceLevel: + return "trace" case DebugLevel: return "debug" case InfoLevel: @@ -47,6 +49,8 @@ func ParseLevel(lvl string) (Level, error) { return InfoLevel, nil case "debug": return DebugLevel, nil + case "trace": + return TraceLevel, nil } var l Level @@ -61,6 +65,7 @@ var AllLevels = []Level{ WarnLevel, InfoLevel, DebugLevel, + TraceLevel, } // These are the different logging levels. You can set the logging level to log @@ -82,6 +87,8 @@ const ( InfoLevel // DebugLevel level. Usually only enabled when debugging. Very verbose logging. DebugLevel + // TraceLevel level. Usually only enabled when debugging. Very verbose logging. Usually reserved for message traces + TraceLevel ) // Won't compile if StdLogger can't be realized by a log.Logger @@ -114,6 +121,7 @@ type FieldLogger interface { WithFields(fields Fields) *Entry WithError(err error) *Entry + Tracef(format string, args ...interface{}) Debugf(format string, args ...interface{}) Infof(format string, args ...interface{}) Printf(format string, args ...interface{}) @@ -123,6 +131,7 @@ type FieldLogger interface { Fatalf(format string, args ...interface{}) Panicf(format string, args ...interface{}) + Trace(args ...interface{}) Debug(args ...interface{}) Info(args ...interface{}) Print(args ...interface{}) @@ -132,6 +141,7 @@ type FieldLogger interface { Fatal(args ...interface{}) Panic(args ...interface{}) + Traceln(args ...interface{}) Debugln(args ...interface{}) Infoln(args ...interface{}) Println(args ...interface{}) diff --git a/text_formatter.go b/text_formatter.go index d4663b8..74dffcf 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -183,7 +183,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) { var levelColor int switch entry.Level { - case DebugLevel: + case DebugLevel, TraceLevel: levelColor = gray case WarnLevel: levelColor = yellow diff --git a/writer.go b/writer.go index 7bdebed..9e1f751 100644 --- a/writer.go +++ b/writer.go @@ -24,6 +24,8 @@ func (entry *Entry) WriterLevel(level Level) *io.PipeWriter { var printFunc func(args ...interface{}) switch level { + case TraceLevel: + printFunc = entry.Trace case DebugLevel: printFunc = entry.Debug case InfoLevel: From c7a33dc5de7efe87c53ee9598ab7b73a508f0be7 Mon Sep 17 00:00:00 2001 From: Giedrius Dubinskas Date: Mon, 2 Oct 2017 19:38:29 +0300 Subject: [PATCH 85/99] Add Trace level logging --- exported.go | 4 ++-- logrus.go | 3 ++- logrus_test.go | 9 +++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/exported.go b/exported.go index fb261aa..b9f1c9d 100644 --- a/exported.go +++ b/exported.go @@ -122,7 +122,7 @@ func Fatal(args ...interface{}) { std.Fatal(args...) } -// Tracef logs a message at level Debug on the standard logger. +// Tracef logs a message at level Trace on the standard logger. func Tracef(format string, args ...interface{}) { std.Tracef(format, args...) } @@ -167,7 +167,7 @@ func Fatalf(format string, args ...interface{}) { std.Fatalf(format, args...) } -// Traceln logs a message at level Debug on the standard logger. +// Traceln logs a message at level Trace on the standard logger. func Traceln(args ...interface{}) { std.Traceln(args...) } diff --git a/logrus.go b/logrus.go index 601d5e0..d657c41 100644 --- a/logrus.go +++ b/logrus.go @@ -87,7 +87,8 @@ const ( InfoLevel // DebugLevel level. Usually only enabled when debugging. Very verbose logging. DebugLevel - // TraceLevel level. Usually only enabled when debugging. Very verbose logging. Usually reserved for message traces + // TraceLevel level. Usually only enabled when debugging. Very verbose logging. + // Usually reserved for message traces. TraceLevel ) diff --git a/logrus_test.go b/logrus_test.go index 97d15d7..210feef 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -303,6 +303,7 @@ func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) { } func TestConvertLevelToString(t *testing.T) { + assert.Equal(t, "trace", TraceLevel.String()) assert.Equal(t, "debug", DebugLevel.String()) assert.Equal(t, "info", InfoLevel.String()) assert.Equal(t, "warning", WarnLevel.String()) @@ -368,6 +369,14 @@ func TestParseLevel(t *testing.T) { assert.Nil(t, err) assert.Equal(t, DebugLevel, l) + l, err = ParseLevel("trace") + assert.Nil(t, err) + assert.Equal(t, TraceLevel, l) + + l, err = ParseLevel("TRACE") + assert.Nil(t, err) + assert.Equal(t, TraceLevel, l) + l, err = ParseLevel("invalid") assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error()) } From ef9d84e9b3d46ec3b1520d235295a78c586bdc11 Mon Sep 17 00:00:00 2001 From: Maxim Korolyov Date: Tue, 28 Aug 2018 18:13:29 +0300 Subject: [PATCH 86/99] Added trace log level. --- README.md | 3 ++- entry.go | 34 +++++++++++++++++----------------- logrus.go | 3 +-- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 072e99b..f088432 100644 --- a/README.md +++ b/README.md @@ -246,9 +246,10 @@ A list of currently known of service hook can be found in this wiki [page](https #### Level logging -Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic. +Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic. ```go +log.Trace("Something very low level.") log.Debug("Useful debugging information.") log.Info("Something noteworthy happened!") log.Warn("You should probably take a look at this.") diff --git a/entry.go b/entry.go index eb4ae5c..a76be76 100644 --- a/entry.go +++ b/entry.go @@ -23,9 +23,9 @@ func init() { var ErrorKey = "error" // An entry is the final or intermediate Logrus logging entry. It contains all -// the fields passed with WithField{,s}. It's finally logged when Debug, Info, -// Warn, Error, Fatal or Panic is called on it. These objects can be reused and -// passed around as much as you wish to avoid field duplication. +// the fields passed with WithField{,s}. It's finally logged when Trace, Debug, +// Info, Warn, Error, Fatal or Panic is called on it. These objects can be +// reused and passed around as much as you wish to avoid field duplication. type Entry struct { Logger *Logger @@ -35,11 +35,11 @@ type Entry struct { // Time at which the log entry was created Time time.Time - // Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic + // Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic // This field will be set on entry firing and the value will be equal to the one in Logger struct field. Level Level - // Message passed to Debug, Info, Warn, Error, Fatal or Panic + // Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic Message string // When formatter is called in entry.log(), a Buffer may be set to entry @@ -162,18 +162,18 @@ func (entry *Entry) write() { } } -func (entry *Entry) Debug(args ...interface{}) { - if entry.Logger.IsLevelEnabled(DebugLevel) { - entry.log(DebugLevel, fmt.Sprint(args...)) - } -} - func (entry *Entry) Trace(args ...interface{}) { if entry.Logger.IsLevelEnabled(TraceLevel) { entry.log(TraceLevel, fmt.Sprint(args...)) } } +func (entry *Entry) Debug(args ...interface{}) { + if entry.Logger.IsLevelEnabled(DebugLevel) { + entry.log(DebugLevel, fmt.Sprint(args...)) + } +} + func (entry *Entry) Print(args ...interface{}) { entry.Info(args...) } @@ -216,18 +216,18 @@ func (entry *Entry) Panic(args ...interface{}) { // Entry Printf family functions -func (entry *Entry) Debugf(format string, args ...interface{}) { - if entry.Logger.IsLevelEnabled(DebugLevel) { - entry.Debug(fmt.Sprintf(format, args...)) - } -} - func (entry *Entry) Tracef(format string, args ...interface{}) { if entry.Logger.IsLevelEnabled(TraceLevel) { entry.Trace(fmt.Sprintf(format, args...)) } } +func (entry *Entry) Debugf(format string, args ...interface{}) { + if entry.Logger.IsLevelEnabled(DebugLevel) { + entry.Debug(fmt.Sprintf(format, args...)) + } +} + func (entry *Entry) Infof(format string, args ...interface{}) { if entry.Logger.IsLevelEnabled(InfoLevel) { entry.Info(fmt.Sprintf(format, args...)) diff --git a/logrus.go b/logrus.go index d657c41..8834b5b 100644 --- a/logrus.go +++ b/logrus.go @@ -87,8 +87,7 @@ const ( InfoLevel // DebugLevel level. Usually only enabled when debugging. Very verbose logging. DebugLevel - // TraceLevel level. Usually only enabled when debugging. Very verbose logging. - // Usually reserved for message traces. + // TraceLevel level. Designates finer-grained informational events than the Debug. TraceLevel ) From b54cafe5cee93a35bf6b061e261e8265ea0a628c Mon Sep 17 00:00:00 2001 From: Loren Osborn Date: Thu, 18 Oct 2018 09:33:43 -0700 Subject: [PATCH 87/99] Addresses @stevvooe's backward compatibility concerns. --- logrus.go | 12 +++++++++--- logrus_test.go | 7 +++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/logrus.go b/logrus.go index 8834b5b..03046b6 100644 --- a/logrus.go +++ b/logrus.go @@ -121,7 +121,6 @@ type FieldLogger interface { WithFields(fields Fields) *Entry WithError(err error) *Entry - Tracef(format string, args ...interface{}) Debugf(format string, args ...interface{}) Infof(format string, args ...interface{}) Printf(format string, args ...interface{}) @@ -131,7 +130,6 @@ type FieldLogger interface { Fatalf(format string, args ...interface{}) Panicf(format string, args ...interface{}) - Trace(args ...interface{}) Debug(args ...interface{}) Info(args ...interface{}) Print(args ...interface{}) @@ -141,7 +139,6 @@ type FieldLogger interface { Fatal(args ...interface{}) Panic(args ...interface{}) - Traceln(args ...interface{}) Debugln(args ...interface{}) Infoln(args ...interface{}) Println(args ...interface{}) @@ -158,3 +155,12 @@ type FieldLogger interface { // IsFatalEnabled() bool // IsPanicEnabled() bool } + +// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is +// here for consistancy. Do not use. Use Logger or Entry instead. +type Ext1FieldLogger interface { + FieldLogger + Tracef(format string, args ...interface{}) + Trace(args ...interface{}) + Traceln(args ...interface{}) +} diff --git a/logrus_test.go b/logrus_test.go index 210feef..748f0ba 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -453,9 +453,12 @@ func TestReplaceHooks(t *testing.T) { } // Compile test -func TestLogrusInterface(t *testing.T) { +func TestLogrusInterfaces(t *testing.T) { var buffer bytes.Buffer - fn := func(l FieldLogger) { + // This verifies FieldLogger and Ext1FieldLogger work as designed. + // Please don't use them. Use Logger and Entry directly. + fn := func(xl Ext1FieldLogger) { + var l FieldLogger = xl b := l.WithField("key", "value") b.Debug("Test") } From ed3ffa01907342213754448cb455dcc26d8e0a6d Mon Sep 17 00:00:00 2001 From: Loren Osborn Date: Thu, 18 Oct 2018 22:35:59 -0700 Subject: [PATCH 88/99] PR#844: Added Trace to TestLogLevelEnabled() (requested by @dgsb) --- logrus_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/logrus_test.go b/logrus_test.go index 748f0ba..ccdf5cc 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -506,6 +506,7 @@ func TestLogLevelEnabled(t *testing.T) { assert.Equal(t, false, log.IsLevelEnabled(WarnLevel)) assert.Equal(t, false, log.IsLevelEnabled(InfoLevel)) assert.Equal(t, false, log.IsLevelEnabled(DebugLevel)) + assert.Equal(t, false, log.IsLevelEnabled(TraceLevel)) log.SetLevel(FatalLevel) assert.Equal(t, true, log.IsLevelEnabled(PanicLevel)) @@ -514,6 +515,7 @@ func TestLogLevelEnabled(t *testing.T) { assert.Equal(t, false, log.IsLevelEnabled(WarnLevel)) assert.Equal(t, false, log.IsLevelEnabled(InfoLevel)) assert.Equal(t, false, log.IsLevelEnabled(DebugLevel)) + assert.Equal(t, false, log.IsLevelEnabled(TraceLevel)) log.SetLevel(ErrorLevel) assert.Equal(t, true, log.IsLevelEnabled(PanicLevel)) @@ -522,6 +524,7 @@ func TestLogLevelEnabled(t *testing.T) { assert.Equal(t, false, log.IsLevelEnabled(WarnLevel)) assert.Equal(t, false, log.IsLevelEnabled(InfoLevel)) assert.Equal(t, false, log.IsLevelEnabled(DebugLevel)) + assert.Equal(t, false, log.IsLevelEnabled(TraceLevel)) log.SetLevel(WarnLevel) assert.Equal(t, true, log.IsLevelEnabled(PanicLevel)) @@ -530,6 +533,7 @@ func TestLogLevelEnabled(t *testing.T) { assert.Equal(t, true, log.IsLevelEnabled(WarnLevel)) assert.Equal(t, false, log.IsLevelEnabled(InfoLevel)) assert.Equal(t, false, log.IsLevelEnabled(DebugLevel)) + assert.Equal(t, false, log.IsLevelEnabled(TraceLevel)) log.SetLevel(InfoLevel) assert.Equal(t, true, log.IsLevelEnabled(PanicLevel)) @@ -538,6 +542,7 @@ func TestLogLevelEnabled(t *testing.T) { assert.Equal(t, true, log.IsLevelEnabled(WarnLevel)) assert.Equal(t, true, log.IsLevelEnabled(InfoLevel)) assert.Equal(t, false, log.IsLevelEnabled(DebugLevel)) + assert.Equal(t, false, log.IsLevelEnabled(TraceLevel)) log.SetLevel(DebugLevel) assert.Equal(t, true, log.IsLevelEnabled(PanicLevel)) @@ -546,4 +551,14 @@ func TestLogLevelEnabled(t *testing.T) { assert.Equal(t, true, log.IsLevelEnabled(WarnLevel)) assert.Equal(t, true, log.IsLevelEnabled(InfoLevel)) assert.Equal(t, true, log.IsLevelEnabled(DebugLevel)) + assert.Equal(t, false, log.IsLevelEnabled(TraceLevel)) + + log.SetLevel(TraceLevel) + assert.Equal(t, true, log.IsLevelEnabled(PanicLevel)) + assert.Equal(t, true, log.IsLevelEnabled(FatalLevel)) + assert.Equal(t, true, log.IsLevelEnabled(ErrorLevel)) + assert.Equal(t, true, log.IsLevelEnabled(WarnLevel)) + assert.Equal(t, true, log.IsLevelEnabled(InfoLevel)) + assert.Equal(t, true, log.IsLevelEnabled(DebugLevel)) + assert.Equal(t, true, log.IsLevelEnabled(TraceLevel)) } From bb98c6c5333eab6d403627978bbde01d60f176a7 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sun, 21 Oct 2018 11:53:14 +0200 Subject: [PATCH 89/99] Fix the version of windows coloring library dependency --- go.mod | 2 +- go.sum | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 79f11b7..94574cc 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/sirupsen/logrus require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe + github.com/konsorten/go-windows-terminal-sequences v1.0.1 github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.1.1 // indirect github.com/stretchr/testify v1.2.2 diff --git a/go.sum b/go.sum index 16cab75..133d34a 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs= github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= From 5c1f2cd52c2defcc298ef342d9b3eebe160b33ad Mon Sep 17 00:00:00 2001 From: Shun Yanaura Date: Fri, 19 Oct 2018 20:35:56 +0900 Subject: [PATCH 90/99] Make logrus.Level implement encoding.TextUnmarshaler --- logrus.go | 12 ++++++++++++ logrus_test.go | 13 +++++++++++++ 2 files changed, 25 insertions(+) diff --git a/logrus.go b/logrus.go index 6fff506..5dab4d4 100644 --- a/logrus.go +++ b/logrus.go @@ -53,6 +53,18 @@ func ParseLevel(lvl string) (Level, error) { return l, fmt.Errorf("not a valid logrus Level: %q", lvl) } +// UnmarshalText implements encoding.TextUnmarshaler. +func (level *Level) UnmarshalText(text []byte) error { + l, err := ParseLevel(string(text)) + if err != nil { + return err + } + + *level = Level(l) + + return nil +} + // A constant exposing all logging levels var AllLevels = []Level{ PanicLevel, diff --git a/logrus_test.go b/logrus_test.go index 97d15d7..8725013 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -372,6 +372,19 @@ func TestParseLevel(t *testing.T) { assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error()) } +func TestUnmarshalText(t *testing.T) { + var u Level + for _, level := range AllLevels { + t.Run(level.String(), func(t *testing.T) { + assert.NoError(t, u.UnmarshalText([]byte(level.String()))) + assert.Equal(t, level, u) + }) + } + t.Run("invalid", func(t *testing.T) { + assert.Error(t, u.UnmarshalText([]byte("invalid"))) + }) +} + func TestGetSetLevelRace(t *testing.T) { wg := sync.WaitGroup{} for i := 0; i < 100; i++ { From 0c525822dcfe68ae420b32def223c5c7783f60c6 Mon Sep 17 00:00:00 2001 From: Fabien Meurillon Date: Wed, 24 Oct 2018 11:03:07 +0200 Subject: [PATCH 91/99] Add GELF to third party formatters --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f088432..e3d2542 100644 --- a/README.md +++ b/README.md @@ -330,6 +330,7 @@ The built-in logging formatters are: Third party logging formatters: * [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine. +* [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html). * [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events. * [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout. * [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦. From 5fcd19eae6dad060b6bf73cde67273d0f2eed02f Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sat, 27 Oct 2018 15:19:06 +0200 Subject: [PATCH 92/99] add a SetReportCaller on Logger object --- exported.go | 4 +--- logger.go | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/exported.go b/exported.go index 979582d..7342613 100644 --- a/exported.go +++ b/exported.go @@ -27,9 +27,7 @@ func SetFormatter(formatter Formatter) { // SetReportCaller sets whether the standard logger will include the calling // method as a field. func SetReportCaller(include bool) { - std.mu.Lock() - defer std.mu.Unlock() - std.ReportCaller = include + std.SetReportCaller(include) } // SetLevel sets the standard logger level. diff --git a/logger.go b/logger.go index 68fca98..5ceca0e 100644 --- a/logger.go +++ b/logger.go @@ -399,6 +399,12 @@ func (logger *Logger) SetOutput(output io.Writer) { logger.Out = output } +func (logger *Logger) SetReportCaller(reportCaller bool) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.ReportCaller = reportCaller +} + // ReplaceHooks replaces the logger hooks and returns the old ones func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks { logger.mu.Lock() From 975c406ddb02ea083d859e81a86dcd8a3854217a Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sat, 27 Oct 2018 15:21:30 +0200 Subject: [PATCH 93/99] Use a sync.Once to init the reportCaller data --- entry.go | 30 +++++++++++++++++++----------- logrus_test.go | 4 ++-- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/entry.go b/entry.go index df37550..5f40c58 100644 --- a/entry.go +++ b/entry.go @@ -11,16 +11,23 @@ import ( "time" ) -var bufferPool *sync.Pool +var ( + bufferPool *sync.Pool -// qualified package name, cached at first use -var LogrusPackage string + // qualified package name, cached at first use + logrusPackage string -// Positions in the call stack when tracing to report the calling method -var minimumCallerDepth int + // Positions in the call stack when tracing to report the calling method + minimumCallerDepth int -const maximumCallerDepth int = 25 -const knownLogrusFrames int = 4 + // Used for caller information initialisation + callerInitOnce sync.Once +) + +const ( + maximumCallerDepth int = 25 + knownLogrusFrames int = 4 +) func init() { bufferPool = &sync.Pool{ @@ -143,19 +150,20 @@ func getCaller() (method string) { depth := runtime.Callers(minimumCallerDepth, pcs) // cache this package's fully-qualified name - if LogrusPackage == "" { - LogrusPackage = getPackageName(runtime.FuncForPC(pcs[0]).Name()) + callerInitOnce.Do(func() { + logrusPackage = getPackageName(runtime.FuncForPC(pcs[0]).Name()) // now that we have the cache, we can skip a minimum count of known-logrus functions + // XXX this is dubious, the number of frames may vary store an entry in a logger interface minimumCallerDepth = knownLogrusFrames - } + }) for i := 0; i < depth; i++ { fullFuncName := runtime.FuncForPC(pcs[i]).Name() pkg := getPackageName(fullFuncName) // If the caller isn't part of this package, we're done - if pkg != LogrusPackage { + if pkg != logrusPackage { return fullFuncName } } diff --git a/logrus_test.go b/logrus_test.go index d829369..2fe3653 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -92,7 +92,7 @@ func logSomething(t *testing.T, message string) Fields { logger.ReportCaller = true // override the filter to allow reporting of functions within the logrus package - LogrusPackage = "bogusForTesting" + logrusPackage = "bogusForTesting" entry := logger.WithFields(Fields{ "foo": "bar", @@ -104,7 +104,7 @@ func logSomething(t *testing.T, message string) Fields { assert.Nil(t, err) // now clear the override so as not to mess with other usage - LogrusPackage = "" + logrusPackage = "" return fields } From ec57031db11c2a7783cbc010a6c2c9ab10a8e039 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sun, 28 Oct 2018 10:12:11 +0100 Subject: [PATCH 94/99] store a runtime.Frame in Entry instead of the caller function name --- entry.go | 16 ++++++++-------- json_formatter.go | 2 +- json_formatter_test.go | 7 ++++--- text_formatter.go | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/entry.go b/entry.go index 5f40c58..cc85d3a 100644 --- a/entry.go +++ b/entry.go @@ -61,7 +61,7 @@ type Entry struct { Level Level // Calling method, with package name - Caller string + Caller *runtime.Frame // Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic Message string @@ -144,10 +144,11 @@ func getPackageName(f string) string { } // getCaller retrieves the name of the first non-logrus calling function -func getCaller() (method string) { +func getCaller() *runtime.Frame { // Restrict the lookback frames to avoid runaway lookups pcs := make([]uintptr, maximumCallerDepth) depth := runtime.Callers(minimumCallerDepth, pcs) + frames := runtime.CallersFrames(pcs[:depth]) // cache this package's fully-qualified name callerInitOnce.Do(func() { @@ -158,24 +159,23 @@ func getCaller() (method string) { minimumCallerDepth = knownLogrusFrames }) - for i := 0; i < depth; i++ { - fullFuncName := runtime.FuncForPC(pcs[i]).Name() - pkg := getPackageName(fullFuncName) + for f, again := frames.Next(); again; f, again = frames.Next() { + pkg := getPackageName(f.Function) // If the caller isn't part of this package, we're done if pkg != logrusPackage { - return fullFuncName + return &f } } // if we got here, we failed to find the caller's context - return "" + return nil } func (entry Entry) HasCaller() (has bool) { return entry.Logger != nil && entry.Logger.ReportCaller && - entry.Caller != "" + entry.Caller != nil } // This function is not declared with a pointer value because otherwise diff --git a/json_formatter.go b/json_formatter.go index bdb569e..f5d45bb 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -82,7 +82,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String() if entry.HasCaller() { - data[f.FieldMap.resolve(FieldKeyFunc)] = entry.Caller + data[f.FieldMap.resolve(FieldKeyFunc)] = entry.Caller.Function } var b *bytes.Buffer diff --git a/json_formatter_test.go b/json_formatter_test.go index 13fbf31..695c36e 100644 --- a/json_formatter_test.go +++ b/json_formatter_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "runtime" "strings" "testing" ) @@ -167,8 +168,8 @@ func TestFieldsInNestedDictionary(t *testing.T) { } logEntry := WithFields(Fields{ - "level": "level", - "test": "test", + "level": "level", + "test": "test", }) logEntry.Level = InfoLevel @@ -291,7 +292,7 @@ func TestFieldClashWithCaller(t *testing.T) { SetReportCaller(true) formatter := &JSONFormatter{} e := WithField("func", "howdy pardner") - e.Caller = "somefunc" + e.Caller = &runtime.Frame{Function: "somefunc"} b, err := formatter.Format(e) if err != nil { t.Fatal("Unable to format entry: ", err) diff --git a/text_formatter.go b/text_formatter.go index 409edc0..354387c 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -208,7 +208,7 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin caller := "" if entry.HasCaller() { - caller = fmt.Sprintf(" %s()", entry.Caller) + caller = fmt.Sprintf(" %s()", entry.Caller.Function) } if f.DisableTimestamp { From fa01b530978c5b5bc0eade9c6198f409fd5c6859 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sun, 28 Oct 2018 14:07:57 +0100 Subject: [PATCH 95/99] move test functions and test utils functions in their own package --- hook_test.go | 5 ++- internal/testutils/testutils.go | 58 ++++++++++++++++++++++++ logrus_test.go | 79 +++++++-------------------------- 3 files changed, 79 insertions(+), 63 deletions(-) create mode 100644 internal/testutils/testutils.go diff --git a/hook_test.go b/hook_test.go index 7196e99..b967593 100644 --- a/hook_test.go +++ b/hook_test.go @@ -1,4 +1,4 @@ -package logrus +package logrus_test import ( "bytes" @@ -8,6 +8,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + . "github.com/sirupsen/logrus" + . "github.com/sirupsen/logrus/internal/testutils" ) type TestHook struct { diff --git a/internal/testutils/testutils.go b/internal/testutils/testutils.go new file mode 100644 index 0000000..20bc3c3 --- /dev/null +++ b/internal/testutils/testutils.go @@ -0,0 +1,58 @@ +package testutils + +import ( + "bytes" + "encoding/json" + "strconv" + "strings" + "testing" + + . "github.com/sirupsen/logrus" + + "github.com/stretchr/testify/require" +) + +func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) { + var buffer bytes.Buffer + var fields Fields + + logger := New() + logger.Out = &buffer + logger.Formatter = new(JSONFormatter) + + log(logger) + + err := json.Unmarshal(buffer.Bytes(), &fields) + require.Nil(t, err) + + assertions(fields) +} + +func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) { + var buffer bytes.Buffer + + logger := New() + logger.Out = &buffer + logger.Formatter = &TextFormatter{ + DisableColors: true, + } + + log(logger) + + fields := make(map[string]string) + for _, kv := range strings.Split(buffer.String(), " ") { + if !strings.Contains(kv, "=") { + continue + } + kvArr := strings.Split(kv, "=") + key := strings.TrimSpace(kvArr[0]) + val := kvArr[1] + if kvArr[1][0] == '"' { + var err error + val, err = strconv.Unquote(val) + require.NoError(t, err) + } + fields[key] = val + } + assertions(fields) +} diff --git a/logrus_test.go b/logrus_test.go index 2fe3653..3b1d256 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -1,63 +1,20 @@ -package logrus +package logrus_test import ( "bytes" "encoding/json" "io/ioutil" - "strconv" - "strings" "sync" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + . "github.com/sirupsen/logrus" + . "github.com/sirupsen/logrus/internal/testutils" ) -func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) { - var buffer bytes.Buffer - var fields Fields - - logger := New() - logger.Out = &buffer - logger.Formatter = new(JSONFormatter) - - log(logger) - - err := json.Unmarshal(buffer.Bytes(), &fields) - assert.Nil(t, err) - - assertions(fields) -} - -func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) { - var buffer bytes.Buffer - - logger := New() - logger.Out = &buffer - logger.Formatter = &TextFormatter{ - DisableColors: true, - } - - log(logger) - - fields := make(map[string]string) - for _, kv := range strings.Split(buffer.String(), " ") { - if !strings.Contains(kv, "=") { - continue - } - kvArr := strings.Split(kv, "=") - key := strings.TrimSpace(kvArr[0]) - val := kvArr[1] - if kvArr[1][0] == '"' { - var err error - val, err = strconv.Unquote(val) - assert.NoError(t, err) - } - fields[key] = val - } - assertions(fields) -} - // TestReportCaller verifies that when ReportCaller is set, the 'func' field // is added, and when it is unset it is not set or modified // Verify that functions within the Logrus package aren't considered when @@ -78,7 +35,8 @@ func TestReportCallerWhenConfigured(t *testing.T) { }, func(fields Fields) { assert.Equal(t, "testWithCaller", fields["msg"]) assert.Equal(t, "info", fields["level"]) - assert.Equal(t, "testing.tRunner", fields["func"]) + assert.Equal(t, + "github.com/sirupsen/logrus_test.TestReportCallerWhenConfigured.func3", fields["func"]) }) } @@ -91,9 +49,6 @@ func logSomething(t *testing.T, message string) Fields { logger.Formatter = new(JSONFormatter) logger.ReportCaller = true - // override the filter to allow reporting of functions within the logrus package - logrusPackage = "bogusForTesting" - entry := logger.WithFields(Fields{ "foo": "bar", }) @@ -103,8 +58,6 @@ func logSomething(t *testing.T, message string) Fields { err := json.Unmarshal(buffer.Bytes(), &fields) assert.Nil(t, err) - // now clear the override so as not to mess with other usage - logrusPackage = "" return fields } @@ -114,7 +67,7 @@ func TestReportCallerHelperDirect(t *testing.T) { assert.Equal(t, "direct", fields["msg"]) assert.Equal(t, "info", fields["level"]) - assert.Regexp(t, "github.com/.*/logrus.logSomething", fields["func"]) + assert.Regexp(t, "github.com/.*/logrus_test.logSomething", fields["func"]) } // TestReportCallerHelperDirect - verify reference when logging from a function called via pointer @@ -124,7 +77,7 @@ func TestReportCallerHelperViaPointer(t *testing.T) { assert.Equal(t, "via pointer", fields["msg"]) assert.Equal(t, "info", fields["level"]) - assert.Regexp(t, "github.com/.*/logrus.logSomething", fields["func"]) + assert.Regexp(t, "github.com/.*/logrus_test.logSomething", fields["func"]) } func TestPrint(t *testing.T) { @@ -286,7 +239,7 @@ func TestWithTimeShouldOverrideTime(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.WithTime(now).Info("foobar") }, func(fields Fields) { - assert.Equal(t, fields["time"], now.Format(defaultTimestampFormat)) + assert.Equal(t, fields["time"], now.Format(time.RFC3339)) }) } @@ -296,7 +249,7 @@ func TestWithTimeShouldNotOverrideFields(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.WithField("herp", "derp").WithTime(now).Info("blah") }, func(fields Fields) { - assert.Equal(t, fields["time"], now.Format(defaultTimestampFormat)) + assert.Equal(t, fields["time"], now.Format(time.RFC3339)) assert.Equal(t, fields["herp"], "derp") }) } @@ -307,7 +260,7 @@ func TestWithFieldShouldNotOverrideTime(t *testing.T) { LogAndAssertJSON(t, func(log *Logger) { log.WithTime(now).WithField("herp", "derp").Info("blah") }, func(fields Fields) { - assert.Equal(t, fields["time"], now.Format(defaultTimestampFormat)) + assert.Equal(t, fields["time"], now.Format(time.RFC3339)) assert.Equal(t, fields["herp"], "derp") }) } @@ -385,11 +338,12 @@ func TestNestedLoggingReportsCorrectCaller(t *testing.T) { llog.Info("looks delicious") err := json.Unmarshal(buffer.Bytes(), &fields) - assert.NoError(t, err, "should have decoded first message") + require.NoError(t, err, "should have decoded first message") assert.Equal(t, len(fields), 5, "should have msg/time/level/func/context fields") assert.Equal(t, "looks delicious", fields["msg"]) assert.Equal(t, "eating raw fish", fields["context"]) - assert.Equal(t, "testing.tRunner", fields["func"]) + assert.Equal(t, + "github.com/sirupsen/logrus_test.TestNestedLoggingReportsCorrectCaller", fields["func"]) buffer.Reset() @@ -415,7 +369,8 @@ func TestNestedLoggingReportsCorrectCaller(t *testing.T) { assert.Equal(t, "Brown", fields["James"]) assert.Equal(t, "The hardest workin' man in show business", fields["msg"]) assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry") - assert.Equal(t, "testing.tRunner", fields["func"]) + assert.Equal(t, + "github.com/sirupsen/logrus_test.TestNestedLoggingReportsCorrectCaller", fields["func"]) logger.ReportCaller = false // return to default value } From d2654b752f1ef4f33cce0b405c0f118bf1b69bdd Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sun, 28 Oct 2018 17:39:39 +0100 Subject: [PATCH 96/99] add file and line number in output when report caller is enabled --- formatter.go | 5 +++++ json_formatter.go | 1 + logrus_test.go | 10 ++++++++-- text_formatter.go | 10 ++++++++-- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/formatter.go b/formatter.go index db37848..4088837 100644 --- a/formatter.go +++ b/formatter.go @@ -10,6 +10,7 @@ const ( FieldKeyTime = "time" FieldKeyLogrusError = "logrus_error" FieldKeyFunc = "func" + FieldKeyFile = "file" ) // The Formatter interface is used to implement a custom Formatter. It takes an @@ -69,5 +70,9 @@ func prefixFieldClashes(data Fields, fieldMap FieldMap, reportCaller bool) { if l, ok := data[funcKey]; ok { data["fields."+funcKey] = l } + fileKey := fieldMap.resolve(FieldKeyFile) + if l, ok := data[fileKey]; ok { + data["fields."+fileKey] = l + } } } diff --git a/json_formatter.go b/json_formatter.go index f5d45bb..2605753 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -83,6 +83,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String() if entry.HasCaller() { data[f.FieldMap.resolve(FieldKeyFunc)] = entry.Caller.Function + data[f.FieldMap.resolve(FieldKeyFile)] = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) } var b *bytes.Buffer diff --git a/logrus_test.go b/logrus_test.go index 3b1d256..27cddaf 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "io/ioutil" + "os" "sync" "testing" "time" @@ -339,11 +340,14 @@ func TestNestedLoggingReportsCorrectCaller(t *testing.T) { err := json.Unmarshal(buffer.Bytes(), &fields) require.NoError(t, err, "should have decoded first message") - assert.Equal(t, len(fields), 5, "should have msg/time/level/func/context fields") + assert.Equal(t, 6, len(fields), "should have msg/time/level/func/context fields") assert.Equal(t, "looks delicious", fields["msg"]) assert.Equal(t, "eating raw fish", fields["context"]) assert.Equal(t, "github.com/sirupsen/logrus_test.TestNestedLoggingReportsCorrectCaller", fields["func"]) + cwd, err := os.Getwd() + require.NoError(t, err) + assert.Equal(t, cwd+"/logrus_test.go:339", fields["file"]) buffer.Reset() @@ -361,7 +365,7 @@ func TestNestedLoggingReportsCorrectCaller(t *testing.T) { 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, 11, len(fields), "should have all builtin fields plus foo,bar,baz,...") assert.Equal(t, "Stubblefield", fields["Clyde"]) assert.Equal(t, "Starks", fields["Jab'o"]) assert.Equal(t, "https://www.youtube.com/watch?v=V5DTznu-9v0", fields["uri"]) @@ -371,6 +375,8 @@ func TestNestedLoggingReportsCorrectCaller(t *testing.T) { assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry") assert.Equal(t, "github.com/sirupsen/logrus_test.TestNestedLoggingReportsCorrectCaller", fields["func"]) + require.NoError(t, err) + assert.Equal(t, cwd+"/logrus_test.go:364", fields["file"]) logger.ReportCaller = false // return to default value } diff --git a/text_formatter.go b/text_formatter.go index 354387c..c5f9ca3 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -126,7 +126,8 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError)) } if entry.HasCaller() { - fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc)) + fixedKeys = append(fixedKeys, + f.FieldMap.resolve(FieldKeyFunc), f.FieldMap.resolve(FieldKeyFile)) } if !f.DisableSorting { @@ -172,6 +173,10 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { value = entry.Message case f.FieldMap.resolve(FieldKeyLogrusError): value = entry.err + case f.FieldMap.resolve(FieldKeyFunc): + value = entry.Caller.Function + case f.FieldMap.resolve(FieldKeyFile): + value = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) default: value = entry.Data[key] } @@ -208,7 +213,8 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin caller := "" if entry.HasCaller() { - caller = fmt.Sprintf(" %s()", entry.Caller.Function) + caller = fmt.Sprintf("%s:%d %s()", + entry.Caller.File, entry.Caller.Line, entry.Caller.Function) } if f.DisableTimestamp { From 5a78c38d0e21bb6cfcc02703f25b9232b0312ccd Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sun, 28 Oct 2018 18:12:32 +0100 Subject: [PATCH 97/99] make file name comparison os independant --- logrus_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/logrus_test.go b/logrus_test.go index 27cddaf..7c99825 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "io/ioutil" "os" + "path/filepath" "sync" "testing" "time" @@ -347,7 +348,7 @@ func TestNestedLoggingReportsCorrectCaller(t *testing.T) { "github.com/sirupsen/logrus_test.TestNestedLoggingReportsCorrectCaller", fields["func"]) cwd, err := os.Getwd() require.NoError(t, err) - assert.Equal(t, cwd+"/logrus_test.go:339", fields["file"]) + assert.Equal(t, filepath.ToSlash(cwd+"/logrus_test.go:340"), filepath.ToSlash(fields["file"].(string))) buffer.Reset() @@ -376,7 +377,7 @@ func TestNestedLoggingReportsCorrectCaller(t *testing.T) { assert.Equal(t, "github.com/sirupsen/logrus_test.TestNestedLoggingReportsCorrectCaller", fields["func"]) require.NoError(t, err) - assert.Equal(t, cwd+"/logrus_test.go:364", fields["file"]) + assert.Equal(t, filepath.ToSlash(cwd+"/logrus_test.go:365"), filepath.ToSlash(fields["file"].(string))) logger.ReportCaller = false // return to default value } From d10c2f9e3c9bf2debfd370a038ca8520f73bd405 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Wed, 31 Oct 2018 07:01:35 +0100 Subject: [PATCH 98/99] fix panic in text formatter The panic was caused to a nil pointer access when report caller was activated with output coloring disabled Fixes #852 --- logrus_test.go | 12 ++++++++++++ text_formatter.go | 14 +++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/logrus_test.go b/logrus_test.go index f6a0f44..b12d71c 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -702,3 +702,15 @@ func TestLogLevelEnabled(t *testing.T) { assert.Equal(t, true, log.IsLevelEnabled(DebugLevel)) assert.Equal(t, true, log.IsLevelEnabled(TraceLevel)) } + +func TestReportCallerOnTextFormatter(t *testing.T) { + l := New() + + l.Formatter.(*TextFormatter).ForceColors = true + l.Formatter.(*TextFormatter).DisableColors = false + l.WithFields(Fields{"func": "func", "file": "file"}).Info("test") + + l.Formatter.(*TextFormatter).ForceColors = false + l.Formatter.(*TextFormatter).DisableColors = true + l.WithFields(Fields{"func": "func", "file": "file"}).Info("test") +} diff --git a/text_formatter.go b/text_formatter.go index c5f9ca3..49ec92f 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -164,18 +164,18 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { } else { for _, key := range fixedKeys { var value interface{} - switch key { - case f.FieldMap.resolve(FieldKeyTime): + switch { + case key == f.FieldMap.resolve(FieldKeyTime): value = entry.Time.Format(timestampFormat) - case f.FieldMap.resolve(FieldKeyLevel): + case key == f.FieldMap.resolve(FieldKeyLevel): value = entry.Level.String() - case f.FieldMap.resolve(FieldKeyMsg): + case key == f.FieldMap.resolve(FieldKeyMsg): value = entry.Message - case f.FieldMap.resolve(FieldKeyLogrusError): + case key == f.FieldMap.resolve(FieldKeyLogrusError): value = entry.err - case f.FieldMap.resolve(FieldKeyFunc): + case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller(): value = entry.Caller.Function - case f.FieldMap.resolve(FieldKeyFile): + case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller(): value = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) default: value = entry.Data[key] From bcd833dfe83d3cebad139e4a29ed79cb2318bf95 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Thu, 1 Nov 2018 08:39:56 +0100 Subject: [PATCH 99/99] v1.2.0 changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff04718..cb85d9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.2.0 +This new release introduces: + * A new method `SetReportCaller` in the `Logger` to enable the file, line and calling function from which the trace has been issued + * A new trace level named `Trace` whose level is below `Debug` + * A configurable exit function to be called upon a Fatal trace + * The `Level` object now implements `encoding.TextUnmarshaler` interface + # 1.1.1 This is a bug fix release. * fix the build break on Solaris