Logrus: Multiple io.Writers

Created on 10 Aug 2015  路  4Comments  路  Source: sirupsen/logrus

It would be great for CLI applications if there was a way to have colorized stdout and json/logfmt formatted output to a file at the same time. It is possible to set logger.Out to an io.MultiWriter, but then file output is ansi encoded, when logfmt or json formatted output would be much preferred. Using tee with a logrus enabled cli application sets the output to a purely logfmt formatted output with no coloring.

I have done this previously in my own (now defunct) logging package go-logs by writing to multiple io.Writers and removing coloring for writers that were of type *os.File, but not os.Stout or os.Stderr.

    for _, w := range l.streams {
        wIface := reflect.ValueOf(w).Interface()
        switch wType := wIface.(type) {
        case *os.File:
            if wType == os.Stdout || wType == os.Stderr {
                write(w, ASCII_OUTPUT)
                continue
            }
            write(w, LOGFMT_OUTPUT)
        default:
            write(w, LOGFMT_OUTPUT)
        }
    }

This implementation used reflect on every write, but this would only be necessary in cases where there were more than one io.Writer.

help-wanted

Most helpful comment

mw := io.MultiWriter(os.Stdout, logFile)
logrus.SetOutput(mw)

All 4 comments

I was just dealing with this, and I think the best solution is to create a new hook that writes out to a file, and then the hook can use its own formatter to output. I ended up scrapping the file entirely in favor of using the built-in syslog hook, but it shouldn't be too hard to write a generic "write to file" hook.

Personally, I think this is best handled in Hooks and Formatters. This is how I've done it before to add syslog output.

@demizer if you want to implement a custom formatter that does this, we could then add it to the README list.

// hook has built-in TextFormatter{DisableColors: true} object instance
// TextFormatter initialized outside as default or {ForceColors: true}


package logging

import (
    "fmt"
    "github.com/sirupsen/logrus"
    "os"
)

type LogrusFileHook struct {
    file      *os.File
    flag      int
    chmod     os.FileMode
    formatter *logrus.TextFormatter
}

func NewLogrusFileHook(file string, flag int, chmod os.FileMode) (*LogrusFileHook, error) {
    plainFormatter := &logrus.TextFormatter{DisableColors: true}
    logFile, err := os.OpenFile(file, flag, chmod)
    if err != nil {
        fmt.Fprintf(os.Stderr, "unable to write file on filehook %v", err)
        return nil, err
    }

    return &LogrusFileHook{logFile, flag, chmod, plainFormatter}, err
}

// Fire event
func (hook *LogrusFileHook) Fire(entry *logrus.Entry) error {

    plainformat, err := hook.formatter.Format(entry)
    line := string(plainformat)
    _, err = hook.file.WriteString(line)
    if err != nil {
        fmt.Fprintf(os.Stderr, "unable to write file on filehook(entry.String)%v", err)
        return err
    }

    return nil
}

func (hook *LogrusFileHook) Levels() []logrus.Level {
    return []logrus.Level{
        logrus.PanicLevel,
        logrus.FatalLevel,
        logrus.ErrorLevel,
        logrus.WarnLevel,
        logrus.InfoLevel,
        logrus.DebugLevel,
    }
}

Example (usage):

  logrus := &logrus.Logger{
    Out: os.Stdout
    Formatter: &logrus.TextFormatter{ForceColors: true},
    Hooks: make(logrus.LevelHooks),
    // Minimum level to log at (5 is most verbose (debug), 0 is panic)
    Level: logrus.DebugLevel,
  }
  fileHook, err := NewLogrusFileHook("log1.txt", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
  if err == nil {
    logrus.Hooks.Add(fileHook)
  }

I think there could be a better way to disable color per hook maybe. But since Hooks and Formatters could merge it's something that could be discussed then.

EDIT: Oh. I see there was already a file hook listed in the hooks table in very similar design to above: https://github.com/rifflock/lfshook/blob/master/lfshook.go

mw := io.MultiWriter(os.Stdout, logFile)
logrus.SetOutput(mw)

Was this page helpful?
0 / 5 - 0 ratings