go日志中间件需包装http.responsewriter:定义结构体嵌入原接口,重写writeheader存状态码、write默认设200,用r.requesturi获取原始路径,json日志禁用unicode转义。

Go 日志中间件不是靠配置文件驱动的,而是靠函数组合和接口包装实现的。硬套“配置”概念容易踩坑——比如试图用 YAML 控制日志字段开关,结果发现根本没地方注入。
怎么写一个能捕获状态码的中间件
标准 http.Handler 接口只有一个 ServeHTTP 方法,但原生 ResponseWriter 不暴露状态码。必须自己包装:
- 定义结构体嵌入
http.ResponseWriter,加statusCode和written字段 - 重写
WriteHeader:存状态码、标已写 - 重写
Write:若还没调过WriteHeader,默认设为http.StatusOK - 别漏掉
Flush方法(如果用了http.Flusher)
否则 next.ServeHTTP 里调 http.Error 或直接 w.WriteHeader(404) 时,你拿不到真实值。
为什么 r.URL.Path 总是 "/" 而 RequestURI 才对
反向代理(如 Nginx、Traefik)转发时,r.URL 是 Go 标准库解析后的结果,路径可能被重写成根路径;r.RequestURI 是服务端原始接收字符串,未被篡改。
- 用
r.RequestURI记日志归档,但注意它含查询参数,敏感字段要脱敏 - 别用
r.URL.String()—— 它会补?,还可能丢 fragment - 如果用了
gorilla/mux,路由名可用mux.CurrentRoute(r).GetName(),但得包 panic 捕获
读请求体又不破坏后续 handler 的正确姿势
r.Body 是单次读取的 io.ReadCloser,直接 io.ReadAll(r.Body) 后,下游 c.ShouldBindJSON 或 r.ParseForm() 就会失败。
- 开头就用
http.MaxBytesReader限大小,防 OOM(比如 10MB) - 读进
[]byte后,用bytes.NewReader包一层塞回r.Body - 同步更新
r.ContentLength和r.Header.Set("Content-Length", ...) - GET 请求 body 通常为空,但有些客户端会塞数据,建议按
r.Method == "GET"跳过读取
JSON 日志里中文变 \u4f60\u597d 怎么禁用转义
标准 json.Marshal 默认对非 ASCII 字符转义,这不是 bug,但查日志极不友好。
- 用
json.Encoder替代json.Marshal,调SetEscapeHTML(false)(只去 HTML 实体,不影响 Unicode) - 要彻底禁 Unicode 转义,得自己实现
MarshalJSON或用go-json这类第三方库 - 别在结构体字段加
json:",string"—— 数字会被引号包住,ES/Kibana 解析成 keyword 类型 - 用
zap的话,zap.Stringer+ 自定义String()比 JSON 序列化快得多
真正难的不是写出来,而是脱敏逻辑怎么跟请求体解析耦合——比如 JSON 里嵌套 map、slice,字段名变形(pwd/token/auth_key),还有 query 参数同步更新 r.URL.RawQuery 这种细节,一漏就导致日志泄露或格式错乱。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











