Logrus: How to split logs of different levels to different outputs?

Created on 24 Nov 2017  路  14Comments  路  Source: sirupsen/logrus

I use logrus and I'm requested to to write info and debug log messages to stdout, and warning, errors fatals and panics to stderr.

Currently I see no straightforward solutions for this, as Logger has only one Out.

I was thinking about writing writer that parses log message and writes to Stdout or Stderr depending on it's level, but this could break when log formatter is changed.

So I changed my mind thinking about creating "/dev/null" Writer, and

log.SetOutput(NullWriter{}) // Send all logs to nowhere by default

Then write hooks that write logs to stderr and stdout, and register them for appropriate levels. (Something like this)

Which lead me to the question why have both Out setting and Hooks? Hooks alone would be enough with default hook for stderr and possibility to delete and add another hooks. And if there was already hook for standart outputs, my task would be much simpler. (So this could be regarded as a feature request).

Or what would developer of this library do to solve this?

enhancement need-more-info

Most helpful comment

@gytisgreitai Yes, like I described previously and with tip from @adelowo

import (
    "io"
    "io/ioutil"
    "os"

    log "github.com/sirupsen/logrus"
)

// WriterHook is a hook that writes logs of specified LogLevels to specified Writer
type WriterHook 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 *WriterHook) 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 *WriterHook) Levels() []log.Level {
    return hook.LogLevels
}

// setupLogs adds hooks to send logs to different destinations depending on level
func setupLogs() {
    log.SetOutput(ioutil.Discard) // Send all logs to nowhere by default

    log.AddHook(&WriterHook{ // 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(&WriterHook{ // Send info and debug logs to stdout
        Writer: os.Stdout,
        LogLevels: []log.Level{
            log.InfoLevel,
            log.DebugLevel,
        },
    })
}

All 14 comments

You can use ioutil.Discard instead of writing your custom NullWriter

@bunyk have you solved this issue?

@gytisgreitai Yes, like I described previously and with tip from @adelowo

import (
    "io"
    "io/ioutil"
    "os"

    log "github.com/sirupsen/logrus"
)

// WriterHook is a hook that writes logs of specified LogLevels to specified Writer
type WriterHook 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 *WriterHook) 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 *WriterHook) Levels() []log.Level {
    return hook.LogLevels
}

// setupLogs adds hooks to send logs to different destinations depending on level
func setupLogs() {
    log.SetOutput(ioutil.Discard) // Send all logs to nowhere by default

    log.AddHook(&WriterHook{ // 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(&WriterHook{ // Send info and debug logs to stdout
        Writer: os.Stdout,
        LogLevels: []log.Level{
            log.InfoLevel,
            log.DebugLevel,
        },
    })
}

Could you make a PR? I'd very much like to see this in the core too.

@FlorinAsavoaie I could try, but don't know what @sirupsen will think about this.

Any update here? This feature is very useful

Well, @TennyZhuang I guess @sirupsen could put hook described above somewhere to hooks.go (I'm not sure where should it be), so you could import it, but right now you could just copypaste that code to your logs configuration.

@bunyk your hook seems practical and simple enough to add it in the core. This is a question which comes quite a lot here, so I think there is a real need here. If you have time to put that in a PR, I would happily merge it.

This is funny, I implemented the same solution for the same problem in another project yesterday. (And _then_ came to the issue tracker to see if anyone else had better ideas. :/)

Does @bunyk want to create a PR with this? I'd love to drop what I wrote for an official solution.

@gunnihinn @dgsb and everyone, thanks for kind words. :) I finally found some time, and did #924

Any comments, and edit suggestions are welcome, because I have not written in Go for more than 6 month's, so could forget some stuff.

@bunyk : The hook does NOT work with Debug and Trace levels. Other levels seem fine.

@NguyenHoaiPhuong Could you provide some tests to verify this?

wrote a program to split logs into multiple files with multiple fields with lumberjack log ratation here https://github.com/vikaspushkar/logrusIndifferentFiles

Was this page helpful?
0 / 5 - 0 ratings

Related issues

UnAfraid picture UnAfraid  路  5Comments

drewwells picture drewwells  路  3Comments

Integralist picture Integralist  路  3Comments

demizer picture demizer  路  4Comments

psmason picture psmason  路  4Comments