日志中间件需包装ResponseWriter捕获真实状态码,用r.RequestURI(非r.URL.Path)获取原始路径并脱敏敏感参数,读取r.Body需重放避免破坏后续handler。

日志中间件必须包装 ResponseWriter 才能拿到真实状态码
直接在 next.ServeHTTP 前后打日志,w.WriteHeader 调用可能被业务 handler 隐藏或延迟触发,导致记录的状态码永远是 200。必须实现一个自定义的 ResponseWriter,重写 WriteHeader 和 Write 方法来捕获首次写入的状态码。
- 不要用
w.Header().Get("Content-Length")判断响应是否已发送——它只是 header 缓存,不反映实际写入 - 推荐结构体字段:嵌入
http.ResponseWriter、加statusCode int和written bool标记 - 在
WriteHeader中只赋值statusCode并设written = true;Write中若未写过头,先调用WriteHeader(http.StatusOK)
原始路径要用 r.RequestURI,别信 r.URL.Path
反向代理(如 Nginx、Traefik)或 URL 重写后,r.URL.Path 可能被标准库解析成 / 或丢失前缀,而 r.RequestURI 是服务端收到的原始字符串,未经修改,最可靠。
-
r.RequestURI包含查询参数,敏感字段(如token=、password=)需正则脱敏后再记录 - 不要用
r.URL.String()—— 它会补?、丢 fragment、且依赖ParseForm状态 - 如果用了
gorilla/mux,可额外取mux.CurrentRoute(r).GetName(),但要包recover防 panic
JSON 日志里中文变 \u4f60\u597d 怎么办
Go 标准 json.Marshal 默认对所有非 ASCII 字符转义,不是 bug,但查日志极不友好。禁用 Unicode 转义不能靠 SetEscapeHTML(false) —— 它只影响 、<code>> 等 HTML 字符。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
- 最快方案:用
json.Encoder+ 自定义MarshalJSON方法,对字符串字段做bytes.ReplaceAll清除\u序列(仅限可信输入) - 更稳妥:换
go-json库,支持json.RawMessage和无转义输出 - 字段标签别加
json:",string"—— 数字会被引号包围,Elasticsearch 解析成keyword类型,聚合失效
并发下读 r.Body 会破坏后续 handler
日志想记录请求体?r.Body 是单次读取流,直接 ioutil.ReadAll(r.Body) 后,下游 handler 再读就是空。必须用 io.TeeReader 或重放机制。
立即学习“go语言免费学习笔记(深入)”;
- 安全做法:用
httputil.DumpRequestOut(r, true),它内部做了 body 重放 - 手动重放需调用
r.Body.Close()后,用bytes.NewReader(buf)替换r.Body,并确保替换前后r.ContentLength一致 - 高 QPS 场景下,建议对
POST/PUT且Content-Type: application/json的请求采样记录,避免 I/O 拖慢整体链路
log.Printf,而是状态码捕获的时机、body 读取的副作用、以及多层代理下路径字段的可靠性——这三个点漏掉任一个,日志就不可信。


















