Beego请求日志中间件需用beego.InsertFilter注册、手动recover防panic、自定义ResponseWriter捕获状态码和字节数、优先取X-Forwarded-For获取IP、静态资源日志须单独注册BeforeStatic阶段处理。

Beego 中间件做请求日志,不能只靠 c.Ctx.Input.URI() 打印就完事——那样会漏掉耗时、状态码、body 大小等关键指标,还可能因 panic 导致日志不全或进程崩溃。
中间件注册必须用 beego.InsertFilter,不是 beego.Use
Beego v2 没有 beego.Use 这个 API;所有中间件都得走 beego.InsertFilter 注册,且必须在 beego.Run() 之前调用,通常放在 main.go 的 init() 或 main() 开头。
-
beego.InsertFilter("/api/", beego.BeeApp.FilterType, logMiddleware)—— 拦截所有以/api/开头的请求(注意结尾斜杠,避免匹配到/apixxx) - 若想全局记录,路径填
/*,但不推荐:静态资源(/static/)默认绕过路由链,/*不生效 - 要记录静态资源访问,必须单独注册:
beego.InsertFilter("/static/", beego.BeforeStatic, staticLogMiddleware)
logMiddleware 函数里必须手动 defer recover()
日志中间件常需读取 c.Ctx.Request.Body 或解析 form,这些操作可能 panic(比如 body 已被提前读过、JSON 格式错误)。不加 recover,整个请求会 500,且日志根本打不出来。
- 开头加
defer func() { if err := recover(); err != nil { log.Printf("log middleware panic: %v", err) } }() - 别在中间件里调
c.Ctx.Input.RequestBody—— 它会消耗 body,导致后续 controller 读不到;如真要记录原始 body,得用ioutil.ReadAll+io.NopCloser重置 - 记录耗时要用
time.Now()打点,不是靠ctx.Next()后的时间差——因为ctx.Next()返回时 controller 可能已 panic,时间不准
关键字段提取:状态码、响应体大小、客户端 IP 都得自己捞
Beego 中间件中,c.Ctx.ResponseWriter 是个包装过的 responseWriter,原生不暴露状态码和写入字节数。必须用自定义 wrapper 实现捕获。
- 定义一个结构体实现
http.ResponseWriter接口,覆盖WriteHeader和Write方法,把statusCode和written字段存下来 - 客户端 IP 别只信
c.Ctx.Request.RemoteAddr,它可能是反向代理后的真实地址:优先取c.Ctx.Request.Header.Get("X-Forwarded-For"), fallback 到RemoteAddr - URL 路径用
c.Ctx.Input.URL(),不是Request.RequestURI——后者带 query string,且可能含编码问题 - 不要记录完整 body(尤其 POST),容易拖慢性能、泄露敏感数据;最多记前 256 字节 + 长度摘要
静态资源日志要单独处理,c.Data 在 BeforeStatic 里是空的
beego.BeforeStatic 阶段的上下文极度精简:c.Data、c.TplName、c.Render() 全不可用,连 session 都没初始化。你只能靠 c.Ctx.Request 和 c.Ctx.ResponseWriter 做最基础的日志。
- 无法获取 controller 名、方法名,只能记录路径、状态码、耗时、IP
- 无法做权限校验逻辑(比如检查用户是否有权下载某文件),必须提前在
BeforeRouter阶段拦截 /static/xxx 请求并跳转,或改用 controller 路由托管静态文件 - 如果项目同时需要路由级日志 + 静态资源日志,就得写两个几乎重复的中间件函数,分别注册到
BeforeRouter和BeforeStatic
真正难的不是打印一行日志,而是让每条日志都带准确的状态码、真实耗时、不干扰业务、不因 panic 断链——这些细节一旦漏掉,线上排障时看到的就全是“请求进来了,没日志,也没响应”。


















