必须先调用 spdlog::init_thread_pool() 初始化线程池,否则创建异步 logger 会断言失败或静默降级为同步行为;推荐配置 spdlog::init_thread_pool(8192, 1),队列容量需匹配峰值qps,工作线程数通常设为1即可。

异步日志必须先初始化线程池
不调用 spdlog::init_thread_pool() 就直接创建异步 logger,会触发断言失败或静默降级为同步行为——这是最容易被忽略的坑。线程池是异步模式的基础设施,不是可选配置。
推荐初始化方式:spdlog::init_thread_pool(8192, 1),其中 8192 是无锁队列容量(建议 ≥ 日志峰值 QPS × 0.1 秒),1 是工作线程数(单线程足够应对多数场景;多线程仅在极端吞吐下需评估锁竞争)。
- 队列太小(如设为 128):高并发时
async_logger->info()可能阻塞或丢弃日志(取决于 fallback 策略) - 线程数过多(如 >4):反而因上下文切换和 sink 竞争降低性能,文件写入本身是串行瓶颈
- 未初始化就调用
spdlog::daily_logger_mt():该函数内部虽自动启用异步,但若线程池未初始化,会 fallback 到同步写入,且无任何警告
daily_logger_mt 默认就是异步的,但有前提
spdlog::daily_logger_mt("name", "path.log", 2, 30) 这类工厂函数,只要全局线程池已初始化,就会自动包装成 async_logger 实例——它本质是 spdlog::create_async<:sinks::daily_file_sink_mt></:sinks::daily_file_sink_mt> 的语法糖。
注意两个关键点:
- 必须在调用前完成
spdlog::init_thread_pool(),否则退化为同步 - 日志文件路径(如
"logs/app.log")的父目录必须存在,否则首次写入时会失败并静默禁用该 sink(可通过spdlog::set_error_handler()捕获) - 时间参数
2, 30表示每天 02:30 滚动,滚动动作由后台线程执行,不阻塞业务线程
手动创建 async_logger 更灵活,但也更易出错
需要显式组合 sink 和 thread pool,适合自定义 sink 或精细控制刷新策略。常见错误是误传 sink 实例而非 shared_ptr:
// ❌ 错误:传入栈对象,析构后崩溃
auto file_sink = spdlog::sinks::basic_file_sink_mt("app.log");
auto logger = std::make_shared<:async_logger>("async", file_sink, spdlog::thread_pool());
// ✅ 正确:必须用 shared_ptr 包裹 sink
auto file_sink = std::make_shared<:sinks::basic_file_sink_mt>("app.log");
auto logger = std::make_shared<:async_logger>("async", file_sink, spdlog::thread_pool());
spdlog::register_logger(logger);
</:async_logger></:sinks::basic_file_sink_mt></:async_logger>
另外,spdlog::create_async_nb<sink></sink>(non-blocking 版本)会在队列满时不阻塞也不丢日志,而是直接返回 false,需业务层判断处理——这比默认的阻塞等待更适合实时性要求极高的场景。
flush_on() 和定期 flush 容易被当成“已生效”
logger->flush_on(spdlog::level::err) 只保证 error 级别日志触发立即刷盘,**不影响其他级别日志的异步行为本身**。它不改变消息入队/出队逻辑,只是增加一次同步 I/O 调用。
真正影响可靠性的是:
- 程序异常退出前未调用
spdlog::shutdown():队列中未消费的日志会丢失(哪怕设置了flush_on) - 未设置
file_sink->set_level(spdlog::level::info):低于该级别的日志连入队都不会发生 - 使用
spdlog::set_default_logger()后,后续spdlog::info()才走异步流程;否则直接调用 logger 实例方法才有效
异步模式真正的脆弱点不在配置,而在生命周期管理:线程池、logger、sink 三者的析构顺序稍有错乱,就可能 crash 或静默失效。生产环境务必用 RAII 封装,避免裸指针传递。











