Repository: sirupsen/logrus
Pull request #924 (2019-03-15 11:10:34)

Add hook to send logs to custom writer #678
Implements #678


Patch:
diff --git a/entry.go b/entry.go
index 63e2558..ba5087b 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/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..1160c79
--- /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.Bytes()
+	if err != nil {
+		return err
+	}
+	_, err = hook.Writer.Write(line)
+	return err
+}
+
+// Levels define on which log levels this hook would trigger
+func (hook *Hook) Levels() []log.Level {
+	return hook.LogLevels
+}


Test patch:
diff --git a/hooks/writer/writer_test.go b/hooks/writer/writer_test.go
new file mode 100644
index 0000000..a30d3b0
--- /dev/null
+++ b/hooks/writer/writer_test.go
@@ -0,0 +1,38 @@
+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,
+		DisableColors:    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")
+}