应在FINISH阶段获取请求和响应信息,因BEFORE阶段Response.Status和Size均为0;需在BEFORE存time.Now()到Request.Tmp,FINISH时计算耗时并读取Status、Size,再记录日志。

Revel中间件里怎么拿到请求和响应信息
Revel的Web中间件本质是revel.Filter函数类型,签名是func(revel.Result, *revel.Request, *revel.Response)。但注意:中间件执行时,revel.Result还没生成(比如Controller还没跑),所以不能靠它拿状态码或响应体;真正能用的是*revel.Request和*revel.Response——前者有Request.URL、Request.Method、Request.RemoteAddr等,后者在中间件结束前只写入了部分字段(如Response.Status可能还是0),必须等后续Filter链走完才能读全。
为什么不能在BEGIN阶段就记录完整日志
Revel的Filter执行分阶段:BEFORE(Controller前)、AFTER(Controller后但Result未写)、FINISH(Response已写出)。如果在BEFORE阶段记日志,你拿不到状态码、响应耗时、响应体大小;如果在FINISH阶段,Response.Status和Response.Size才可靠。常见错误是把日志逻辑放在BEFORE,结果日志里全是0状态码和空响应长度。
- 正确位置是
revel.FINISH阶段 - 需手动计算耗时:在
BEFORE存time.Now()到Request.Tmp,在FINISH取差值 -
Response.Size在FINISH里才准确,但注意它不含HTTP头大小
一个可用的访问日志中间件示例
直接贴核心逻辑,不包装成独立包:
func AccessLog(rw http.ResponseWriter, r *http.Request, c *revel.Controller) revel.Result {
// BEFORE 阶段:记录开始时间
if c.Request != nil {
c.Request.Tmp["start"] = time.Now()
}
// 执行后续Filter/Controller
result := revel.NEXT(rw, r, c)
// FINISH 阶段:记录日志
if c.Request != nil && c.Response != nil {
start, _ := c.Request.Tmp["start"].(time.Time)
duration := time.Since(start).Microseconds()
status := c.Response.Status
if status == 0 {
status = 200 // 默认兜底
}
size := c.Response.Size
log.Printf("[%s] %s %s %d %dμs %d",
c.Request.Time.Format("02/Jan/2006:15:04:05 -0700"),
c.Request.Method,
c.Request.URL.Path,
status,
duration,
size)
}
return result
}
然后在app/init.go的init()里注册:revel.InterceptFunc(AccessLog, revel.FINISH)。注意别用InterceptMethod,那是针对Controller方法的。
容易被忽略的细节
Revel默认会把静态文件(/public下)绕过Filter链,所以这个中间件不会记录CSS/JS请求;如果需要,得在routes里显式配GET /public/*filepath并指向自定义Handler。另外,c.Response.Size在发生panic或writeHeader失败时可能不准,生产环境建议加defer recover兜底,但Revel自身panic处理机制会覆盖你的recover,所以更稳妥的方式是用revel.AppLog替代log.Printf,并确保app.conf里results.log = true开启内置日志输出。


















