日志与错误在大型项目中的规范
面试问法:大型 GoFrame 项目里,日志和错误怎么打才不乱?有没有分层规范? 一句话结论:日志按“请求入口/业务/错误”分层,统一中间件记访问日志与错误堆栈,业务日志全程带 ctx(自动挂 TraceId);错误一律错误码化(gcode + gerror)抛给上层,展示文案与内部信息分离。规范的核心是:谁打、在哪打、打到什么级别、什么能落盘。
日志分层约定
| 层 | 内容 | 落点 |
|---|---|---|
| 访问日志 | method/path/status/耗时/IP/uid | 全局后置中间件,一行一条 |
| 业务日志 | 关键业务动作与上下文 | service 内 g.Log().Ctx(ctx).Infof |
| 错误日志 | err + gerror 堆栈 + code | 统一响应中间件记一次,业务不重复打 |
| 启动/任务日志 | 初始化、定时任务执行 | cmd 与任务入口 |
- 级别纪律:Debug 只在开发;Info 记业务主流程;Warn 记可降级异常;Error 记需要人看的错误——每类日志都别刷屏。
- 敏感信息:密码、Token、身份证等一律脱敏或禁止落盘;不要原样打整个请求体。
- 存储:按模块/服务拆文件并轮转(glog 提供文件输出与轮转配置,能力以官方文档为准),或直接对接采集系统。
错误规范
// 1. 只抛业务码,不吞错误、不打印裸错误字符串
if user == nil {
return nil, gerror.NewCode(code.UserNotExist)
}
// 2. 需要追加上下文时用 gerror.Wrap 保留原始错误与堆栈
if err := dao.Xxx.Ctx(ctx).Update(); err != nil {
return nil, gerror.Wrap(err, "update order failed")
}
// 3. 统一出口:响应中间件取码输出,错误堆栈只进日志
if err := r.GetError(); err != nil {
c := gerror.Code(err)
g.Log().Errorf(r.Context(), "%+v", err)
r.Response.WriteJson(...)
}
- 不要在循环里打错误日志(最终统一出口记一次即可);
panic用恢复中间件兜底并记录堆栈。 - 给用户看的 message 要友好稳定,内部 detail/code 用于排查;文案变化不破坏接口契约。
常见追问
- 追问:TraceId 怎么进日志?——保证 ctx 贯穿,链路开启后日志组件自动输出 TraceId 片段(见链路追踪章节),查询系统按它捞全链路日志。
- 追问:日志性能怎么办?——热路径避免大对象序列化进日志、控制级别开关、异步写盘能力视版本而定(以官方文档为准),超大规模再上采样与结构化采集。
记忆点
- 一层请求一层错误一层业务;日志带 ctx、错误带 code;展示与排查信息分离。