slog.HandlerOptions{Level:...}控制最终输出最低级别,debug日志在Handler层被丢弃而非手动开关;responseWriter需包装以获取真实状态码;debug日志须防读取body、脱敏敏感信息、带明确上下文;环境级别应分本地Debug、测试Info、生产Info且禁用Debug。

如何用 slog 控制中间件日志级别
Go 1.21+ 内置的 slog 是配置日志级别的最轻量、最可靠选择,无需引入第三方。关键不是“怎么设级别”,而是“在哪设、谁生效、谁被忽略”。
-
slog.HandlerOptions{Level: ...}控制最终输出的最低级别,比如设为slog.LevelInfo,则所有Debug级日志直接被丢弃,不进编码器、不序列化、不 IO - 中间件中调用
log.DebugContext或log.InfoContext本身不触发输出,只是打上级别标签;真正过滤发生在 Handler 层 - 不能靠
if env == "dev" { log.Debug(...) }手动开关——这会让 debug 日志逻辑仍被执行(如拼接字符串、读取 body),造成隐性开销 - 推荐初始化 logger 时就绑定全局最小级别:
slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level})),其中level可从环境变量或配置文件读取
为什么 responseWriter 必须包装才能拿到状态码
标准 http.ResponseWriter 不暴露已写入的状态码,WriteHeader 调用后也无法回读。不包装就只能默认记 200,导致错误请求(如 404、500)在日志里全显示为成功。
- 自定义
responseWriter类型必须实现WriteHeader方法,并在其中保存status字段 - 务必在
Write方法里补 fallback:若status == 0,说明WriteHeader没被显式调用,则按 HTTP 规范设为 200 - 禁止在
Write里调WriteHeader——HTTP 协议明令禁止多次调用,会 panic - 示例关键行:
rw.status = status(在WriteHeader中)、if rw.status == 0 { rw.status = 200 }(在Write中)
debug 级别日志最容易踩的三个坑
debug 日志本意是辅助排查,但放错位置反而拖慢服务、泄露敏感信息、污染日志流。
- 不要在中间件里直接读
r.Body:它是一次性流,读完后续 handler 就收不到 body 了;要用io.TeeReader或io.MultiReader复制一份 - 敏感字段必须脱敏:对
Authorization、X-API-Key、password等 header 或 query 参数,统一替换为[REDACTED],不能只靠“不打印”来规避 - debug 日志要带明确上下文:比如
log.DebugContext(ctx, "request body", "body_len", len(buf)),避免只写"debug body"这种无意义字符串,后期根本无法关联请求
不同环境该设什么级别才合理
级别不是越低越好,也不是上线就全切 Info。核心是让每条日志都有明确归宿和可操作性。
立即学习“go语言免费学习笔记(深入)”;
- 本地开发:
slog.LevelDebug,但仅限本机,且 body/header 记录需手动开启开关(比如环境变量LOG_BODY=1) - 测试/预发环境:
slog.LevelInfo,记录路径、方法、耗时、状态码;Warn级用于耗时 >500ms 或重定向次数 >3 - 生产环境:
slog.LevelInfo是底线,Warn和Error必须可被监控系统捕获;Debug级日志完全关闭,除非临时 patch 加 trace ID 后动态启用 - 特别注意:
Fatal在中间件里几乎不用——它会调用os.Exit,直接杀死整个进程,应改用Error+ 健康检查兜底
ctx 真的绑定了该 logger。漏掉任一环,log.DebugContext 就退化成空操作,你还以为日志开了。


















