From 7a1f601cfd7604dd99fa6d6af0aa3e2a3f3de6b4 Mon Sep 17 00:00:00 2001 From: Tony Lee Date: Wed, 31 Aug 2016 23:54:59 +1000 Subject: [PATCH 01/40] Added ability to disable level text truncation. Fixes #406 --- text_formatter.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/text_formatter.go b/text_formatter.go index cce61f2..a51bf19 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -54,6 +54,9 @@ type TextFormatter struct { // that log extremely frequently and don't use the JSON formatter this may not // be desired. DisableSorting bool + + // Disables the truncation of the level text to 4 characters. + DisableLevelTruncation bool } func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { @@ -113,7 +116,10 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin levelColor = blue } - levelText := strings.ToUpper(entry.Level.String())[0:4] + levelText := strings.ToUpper(entry.Level.String()) + if !f.DisableLevelTruncation { + levelText = levelText[0:4] + } if !f.FullTimestamp { fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) From e5b6713580618875560ec106fc60a2ac82e14c96 Mon Sep 17 00:00:00 2001 From: Tony Lee Date: Thu, 1 Sep 2016 00:28:23 +1000 Subject: [PATCH 02/40] Added testing for DisableLevelTruncation --- text_formatter_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/text_formatter_test.go b/text_formatter_test.go index e25a44f..6b5353d 100644 --- a/text_formatter_test.go +++ b/text_formatter_test.go @@ -3,6 +3,7 @@ package logrus import ( "bytes" "errors" + "strings" "testing" "time" ) @@ -57,5 +58,43 @@ func TestTimestampFormat(t *testing.T) { checkTimeStr("") } +func TestDisableLevelTruncation(t *testing.T) { + entry := &Entry{ + Time: time.Now(), + Message: "testing", + } + keys := []string{} + timestampFormat := "Mon Jan 2 15:04:05 -0700 MST 2006" + checkDisableTruncation := func(disabled bool, level Level) { + tf := &TextFormatter{DisableLevelTruncation: disabled} + var b bytes.Buffer + entry.Level = level + tf.printColored(&b, entry, keys, timestampFormat) + logLine := (&b).String() + if disabled { + expected := strings.ToUpper(level.String()) + if !strings.Contains(logLine, expected) { + t.Errorf("level string expected to be %s when truncation disabled", expected) + } + } else { + expected := strings.ToUpper(level.String()) + if len(level.String()) > 4 { + if strings.Contains(logLine, expected) { + t.Errorf("level string %s expected to be truncated to %s when truncation is enabled", expected, expected[0:4]) + } + } else { + if !strings.Contains(logLine, expected) { + t.Errorf("level string expected to be %s when truncation is enabled and level string is below truncation threshold", expected) + } + } + } + } + + checkDisableTruncation(true, DebugLevel) + checkDisableTruncation(true, InfoLevel) + checkDisableTruncation(false, ErrorLevel) + checkDisableTruncation(false, InfoLevel) +} + // TODO add tests for sorting etc., this requires a parser for the text // formatter output. From 1f59c9ad125a4f3f3d172f2b3d67cee2fa217b74 Mon Sep 17 00:00:00 2001 From: Tony Lee Date: Tue, 24 Jan 2017 23:05:25 +1100 Subject: [PATCH 03/40] Add DisableLevelTruncation description to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 206c746..7622d31 100644 --- a/README.md +++ b/README.md @@ -318,6 +318,8 @@ The built-in logging formatters are: * *Note:* to force colored output when there is no TTY, set the `ForceColors` field to `true`. To force no colored output even if there is a TTY set the `DisableColors` field to `true` + * When colors are enabled, levels are truncated to 4 characters by default. To disable + truncation set the `DisableLevelTruncation` field to `true`. * `logrus.JSONFormatter`. Logs fields as JSON. Third party logging formatters: From bf1fb70b2b4308a0aed02f31c5f327be46620269 Mon Sep 17 00:00:00 2001 From: Neil Isaac Date: Tue, 21 Nov 2017 22:43:47 -0500 Subject: [PATCH 04/40] Add FieldMap support to TestFormatter --- text_formatter.go | 15 ++++++++++++--- text_formatter_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/text_formatter.go b/text_formatter.go index be412aa..8592825 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -60,6 +60,15 @@ type TextFormatter struct { // Whether the logger's out is to a terminal isTerminal bool + // FieldMap allows users to customize the names of keys for default fields. + // As an example: + // formatter := &JSONFormatter{ + // FieldMap: FieldMap{ + // FieldKeyTime: "@timestamp", + // FieldKeyLevel: "@level", + // FieldKeyMsg: "@message"}} + FieldMap FieldMap + sync.Once } @@ -109,11 +118,11 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { f.printColored(b, entry, keys, timestampFormat) } else { if !f.DisableTimestamp { - f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat)) + f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyTime), entry.Time.Format(timestampFormat)) } - f.appendKeyValue(b, "level", entry.Level.String()) + f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyLevel), entry.Level.String()) if entry.Message != "" { - f.appendKeyValue(b, "msg", entry.Message) + f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyMsg), entry.Message) } for _, key := range keys { f.appendKeyValue(b, key, entry.Data[key]) diff --git a/text_formatter_test.go b/text_formatter_test.go index d93b931..789d52d 100644 --- a/text_formatter_test.go +++ b/text_formatter_test.go @@ -7,6 +7,8 @@ import ( "strings" "testing" "time" + + "github.com/stretchr/testify/assert" ) func TestFormatting(t *testing.T) { @@ -137,5 +139,32 @@ func TestDisableTimestampWithColoredOutput(t *testing.T) { } } +func TestTextFormatterFieldMap(t *testing.T) { + formatter := &TextFormatter{ + DisableColors: true, + FieldMap: FieldMap{ + FieldKeyMsg: "message", + FieldKeyLevel: "somelevel", + FieldKeyTime: "timeywimey", + }, + } + + entry := &Entry{ + Message: "oh hi", + Level: WarnLevel, + Time: time.Date(1981, time.February, 24, 4, 28, 3, 100, time.UTC), + } + + b, err := formatter.Format(entry) + if err != nil { + t.Fatal("Unable to format entry: ", err) + } + + assert.Equal(t, + `timeywimey="1981-02-24T04:28:03Z" somelevel=warning message="oh hi"`+"\n", + string(b), + "Formatted doesn't respect correct FieldMap") +} + // TODO add tests for sorting etc., this requires a parser for the text // formatter output. From b9eceae8f663facb1b89f517fa03e4ae43a5c517 Mon Sep 17 00:00:00 2001 From: Neil Isaac Date: Tue, 21 Nov 2017 22:56:37 -0500 Subject: [PATCH 05/40] fix example --- text_formatter.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text_formatter.go b/text_formatter.go index 8592825..aa0694c 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -62,11 +62,11 @@ type TextFormatter struct { // FieldMap allows users to customize the names of keys for default fields. // As an example: - // formatter := &JSONFormatter{ + // formatter := &TextFormatter{ // FieldMap: FieldMap{ - // FieldKeyTime: "@timestamp", + // FieldKeyTime: "@timestamp", // FieldKeyLevel: "@level", - // FieldKeyMsg: "@message"}} + // FieldKeyMsg: "@message"}} FieldMap FieldMap sync.Once From 977e03308a73cbc954765de9e2efce93f1416294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Linhares?= Date: Mon, 22 Jan 2018 10:35:26 -0500 Subject: [PATCH 06/40] Fix deadlock on panics at Entry.log When calling Entry.log a panic inside some of the locking blocks could cause the whole logger to deadlock. One of the ways this could happen is for a hook to cause a panic, when this happens the lock is never unlocked and the library deadlocks, causing the code that is calling it to deadlock as well. This changes how locking happens with unlocks at defer blocks so even if a panic happens somewhere along the log call the library will still unlock and continue to function. --- entry.go | 49 ++++++++++++++++++++++++++++--------------------- entry_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 21 deletions(-) diff --git a/entry.go b/entry.go index 1fad45e..df6f92d 100644 --- a/entry.go +++ b/entry.go @@ -94,32 +94,16 @@ func (entry Entry) log(level Level, msg string) { entry.Level = level entry.Message = msg - entry.Logger.mu.Lock() - err := entry.Logger.Hooks.Fire(level, &entry) - entry.Logger.mu.Unlock() - if err != nil { - entry.Logger.mu.Lock() - fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) - entry.Logger.mu.Unlock() - } + entry.fireHooks() + buffer = bufferPool.Get().(*bytes.Buffer) buffer.Reset() defer bufferPool.Put(buffer) entry.Buffer = buffer - serialized, err := entry.Logger.Formatter.Format(&entry) + + entry.write() + entry.Buffer = nil - if err != nil { - entry.Logger.mu.Lock() - fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err) - entry.Logger.mu.Unlock() - } else { - entry.Logger.mu.Lock() - _, err = entry.Logger.Out.Write(serialized) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) - } - entry.Logger.mu.Unlock() - } // To avoid Entry#log() returning a value that only would make sense for // panic() to use in Entry#Panic(), we avoid the allocation by checking @@ -129,6 +113,29 @@ func (entry Entry) log(level Level, msg string) { } } +func (entry *Entry) fireHooks() { + entry.Logger.mu.Lock() + defer entry.Logger.mu.Unlock() + err := entry.Logger.Hooks.Fire(entry.Level, entry) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) + } +} + +func (entry *Entry) write() { + serialized, err := entry.Logger.Formatter.Format(entry) + entry.Logger.mu.Lock() + defer entry.Logger.mu.Unlock() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err) + } else { + _, err = entry.Logger.Out.Write(serialized) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) + } + } +} + func (entry *Entry) Debug(args ...interface{}) { if entry.Logger.level() >= DebugLevel { entry.log(DebugLevel, fmt.Sprint(args...)) diff --git a/entry_test.go b/entry_test.go index 99c3b41..a81e2b3 100644 --- a/entry_test.go +++ b/entry_test.go @@ -75,3 +75,41 @@ func TestEntryPanicf(t *testing.T) { entry := NewEntry(logger) entry.WithField("err", errBoom).Panicf("kaboom %v", true) } + +const ( + badMessage = "this is going to panic" + panicMessage = "this is broken" +) + +type panickyHook struct{} + +func (p *panickyHook) Levels() []Level { + return []Level{InfoLevel} +} + +func (p *panickyHook) Fire(entry *Entry) error { + if entry.Message == badMessage { + panic(panicMessage) + } + + return nil +} + +func TestEntryHooksPanic(t *testing.T) { + logger := New() + logger.Out = &bytes.Buffer{} + logger.Level = InfoLevel + logger.Hooks.Add(&panickyHook{}) + + defer func() { + p := recover() + assert.NotNil(t, p) + assert.Equal(t, panicMessage, p) + + entry := NewEntry(logger) + entry.Info("another message") + }() + + entry := NewEntry(logger) + entry.Info(badMessage) +} From 516f6c178d8d405bf866692998d3d5885cb855ee Mon Sep 17 00:00:00 2001 From: Joni Collinge Date: Tue, 30 Jan 2018 15:58:49 +0000 Subject: [PATCH 07/40] Add Application Insights hook to README Adds README link to Application Insights hook --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 08584b5..213e651 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,7 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v | [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. | | [Amazon Kinesis](https://github.com/evalphobia/logrus_kinesis) | Hook for logging to [Amazon Kinesis](https://aws.amazon.com/kinesis/) | | [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) | +| [Application Insights](https://github.com/jjcollinge/logrus-appinsights) | Hook for [Application Insights](https://azure.microsoft.com/en-us/services/application-insights/) | [AzureTableHook](https://github.com/kpfaulkner/azuretablehook/) | Hook for logging to Azure Table Storage| | [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. | | [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic | From 0cf9f0bff936165cf3297ddc2d3d53d14a250c8f Mon Sep 17 00:00:00 2001 From: Joni Collinge Date: Tue, 30 Jan 2018 16:00:33 +0000 Subject: [PATCH 08/40] Made text consistent with other hooks --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 213e651..f986a5e 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,7 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v | [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. | | [Amazon Kinesis](https://github.com/evalphobia/logrus_kinesis) | Hook for logging to [Amazon Kinesis](https://aws.amazon.com/kinesis/) | | [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) | -| [Application Insights](https://github.com/jjcollinge/logrus-appinsights) | Hook for [Application Insights](https://azure.microsoft.com/en-us/services/application-insights/) +| [Application Insights](https://github.com/jjcollinge/logrus-appinsights) | Hook for logging to [Application Insights](https://azure.microsoft.com/en-us/services/application-insights/) | [AzureTableHook](https://github.com/kpfaulkner/azuretablehook/) | Hook for logging to Azure Table Storage| | [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. | | [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic | From efbfdb5f09fe8f6efee1efd21ae690156304cf36 Mon Sep 17 00:00:00 2001 From: Michael Haines Date: Mon, 5 Feb 2018 12:42:00 -0700 Subject: [PATCH 09/40] 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 10/40] 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 11/40] 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 178041e53c9337c7c7781963f2dbb46080484237 Mon Sep 17 00:00:00 2001 From: Phillip Johnsen Date: Mon, 5 Feb 2018 21:59:23 +0100 Subject: [PATCH 12/40] Fix typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08584b5..e5da2c2 100644 --- a/README.md +++ b/README.md @@ -220,7 +220,7 @@ Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in ```go import ( log "github.com/sirupsen/logrus" - "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake" + "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake" logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" "log/syslog" ) From be569094e9c87b6de4fa8909e1e7db1603702f9f Mon Sep 17 00:00:00 2001 From: Jay Ching Lim Date: Mon, 12 Feb 2018 17:26:48 -0500 Subject: [PATCH 13/40] Make fireHooks() method receive a copy of Entry structure to avoid race conditions Signed-off-by: Jay Ching Lim --- entry.go | 6 ++++-- hooks/test/test_test.go | 26 ++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/entry.go b/entry.go index df6f92d..778f4c9 100644 --- a/entry.go +++ b/entry.go @@ -113,10 +113,12 @@ func (entry Entry) log(level Level, msg string) { } } -func (entry *Entry) fireHooks() { +// This function is not declared with a pointer value because otherwise +// race conditions will occur when using multiple goroutines +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_test.go b/hooks/test/test_test.go index 3f55cfe..dea768e 100644 --- a/hooks/test/test_test.go +++ b/hooks/test/test_test.go @@ -1,6 +1,7 @@ package test import ( + "sync" "testing" "github.com/sirupsen/logrus" @@ -8,7 +9,6 @@ import ( ) func TestAllHooks(t *testing.T) { - assert := assert.New(t) logger, hook := NewNullLogger() @@ -35,5 +35,27 @@ func TestAllHooks(t *testing.T) { assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level) assert.Equal("Hello error", hook.LastEntry().Message) assert.Equal(1, len(hook.Entries)) - +} + +func TestLoggingWithHooksRace(t *testing.T) { + assert := assert.New(t) + logger, hook := NewNullLogger() + + var wg sync.WaitGroup + wg.Add(100) + + for i := 0; i < 100; i++ { + go func() { + logger.Info("info") + wg.Done() + }() + } + + assert.Equal(logrus.InfoLevel, hook.LastEntry().Level) + assert.Equal("info", hook.LastEntry().Message) + + wg.Wait() + + entries := hook.AllEntries() + assert.Equal(100, len(entries)) } From efab7f37b72e69e1112ed5f79384698df7030da8 Mon Sep 17 00:00:00 2001 From: Lyra Naeseth Date: Tue, 13 Feb 2018 17:44:51 -0800 Subject: [PATCH 14/40] Have prefixFieldClashes respect the JSON FieldMap Currently, it will fix clashes that don't exist and miss clashes that do exist due to field remapping. Fixes #708 --- formatter.go | 17 +++++++------ json_formatter.go | 2 +- json_formatter_test.go | 55 ++++++++++++++++++++++++++++++++++++++++++ text_formatter.go | 3 ++- 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/formatter.go b/formatter.go index b183ff5..849dc8b 100644 --- a/formatter.go +++ b/formatter.go @@ -30,16 +30,19 @@ 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) { - if t, ok := data["time"]; ok { - data["fields.time"] = t +func prefixFieldClashes(data Fields, fieldMap FieldMap) { + timeKey := fieldMap.resolve(FieldKeyTime) + if t, ok := data[timeKey]; ok { + data["fields."+timeKey] = t } - if m, ok := data["msg"]; ok { - data["fields.msg"] = m + msgKey := fieldMap.resolve(FieldKeyMsg) + if m, ok := data[msgKey]; ok { + data["fields."+msgKey] = m } - if l, ok := data["level"]; ok { - data["fields.level"] = l + levelKey := fieldMap.resolve(FieldKeyLevel) + if l, ok := data[levelKey]; ok { + data["fields."+levelKey] = l } } diff --git a/json_formatter.go b/json_formatter.go index fb01c1b..7064947 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -58,7 +58,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { data[k] = v } } - prefixFieldClashes(data) + prefixFieldClashes(data, f.FieldMap) timestampFormat := f.TimestampFormat if timestampFormat == "" { diff --git a/json_formatter_test.go b/json_formatter_test.go index 51093a7..1c140d0 100644 --- a/json_formatter_test.go +++ b/json_formatter_test.go @@ -3,6 +3,7 @@ package logrus import ( "encoding/json" "errors" + "fmt" "strings" "testing" ) @@ -106,6 +107,60 @@ func TestFieldClashWithLevel(t *testing.T) { } } +func TestFieldClashWithRemappedFields(t *testing.T) { + formatter := &JSONFormatter{ + FieldMap: FieldMap{ + FieldKeyTime: "@timestamp", + FieldKeyLevel: "@level", + FieldKeyMsg: "@message", + }, + } + + b, err := formatter.Format(WithFields(Fields{ + "@timestamp": "@timestamp", + "@level": "@level", + "@message": "@message", + "timestamp": "timestamp", + "level": "level", + "msg": "msg", + })) + 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) + } + + for _, field := range []string{"timestamp", "level", "msg"} { + if entry[field] != field { + t.Errorf("Expected field %v to be untouched; got %v", field, entry[field]) + } + + remappedKey := fmt.Sprintf("fields.%s", field) + if remapped, ok := entry[remappedKey]; ok { + t.Errorf("Expected %s to be empty; got %v", remappedKey, remapped) + } + } + + for _, field := range []string{"@timestamp", "@level", "@message"} { + if entry[field] == field { + t.Errorf("Expected field %v to be mapped to an Entry value", field) + } + + remappedKey := fmt.Sprintf("fields.%s", field) + if remapped, ok := entry[remappedKey]; ok { + if remapped != field { + t.Errorf("Expected field %v to be copied to %s; got %v", field, remappedKey, remapped) + } + } else { + t.Errorf("Expected field %v to be copied to %s; was absent", field, remappedKey) + } + } +} + func TestJSONEntryEndsWithNewline(t *testing.T) { formatter := &JSONFormatter{} diff --git a/text_formatter.go b/text_formatter.go index 61b21ca..62763eb 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -20,6 +20,7 @@ const ( var ( baseTimestamp time.Time + emptyFieldMap FieldMap ) func init() { @@ -82,7 +83,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { b = &bytes.Buffer{} } - prefixFieldClashes(entry.Data) + prefixFieldClashes(entry.Data, emptyFieldMap) f.Do(func() { f.init(entry) }) From f4118d2ead4e96d931fd6751a831165147c99889 Mon Sep 17 00:00:00 2001 From: Andrew Rezcov Date: Fri, 16 Feb 2018 16:50:54 +0300 Subject: [PATCH 15/40] reamde: add logrus-clickhouse-hook --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bc3f9bc..be18d2f 100644 --- a/README.md +++ b/README.md @@ -250,6 +250,7 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v | [Application Insights](https://github.com/jjcollinge/logrus-appinsights) | Hook for logging to [Application Insights](https://azure.microsoft.com/en-us/services/application-insights/) | [AzureTableHook](https://github.com/kpfaulkner/azuretablehook/) | Hook for logging to Azure Table Storage| | [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. | +| [ClickHouse](https://github.com/oxgrouby/logrus-clickhouse-hook) | Send logs to [ClickHouse](https://clickhouse.yandex/) | | [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic | | [Discordrus](https://github.com/kz/discordrus) | Hook for logging to [Discord](https://discordapp.com/) | | [ElasticSearch](https://github.com/sohlich/elogrus) | Hook for logging to ElasticSearch| From 1893e9a3ed8dc59e4aee1e08451d2e2b68030f23 Mon Sep 17 00:00:00 2001 From: earlzo Date: Tue, 20 Feb 2018 16:28:12 +0800 Subject: [PATCH 16/40] Fixed: comment --- entry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entry.go b/entry.go index 778f4c9..d075d72 100644 --- a/entry.go +++ b/entry.go @@ -48,7 +48,7 @@ type Entry struct { func NewEntry(logger *Logger) *Entry { return &Entry{ Logger: logger, - // Default is three fields, give a little extra room + // Default is five fields, give a little extra room Data: make(Fields, 5), } } From c840e59446e71aa3fec56f9317fd2570cc8b3f5f Mon Sep 17 00:00:00 2001 From: Grace Noah Date: Fri, 23 Feb 2018 21:35:25 +0000 Subject: [PATCH 17/40] add gopherjs build tag it behaves the same way as the appengine tag fix #716 --- terminal_bsd.go | 2 +- terminal_check_appengine.go | 2 +- terminal_check_notappengine.go | 2 +- terminal_linux.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/terminal_bsd.go b/terminal_bsd.go index d7b3893..4880d13 100644 --- a/terminal_bsd.go +++ b/terminal_bsd.go @@ -1,5 +1,5 @@ // +build darwin freebsd openbsd netbsd dragonfly -// +build !appengine +// +build !appengine,!gopherjs package logrus diff --git a/terminal_check_appengine.go b/terminal_check_appengine.go index 2403de9..3de08e8 100644 --- a/terminal_check_appengine.go +++ b/terminal_check_appengine.go @@ -1,4 +1,4 @@ -// +build appengine +// +build appengine gopherjs package logrus diff --git a/terminal_check_notappengine.go b/terminal_check_notappengine.go index 116bcb4..067047a 100644 --- a/terminal_check_notappengine.go +++ b/terminal_check_notappengine.go @@ -1,4 +1,4 @@ -// +build !appengine +// +build !appengine,!gopherjs package logrus diff --git a/terminal_linux.go b/terminal_linux.go index 88d7298..f29a009 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 +// +build !appengine,!gopherjs package logrus From 91b159d34d862d10fa1e5d3ffb251656af545580 Mon Sep 17 00:00:00 2001 From: Dylan Meissner Date: Sun, 11 Feb 2018 07:37:38 -0800 Subject: [PATCH 18/40] Add Kafka REST Proxy hook to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bc3f9bc..f77819b 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,7 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v | [Influxus](http://github.com/vlad-doru/influxus) | Hook for concurrently logging to [InfluxDB](http://influxdata.com/) | | [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` | | [KafkaLogrus](https://github.com/tracer0tong/kafkalogrus) | Hook for logging to Kafka | +| [Kafka REST Proxy](https://github.com/Nordstrom/logrus-kafka-rest-proxy) | Hook for logging to [Kafka REST Proxy](https://docs.confluent.io/current/kafka-rest/docs) | | [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem | | [Logbeat](https://github.com/macandmia/logbeat) | Hook for logging to [Opbeat](https://opbeat.com/) | | [Logentries](https://github.com/jcftang/logentriesrus) | Hook for logging to [Logentries](https://logentries.com/) | From c155da19408a8799da419ed3eeb0cb5db0ad5dbc Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Sun, 11 Mar 2018 18:51:37 -0400 Subject: [PATCH 19/40] changelog: add 1.0.5 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc58f64..1bd1deb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.0.5 + +* Fix hooks race (#707) +* Fix panic deadlock (#695) + # 1.0.4 * Fix race when adding hooks (#612) From b537da569f520c93fccbef2c4f327d6001a487a3 Mon Sep 17 00:00:00 2001 From: Wilson Date: Fri, 9 Mar 2018 21:57:56 -0800 Subject: [PATCH 20/40] Fix run-on sentence --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f77819b..1d8eb0d 100644 --- a/README.md +++ b/README.md @@ -495,7 +495,7 @@ logrus.RegisterExitHandler(handler) #### Thread safety -By default Logger is protected by mutex for concurrent writes, this mutex is invoked when calling hooks and writing logs. +By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs. If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking. Situation when locking is not needed includes: From 19b9c9e1ff590d9fd87306010add170f8f4cf07d Mon Sep 17 00:00:00 2001 From: Olzhas Ilyubayev Date: Wed, 28 Mar 2018 12:10:14 +0600 Subject: [PATCH 21/40] delete dead link https://github.com/ripcurld00d/logrus-logzio-hook isn't available now --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9751da1..e2dbb27 100644 --- a/README.md +++ b/README.md @@ -294,7 +294,6 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v | [Telegram](https://github.com/rossmcdonald/telegram_hook) | Hook for logging errors to [Telegram](https://telegram.org/) | | [TraceView](https://github.com/evalphobia/logrus_appneta) | Hook for logging to [AppNeta TraceView](https://www.appneta.com/products/traceview/) | | [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) | -| [logz.io](https://github.com/ripcurld00d/logrus-logzio-hook) | Hook for logging to [logz.io](https://logz.io), a Log as a Service using Logstash | | [SQS-Hook](https://github.com/tsarpaul/logrus_sqs) | Hook for logging to [Amazon Simple Queue Service (SQS)](https://aws.amazon.com/sqs/) | #### Level logging From 5513c600346d78234f1addc60f4953908cdd79c5 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Mon, 16 Apr 2018 14:16:44 +0200 Subject: [PATCH 22/40] Improve documentation for Fatal* class functions --- exported.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exported.go b/exported.go index 013183e..dfd982a 100644 --- a/exported.go +++ b/exported.go @@ -107,7 +107,7 @@ func Panic(args ...interface{}) { std.Panic(args...) } -// Fatal logs a message at level Fatal on the standard logger. +// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1. func Fatal(args ...interface{}) { std.Fatal(args...) } @@ -147,7 +147,7 @@ func Panicf(format string, args ...interface{}) { std.Panicf(format, args...) } -// Fatalf logs a message at level Fatal on the standard logger. +// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1. func Fatalf(format string, args ...interface{}) { std.Fatalf(format, args...) } @@ -187,7 +187,7 @@ func Panicln(args ...interface{}) { std.Panicln(args...) } -// Fatalln logs a message at level Fatal on the standard logger. +// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1. func Fatalln(args ...interface{}) { std.Fatalln(args...) } From e63a8df3408cef283af0fdc0335b235093e2f657 Mon Sep 17 00:00:00 2001 From: dbs5 Date: Mon, 16 Apr 2018 20:20:18 +0200 Subject: [PATCH 23/40] added Anexia CloudLog to list of hooks --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a29222c..46a63e5 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,7 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v | [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. | | [Amazon Kinesis](https://github.com/evalphobia/logrus_kinesis) | Hook for logging to [Amazon Kinesis](https://aws.amazon.com/kinesis/) | | [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) | +| [Anexia CloudLog](https://github.com/anexia-it/go-logrus-cloudlog) | Hook for logging to Anexia CloudLog | | [Application Insights](https://github.com/jjcollinge/logrus-appinsights) | Hook for logging to [Application Insights](https://azure.microsoft.com/en-us/services/application-insights/) | [AzureTableHook](https://github.com/kpfaulkner/azuretablehook/) | Hook for logging to Azure Table Storage| | [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. | From 507c822874d4d270d57b229fb5fcee832fbe92d4 Mon Sep 17 00:00:00 2001 From: lyric Date: Fri, 27 Apr 2018 17:53:47 +0800 Subject: [PATCH 24/40] add mysql hook --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a29222c..33711e4 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,7 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v | [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail | | [Mattermost](https://github.com/shuLhan/mattermost-integration/tree/master/hooks/logrus) | Hook for logging to [Mattermost](https://mattermost.com/) | | [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb | +| [MySQL](https://github.com/LyricTian/logrus-mysql-hook) | Hook for logging to mysql | | [NATS-Hook](https://github.com/rybit/nats_logrus_hook) | Hook for logging to [NATS](https://nats.io) | | [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit | | [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. | From 8369e2f077642788e2ef31f342a0a4621b2b4607 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sat, 12 May 2018 15:51:19 +0200 Subject: [PATCH 25/40] Fix a race condition in TestLoggingWithHooksRace --- hooks/test/test_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/test/test_test.go b/hooks/test/test_test.go index dea768e..742be55 100644 --- a/hooks/test/test_test.go +++ b/hooks/test/test_test.go @@ -51,11 +51,11 @@ func TestLoggingWithHooksRace(t *testing.T) { }() } + wg.Wait() + assert.Equal(logrus.InfoLevel, hook.LastEntry().Level) assert.Equal("info", hook.LastEntry().Message) - wg.Wait() - entries := hook.AllEntries() assert.Equal(100, len(entries)) } From b1e82bef65601dc1a84c16904c337b0a4011ae74 Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sun, 13 May 2018 10:29:45 +0200 Subject: [PATCH 26/40] Update go versions in travis configuration. We only keep the latest 3 language release. Fixes #753 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a23296a..aebdc35 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: go go: - - 1.6.x - - 1.7.x - 1.8.x + - 1.9.x + - 1.10.x - tip env: - GOMAXPROCS=4 GORACE=halt_on_error=1 From aa6766adfe97f5f5d05f9bd694986d171db9b4ff Mon Sep 17 00:00:00 2001 From: taylorchu Date: Tue, 15 May 2018 10:07:01 -0700 Subject: [PATCH 27/40] 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 caed59ec68033ac87a3ef67eccf1d52282efc1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=87o?= Date: Thu, 17 May 2018 11:02:39 +0200 Subject: [PATCH 28/40] Fix Logger.WithField doscription I was puzzled by function documentation not mentioning it works with Error level, so I had to check it out by creating example before I add logrus as a dependency on the company project. Example confirmed what logic was telling me that Logger.WithFields works with Error level of logs. This is is a fix of this small documentation oversight. --- logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logger.go b/logger.go index fdaf8a6..0ac8ce2 100644 --- a/logger.go +++ b/logger.go @@ -88,7 +88,7 @@ func (logger *Logger) releaseEntry(entry *Entry) { } // Adds a field to the log entry, note that it doesn't log until you call -// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry. +// Debug, Print, Info, Warn, Error, Fatal or Panic. It only creates a log entry. // If you want multiple fields, use `WithFields`. func (logger *Logger) WithField(key string, value interface{}) *Entry { entry := logger.newEntry() From 098a5a7cd74c3ce1cf98e83aed6d823ebe96fe3e Mon Sep 17 00:00:00 2001 From: David Bariod Date: Sat, 19 May 2018 21:26:32 +0200 Subject: [PATCH 29/40] Move the hook services list to a wiki page --- README.md | 58 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 7f86254..072e99b 100644 --- a/README.md +++ b/README.md @@ -241,62 +241,8 @@ func init() { ``` Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md). -| Hook | Description | -| ----- | ----------- | -| [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. | -| [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. | -| [Amazon Kinesis](https://github.com/evalphobia/logrus_kinesis) | Hook for logging to [Amazon Kinesis](https://aws.amazon.com/kinesis/) | -| [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) | -| [Anexia CloudLog](https://github.com/anexia-it/go-logrus-cloudlog) | Hook for logging to Anexia CloudLog | -| [Application Insights](https://github.com/jjcollinge/logrus-appinsights) | Hook for logging to [Application Insights](https://azure.microsoft.com/en-us/services/application-insights/) -| [AzureTableHook](https://github.com/kpfaulkner/azuretablehook/) | Hook for logging to Azure Table Storage| -| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. | -| [ClickHouse](https://github.com/oxgrouby/logrus-clickhouse-hook) | Send logs to [ClickHouse](https://clickhouse.yandex/) | -| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic | -| [Discordrus](https://github.com/kz/discordrus) | Hook for logging to [Discord](https://discordapp.com/) | -| [ElasticSearch](https://github.com/sohlich/elogrus) | Hook for logging to ElasticSearch| -| [Firehose](https://github.com/beaubrewer/logrus_firehose) | Hook for logging to [Amazon Firehose](https://aws.amazon.com/kinesis/firehose/) -| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd | -| [Go-Slack](https://github.com/multiplay/go-slack) | Hook for logging to [Slack](https://slack.com) | -| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) | -| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. | -| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger | -| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb | -| [Influxus](http://github.com/vlad-doru/influxus) | Hook for concurrently logging to [InfluxDB](http://influxdata.com/) | -| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` | -| [KafkaLogrus](https://github.com/tracer0tong/kafkalogrus) | Hook for logging to Kafka | -| [Kafka REST Proxy](https://github.com/Nordstrom/logrus-kafka-rest-proxy) | Hook for logging to [Kafka REST Proxy](https://docs.confluent.io/current/kafka-rest/docs) | -| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem | -| [Logbeat](https://github.com/macandmia/logbeat) | Hook for logging to [Opbeat](https://opbeat.com/) | -| [Logentries](https://github.com/jcftang/logentriesrus) | Hook for logging to [Logentries](https://logentries.com/) | -| [Logentrus](https://github.com/puddingfactory/logentrus) | Hook for logging to [Logentries](https://logentries.com/) | -| [Logmatic.io](https://github.com/logmatic/logmatic-go) | Hook for logging to [Logmatic.io](http://logmatic.io/) | -| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) | -| [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) | -| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail | -| [Mattermost](https://github.com/shuLhan/mattermost-integration/tree/master/hooks/logrus) | Hook for logging to [Mattermost](https://mattermost.com/) | -| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb | -| [MySQL](https://github.com/LyricTian/logrus-mysql-hook) | Hook for logging to mysql | -| [NATS-Hook](https://github.com/rybit/nats_logrus_hook) | Hook for logging to [NATS](https://nats.io) | -| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit | -| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. | -| [PostgreSQL](https://github.com/gemnasium/logrus-postgresql-hook) | Send logs to [PostgreSQL](http://postgresql.org) | -| [Promrus](https://github.com/weaveworks/promrus) | Expose number of log messages as [Prometheus](https://prometheus.io/) metrics | -| [Pushover](https://github.com/toorop/logrus_pushover) | Send error via [Pushover](https://pushover.net) | -| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) | -| [Redis-Hook](https://github.com/rogierlommers/logrus-redis-hook) | Hook for logging to a ELK stack (through Redis) | -| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar | -| [Scribe](https://github.com/sagar8192/logrus-scribe-hook) | Hook for logging to [Scribe](https://github.com/facebookarchive/scribe)| -| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. | -| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. | -| [Stackdriver](https://github.com/knq/sdhook) | Hook for logging to [Google Stackdriver](https://cloud.google.com/logging/) | -| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)| -| [Syslog](https://github.com/sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. | -| [Syslog TLS](https://github.com/shinji62/logrus-syslog-ng) | Send errors to remote syslog server with TLS support. | -| [Telegram](https://github.com/rossmcdonald/telegram_hook) | Hook for logging errors to [Telegram](https://telegram.org/) | -| [TraceView](https://github.com/evalphobia/logrus_appneta) | Hook for logging to [AppNeta TraceView](https://www.appneta.com/products/traceview/) | -| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) | -| [SQS-Hook](https://github.com/tsarpaul/logrus_sqs) | Hook for logging to [Amazon Simple Queue Service (SQS)](https://aws.amazon.com/sqs/) | +A list of currently known of service hook can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks) + #### Level logging From 070c81def33f6362a8267b6a4e56fb7bf23fc6b5 Mon Sep 17 00:00:00 2001 From: Moriyoshi Koizumi Date: Wed, 30 May 2018 09:50:59 +0000 Subject: [PATCH 30/40] 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 5d60369ef3a5c165e66ece9cdebb2d4177729d84 Mon Sep 17 00:00:00 2001 From: Neil Isaac Date: Mon, 18 Jun 2018 21:32:35 -0400 Subject: [PATCH 31/40] Fixed prefixFieldClashes for TextFormatter and added coverage --- formatter.go | 3 +++ text_formatter.go | 8 ++++---- text_formatter_test.go | 14 +++++++++++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/formatter.go b/formatter.go index 849dc8b..83c7494 100644 --- a/formatter.go +++ b/formatter.go @@ -34,15 +34,18 @@ func prefixFieldClashes(data Fields, fieldMap FieldMap) { timeKey := fieldMap.resolve(FieldKeyTime) if t, ok := data[timeKey]; ok { data["fields."+timeKey] = t + delete(data, timeKey) } msgKey := fieldMap.resolve(FieldKeyMsg) if m, ok := data[msgKey]; ok { data["fields."+msgKey] = m + delete(data, msgKey) } levelKey := fieldMap.resolve(FieldKeyLevel) if l, ok := data[levelKey]; ok { data["fields."+levelKey] = l + delete(data, levelKey) } } diff --git a/text_formatter.go b/text_formatter.go index 5af4e56..3e55040 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 @@ -81,7 +80,8 @@ func (f *TextFormatter) init(entry *Entry) { // Format renders a single log entry func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { - var b *bytes.Buffer + prefixFieldClashes(entry.Data, f.FieldMap) + keys := make([]string, 0, len(entry.Data)) for k := range entry.Data { keys = append(keys, k) @@ -90,14 +90,14 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { if !f.DisableSorting { sort.Strings(keys) } + + var b *bytes.Buffer if entry.Buffer != nil { b = entry.Buffer } else { b = &bytes.Buffer{} } - prefixFieldClashes(entry.Data, emptyFieldMap) - f.Do(func() { f.init(entry) }) isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors diff --git a/text_formatter_test.go b/text_formatter_test.go index 4f21861..7245f94 100644 --- a/text_formatter_test.go +++ b/text_formatter_test.go @@ -191,6 +191,12 @@ func TestTextFormatterFieldMap(t *testing.T) { Message: "oh hi", Level: WarnLevel, Time: time.Date(1981, time.February, 24, 4, 28, 3, 100, time.UTC), + Data: Fields{ + "field1": "f1", + "message": "messagefield", + "somelevel": "levelfield", + "timeywimey": "timeywimeyfield", + }, } b, err := formatter.Format(entry) @@ -199,7 +205,13 @@ func TestTextFormatterFieldMap(t *testing.T) { } assert.Equal(t, - `timeywimey="1981-02-24T04:28:03Z" somelevel=warning message="oh hi"`+"\n", + `timeywimey="1981-02-24T04:28:03Z" `+ + `somelevel=warning `+ + `message="oh hi" `+ + `field1=f1 `+ + `fields.message=messagefield `+ + `fields.somelevel=levelfield `+ + `fields.timeywimey=timeywimeyfield`+"\n", string(b), "Formatted doesn't respect correct FieldMap") } From 6b28c2c7d76fb49e829b83ab6dc2f25327db3189 Mon Sep 17 00:00:00 2001 From: Neil Isaac Date: Mon, 18 Jun 2018 21:39:53 -0400 Subject: [PATCH 32/40] error message --- text_formatter_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text_formatter_test.go b/text_formatter_test.go index 7245f94..921d052 100644 --- a/text_formatter_test.go +++ b/text_formatter_test.go @@ -213,7 +213,7 @@ func TestTextFormatterFieldMap(t *testing.T) { `fields.somelevel=levelfield `+ `fields.timeywimey=timeywimeyfield`+"\n", string(b), - "Formatted doesn't respect correct FieldMap") + "Formatted output doesn't respect FieldMap") } // TODO add tests for sorting etc., this requires a parser for the text From 2ce6c0cb44b88b3cf9b97e7513269d00c7eee11c Mon Sep 17 00:00:00 2001 From: Przemyslaw Wegrzyn Date: Tue, 19 Jun 2018 14:31:57 +0200 Subject: [PATCH 33/40] Support for Entry data under nested JSON dictionary. --- json_formatter.go | 10 ++++++++++ json_formatter_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/json_formatter.go b/json_formatter.go index 7064947..dab1761 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -33,6 +33,9 @@ type JSONFormatter struct { // DisableTimestamp allows disabling automatic timestamps in output DisableTimestamp bool + // DataKey allows users to put all the log entry parameters into a nested dictionary at a given key. + DataKey string + // FieldMap allows users to customize the names of keys for default fields. // As an example: // formatter := &JSONFormatter{ @@ -58,6 +61,13 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { data[k] = v } } + + if f.DataKey != "" { + newData := make(Fields, 4) + newData[f.DataKey] = data + data = newData + } + prefixFieldClashes(data, f.FieldMap) timestampFormat := f.TimestampFormat diff --git a/json_formatter_test.go b/json_formatter_test.go index 1c140d0..0dde300 100644 --- a/json_formatter_test.go +++ b/json_formatter_test.go @@ -161,6 +161,48 @@ func TestFieldClashWithRemappedFields(t *testing.T) { } } +func TestFieldsInNestedDictionary(t *testing.T) { + formatter := &JSONFormatter{ + DataKey: "args", + } + + logEntry := WithFields(Fields{ + "level": "level", + "test": "test", + }) + logEntry.Level = InfoLevel + + b, err := formatter.Format(logEntry) + 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) + } + + args := entry["args"].(map[string]interface{}) + + for _, field := range []string{"test", "level"} { + if value, present := args[field]; !present || value != field { + t.Errorf("Expected field %v to be present under 'args'; untouched", field) + } + } + + for _, field := range []string{"test", "fields.level"} { + if _, present := entry[field]; present { + t.Errorf("Expected field %v not to be present at top level", field) + } + } + + // with nested object, "level" shouldn't clash + if entry["level"] != "info" { + t.Errorf("Expected 'level' field to contain 'info'") + } +} + func TestJSONEntryEndsWithNewline(t *testing.T) { formatter := &JSONFormatter{} From fc9bbf2f57995271c5cd6911ede7a2ebc5ea7c6f Mon Sep 17 00:00:00 2001 From: Daniel Bershatsky Date: Wed, 27 Jun 2018 20:29:28 +0300 Subject: [PATCH 34/40] [#241] Allow to set writer during logger usage. --- exported.go | 4 +--- logger.go | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/exported.go b/exported.go index 013183e..6c98afd 100644 --- a/exported.go +++ b/exported.go @@ -15,9 +15,7 @@ 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. diff --git a/logger.go b/logger.go index 0ac8ce2..0c1b05e 100644 --- a/logger.go +++ b/logger.go @@ -316,6 +316,12 @@ func (logger *Logger) SetLevel(level Level) { atomic.StoreUint32((*uint32)(&logger.Level), uint32(level)) } +func (logger *Logger) SetOutput(out io.Writer) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.Out = out +} + func (logger *Logger) AddHook(hook Hook) { logger.mu.Lock() defer logger.mu.Unlock() From 52b92f5b89ba5a81f021ad845bcd3a0d3ba1b2ac Mon Sep 17 00:00:00 2001 From: Simon Brisson Date: Thu, 28 Jun 2018 16:33:52 -0400 Subject: [PATCH 35/40] 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 36/40] 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 37/40] 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 38/40] 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 39/40] 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 40/40] 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) }