go?logger不侵入業(yè)務(wù)代碼使用slog替換zap并實(shí)現(xiàn)callerSkip詳解
快速體驗(yàn)
以下是 項(xiàng)目中 已經(jīng)用slog替換 zap 后的 logger 使用方法,與替換前使用方式相同,無(wú)任何感知
package main import "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) // 設(shè)置等級(jí) logger.Debugw("debug hello slog", "key", "value") // 設(shè)置了等級(jí)之后展示 debug // with newLog := logger.With("newkey", "newValue") newLog.Debugw("new hello slog") // 會(huì)打印 newkey:newValue logger.Debugw("old hello slog") // 不會(huì)打印 newkey:newValue }
slog 基礎(chǔ)使用
Go 1.21版本中 將 golang.org/x/exp/slog 引入了go標(biāo)準(zhǔn)庫(kù) 路徑為 log/slog。
新項(xiàng)目的 如果不使用第三方包,可以直接用slog當(dāng)你的 logger
slog 簡(jiǎn)單示例
默認(rèn) 輸出級(jí)別是info以上,所以debug是打印不出來(lái).
import "log/slog" func main() { slog.Info("finished", "key", "value") slog.Debug("finished", "key", "value") }
輸出
2023/09/08 00:27:24 INFO finished key=value
slog 格式化
HandlerOptions Level:設(shè)置日志等級(jí) AddSource:打印文件相關(guān)信息
func main() { 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"}
slog 切換日志等級(jí)
看 slog源碼 HandlerOptions 的 Level 是一個(gè) interface,slog 自帶的 slog.LevelVar 實(shí)現(xiàn)了這個(gè) interface,也可以自己定義實(shí)現(xiàn) 下面是部分源碼
type Leveler interface { Level() Level } type LevelVar struct { val atomic.Int64 } // Level returns v's level. func (v *LevelVar) Level() Level { return Level(int(v.val.Load())) } // Set sets v's level to l. func (v *LevelVar) Set(l Level) { v.val.Store(int64(l)) }
通過(guò) slog.LevelVar 設(shè)置debug等級(jí)后,第二次的debug日志是可以打印出來(lái)
func main() { levelVar := &slog.LevelVar{} levelVar.Set(slog.LevelInfo) opts := &slog.HandlerOptions{AddSource: true, Level: levelVar} logger := slog.New(slog.NewJSONHandler(os.Stdout, opts)) logger.Info("finished", "key", "value") levelVar.Set(slog.LevelDebug) logger.Debug("finished", "key", "value") }
想要實(shí)現(xiàn) 文章開(kāi)頭 通過(guò) logger.SetLevel(logger.DebugLevel) 快速切換等級(jí),可以選擇將slog.Logger 與 slog.LevelVar 封裝到同一結(jié)構(gòu),比如
type SlogLogger struct { logger *slog.Logger level *slog.LevelVar }
下文 slog 替換 zap 有詳細(xì)代碼體現(xiàn)
原有 logger zap實(shí)現(xiàn)
原有項(xiàng)目已經(jīng)實(shí)現(xiàn)了一套logger,使用zap log 以下代碼都是在 logger 包下
原zap代碼
logger interface LoggerInterface
package logger type 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 實(shí)現(xiàn) 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 之類(lèi)其他實(shí)現(xiàn)接口的方法 }
全局初始化logger,因代碼量太大,以下是偽代碼,主要提供思路
package logger // 全局 log,也可以單獨(dú) NewLogger 獲取新的實(shí)例 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 之類(lèi)
在項(xiàng)目中通過(guò) 如下使用 logger
import "github.com/webws/go-moda/logger" func main() { logger.Infow("hello", "key", "value") // 打印json }
slog 不侵入業(yè)務(wù) 替換zap
logger interface 接口保持不變
slog 實(shí)現(xiàn) 代碼
package logger import ( "log/slog" "os" "runtime" ) var _ LoggerInterface = (*SlogLogger)(nil) type SlogLogger struct { logger *slog.Logger level *slog.LevelVar // true 代表使用slog打印文件路徑,false 會(huì)使用自定的方法給日志 增加字段 file line addSource bool } // newSlog func 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 之類(lèi)的 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 獲取調(diào)用方的文件和文件號(hào) // slog 原生 暫不支持 callerSkip,使用此函數(shù)啃根會(huì)有性能問(wèn)題,最好等slog提供 CallerSkip 的參數(shù) 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,也可以單獨(dú) NewLogger 獲取新的實(shí)例 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 之類(lèi)
一樣可以 通過(guò) 如下使用 logger,與使用zap時(shí)一樣
import "github.com/webws/go-moda/logger" func main() { logger.Infow("hello", "key", "value") // 打印json }
slog 實(shí)現(xiàn) callerSkip 功能
slog 的 addsource 參數(shù) 會(huì)打印文件名和行號(hào),但 并不能像 zap 那樣支持 callerSkip,也就是說(shuō) 如果將 slog 封裝在 logger 目錄的log.go 文件下,使用logger進(jìn)行打印,展示的文件會(huì)一只是log.go
看了 slog 的源碼, 使用了 runtime.Callers 在內(nèi)部實(shí)現(xiàn)了 callerSkip 功能,但是沒(méi)有對(duì)外暴露 callerSkip 參數(shù)
可以看我上面代碼 自己封裝了一個(gè)方法: ApppendFileLine, 使用 runtime.Callers 獲取到 文件名 和 行號(hào),增加 file 和 line 的key value到日志
可能會(huì)有性能問(wèn)題,希望slog能對(duì)外提供一個(gè) callerSkip 參數(shù)
說(shuō)明
文章中貼的代碼不多,主要提供思路,雖然省略了一些方法和 全局logger的實(shí)現(xiàn)方式
如要查看logger實(shí)現(xiàn)細(xì)節(jié),可查看 在文章開(kāi)頭 快速體驗(yàn) 引用的包
也可以直接看下我這個(gè) 倉(cāng)庫(kù) go-moda 里使用 slog 和 zap 的封裝
以上就是go logger不侵入業(yè)務(wù)代碼使用slog替換zap并實(shí)現(xiàn)callerSkip詳解的詳細(xì)內(nèi)容,更多關(guān)于go logger slog替換zap的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Goland 關(guān)閉自動(dòng)移除未使用的包操作
這篇文章主要介紹了Goland 關(guān)閉自動(dòng)移除未使用的包操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12Golang使用gorm實(shí)現(xiàn)分頁(yè)功能的示例代碼
在提供列表接口時(shí)一般要用到分頁(yè),對(duì)于存儲(chǔ)在某些數(shù)據(jù)庫(kù)中的數(shù)據(jù)進(jìn)行分頁(yè)起來(lái)非常的方便,下文給出一個(gè)通過(guò)gorm進(jìn)行分頁(yè)并通過(guò)http返回?cái)?shù)據(jù)的例子,感興趣的小伙幫跟著小編一起來(lái)看看吧2024-10-10使用Golang的singleflight防止緩存擊穿的方法
這篇文章主要介紹了使用Golang的singleflight防止緩存擊穿的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04Go語(yǔ)言利用ffmpeg轉(zhuǎn)hls實(shí)現(xiàn)簡(jiǎn)單視頻直播
這篇文章主要為大家介紹了Go語(yǔ)言利用ffmpeg轉(zhuǎn)hls實(shí)現(xiàn)簡(jiǎn)單視頻直播,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04Go開(kāi)發(fā)中有哪幾種無(wú)法恢復(fù)的致命場(chǎng)景分析
這篇文章主要為大家介紹了Go有哪幾種無(wú)法恢復(fù)的致命場(chǎng)景示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08Linux環(huán)境下編譯并運(yùn)行g(shù)o項(xiàng)目的全過(guò)程
Go語(yǔ)言是Google的開(kāi)源編程語(yǔ)言,廣泛應(yīng)用于云計(jì)算、分布式系統(tǒng)開(kāi)發(fā)等領(lǐng)域,在Linux上也有大量的應(yīng)用場(chǎng)景,這篇文章主要給大家介紹了關(guān)于Linux環(huán)境下編譯并運(yùn)行g(shù)o項(xiàng)目的相關(guān)資料,需要的朋友可以參考下2023-11-11