疾速体验
以下是 我的项目中 曾经用slog替换 zap 后的 logger 应用办法,无任何感知,与之前截然不同
package mainimport "github.com/webws/go-moda/logger"func main() { // 格式化打印 {"time":"2023-09-08T01:25:21.313463+08:00","level":"INFO","msg":"info hello slog","key":"value","file":"/Users/xxx/w/pro/go-moda/example/logger/main.go","line":6} logger.Infow("info hello slog", "key", "value") // 打印json logger.Debugw("debug hello slog", "key", "value") // 不展现 logger.SetLevel(logger.DebugLevel) // 设置等级 logger.Debugw("debug hello slog", "key", "value") // 设置了等级之后展现 debug // with newLog := logger.With("newkey", "newValue") newLog.Debugw("new hello slog") // 会打印 newkey:newValue logger.Debugw("old hello slog") // 不会打印 newkey:newValue}
slog 根底应用
Go 1.21版本中 将 golang.org/x/exp/slog 引入了go规范库 门路为 log/slog。
新我的项目的 如果不应用第三方包,能够间接用slog当你的 logger
slog 简略示例:
slog.Info("finished", "key", "value") slog.Debug("finished", "key1", "value1")
以下是打印日志 默认slog 输入级别是info以上,所以debug是打印不进去.
2023/09/08 00:27:24 INFO finished key=value
json格式化,设置日志等级,并打印调用函数和文件
opts := &slog.HandlerOptions{AddSource: true, Level: slog.LevelInfo} logger := slog.New(slog.NewJSONHandler(os.Stdout, opts)) logger.Info("finished", "key", "value")
输入
{"time":"2023-09-08T00:34:22.035962+08:00","level":"INFO","source":{"function":"callvis/slog.TestLogJsonHandler","file":"/Users/websong/w/pro/go-note/slog/main_test.go","line":39},"msg":"finished","key":"value"}
原有 logger zap实现
原有的我的项目曾经实现了一套logger,应用zap log 实现接口
原有代码示例
logger interface LoggerInterface
package loggertype LoggerInterface interface { Debugw(msg string, keysAndValues ...interface{}) Infow(msg string, keysAndValues ...interface{}) Errorw(msg string, keysAndValues ...interface{}) Fatalw(msg string, keysAndValues ...interface{}) SetLevel(level Level) With(keyValues ...interface{}) LoggerInterface}
zap log 实现 LoggerInterface
type ZapSugaredLogger struct { logger *zap.SugaredLogger zapConfig *zap.Config}func buildZapLog(level Level) LoggerInterface { encoderConfig := zapcore.EncoderConfig{ TimeKey: "ts", LevelKey: "level", NameKey: "logger", CallerKey: "caller", MessageKey: "msg", StacktraceKey: "stacktrace", LineEnding: zapcore.DefaultLineEnding, EncodeDuration: zapcore.SecondsDurationEncoder, EncodeTime: zapcore.ISO8601TimeEncoder, EncodeLevel: zapcore.LowercaseLevelEncoder, EncodeCaller: zapcore.ShortCallerEncoder, } zapConfig := &zap.Config{ Level: zap.NewAtomicLevelAt(zapcore.Level(level)), Development: true, DisableCaller: false, DisableStacktrace: true, Sampling: &zap.SamplingConfig{Initial: 100, Thereafter: 100}, Encoding: "json", EncoderConfig: encoderConfig, OutputPaths: []string{"stderr"}, ErrorOutputPaths: []string{"stderr"}, } l, err := zapConfig.Build(zap.AddCallerSkip(2)) if err != nil { fmt.Printf("zap build logger fail err=%v", err) return nil } return &ZapSugaredLogger{ logger: l.Sugar(), zapConfig: zapConfig, } func (l *ZapSugaredLogger) Debugw(msg string, keysAndValues ...interface{}) { l.logger.Debugw(msg, keysAndValues...) } func (l *ZapSugaredLogger) Errorw(msg string, keysAndValues ...interface{}) { l.logger.Errorw(msg, keysAndValues...) } // ...省略info 之类其余实现接口的办法 }
全局初始化logger,因代码量太大,以下是伪代码,次要提供思路,为下文 slog 无侵入替换zap 预热
package logger// 全局 log,也能够独自 NewLogger 获取新的实例var globalog = newlogger(DebugLevel)func newlogger(level Level) *Logger { l := &Logger{logger: buildZapLog(level)} return l}func Infow(msg string, keysAndValues ...interface{}) { globalog.logger.Infow(msg, keysAndValues...)}// ...省略其余全局办法,比方DebugW 之类
在我的项目里就能够通过logger 应用日志
logger.Debugw("msg1", "k1", "v1") // debug logger.SetLevel(DebugLevel) //设置等级 logger.Debugw("msg3", "k3", "v3") newLogger := logger.With("name", "song") logger.Infow("msg4", "k4", "v4") // print
slog 不侵入业务 替换zap
logger interface 接口放弃不变
slog 实现 代码
package loggerimport ( "log/slog" "os" "runtime")var _ LoggerInterface = (*SlogLogger)(nil)type SlogLogger struct { logger *slog.Logger level *slog.LevelVar // true 代表应用slog打印文件门路,false 会应用自定的办法给日志 减少字段 file line addSource bool}// newSlogfunc newSlog(level Level, addSource bool) LoggerInterface { levelVar := &slog.LevelVar{} levelVar.Set(slog.LevelInfo) opts := &slog.HandlerOptions{AddSource: addSource, Level: levelVar} logger := slog.New(slog.NewJSONHandler(os.Stdout, opts)) return &SlogLogger{ logger: logger, level: levelVar, }}func (l *SlogLogger) Fatalw(msg string, keysAndValues ...interface{}) { keysAndValues = l.ApppendFileLine(keysAndValues...) l.logger.Error(msg, keysAndValues...) os.Exit(1)}func (l *SlogLogger) Infow(msg string, keysAndValues ...interface{}) { keysAndValues = l.ApppendFileLine(keysAndValues...) l.logger.Info(msg, keysAndValues...)}// 省略继承接口的其余办法 DebugW 之类的func (l *SlogLogger) SetLevel(level Level) { zapLevelToSlogLevel(level) l.level.Set(slog.Level(zapLevelToSlogLevel(level)))}// func (l *SlogLogger) With(keyValues ...interface{}) LoggerInterface { newLog := l.logger.With(keyValues...) return &SlogLogger{ logger: newLog, level: l.level, }}// ApppendFileLine 获取调用方的文件和文件号// slog 原生 暂不反对 callerSkip,应用此函数啃根会有性能问题,最好等slog提供 CallerSkip 的参数func (l *SlogLogger) ApppendFileLine(keyValues ...interface{}) []interface{} { l.addSource = false if !l.addSource { var pc uintptr var pcs [1]uintptr // skip [runtime.Callers, this function, this function's caller] runtime.Callers(4, pcs[:]) pc = pcs[0] fs := runtime.CallersFrames([]uintptr{pc}) f, _ := fs.Next() keyValues = append(keyValues, "file", f.File, "line", f.Line) return keyValues } return keyValues}
全局初始化logger,以下伪代码
package logger// 全局 log,也能够独自 NewLogger 获取新的实例var globalog = newlogger(DebugLevel)func newlogger(level Level) *Logger { l := &Logger{logger: newSlog(level, false)} return l}func Infow(msg string, keysAndValues ...interface{}) { globalog.logger.Infow(msg, keysAndValues...)}// ...省略其余全局办法,比方DebugW 之类
slog 实现 callerSkip 性能
slog 的 addsource 参数 会打印文件名和行号,但 并不能像 zap 那样反对 callerSkip,也就是说 如果将 slog 封装在 logger 目录的log.go 文件下,应用logger进行打印,展现的文件会一只是log.go
看了 slog 的源码,其实slog 应用了 runtime.Callers 在外部实现了 callerSkip 性能,然而没有对外裸露 callerSkip 参数
我就封装了 ApppendFileLine 办法,应用 runtime.Callers 获取到 文件名 和 行号,减少 file 和 line 的key value到日志
可能会有性能问题,心愿slog能对外提供一个 callerSkip 参数
var pc uintptr var pcs [1]uintptr // skip [runtime.Callers, this function, this function's caller] runtime.Callers(4, pcs[:]) pc = pcs[0] fs := runtime.CallersFrames([]uintptr{pc}) f, _ := fs.Next() keyValues = append(keyValues, "file", f.File, "line", f.Line)
阐明
文章中贴的代码不多,次要提供思路,尽管省略了一些办法和 全局logger的实现形式,也写了两个多小时
如要查看logger实现细节,可查看 在文章结尾 疾速体验 援用的包 github.com/webws/go-moda/logger
也能够间接看下我这个 仓库 go-moda 里应用 slog 和 zap 的封装