Golang日志框架 | 不想直接用 zap 包变量打印日志怎么办?

122 阅读2分钟

背景: 在 Go 里面,我们可以使用 zap 来作为日志框架。

安装包:go install -u go.uber.org/zap

痛点: Go 中常用的日志框架 zap 的本质是用包变量zap.L()来打印日志,如zap.L().Debug("调用腾讯短信服务", zap.Any("req", req))等,但若遇到不同模块采用不同日志框架的实现的需求时,需要修改大量的包变量代码且无法实现根据模块自定义打印日志(如文章模块用 CommonLogger 实现,用户模块用 SensitiveLogger 实现),甚至可能 zap 不再被维护或者因为业务特征要更换日志框架,该怎么办?

问题的关键:在业务代码中强耦合了 zap 日志框架的包变量调用。

解决:适配器模式下自定义 zapLogger*zap.logger 封装(借鉴zap的风格),并将依赖注入的风格运用到各模块中。此外,自定义了Int64()Error() 等返回 Field 的包方法,优化调用者使用日志接口的体验。

代码:

package logger

type Logger interface {
    Debug(msg string, args ...Field)
    Info(msg string, args ...Field)
    Warn(msg string, args ...Field)
    Error(msg string, args ...Field)
}

type Field struct {
    Key string
    Val any
}

func example() {
    var l Logger
    // 这是一个新用户 union_id=123
    l.Info("这是一个新用户", Field{Key: "union_id", Val: 123})
    
    // 为避免调用者每次都需要创建 Field,笔者实现了如下包方法
}

package logger

func Error(err error) Field {
    return Field{Key: "error", Val: err}
}

func Int64(key string, val int64) Field {
    return Field{Key: key, Val: val}
}

func Int32(key string, val int32) Field {
    return Field{Key: key, Val: val}
}

func String(key string, val string) Field {
    return Field{Key: key, Val: val}
}

func Int(key string, val int) Field {
    return Field{Key: key, Val: val}
}
package logger

import "go.uber.org/zap"

type ZapLogger struct {
    l *zap.Logger
}

func NewZapLogger(l *zap.Logger) Logger{
    return &ZapLogger{l: l}
}

func (z *ZapLogger) Debug(msg string, args ...Field) {
    z.l.Debug(msg, z.toArgs(args)...)
}

func (z *ZapLogger) Info(msg string, args ...Field) {
    z.l.Info(msg, z.toArgs(args)...)
}

func (z *ZapLogger) Warn(msg string, args ...Field) {
    z.l.Warn(msg, z.toArgs(args)...)
}

func (z *ZapLogger) Error(msg string, args ...Field) {
    z.l.Error(msg, z.toArgs(args)...)
}

func (z *ZapLogger) toArgs(args []Field) []zap.Field {
    res := make([]zap.Field, 0, len(args))
    for _, arg := range args {
       res = append(res, zap.Any(arg.Key, arg.Val))
    }
    return res
}
package logger

type NopLogger struct {
}

func NewNopLogger() *NopLogger {
    return &NopLogger{}
}

func (n *NopLogger) Debug(msg string, args ...Field) {

}

func (n *NopLogger) Info(msg string, args ...Field) {

}

func (n *NopLogger) Warn(msg string, args ...Field) {

}

func (n *NopLogger) Error(msg string, args ...Field) {

}

ioc包中进行初始化

package ioc

import (
    "go.uber.org/zap"
    "refactor-webook/webook/pkg/logger"
)

func InitLogger() logger.Logger {
    l, err := zap.NewDevelopment()
    if err != nil {
       panic(err)
    }
    return logger.NewZapLogger(l)
}