How to log to stdout or stderr based on log level using uber-go/zap?
Asked Answered
W

1

4

I am trying to set up logging using this package github.com/uber-go/zap. I need to write:

  • Info logs to stdout
  • Error and Fatal logs to stderr

I tried to do this by setting up and building zap.Config like this:

    cfg = &zap.Config{
        Encoding:         "json",
        Level:            zap.NewAtomicLevelAt(zapcore.DebugLevel),
        OutputPaths:      []string{"stdout"},
        ErrorOutputPaths: []string{"stderr"},
        EncoderConfig: zapcore.EncoderConfig{
            MessageKey: "message",

            LevelKey:    "level",
            EncodeLevel: zapcore.CapitalLevelEncoder,

            TimeKey:    "time",
            EncodeTime: zapcore.ISO8601TimeEncoder,

            CallerKey:    "caller",
            EncodeCaller: zapcore.ShortCallerEncoder,

            EncodeDuration: zapcore.MillisDurationEncoder,
        },
    }

Also I tried this way:

cfg = zap.NewProductionConfig()
    cfg.OutputPaths = []string{"stdout"}
    logger, err = cfg.Build(zap.AddCaller(), zap.AddCallerSkip(1))

But all logs are written to either stdout or stderr. How can I separate it?

Weedy answered 21/7, 2021 at 15:49 Comment(0)
C
7

Use zapcore.NewTee with two cores:

  • one that locks zapcore.InfoLevel to stdout
  • one that locks zapcore.ErrorLevel and zapcore.FatalLevel to stderr

This is a minimal program to demonstrate the usage:

package main

import (
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "os"
)

func main() {
    // info level enabler
    infoLevel := zap.LevelEnablerFunc(func(level zapcore.Level) bool {
        return level == zapcore.InfoLevel
    })

    // error and fatal level enabler
    errorFatalLevel := zap.LevelEnablerFunc(func(level zapcore.Level) bool {
        return level == zapcore.ErrorLevel || level == zapcore.FatalLevel
    })

    // write syncers
    stdoutSyncer := zapcore.Lock(os.Stdout)
    stderrSyncer := zapcore.Lock(os.Stderr)

    // tee core
    core := zapcore.NewTee(
        zapcore.NewCore(
            zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
            stdoutSyncer,
            infoLevel,
        ),
        zapcore.NewCore(
            zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
            stderrSyncer,
            errorFatalLevel,
        ),
    )

    // finally construct the logger with the tee core
    logger := zap.New(core)

    logger.Info("info log")
    logger.Error("error log")
}

Note that the program above logs two messages, one at info level and one at error level. To see that it prints both to the correct device, run it and redirect stdout or stderr to /dev/null — thus printing to the console only the other one:

$ go build main.go

$ ./main 2>/dev/null # shows only stdout
{"level":"info","ts":1626900981.520349,"msg":"info log"}

$ ./main 1>/dev/null # shows only stderr
{"level":"error","ts":1626901025.056065,"msg":"error log"}
Co answered 21/7, 2021 at 21:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.