正确引入spdlog需配置头文件路径:cmake用target_include_directories,手动编译加-i,vcpkg/conan需导出include路径;初始化须在main后或显式调用init_thread_pool;中文乱码需匹配utf-8编码与控制台设置;异步日志必须调用spdlog::shutdown()确保flush。

spdlog 头文件怎么引入才不报错
直接 #include <spdlog></spdlog> 就行,但前提是你的项目能正确找到头文件路径。常见错误是编译器提示 fatal error: spdlog/spdlog.h: No such file or directory —— 这不是代码写错了,是没配好 include 路径。
如果你用 CMake,得在 CMakeLists.txt 里加 target_include_directories(your_target PRIVATE ${SPDLOG_INCLUDE_DIR});如果手动编译,就得用 -I/path/to/spdlog/include。注意:别把整个 spdlog 目录加进去,只加到 include 这一级,否则 #include <spdlog></spdlog> 会找不到。
- 用 vcpkg 安装的话,
SPDLOG_INCLUDE_DIR通常是vcpkg/installed/x64-linux/include(平台不同路径不同) - 用 conan 的话,确保
conan install后调用了conan_basic_setup()或正确导出了 include 路径 - 别把
spdlog.h拷进自己工程目录再#include "spdlog.h"—— 这样后续升级困难,且容易漏掉details/下的依赖头文件
初始化 logger 时为什么一运行就 crash
最常见原因是调用 spdlog::get() 或 spdlog::stdout_logger_mt() 前没初始化 spdlog 全局状态,尤其在多线程环境或静态库链接时。spdlog 内部依赖一个全局 registry,如果 logger 创建早于 main()(比如全局变量构造函数里),而 registry 还没 ready,就会访问空指针。
安全做法是:所有 logger 实例都在 main() 开始后创建,或者显式调用 spdlog::init_thread_pool()(如果要用异步日志)。不要在全局作用域写 auto logger = spdlog::basic_logger_mt("app", "logs/app.txt");。
- 异步 logger 必须配线程池:
spdlog::init_thread_pool(8192, 1);,否则spdlog::daily_logger_mt()等会抛spdlog::spdlog_ex - Windows 下若用 MinGW 编译,确保定义了
SPDLOG_COMPILED_LIB并链接spdlog.lib,否则静态成员未实例化也会 crash - logger 名字不能重复,
spdlog::get("net")返回 null 表示名字已被占用或未注册,别直接解引用
日志输出中文乱码或时间格式不对
spdlog 默认用系统 locale 输出时间、字符串,Linux/macOS 一般没问题,Windows 控制台默认是 GBK,但 spdlog 内部用 UTF-8 字符串,直接 printf 到控制台就会乱码。时间格式则取决于 pattern_formatter 的设置,不是“自动识别系统格式”。
解决中文:Windows 上要么改控制台代码页为 UTF-8(chcp 65001),要么用 windows_console_sink 替代 stdout_sink;Linux/macOS 建议确认终端 LANG 是 en_US.UTF-8 或类似值。
- 时间格式必须显式指定:
logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v");,%e是毫秒,%z是时区,不设 pattern 就用默认的(不含毫秒、无时区) - 文件 sink 默认不带 BOM,UTF-8 中文写入文本编辑器可能识别成 ANSI,可在打开文件时手动选 UTF-8 with BOM,或自己在首行写
\xEF\xBB\xBF - pattern 中
%v是用户消息,它不会自动转义换行符;如果传入含\n的字符串,日志里就是多行——这不是 bug,是设计如此
异步日志 flush 不及时或程序退出丢日志
异步 logger 把日志塞进队列就返回,不等真正写入磁盘。如果程序快速退出(比如 main 结束没等队列清空),或者 SIGINT 杀进程,未处理的日志就丢了。
必须在退出前主动 flush + 关闭 sink。spdlog 不会自动注册 atexit handler,也不拦截信号。别指望析构函数能救你——全局 logger 析构顺序不确定,可能比 sink 先析构。
- 推荐在 main 结尾加:
spdlog::shutdown();,它会 flush 所有 logger 并释放线程池 - 如果用
spdlog::daily_logger_mt,每天滚动靠的是首次写入时检查时间,不是后台定时器;所以如果一天内完全没打日志,就不会生成新文件 - 异步模式下
logger->flush_on(spdlog::level::err)只对同步操作生效,异步日志仍走队列;如需紧急刷盘,得调logger->flush()手动触发
async logger 的性能优势明显,但代价是必须自己管生命周期。很多人卡在“为什么我打了 error 日志却没看到文件”,其实只是没调 shutdown()。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











