spdlog 是一个快速、头文件式的 C++ 日志库,非常易于使用且功能强大。以下是 spdlog 的基本使用指南。
-
特点: 高性能、头部-only、支持多线程
-
优点: 非常快的异步日志记录,丰富的功能,活跃的社区
编译安装
从源码编译安装
bash
git clone https://github.com/gabime/spdlog.git
cd spdlog
mkdir build && cd build
cmake .. -DSPDLOG_BUILD_EXAMPLE=OFF
cmake --build . --config Release
sudo cmake --install .
编译选项
常用 CMake 选项:
-
-DSPDLOG_BUILD_EXAMPLE=ON/OFF
- 是否编译示例 -
-DSPDLOG_BUILD_TESTS=ON/OFF
- 是否编译测试 -
-DSPDLOG_BUILD_SHARED=ON/OFF
- 是否构建共享库 -
-DSPDLOG_FMT_EXTERNAL=ON/OFF
- 使用外部 fmt 库
基本使用
1. 快速开始
cpp
#include <spdlog/spdlog.h>
int main() {
spdlog::info("Welcome to spdlog!");
spdlog::error("Some error message with arg: {}", 1);
spdlog::warn("Easy padding in numbers like {:08d}", 12);
spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
spdlog::info("Support for floats {:03.2f}", 1.23456);
spdlog::info("Positional args are {1} {0}..", "too", "supported");
spdlog::info("{:<30}", "left aligned");
}
2. 创建日志器
cpp
// 创建控制台日志器
auto console = spdlog::stdout_color_mt("console");
// 创建文件日志器
auto file_logger = spdlog::basic_logger_mt("basic_logger", "logs/basic.txt");
3. 使用日志器
cpp
console->info("This is an info message");
file_logger->warn("This warning will go to the file");
4. 自动刷新与手工刷新日志写入文件
1)默认自动刷新机制
spdlog 的 基本文件日志器 (basic_file_sink
) 默认会在以下情况下自动刷新(写入文件):
触发条件 | 说明 |
---|---|
缓冲区满 | 默认缓冲区大小通常为 8192 字节(8KB),当缓冲区填满时自动写入文件 |
每条日志后 (可选) | 如果设置 flush_on 为特定日志级别(如 spdlog::level::err ),达到该级别的日志会立即触发刷新 |
析构时 | 当日志器被销毁(如程序退出时 spdlog::shutdown() ),会强制刷新缓冲区 |
auto logger = spdlog::basic_logger_mt("file_logger", "logs.txt");
logger->set_pattern("[%Y-%m-%d %H:%M:%S] [%l] %v");
// 设置缓冲区大小为 4KB(更频繁刷新)
logger->sinks()[0]->set_buffer_size(4096);
// 遇到 ERROR 或更严重的日志时立即刷新
logger->flush_on(spdlog::level::err);
2)手工刷新
flush()
方法
功能:
-
强制将缓冲区中的所有日志消息立即写入目标(文件、控制台等)
-
不会丢弃任何日志消息
-
保证调用时刻之前的所有日志都被写入
spdlog::info("这是一条重要消息");
spdlog::default_logger()->flush(); // 确保消息立即写入
// 或者在程序关键点强制刷新
void criticalOperation() {
spdlog::info("开始关键操作");
// ...操作代码...
spdlog::default_logger()->flush(); // 确保操作前的日志已写入
}
5. spdlog 中 drop_all()
功能
-
立即丢弃所有挂起的日志消息(在缓冲区中但尚未写入的消息)
-
不会执行任何实际的写入操作
-
只影响调用它的特定日志器
// 紧急情况下丢弃非关键日志
void handleMemoryEmergency() {
auto logger = spdlog::get("non_critical");
if (logger) {
logger->drop_all(); // 快速释放内存
}
}
// 快速退出时避免I/O延迟
void fastExit() {
spdlog::info("程序即将快速退出");
spdlog::default_logger()->drop_all(); // 不保证写入最后一条日志
std::_Exit(1);
}
6.
spdlog 中 shutdown()
功能
-
全局关闭整个 spdlog 系统
-
会先 flush 所有日志器的消息
-
释放所有资源(文件句柄、线程池等)
-
注销所有已注册的日志器
-
调用后不能再使用 spdlog
// 正常程序退出
int main() {
spdlog::info("程序启动");
// ...程序逻辑...
spdlog::info("程序正常退出");
spdlog::shutdown(); // 保证所有日志写入
return 0;
}
// 重新配置日志系统
void reconfigureLogging() {
spdlog::shutdown(); // 先完全关闭
// 重新初始化
auto logger = spdlog::basic_logger_mt("new_logger", "new.log");
spdlog::set_default_logger(logger);
}
高级特性
1. 日志级别
cpp
spdlog::set_level(spdlog::level::debug); // 设置全局日志级别
console->set_level(spdlog::level::trace); // 设置特定日志器级别
console->trace("This is a trace message");
console->debug("This is a debug message");
2. 格式化
cpp
// 自定义格式
spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v");
// 格式说明:
// %v - 实际日志文本
// %t - 线程ID
// %n - 日志器名称
// %l - 日志级别
// %L - 短日志级别
// %^ - 开始颜色范围
// %$ - 结束颜色范围
3. 异步日志
cpp
// 创建异步日志器
auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", "logs/async_log.txt");
// 或转换现有日志器为异步
auto logger = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>("async_logger", "logs/async_log.txt");
4. 多接收器日志器
cpp
// 创建同时输出到控制台和文件的日志器
auto sinks = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("multisink.txt");
auto logger = std::make_shared<spdlog::logger>("multi_sink", {sinks, file_sink});
spdlog::register_logger(logger);
logger->info("This message goes to both console and file");
5. 每日日志文件
cpp
// 创建每天生成新文件的日志器
auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 0, 0);
6. 日志文件大小限制
spdlog 提供了日志文件大小限制的功能,可以通过 rotating_logger_mt
或 rotating_logger_st
创建支持文件旋转的日志器,当日志文件达到指定大小时会自动创建新文件。
std::shared_ptr<spdlog::logger> spdlog::rotating_logger_mt(
const std::string& logger_name,
const std::string& filename,
size_t max_file_size,
size_t max_files,
bool rotate_on_open = false,
const file_event_handlers& event_handlers = {}
);
参数说明
参数 | 说明 |
---|---|
logger_name | 日志记录器的名称(唯一标识) |
filename | 日志文件路径 |
max_file_size | 单个日志文件的最大大小(字节) |
max_files | 保留的最大日志文件数量(旧的会被删除) |
rotate_on_open | (可选) 是否在打开时立即轮转(默认 false ) |
event_handlers | (可选) 文件操作事件回调(如打开、关闭等) |
返回值
-
类型:
std::shared_ptr<spdlog::logger>
-
用途: 用于后续的日志记录操作(如
logger->info()
,logger->error()
等)
示例代码:
#include <spdlog/spdlog.h>
#include <spdlog/sinks/rotating_file_sink.h>
int main() {
// 创建旋转日志器,限制单个文件大小为30MB,最多保留5个文件
auto max_size = 30 * 1024 * 1024; // 30MB
auto max_files = 5;
auto logger = spdlog::rotating_logger_mt("rotating_logger", "logs/rotating.txt", max_size, max_files);
logger->info("This will be written to rotating.txt");
// 当日志文件达到30MB时,会重命名为rotating.1.txt,创建新的rotating.txt
// 当有5个备份文件后,最旧的会被删除
}
日志轮转机制
1)当日志文件大小超过 max_file_size 时,会自动创建新文件(如 mylog.log, mylog.1.log, mylog.2.log 等)。
2)旧文件会按序号递增,超过 max_files 的文件会被删除。
异步示例代码:
#include <spdlog/async.h>
int main() {
spdlog::init_thread_pool(8192, 1); // 队列大小,线程数
auto max_size = 30 * 1024 * 1024; // 30MB
auto max_files = 5;
auto async_logger = spdlog::create_async<spdlog::sinks::rotating_file_sink_mt>(
"async_rotating", "logs/async_rotating.txt", max_size, max_files);
async_logger->info("This is an async message with rotation");
}
7. 日志打印函数名称和行号
使用 SPDLOG_LOGGER_<LEVEL>
宏
spdlog 提供了内置的宏,可以自动捕获 文件名、函数名、行号。
宏 | 日志级别 | 说明 |
---|---|---|
SPDLOG_LOGGER_TRACE(logger, fmt, ...) | trace | 最详细的调试信息(需启用 SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE ) |
SPDLOG_LOGGER_DEBUG(logger, fmt, ...) | debug | 调试信息(需启用 SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG ) |
SPDLOG_LOGGER_INFO(logger, fmt, ...) | info | 常规信息 |
SPDLOG_LOGGER_WARN(logger, fmt, ...) | warn | 警告信息 |
SPDLOG_LOGGER_ERROR(logger, fmt, ...) | error | 错误信息 |
SPDLOG_LOGGER_CRITICAL(logger, fmt, ...) | critical | 严重错误(通常会导致程序终止) |
示例代码:
#include <spdlog/spdlog.h>
int main() {
// 初始化 logger(以 rotating_logger_mt 为例)
auto logger = spdlog::rotating_logger_mt("my_logger", "logs/app.log", 1024 * 1024, 3);
// 使用宏记录日志(自动包含文件名、函数名、行号)
SPDLOG_LOGGER_INFO(logger, "This is an info message");
SPDLOG_LOGGER_WARN(logger, "Warning: something might be wrong");
return 0;
}
输出:
[2025-04-20 12:34:56] [my_logger] [info] [main.cpp:5 (main)] This is an info message
[2025-04-20 12:34:56] [my_logger] [warning] [main.cpp:6 (main)] Warning: something might be wrong
8. spdlog中支持QT字符串(QString)写入
方法1:转换为std::string或QByteArray
最简单的方法是先将QString转换为标准字符串:
#include <spdlog/spdlog.h>
#include <QString>
QString qtStr = "Hello from QT";
spdlog::info("Message: {}", qtStr.toStdString());
// 或者
spdlog::info("Message: {}", qtStr.toUtf8().constData());
方法2:自定义格式化器
你可以为QString添加自定义格式化支持:
#include <spdlog/fmt/fmt.h>
#include <QString>
template <>
struct fmt::formatter<QString> : fmt::formatter<std::string> {
auto format(const QString& qstr, format_context& ctx) {
return fmt::formatter<std::string>::format(qstr.toStdString(), ctx);
}
};
// 使用示例
QString qtStr = "Formatted QT string";
spdlog::info("Message: {}", qtStr);
9. 在spdlog中支持QT字符串(QString)并确保中文不乱码
方法1:使用toUtf8()确保UTF-8编码(推荐)
#include <spdlog/spdlog.h>
#include <QString>
QString chineseStr = "你好,世界!";
spdlog::info("中文消息: {}", chineseStr.toUtf8().constData());
这是最可靠的方法,因为:
-
UTF-8是spdlog内部使用的编码
-
QT的toUtf8()方法会正确转换所有Unicode字符
-
避免了本地编码转换可能带来的问题
方法2:设置全局编码(适用于QT5)
#include <QTextCodec>
int main(int argc, char *argv[]) {
QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
QString chineseStr = "中文内容";
spdlog::info("消息: {}", chineseStr.toLocal8Bit().constData());
return 0;
}
10. logger->set_level(spdlog::level::debug)
设置了日志级别为 debug
,但 SPDLOG_LOGGER_DEBUG(logger, "这是一条 DEBUG 日志")
仍然没有输出.
方法1
在 包含 spdlog 头文件之前,定义宏:
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG // 启用 DEBUG 级别
#include <spdlog/spdlog.h>
方法2
日志级别被全局覆盖,如果调用了 spdlog::set_level()
,它会覆盖所有 logger 的级别:
spdlog::set_level(spdlog::level::info); // 全局设置为 info,DEBUG 不会输出
auto logger = spdlog::default_logger();
logger->set_level(spdlog::level::debug); // 会被全局设置覆盖
SPDLOG_LOGGER_DEBUG(logger, "仍然不会输出");
11. 禁用日志器
// 完全禁用某个日志器
logger->set_level(spdlog::level::off);
异常处理
try {
// spdlog 代码
} catch (const spdlog::spdlog_ex& ex) {
std::cout << "Log initialization failed: " << ex.what() << std::endl;
}
完整示例
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/async.h>
int main() {
try {
// 控制台日志器
auto console = spdlog::stdout_color_mt("console");
console->info("Welcome to spdlog!");
// 文件日志器
auto file_logger = spdlog::basic_logger_mt("file_logger", "logs/basic-log.txt");
file_logger->info("Logging to file");
// 异步日志器
spdlog::init_thread_pool(8192, 1); // 队列大小,线程数
auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", "logs/async-log.txt");
async_file->info("This is async log message");
// 修改日志格式
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%n] [%l] [%t] %v");
// 多接收器日志器
auto sinks = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("multisink.txt");
auto multi_logger = std::make_shared<spdlog::logger>("multi_sink", {sinks, file_sink});
spdlog::register_logger(multi_logger);
multi_logger->info("This goes to both console and file");
// 每日日志
auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt");
daily_logger->info("Daily log message");
// 刷新所有日志
spdlog::drop_all();
} catch (const spdlog::spdlog_ex& ex) {
std::cout << "Log initialization failed: " << ex.what() << std::endl;
return 1;
}
return 0;
}
性能建议
-
对于高性能场景,使用异步日志
-
在发布版本中考虑降低日志级别
-
避免在日志语句中进行复杂计算