背景: 在 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)
}