beego logs.async() 通过将日志推入带缓冲 channel 并由独立 goroutine 消费,避免 i/o 阻塞主业务;但需注意进程退出前调用 close() 防丢失,且 file 引擎的 daily/rotate 仍会同步卡顿。

beego logs.Async() 为什么能提升性能
同步日志会阻塞当前 goroutine,直到所有输出引擎(如 file、console)都完成写入;而 Async() 启用后,日志消息被推入一个带缓冲的 channel,由独立 goroutine 异步消费。这避免了磁盘 I/O 或网络延迟拖慢主业务逻辑。
但要注意:异步模式下日志丢失风险存在——进程崩溃或未调用 logs.Close() 时,channel 中未消费的消息会丢弃。生产环境务必在程序退出前显式调用 logs.Close() 等待队列清空。
-
logs.NewLogger(10000)的参数是消息 channel 容量,不是缓存大小;设太小会导致日志被丢弃(DropMsg计数器上升) - 默认 channel 容量是 1000,高并发写日志场景建议调大到 5000~10000
- 异步模式下
logs.Debug()等调用几乎不耗时,但实际写入有毫秒级延迟,不适合调试依赖即时输出的逻辑
file 引擎开启异步后仍卡顿?检查 daily 和 rotate 配置
即使启用了 Async(),file 引擎在每日 rollover 或文件尺寸触发 rotate 时,仍会同步执行重命名、切分、压缩等操作,造成瞬时阻塞。
常见表现:每天凌晨 0 点或日志达到 maxsize 时,接口响应时间突增 100ms+。
- 关闭
daily(设为false),改用固定文件名 + 外部 logrotate 工具管理,可彻底规避此问题 - 若必须 daily,把
maxdays设小(如 3),减少清理旧文件时的遍历开销 -
rotate: false可禁用自动切分,但需确保maxsize足够大,避免单文件无限膨胀
如何让 console 输出也带文件名和行号
console 引擎默认不显示调用位置,即使全局开了 logs.EnableFuncCallDepth(true),它也不会自动读取 loggerFuncCallDepth 字段。
正确做法是初始化 logger 后,手动设置调用栈深度:
log := logs.NewLogger(10000)
log.Async()
log.SetLogger(logs.AdapterConsole, `{"level":7}`)
log.SetLogFuncCallDepth(3) // 注意:这里必须显式调
为什么是 3?因为 logs.Debug() → 封装层 → 实际调用点,通常要跳过 2~3 层内部函数。设小了显示的是 logs 包内部文件,设大了可能 panic。
- 仅对
AdapterConsole和AdapterFile有效;conn、smtp不支持该字段 - 如果用了自定义封装函数(比如
MyLog.Debug()),需把SetLogFuncCallDepth值再 +1
多个日志实例共用 Async channel 的陷阱
Beego 的 logs 模块是全局单例设计,logs.NewLogger() 创建的是新实例,但 logs.Async() 是作用于全局默认 logger(即 BeeLogger)的。如果你混用两种方式:
- 直接调
logs.Debug()→ 走全局 logger,受logs.Async()影响 - 又自己 new 一个
log := logs.NewLogger()并调log.Debug()→ 这个实例默认不同步,也**不会**自动继承全局的 async 设置
结果就是:一半日志异步,一半同步,性能毛刺难以定位。统一方案只有两个:
- 全部走
logs.xxx()全局函数,并在启动时只调一次logs.Async() - 全部用独立实例,每个都显式调
log.Async()
别交叉使用——这是最常被忽略的配置一致性问题。











