1

I'm using zerolog for Go logging:

zerolog.SetGlobalLevel(zerolog.InfoLevel)
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "15:04:05"})

This prints all the messages into the os.Stderr, but I want to split the logs into multiple outputs, with the next condition:

DebugLevel, InfoLevel, and WarnLevel will be printed to os.Stdout,
and ErrorLevel, FatalLevel, and PanicLevel will be printed to os.Stderr.

How can I achieve that?

baruchiro
  • 5,088
  • 5
  • 44
  • 66

2 Answers2

1

You could implement LevelWriter. For example (playground):

var (
    // Using different time formats to make it clear which logger is being used
    debugOut = zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC1123} // time format: "Mon, 02 Jan 2006 15:04:05 MST"
    errorOut = zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339} // time format: "2006-01-02T15:04:05Z07:00"
)

// logOut implements zerolog.LevelWriter
type logOut struct{}

// Write should not be called
func (l logOut) Write(p []byte) (n int, err error) {
    return os.Stdout.Write(p)
}

// WriteLevel write to the appropriate output
func (l logOut) WriteLevel(level zerolog.Level, p []byte) (n int, err error) {
    if level <= zerolog.WarnLevel {
        return debugOut.Write(p)
    } else {
        return errorOut.Write(p)
    }
}

func main() {
    logger := zerolog.New(logOut{}).With().Timestamp().Logger()

    logger.Info().Str("foo", "bar").Msg("Info")   // Output: "Tue, 08 Aug 2023 22:12:31 NZST INF Info foo=bar"
    logger.Error().Str("foo", "bar").Msg("Error") // Output: "2023-08-08T22:12:31+12:00 ERR Error foo=bar"
}
Brits
  • 14,829
  • 2
  • 18
  • 31
0

You need to use MultiLevelWriter, to forward the logs into multiple LevelWriters outputs (although it is not in the function signature, you should give it LevelWriters, otherwise it will not do any special logic).

The LevelWriter is an interface with io.Writer and WriteLevel(level Level, p []byte) (n int, err error), so you can add any zerolog writer you want (since it is an io.Writer), and use it with a level condition on WriteLevel.

Here is an example for a LevelWriter:

type SpecificLevelWriter struct {
    io.Writer
    Levels []zerolog.Level
}

func (w SpecificLevelWriter) WriteLevel(level zerolog.Level, p []byte) (int, error) {
    for _, l := range w.Levels {
        if l == level {
            return w.Write(p)
        }
    }
    return len(p), nil
}

Now you need to use it twice with MultiLevelWriter:

func CreateLogger() zerolog.Logger {
    writer := zerolog.MultiLevelWriter(
        SpecificLevelWriter{
            Writer: zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: "15:04:05"},
            Levels: []zerolog.Level{
                zerolog.DebugLevel, zerolog.InfoLevel, zerolog.WarnLevel,
            },
        },
        SpecificLevelWriter{
            Writer: zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "15:04:05"},
            Levels: []zerolog.Level{
                zerolog.ErrorLevel, zerolog.FatalLevel, zerolog.PanicLevel,
            },
        },
    )

    return zerolog.New(writer).Logger()
}

Note that as I said, zerologs loggers are io.Writer, you can replace the os.Stdout with zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: "15:04:05"}

baruchiro
  • 5,088
  • 5
  • 44
  • 66