From d8e3add56fa5b0b0c7f4917554f16ac93a1ff90a Mon Sep 17 00:00:00 2001 From: tbunyk Date: Fri, 15 Mar 2019 13:09:11 +0200 Subject: [PATCH 1/3] Add hook to send logs to custom writer #678 --- hooks/writer/README.md | 43 +++++++++++++++++++++++++++++++++++++ hooks/writer/writer.go | 29 +++++++++++++++++++++++++ hooks/writer/writer_test.go | 37 +++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 hooks/writer/README.md create mode 100644 hooks/writer/writer.go create mode 100644 hooks/writer/writer_test.go diff --git a/hooks/writer/README.md b/hooks/writer/README.md new file mode 100644 index 0000000..6967630 --- /dev/null +++ b/hooks/writer/README.md @@ -0,0 +1,43 @@ +# Writer Hooks for Logrus + +Send logs of given levels to any object with `io.Writer` interface. + +## Usage + +If you want for example send high level logs to `Stderr` and +logs of normal execution to `Stdout`, you could do it like this: + +```go +package main + +import ( + "io/ioutil" + "os" + + log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus/hooks/writer" +) + +func main() { + log.SetOutput(ioutil.Discard) // Send all logs to nowhere by default + + log.AddHook(&writer.Hook{ // Send logs with level higher than warning to stderr + Writer: os.Stderr, + LogLevels: []log.Level{ + log.PanicLevel, + log.FatalLevel, + log.ErrorLevel, + log.WarnLevel, + }, + }) + log.AddHook(&writer.Hook{ // Send info and debug logs to stdout + Writer: os.Stdout, + LogLevels: []log.Level{ + log.InfoLevel, + log.DebugLevel, + }, + }) + log.Info("This will go to stdout") + log.Warn("This will go to stderr") +} +``` diff --git a/hooks/writer/writer.go b/hooks/writer/writer.go new file mode 100644 index 0000000..0532dee --- /dev/null +++ b/hooks/writer/writer.go @@ -0,0 +1,29 @@ +package writer + +import ( + "io" + + log "github.com/sirupsen/logrus" +) + +// Hook is a hook that writes logs of specified LogLevels to specified Writer +type Hook struct { + Writer io.Writer + LogLevels []log.Level +} + +// Fire will be called when some logging function is called with current hook +// It will format log entry to string and write it to appropriate writer +func (hook *Hook) Fire(entry *log.Entry) error { + line, err := entry.String() + if err != nil { + return err + } + _, err = hook.Writer.Write([]byte(line)) + return err +} + +// Levels define on which log levels this hook would trigger +func (hook *Hook) Levels() []log.Level { + return hook.LogLevels +} diff --git a/hooks/writer/writer_test.go b/hooks/writer/writer_test.go new file mode 100644 index 0000000..22cfdff --- /dev/null +++ b/hooks/writer/writer_test.go @@ -0,0 +1,37 @@ +package writer + +import ( + "bytes" + "io/ioutil" + "testing" + + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +func TestDifferentLevelsGoToDifferentWriters(t *testing.T) { + var a, b bytes.Buffer + + log.SetFormatter(&log.TextFormatter{ + DisableTimestamp: true, + }) + log.SetOutput(ioutil.Discard) // Send all logs to nowhere by default + + log.AddHook(&Hook{ + Writer: &a, + LogLevels: []log.Level{ + log.WarnLevel, + }, + }) + log.AddHook(&Hook{ // Send info and debug logs to stdout + Writer: &b, + LogLevels: []log.Level{ + log.InfoLevel, + }, + }) + log.Warn("send to a") + log.Info("send to b") + + assert.Equal(t, a.String(), "level=warning msg=\"send to a\"\n") + assert.Equal(t, b.String(), "level=info msg=\"send to b\"\n") +} From 470f2e08fc71f6304f7d49b42faae079736fdf2f Mon Sep 17 00:00:00 2001 From: tbunyk Date: Fri, 15 Mar 2019 13:19:10 +0200 Subject: [PATCH 2/3] Fix some test conditions --- hooks/writer/writer_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/hooks/writer/writer_test.go b/hooks/writer/writer_test.go index 22cfdff..a30d3b0 100644 --- a/hooks/writer/writer_test.go +++ b/hooks/writer/writer_test.go @@ -14,6 +14,7 @@ func TestDifferentLevelsGoToDifferentWriters(t *testing.T) { log.SetFormatter(&log.TextFormatter{ DisableTimestamp: true, + DisableColors: true, }) log.SetOutput(ioutil.Discard) // Send all logs to nowhere by default From c88f8de1fee0d8ecbc5649f4e4f12c96a22b21da Mon Sep 17 00:00:00 2001 From: tbunyk Date: Mon, 18 Mar 2019 16:07:31 +0200 Subject: [PATCH 3/3] Add Bytes() method to Entry, and use it to avoid double type cast --- entry.go | 7 ++++++- hooks/writer/writer.go | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/entry.go b/entry.go index 3d2c9e0..7e8ad25 100644 --- a/entry.go +++ b/entry.go @@ -85,10 +85,15 @@ func NewEntry(logger *Logger) *Entry { } } +// Returns the bytes representation of this entry from the formatter. +func (entry *Entry) Bytes() ([]byte, error) { + return entry.Logger.Formatter.Format(entry) +} + // Returns the string representation from the reader and ultimately the // formatter. func (entry *Entry) String() (string, error) { - serialized, err := entry.Logger.Formatter.Format(entry) + serialized, err := entry.Bytes() if err != nil { return "", err } diff --git a/hooks/writer/writer.go b/hooks/writer/writer.go index 0532dee..1160c79 100644 --- a/hooks/writer/writer.go +++ b/hooks/writer/writer.go @@ -15,11 +15,11 @@ type Hook struct { // Fire will be called when some logging function is called with current hook // It will format log entry to string and write it to appropriate writer func (hook *Hook) Fire(entry *log.Entry) error { - line, err := entry.String() + line, err := entry.Bytes() if err != nil { return err } - _, err = hook.Writer.Write([]byte(line)) + _, err = hook.Writer.Write(line) return err }