程序可观测-zap日志

zap日志

参考

www.liwenzhou.com/posts/Go/zap/

github.com/uber-go/zap/blob/master...

github.com/flipped-aurora/gin-vue-...

github.com/douyu/jupiter/tree/mast...

本文地址

github.com/luxun9527/go-lib/tree/m... 您的star就是我更新的动力。

我的设置

分享一个我参考别的开源框架对zap日志的一些设置

package logger

import (
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "gopkg.in/natefinch/lumberjack.v2"
    "log"
    "os"
    "time"
)

var L *zap.Logger

const (
    // _defaultBufferSize specifies the default size used by Buffer.
    _defaultBufferSize = 256 * 1024 // 256 kB

    // _defaultFlushInterval specifies the default flush interval for
    // Buffer.
    _defaultFlushInterval = 30 * time.Second
)

func InitLogger(loggerConfig Config) {
    L = loggerConfig.Build()
}

func (lc *Config) parseLevel() zap.AtomicLevel {
    level, err := zap.ParseAtomicLevel(lc.Level)
    if err != nil {
        log.Panicf("init level failed level %s err %v", lc.Level, err)
    }
    return level
}

type Config struct {
    //日志级别 debug info warn panic
    Level string
    //panic时候 是否显示堆栈 panic级别的日志输出堆栈信息。
    Stacktrace bool
    //添加调用者信息
    AddCaller bool
    //调用链,往上多少级 ,在一些中间件,对日志有包装,可以通过这个选项指定。
    CallerShip int
    //输出到哪里标准输出console,还是文件file
    Mode string
    //文件名称加路径
    FileName string
    //error级别的日志输入到不同的地方
    ErrorFileName string
    // 日志文件大小 单位MB 默认500MB
    MaxSize int
    //日志保留天数
    MaxAge int
    //日志最大保留的个数
    MaxBackup int
    //异步日志 日志将先输入到内存到,定时批量落盘。如果设置这个值,要保证在程序退出的时候调用Sync(),在开发阶段不用设置为true。
    Async bool
    //是否 输出json格式的数据,JSON格式相对于console格式,不方便阅读,但是对机器更加友好
    //最佳实践,在开发的时候json为false,mode为console
    Json bool
    //是否日志压缩
    Compress    bool
    options     []zap.Option
    atomicLevel zap.AtomicLevel
}

func (lc *Config) UpdateLevel(level zapcore.Level) {
    lc.atomicLevel.SetLevel(level)
}

func (lc *Config) Build() *zap.Logger {
    if lc.Mode == "file" && lc.FileName == "" {
        log.Printf("file mode, but file name is empty")
    }
    var (
        ws      zapcore.WriteSyncer
        errorWs zapcore.WriteSyncer
        encoder zapcore.Encoder
    )
    encoderConfig := zapcore.EncoderConfig{
        //当存储的格式为JSON的时候这些作为可以key
        MessageKey:    "message",
        LevelKey:      "atomicLevel",
        TimeKey:       "time",
        NameKey:       "logger",
        CallerKey:     "caller",
        StacktraceKey: "stacktrace",
        LineEnding:    zapcore.DefaultLineEnding,
        //以上字段输出的格式
        EncodeLevel:    zapcore.LowercaseLevelEncoder,
        EncodeTime:     CustomTimeEncoder,
        EncodeDuration: zapcore.SecondsDurationEncoder,
        EncodeCaller:   zapcore.FullCallerEncoder,
    }
    if lc.Mode == "console" {
        ws = zapcore.Lock(os.Stdout)
        errorWs = zapcore.Lock(os.Stderr)
        //输出到控制台彩色。
        if !lc.Json {
            encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
        }
    } else {
        normalConfig := &lumberjack.Logger{
            Filename:   lc.FileName,
            MaxSize:    lc.MaxSize,
            MaxAge:     lc.MaxAge,
            MaxBackups: lc.MaxBackup,
            LocalTime:  true,
            Compress:   lc.Compress,
        }
        if lc.ErrorFileName != "" {
            errorConfig := &lumberjack.Logger{
                Filename:   lc.ErrorFileName,
                MaxSize:    lc.MaxSize,
                MaxAge:     lc.MaxAge,
                MaxBackups: lc.MaxBackup,
                LocalTime:  true,
                Compress:   lc.Compress,
            }
            errorWs = zapcore.Lock(zapcore.AddSync(errorConfig))
        }

        ws = zapcore.Lock(zapcore.AddSync(normalConfig))

    }
    if lc.Async {
        ws = &zapcore.BufferedWriteSyncer{
            WS:            ws,
            Size:          _defaultBufferSize,
            FlushInterval: _defaultFlushInterval,
        }
        if errorWs != nil {
            errorWs = &zapcore.BufferedWriteSyncer{
                WS:            errorWs,
                Size:          _defaultBufferSize,
                FlushInterval: _defaultFlushInterval,
            }
        }

    }
    if lc.Json {
        encoder = zapcore.NewJSONEncoder(encoderConfig)
    } else {
        encoder = zapcore.NewConsoleEncoder(encoderConfig)
    }
    var (
        core zapcore.Core
    )
    atomicLevel := lc.parseLevel()
    if lc.ErrorFileName != "" && lc.Mode == "file" {
        lowCore := zapcore.NewCore(encoder, ws, atomicLevel)
        c := []zapcore.Core{lowCore}
        if errorWs != nil {
            highCore := zapcore.NewCore(encoder, errorWs, zapcore.ErrorLevel)
            c = append(c, highCore)
        }
        core = zapcore.NewTee(c...)
    } else {
        core = zapcore.NewCore(encoder, ws, atomicLevel)
    }
    logger := zap.New(core)
    //是否新增调用者信息
    if lc.AddCaller {
        lc.options = append(lc.options, zap.AddCaller())
        if lc.CallerShip != 0 {
            lc.options = append(lc.options, zap.AddCallerSkip(lc.CallerShip))
        }
    }
    //当错误时是否添加堆栈信息
    if lc.Stacktrace {
        lc.options = append(lc.options, zap.AddStacktrace(zap.PanicLevel))
    }

    lc.atomicLevel = atomicLevel
    return logger.WithOptions(lc.options...)

}

func CustomTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
    enc.AppendString(t.Format("2006-01-02-15:04:05"))
}

如果您要了解zap的基础用法您可以参考一下。如果您深入了解,推荐您直接看zap的example和zap的源码。github.com/uber-go/zap/blob/master...

基本概念

决定日志输出的三个参数

1、enc Encoder 决定日志输出的格式(比如日志的时间格式,是否有颜色,日志等级是否大写等)

2、ws WriteSyncer 决定日志输出的位置(输出到标准输出还是文件)

3、 enab LevelEnabler 决定日志输出的日志级别

options

一些附加的配置

//是否加上调用者
zap.AddCaller()
//在哪个等级的日志加上堆栈
zap.AddStacktrace(zap.PanicLevel)
//调用链跳过几级,比如你自己封装zap的logger,如果要记住你在哪个地方调用了Info()方法,要加上这个optionAddCallerSkip(1)
func()Info{
    logger.Info()
}
zap.AddCallerSkip(lc.CallerShip)
logger.WithOptions(lc.options...)

如果要定制zap的日志,修改对应的配置就好了

// NewCore creates a Core that writes logs to a WriteSyncer.
func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core {
    return &ioCore{
       LevelEnabler: enab,
       enc:          enc,
       out:          ws,
    }
}

//通过配置创建一个core
core = zapcore.NewCore(encoder, ws, atomicLevel)
//初始化logger
var  logger *zap.Logger  := zap.New(core)
//加上一些option
logger.WithOptions(lc.options...)

l.Debug("this a debug level log", zap.Any("test", "t"))
l.Info("this a info level log", zap.Any("test", "t"))
l.Warn("this a warn level log", zap.Any("test", "t"))
l.Error("this a error level log", zap.Any("test", "t"))
l.Panic("this a panic level log", zap.Any("test", "t"))

2023-11-12-20:41:54    DEBUG    E:/demoproject/go-lib/utils/zap/zap_test.go:33    this a debug level log    {"test": "t"}
2023-11-12-20:41:54    INFO    E:/demoproject/go-lib/utils/zap/zap_test.go:34    this a info level log    {"test": "t"}
2023-11-12-20:41:54    WARN    E:/demoproject/go-lib/utils/zap/zap_test.go:35    this a warn level log    {"test": "t"}
2023-11-12-20:41:54    ERROR    E:/demoproject/go-lib/utils/zap/zap_test.go:36    this a error level log    {"test": "t"}
2023-11-12-20:41:54    PANIC    E:/demoproject/go-lib/utils/zap/zap_test.go:37    this a panic level log    {"test": "t"}
go-lib/utils/zap.TestZap
    E:/demoproject/go-lib/utils/zap/zap_test.go:37
testing.tRunner
    D:/go/src/testing/testing.go:1576
