原文链接: https://tangx.in/posts/2023/01/06/how-to-set-debug-level-in-golang-slog/
在 golang 中, 日志统一 一直都是一个头疼的问题。
在 exp 中, Go 加入了一个新库 `exp/slog`[1], 希望能转正。
slog
sloglog
func main() {
slog.Debug("debug")
slog.Info("info")
slog.Warn("warn")
slog.Error("err", fmt.Errorf("game over"))
// slog.Fatal("don't support")
}
// 2023/01/06 07:41:50 INFO info
// 2023/01/06 07:41:50 WARN warn
// 2023/01/06 07:41:50 ERROR err err="game over"
Debugslog.SetLevel(level)
sloginfoDEBUGhandlerslogFatalslog
追踪源码, 看实现
不要着急, 先来看看源码。
Debug
func main() {
slog.Debug("debug")
}
default Logger
// Debug calls Logger.Debug on the default logger.
func Debug(msg string, args ...any) {
Default().LogDepth(1, LevelDebug, msg, args...)
}
LogDepthLevelDebugiffalse
func (l *Logger) LogDepth(calldepth int, level Level, msg string, args ...any) {
if !l.Enabled(level) {
return
}
var pcs [1]uintptr
runtime.Callers(calldepth+2, pcs[:])
l.logPC(nil, pcs[0], level, msg, args...)
}
Default()slog.Logger
var defaultLogger atomic.Value
func init() {
defaultLogger.Store(New(newDefaultHandler(log.Output)))
}
// Default returns the default Logger.
func Default() *Logger { return defaultLogger.Load().(*Logger) }
defaultLogger*Logger
defaultLoggerinitnewDefaultHandler()slog.HandlerNew()*slog.LoggerStore()*slog.LoggerDefault()*slog.Logger
newDefaultHandler
func newDefaultHandler(output func(int, string) error) *defaultHandler {
return &defaultHandler{
ch: &commonHandler{json: false},
output: output,
}
}
func (*defaultHandler) Enabled(l Level) bool {
return l >= LevelInfo
}
newDefaultHandlerdefaultHandler
EnabledInfo
slog.Debug
slog 的两大模块
Debug
Debugslog.Handler
slog
slog.LoggerDebug, Info
// To create a new Logger, call [New] or a Logger method
// that begins "With".
type Logger struct {
handler Handler // for structured logging
ctx context.Context
}
// New creates a new Logger with the given non-nil Handler and a nil context.
func New(h Handler) *Logger {
if h == nil {
panic("nil Handler")
}
return &Logger{handler: h}
}
slog.Handler
// Any of the Handler's methods may be called concurrently with itself
// or with other methods. It is the responsibility of the Handler to
// manage this concurrency.
type Handler interface {
// Enabled reports whether the handler handles records at the given level.
// The handler ignores records whose level is lower.
// Enabled is called early, before any arguments are processed,
// to save effort if the log event should be discarded.
Enabled(Level) bool
Handle(r Record) error
WithAttrs(attrs []Attr) Handler
WithGroup(name string) Handler
}
如何实现 Debug 输出
slog
debugHandlerEnableddebugnewDebugLoggernewDebugLoggerslog Default Hanlder
// 定义了自己的 debugHanlder
type debugHandler struct {
slog.Handler
}
// newDebugLogger 使用 slog Default Handler 作为实际载体
func newDebugLogger() *slog.Logger {
dh := &debugHandler{
// handler 使用 slog 默认的 Handler
slog.Default().Handler(),
}
return slog.New(dh)
}
// Enabled 重写了默认的日志登记的判断方法,支持 Debug 日志
func (dh *debugHandler) Enabled(l slog.Level) bool {
return l >= slog.LevelDebug
}
func main() {
// 创建 debugLogger 对象
log := newDebugLogger()
log.Debug("debug")
log.Info("info")
log.Warn("warn")
log.Error("err", fmt.Errorf("game over"))
// log.Fatal("don't support")
}
// 2023/01/06 09:15:18 DEBUG debug
// 2023/01/06 09:15:18 INFO info
// 2023/01/06 09:15:18 WARN warn
// 2023/01/06 09:15:18 ERROR err err="game over"
控制日志等级
很明显, 上面的例子也有一个问题, 不能 自定义日志等级, 所有日志都会打印出来。
slog 官方给了一个 slog 实现自定义日志等级 - Go Playground[3] 的 Demo。
比我们之前的代码多很多, 也不是很复杂。
LevelHandlerslog.Handlerslog.New()LevelLoggerwarn
package main
import (
"os"
"golang.org/x/exp/slog"
)
// A LevelHandler wraps a Handler with an Enabled method
// that returns false for levels below a minimum.
type LevelHandler struct {
level slog.Leveler
handler slog.Handler
}
// NewLevelHandler returns a LevelHandler with the given level.
// All methods except Enabled delegate to h.
func NewLevelHandler(level slog.Leveler, h slog.Handler) *LevelHandler {
// Optimization: avoid chains of LevelHandlers.
if lh, ok := h.(*LevelHandler); ok {
h = lh.Handler()
}
return &LevelHandler{level, h}
}
// Enabled implements Handler.Enabled by reporting whether
// level is at least as large as h's level.
func (h *LevelHandler) Enabled(level slog.Level) bool {
return level >= h.level.Level()
}
// Handle implements Handler.Handle.
func (h *LevelHandler) Handle(r slog.Record) error {
return h.handler.Handle(r)
}
// WithAttrs implements Handler.WithAttrs.
func (h *LevelHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return NewLevelHandler(h.level, h.handler.WithAttrs(attrs))
}
// WithGroup implements Handler.WithGroup.
func (h *LevelHandler) WithGroup(name string) slog.Handler {
return NewLevelHandler(h.level, h.handler.WithGroup(name))
}
// Handler returns the Handler wrapped by h.
func (h *LevelHandler) Handler() slog.Handler {
return h.handler
}
func main() {
th := slog.HandlerOptions{
// Remove time from the output.
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
return slog.Attr{}
}
return a
},
}.NewTextHandler(os.Stdout)
logger := slog.New(NewLevelHandler(slog.LevelWarn, th))
logger.Info("not printed")
logger.Warn("printed")
}
// level=WARN msg=printed
go-jarvis/logr
开源了一个自己实现的 go-jarvis/logr - Github[4]
log.Debug(format, ...any)ValuerStart / Stop
func TestDefault(t *testing.T) {
log := Default().SetLevel(DebugLevel)
err := errors.New("New_ERROR")
log = log.With(
"kk", "vv",
"caller", CallerFile(4, false),
"gg",
)
log.Debug("number=%d", 1)
log.Info("number=%d", 1)
log.Warn(err)
log.Error(err)
ctx := WithLogger(context.Background(), log)
subcaller(ctx)
}
func subcaller(ctx context.Context) {
log := FromContext(ctx)
log = log.Start() // time cost
defer log.Stop()
time.Sleep(532 * time.Millisecond)
log.Info("account=%d", 100)
}
// 2023/01/06 10:31:53 DEBUG number=1 kk=vv caller=logr_test.go:21#TestDefault gg=LACK_Unknown
// 2023/01/06 10:31:53 INFO number=1 kk=vv caller=logr_test.go:22#TestDefault gg=LACK_Unknown
// 2023/01/06 10:31:53 WARN New_ERROR kk=vv caller=logr_test.go:23#TestDefault gg=LACK_Unknown
// 2023/01/06 10:31:53 ERROR New_ERROR kk=vv caller=logr_test.go:24#TestDefault gg=LACK_Unknown
// 2023/01/06 10:31:53 INFO account=100 kk=vv caller=logr_test.go:37#subcaller gg=LACK_Unknown
// 2023/01/06 10:31:53 INFO time-cost kk=vv caller=logr.go:101#Stop gg=LACK_Unknown cost=533ms cost_caller=logr_test.go:38#subcaller
参考资料
[1]
exp/slog
[2]
Golang 库: 为什么 Golang slog 库不支持 slog.Fatal API: https://tangx.in/posts/2023/01/06/why-dont-golang-slog-support-fatal-api/
[3]
slog 实现自定义日志等级 - Go Playground: https://go.dev/play/p/WXrfqyfKGUt
[4]
go-jarvis/logr - Github: https://github.com/go-jarvis/logr
[5]
go-kratos/kratos - Github: https://github.com/go-kratos/kratos