gologginggo-zap

Does Zap logger support escape character '\n' and '\t' to print new line errorVerbose or stacktrace


func InitLogger() {
    loggerMgr, err := zap.NewProduction()
    if err != nil {
        log.Println("error")
    }
    defer loggerMgr.Sync()
    logger := loggerMgr.Sugar()
    logger.Error("START!")
}

The result

{"level":"error","ts":1635248463.347698,"caller":"cgw-go-utils/main.go:36","msg":"START!","stacktrace":"main.raiseError\n\t/Users/I053322/dev/repo/cgw-go-utils/main.go:36\nmain.main\n\t/Users/I053322/dev/repo/cgw-go-utils/main.go:22\nruntime.main\n\t/usr/local/opt/go/libexec/src/runtime/proc.go:225"}

I want to get the result with escape of

{"level":"error","ts":1635248463.347698,"caller":"cgw-go-utils/main.go:36","msg":"START!","stacktrace":"main.raiseError
    /Users/I053322/dev/repo/cgw-go-utils/main.go:36
main.main
    /Users/I053322/dev/repo/cgw-go-utils/main.go:22
runtime.main
    /usr/local/opt/go/libexec/src/runtime/proc.go:225"}

Solution

  • No.

    zap.NewProduction() returns a logger with JSON encoding, and escape sequences as \n and \t are encoded verbatim into JSON. If it didn't, it wouldn't be valid JSON.

    If you really must, and you are okay with producing invalid JSON, you might implement your own encoder by decorating the existing zap JSON encoder.

    Demonstrative code:

    package main
    
    import (
        "bytes"
        "go.uber.org/zap"
        "go.uber.org/zap/buffer"
        "go.uber.org/zap/zapcore"
        "os"
    )
    
    func main() {
        core := zapcore.NewCore(
            &EscapeSeqJSONEncoder{ Encoder: zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) },
            os.Stdout,
            zapcore.InfoLevel,
        )
        logger := zap.New(core)
        logger.Info("foo", zap.String("some_field", "foo\nbar"))
    }
    
    type EscapeSeqJSONEncoder struct {
        zapcore.Encoder
    }
    
    func (enc *EscapeSeqJSONEncoder) Clone() zapcore.Encoder {
        return enc // TODO: change me
    }
    
    func (enc *EscapeSeqJSONEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
        // call EncodeEntry on the embedded interface to get the 
        // original output
        b, err := enc.Encoder.EncodeEntry(entry, fields)
        if err != nil {
            return nil, err
        }
        newb := buffer.NewPool().Get()
    
        // then manipulate that output into what you need it to be
        newb.Write(bytes.Replace(b.Bytes(), []byte("\\n"), []byte("\n"), -1))
        return newb, nil
    }
    

    Outputs:

    {"level":"info","ts":1635257984.618096,"msg":"foo","some_field":"foo
    bar"}
    

    Notes: the function zapcore.NewCore takes a zapcore.Encoder argument, which is an interface. This interface is very troublesome to implement. The idea is to embed it in your custom struct, so that you get all the methods for free.