--- FAIL: TestZap (0.01s)
panic: this a panic level log [recovered]
    panic: this a panic level log

Encoder

encoderConfig := zapcore.EncoderConfig{
    //当存储的格式为JSON的时候这些作为可以key
    MessageKey:    "message",
    LevelKey:      "atomicLevel",
    TimeKey:       "time",
    NameKey:       "logger",
    CallerKey:     "caller",
    StacktraceKey: "stacktrace",
    LineEnding:    zapcore.DefaultLineEnding,
    //以上字段输出的格式
    EncodeLevel:    zapcore.LowercaseLevelEncoder,
    EncodeTime:     CustomTimeEncoder,
    EncodeDuration: zapcore.SecondsDurationEncoder,
    EncodeCaller:   zapcore.FullCallerEncoder,
}
if lc.Json {
    encoder = zapcore.NewJSONEncoder(encoderConfig)
} else {
    encoder = zapcore.NewConsoleEncoder(encoderConfig)
}

WriteSyncer

normalConfig := &lumberjack.Logger{
    Filename:   lc.FileName,
    MaxSize:    lc.MaxSize,
    MaxAge:     lc.MaxAge,
    MaxBackups: lc.MaxBackup,
    LocalTime:  true,
    Compress:   lc.Compress,
}
warnConfig := &lumberjack.Logger{
    Filename:   lc.ErrorFileName,
    MaxSize:    lc.MaxSize,
    MaxAge:     lc.MaxAge,
    MaxBackups: lc.MaxBackup,
    LocalTime:  true,
    Compress:   lc.Compress,
}
ws = zapcore.Lock(zapcore.AddSync(normalConfig))
errorWs = zapcore.Lock(zapcore.AddSync(warnConfig))

LevelEnabler

// LevelEnabler decides whether a given logging level is enabled when logging a
// message.
//
// Enablers are intended to be used to implement deterministic filters;
// concerns like sampling are better implemented as a Core.
//
// Each concrete Level value implements a static LevelEnabler which returns
// true for itself and all higher logging levels. For example WarnLevel.Enabled()
// will return true for WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, and
// FatalLevel, but return false for InfoLevel and DebugLevel.
type LevelEnabler interface {
    Enabled(Level) bool
}
// The bundled Config struct only supports the most common configuration
// options. More complex needs, like splitting logs between multiple files
// or writing to non-file outputs, require use of the zapcore package.
//
// In this example, imagine we're both sending our logs to Kafka and writing
// them to the console. We'd like to encode the console output and the Kafka
// topics differently, and we'd also like special treatment for
// high-priority logs.

// First, define our level-handling logic.
highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
    return lvl >= zapcore.ErrorLevel
})
lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
    return lvl < zapcore.ErrorLevel
})

其他用法

异步落盘日志

使用缓存,定时批量落盘日志,能够显著减少磁盘io,提高性能。如果要使用异步落盘日志,记得在程序关闭的使用调用sync()方法

ws = &zapcore.BufferedWriteSyncer{
    WS:            ws,
    Size:          _defaultBufferSize,
    FlushInterval: _defaultFlushInterval,
}
errorWs = &zapcore.BufferedWriteSyncer{
    WS:            errorWs,
    Size:          _defaultBufferSize,
    FlushInterval: _defaultFlushInterval,
}

动态调整日志级别

AtomicLevel 是并发安全的,如果有需要在程序运行时调整日志的需求可以使用。

func ExampleAtomicLevel() {
    atom := zap.NewAtomicLevel()

    // To keep the example deterministic, disable timestamps in the output.
    encoderCfg := zap.NewProductionEncoderConfig()
    encoderCfg.TimeKey = ""

    logger := zap.New(zapcore.NewCore(
       zapcore.NewJSONEncoder(encoderCfg),
       zapcore.Lock(os.Stdout),
       atom,
    ))
    defer logger.Sync()

    logger.Info("info logging enabled")

    atom.SetLevel(zap.ErrorLevel)
    logger.Info("info logging disabled")
    // Output:
    // {"level":"info","msg":"info logging enabled"}
}

日志输出到多个目的地

这个设置能将日志输出到不同的目的地。

当你有将日志同时输出到不同地方可以设置这个,还可以做到不同的级别的日志输出到不同的地方。

highCore := zapcore.NewCore(encoder, errorWs, zapcore.ErrorLevel)
lowCore := zapcore.NewCore(encoder, ws, atomicLevel)
core = zapcore.NewTee(highCore, lowCore)
logger := zap.New(core)
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!