登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go log/slog 怎样让 Handler 过滤低级别日志

来源:17golang原创

时间:2026-10-18 07:34:52 272浏览 收藏

在生产服务里,日志通常不能把所有 Debug 事件都写入长期存储,但排查问题时又希望临时打开更细的记录。Go log/slog 把这个责任放在 Handler:用 HandlerOptions.Level 设置最低级别,低于阈值的记录在处理前就被过滤。

直接结论:固定环境使用 slog.LevelInfo 或 slog.LevelWarn;需要运行期调级时传入 *slog.LevelVar;如果日志属性本身计算昂贵,先调用 logger.Enabled,再构造属性。

官方地址:https://pkg.go.dev/log/slog

先看清级别过滤的方向

slog.Level 是表示严重程度的整数,数值越大代表越严重。内置 Handler 的规则是“只处理大于等于最低级别的记录”:阈值为 Info 时,Debug 会被丢弃,Info、Warn 和 Error 会继续处理。

最低级别会保留常见用途
DebugDebug 及以上短时排查,输出最细
InfoInfo、Warn、Error生产默认值
WarnWarn、Error降低常规噪声
ErrorError只保留故障记录
log/slog Handler 按最低级别把 Debug、Info、Warn、Error 分成保留区和过滤区的静态结构说明图
图1:log/slog 级别与 Handler 过滤边界的静态结构说明图,不是运行截图。

这个边界属于 Handler,而不是某一次 Info 或 Debug 调用的显示偏好。默认 HandlerOptions.Level 为 Info;如果没有特殊配置,低于 Info 的记录就不会进入内置 Handler 的输出阶段。

用 HandlerOptions.Level 固定生产阈值

TextHandler 和 JSONHandler 都接受 *slog.HandlerOptions。把阈值放在创建 Handler 的位置,日志格式、输出目标和过滤规则就能在启动配置中一起审查。

package main

import (
    "log/slog"
    "os"
)

func newProductionLogger() *slog.Logger {
    // 生产环境只保留 Info 及以上,避免 Debug 日志持续进入标准错误输出。
    opts := &slog.HandlerOptions{Level: slog.LevelInfo, AddSource: true}
    // JSONHandler 负责结构化输出,便于后端按 level 字段检索。
    handler := slog.NewJSONHandler(os.Stdout, opts)
    return slog.New(handler)
}

func main() {
    logger := newProductionLogger()
    // 这条 Debug 记录低于 Info,会被 Handler 直接过滤。
    logger.Debug("cache detail", "key", "user:42")
    // 这条 Warn 记录达到阈值,会进入 JSONHandler。
    logger.Warn("cache miss", "key", "user:42")
}

这里的 AddSource 只是审计信息开关,不改变过滤逻辑。真正决定最低级别的是 Level。如果项目只需要文本输出,把 NewJSONHandler 换成 NewTextHandler 即可,过滤边界仍由同一个选项控制。

用 LevelVar 在排障窗口动态调级

如果每次临时排障都重建 Logger,容易让默认实例、依赖注入和恢复动作变得分散。slog.LevelVar 实现了 slog.Leveler,可以作为 Handler 的阈值,并且支持并发读写。

var programLevel = new(slog.LevelVar) // 零值为 Info,适合作为生产默认阈值

func newLogger() *slog.Logger {
    // Handler 保存的是 LevelVar,因此后续 Set 会影响同一个 Handler。
    handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: programLevel})
    return slog.New(handler)
}

func enableDebugForIncident() {
    // 排障窗口开始:允许 Debug 记录进入 Handler。
    programLevel.Set(slog.LevelDebug)
}

func restoreProductionLevel() {
    // 排障窗口结束:恢复 Info,避免细粒度日志长期放大成本。
    programLevel.Set(slog.LevelInfo)
}

LevelVar 只改变最低级别,不会自动替你设计权限、审计或恢复流程。生产系统应把“谁能调级、调到什么范围、何时恢复”作为配置或运维接口的独立边界记录下来,避免一次临时操作变成永久 Debug。

log/slog LevelVar、Handler 和 Logger 之间的动态阈值关系静态结构说明图
图2:LevelVar 驱动 Handler 阈值、Logger 调用和排障入口的静态关系图,不是运行截图。

昂贵字段要在过滤判断之后计算

Handler 过滤可以避免记录真正交给 Handler,但如果调用方在构造日志参数时已经完成了昂贵工作,过滤就来得太晚。例如堆栈整理、大对象序列化或复杂诊断快照,都应该先判断目标级别是否启用。

func logDebugSnapshot(ctx context.Context, logger *slog.Logger, state State) {
    // Enabled 会询问 Handler;未启用 Debug 时,直接返回,不构造快照。
    if !logger.Enabled(ctx, slog.LevelDebug) {
        return
    }

    // 只有 Debug 真正可能被处理时,才准备昂贵的结构化属性。
    snapshot := buildSnapshot(state)
    logger.LogAttrs(ctx, slog.LevelDebug, "state snapshot",
        slog.Any("state", snapshot))
}

Enabled 的判断应使用与最终日志调用相同的 context 和级别。它只回答“这个级别当前是否会被 Handler 处理”,不会替代敏感字段脱敏、错误分级或调用方权限判断。

发布前按边界复查

复查项应确认的结果
默认阈值生产 Handler 显式设置 Info 或更高,不依赖读者猜测默认值。
动态调级使用一个受控的 LevelVar,并有恢复到生产阈值的动作。
昂贵属性Debug 快照、序列化等工作位于 Enabled 判断之后。
敏感字段过滤级别不等于脱敏策略,用户标识、令牌和请求体仍需单独处理。
自定义 Handler如果自行实现 Handler,Enabled 必须与 Handle 的最低级别语义保持一致。

常见问题

把阈值设成 Warn,Error 还会保留吗?

会。级别数值越高越严重,Warn 阈值会保留 Warn 和 Error,过滤 Debug 与 Info。

修改 LevelVar 后需要重新创建 Logger 吗?

不需要。Handler 持有的是 Leveler,使用同一个 LevelVar 时调用 Set 就能改变后续判断。

Enabled 能保证昂贵参数永远不计算吗?

只有把昂贵计算明确放在判断之后才有这个效果。已经在函数参数、字符串拼接或序列化表达式中完成的工作,Handler 过滤无法撤销。

总结

让 Go log/slog 的 Handler 过滤低级别日志,首选 HandlerOptions.Level;需要运行期调整时使用 LevelVar;需要节省昂贵日志计算时,在构造属性前调用 Logger.Enabled。最后把动态入口、敏感字段和自定义 Handler 的边界纳入发布清单,过滤规则才不会只停留在一行配置上。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>