Go语言log/slog自定义默认Handler:满足需求并解决死锁问题
解决slog自定义Handler死锁并满足需求的方案
死锁原因分析
你遇到的死锁问题,根源在于自定义Handler直接嵌套了slog.Default()返回的默认Handler。默认Handler内部依赖log包的全局互斥锁,当你调用slog.SetDefault将自定义Handler的Logger设为默认后,日志输出时会出现锁的循环等待:自定义Handler的Handle方法调用底层默认Handler的Handle,而默认Handler又会尝试获取已被当前调用路径持有的全局锁,最终导致所有goroutine阻塞,触发"all goroutines are asleep"错误。
满足需求的简便方案
要同时实现三个需求,正确的做法是直接创建与默认Handler配置一致的底层Handler,而非嵌套slog.Default()返回的Handler。默认Handler本质是输出到os.Stderr的TextHandler,且使用默认配置(级别为Info、无额外属性等)。
修正后的代码
package main import ( "context" "log/slog" "os" ) type CustomHandler struct { slog.Handler } func NewCustomHandler() *CustomHandler { // 创建与默认Handler配置一致的TextHandler defaultHandler := slog.NewTextHandler(os.Stderr, nil) return &CustomHandler{Handler: defaultHandler} } func (m *CustomHandler) Handle(ctx context.Context, record slog.Record) error { // 自定义修改日志消息 record.Message = "[do something] " + record.Message return m.Handler.Handle(ctx, record) } func main() { customLogger := slog.New(NewCustomHandler()) // 设置为默认Logger slog.SetDefault(customLogger) slog.Info("Hello") // 正常输出:time=... level=INFO msg="[do something] Hello" }
需求匹配说明
- 使用默认Handler输出格式:通过
slog.NewTextHandler(os.Stderr, nil)创建的Handler,与slog.Default()的默认Handler配置完全一致,输出格式相同。 - 自定义修改日志消息:在
Handle方法中直接修改record.Message,实现消息前缀添加等自定义逻辑。 - 设置为默认处理器:通过
slog.SetDefault将自定义Handler对应的Logger设为全局默认,后续调用slog.Info等方法时会自动使用自定义Handler。
额外说明
如果需要匹配默认Handler的动态配置(比如根据环境变量SLOG_FORMAT切换JSON/Text格式),可以先通过slog.Default().Handler()获取底层Handler的类型,再创建对应类型的Handler。但大多数场景下,直接使用TextHandler即可满足默认格式需求。
内容的提问来源于stack exchange,提问作者ofstudio
相关产品推荐
相关产品推荐

