每个业务类应持有一个以类名命名的spdlog::logger智能指针,通过spdlog::get()获取或创建,确保日志语义清晰且隔离;业务字段如order_id需显式传入,避免隐式上下文;统一日志配置通过spdlog::set_level()初始化后应用,优先使用_mt版本保证线程安全。

日志对象怎么和业务类实例绑定而不冲突
多个业务对象(比如 OrderService、UserService)各自需要打日志,但又不能共用同一个 logger 实例导致上下文混杂(例如线程 A 打 OrderService 日志时被线程 B 的 UserService 日志覆盖 tag 或上下文字段)。关键不是“能不能共享 logger”,而是“如何让每个业务对象拥有语义清晰、隔离的日志输出能力”。
推荐做法是:每个业务类持有一个带名称前缀的 spdlog::logger 智能指针,初始化时用类名或模块名作为 logger 名称。spdlog 会自动管理同名 logger 的单例,避免重复创建;不同名称则天然隔离。
- 在
OrderService构造函数里调用spdlog::get("order"),若不存在则用spdlog::stdout_logger_mt("order")创建 - 避免直接 new 一个裸 logger —— 容易忘记释放,也绕过 spdlog 的全局 registry 管理
- 不要在头文件里定义全局 logger 变量,否则多编译单元链接时可能触发 ODR 违规
如何让日志自动带上业务对象的关键字段(如 order_id、user_id)
硬编码每次 logger->info("order_id={} processed", order_id) 容易漏、难维护。更稳的方式是利用 spdlog 的 set_pattern + 自定义 formatter,或者用 logger->flush_on() 配合 thread local 上下文缓存。
实际更轻量的做法是封装一层业务日志接口:
立即学习“C++免费学习笔记(深入)”;
class OrderService {
std::shared_ptr<spdlog::logger> logger_;
public:
OrderService() : logger_(spdlog::get("order")) {}
void process(const std::string& order_id) {
// 临时注入上下文(仅当前 log 调用生效)
logger_->info("[order_id={}] start processing", order_id);
// 后续所有 logger->xxx 都不会自动带 order_id —— 必须显式传
}
};
真正自动携带需借助 spdlog::sinks::daily_file_sink_mt 的自定义 formatter,或改用 g3log 的 LOGF 宏 + LogWorker 上下文注入 —— 但代价是侵入性增强、调试变难。
- 自动上下文字段只建议用于 trace-id、thread-id 这类跨调用链通用字段
- order_id/user_id 这类业务强相关字段,坚持“谁使用谁传”,避免隐式依赖导致日志查不到关键信息
- 如果真要自动,必须确保 set_pattern 中的占位符(如
%v)与 formatter 返回值严格对齐,否则日志内容错位甚至 crash
多个业务对象共用同一套日志配置(如级别、输出路径)怎么统一控制
spdlog 提供 spdlog::set_level() 和 spdlog::apply_all(),但要注意:这些操作作用于所有已注册 logger,包括你没显式创建的默认 logger(如 spdlog::default_logger()),容易误关/误开。
- 初始化后立刻调用
spdlog::set_level(spdlog::level::info),再创建各业务 logger,保证它们继承该级别 - 运行时动态调级用
spdlog::apply_all([](std::shared_ptr<spdlog::logger> l) { l->set_level(...); }),比遍历spdlog::get更安全 - 避免在业务代码里调用
spdlog::drop_all()—— 会清空所有 logger 实例,导致后续spdlog::get("xxx")返回空指针 - 如果用
rotating_file_sink,注意多个 logger 共享同一 sink 时,rotate 行为由第一个触发者决定,其余 logger 不感知 —— 可能出现部分日志写到旧文件、部分写到新文件
为什么用 spdlog::stdout_logger_mt 而不是 _st
即使业务对象是单线程创建的,也不能假设其日志调用一定发生在线程创建它的线程上。比如 OrderService::process() 可能被异步任务调度器派发到线程池执行 —— 此时若 logger 是 _st 版本,会触发未定义行为(data race 或 crash)。
-
_mt开销极小(原子计数+无锁队列),现代 CPU 上几乎无感知 -
_st仅适用于纯单线程 CLI 工具等极简场景,C++ 服务端业务基本不适用 - 别被名字误导:“multi-threaded” 不是指 logger 自己开线程,而是指它线程安全,可被任意线程并发调用
真正复杂的是 sink 的线程模型选择:比如 daily_file_sink_mt 内部加锁,而 async_logger 把日志推给独立线程处理——后者吞吐高但有延迟、且异常退出时可能丢日志。业务系统通常选前者更稳妥。


















