一个高性能、仅头文件、支持异步的 C++ 日志库
一、核心部件(架构骨架)
1.1 四大组件关系图
┌─────────────────────────────────────────────────────────────────┐
│ 你的代码
│ logger->info("用户 {} 登录", name)
└───────────────────────────┬─────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────┐
│ 📌 Logger(日志记录器)—— 你直接打交道的对象
│ • 职责:接收日志、检查级别、分发消息
│ • 类型:logger(同步)/ async_logger(异步)
│ • 关系:一个 Logger 可拥有多个 Sink
└───────────────────────────┬─────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────┐
│ 📌 Formatter(格式化器)—— 按模板转换消息
│ • 职责:将消息 + 元数据 → 格式化字符串
│ • 示例:spdlog::set_pattern("[%Y-%m-%d] [%l] %v")
└───────────────────────────┬─────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────┐
│ 📌 Sink(接收器/输出目标)—— 实际写入目的地
│ • 职责:将格式化后的字符串写入具体目标
│ • 常见:stdout_color_sink, rotating_file_sink, daily_file_sink
│ • 扩展:可自定义(网络/数据库/消息队列)
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 📌 Registry(注册中心)—— 全局单例,幕后管家
│ • 职责:管理所有 Logger(注册/查找/广播配置)
│ • 关键:spdlog::get("name") 查找,spdlog::set_level 广播
└─────────────────────────────────────────────────────────────────┘
1.2 核心理解要点
| 组件 | 一句话理解 | 比喻 |
|---|
| Logger | 你调用的接口 | 快递员(你交给它包裹) |
| Sink | 实际输出的目标 | 快递目的地(送到哪) |
| Formatter | 消息的格式规则 | 包装样式(怎么包) |
| Registry | 全局管理器 | 快递公司总部(管所有快递员) |
二、必须知道的概念(20个核心术语)
| # | 术语 | 含义 | 关键点 |
|---|
| 1 | Logger | 日志记录器 | 你直接调用的对象 |
| 2 | Sink | 接收器/输出目标 | 实际写入的地方 |
| 3 | Formatter | 格式化器 | 控制日志输出格式 |
| 4 | Registry | 注册中心 | 全局单例,管理所有 Logger |
| 5 | Level | 日志级别 | trace < debug < info < warn < error < critical |
| 6 | _mt | Multi-Threaded | 线程安全(带锁),默认推荐 |
| 7 | _st | Single-Threaded | 线程不安全(无锁),仅单线程用 |
| 8 | 同步日志 | 当前线程直接写 I/O | 简单可靠,适合低频 |
| 9 | 异步日志 | 入队后后台线程写 I/O | 高性能,适合高频,退出前必须 shutdown |
| 10 | Pattern | 格式模板 | 如 "%Y-%m-%d %H:%M:%S [%l] %v" |
| 11 | SPDLOG_* 宏 | 编译时可控的日志宏 | 配合 SPDLOG_ACTIVE_LEVEL 可完全移除 |
| 12 | spdlog::* 函数 | 运行时日志函数 | 简洁,但无法编译时移除 |
| 13 | 文件滚动 | 日志文件自动切分 | 按大小(rotating)或按时间(daily) |
| 14 | 多 Sink | 一个 Logger 多个输出 | 同时输出到控制台 + 文件 + 网络 |
| 15 | Backtrace | 回溯日志 | 缓存 trace 日志,发生错误时转储 |
| 16 | 刷新策略 | 何时写入磁盘 | flush_on(level) + flush_every(seconds) |
| 17 | 溢出策略 | 异步队列满时行为 | block(阻塞)或 overrun_oldest(丢弃旧) |
| 18 | 零拷贝 | 减少内存复制 | 性能优化手段 |
| 19 | 无锁队列 | 无需锁的队列 | 异步日志性能核心 |
| 20 | 仅头文件 | Header-only | 直接 #include 即可,无需编译库 |
三、编译选项(构建配置)
3.1 编译时优化宏(在 #include <spdlog/spdlog.h> 之前定义)
// ========== 性能优化 ==========
// 1. 使用精度稍低但更快的时钟(Linux)
#define SPDLOG_CLOCK_COARSE
// 2. 禁用线程 ID 获取(格式中无 %t 时使用)
#define SPDLOG_NO_THREAD_ID
// 3. 禁用源文件位置(格式中无 %@ 时使用)
#define SPDLOG_NO_SOURCE_LOC
// ========== 编译时日志级别控制 ==========
// 4. 编译时移除低于指定级别的日志(仅对 SPDLOG_* 宏有效)
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO // 移除 trace 和 debug
// 可选值: SPDLOG_LEVEL_TRACE, DEBUG, INFO, WARN, ERROR, CRITICAL, OFF
3.2 CMake 集成示例
# CMakeLists.txt
find_package(spdlog REQUIRED)
# Debug 模式保留所有日志,Release 模式移除 trace/debug
target_compile_definitions(${PROJECT_NAME} PRIVATE
$<$<CONFIG:Release>:SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO>
$<$<CONFIG:Release>:SPDLOG_CLOCK_COARSE>
)
target_link_libraries(${PROJECT_NAME} spdlog::spdlog)
3.3 命令行编译
# Debug 构建
g++ -std=c++17 -DSPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG main.cpp -o app_debug
# Release 构建(移除 trace 和 debug)
g++ -std=c++17 -O2 -DSPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO main.cpp -o app_release
四、必须注意的事项(关键决策点)
4.1 选型决策表
| 场景 | 推荐选择 | 原因 |
|---|
| 多线程环境 | _mt 后缀 | 线程安全,默认选择 |
| 单线程 + 极致性能 | _st 后缀 | 无锁开销,但需明确单线程 |
| 高频日志(> 1000条/秒) | 异步日志 | 不阻塞业务线程 |
| 低频日志(< 100条/秒) | 同步日志 | 简单可靠 |
| 自定义 Logger(多模块) | logger->info() 函数 | 宏无法指定自定义 Logger |
| 调试日志(trace/debug) | SPDLOG_* 宏 | 编译时可完全移除 |
| 业务日志(info/warn/error) | spdlog::info() 或 logger->info() | 简洁 |
| 热路径(循环内) | SPDLOG_* 宏 + 异步 | 极致性能 |
4.2 三原则
| 原则 | 内容 |
|---|
| 1. 线程安全 | 不确定环境时一律用 _mt |
| 2. 异步优先 | 生产环境高频场景用异步 |
| 3. 按需编译 | trace/debug 用宏 + SPDLOG_ACTIVE_LEVEL 控制 |
五、易错内容(避坑清单)
🔴 易错 1:日志不刷新 → 文件为空或丢日志
// ❌ 错误:程序崩溃时日志丢失
spdlog::info("关键操作完成");
// ✅ 正确:设置刷新策略
spdlog::flush\_on(spdlog::level::err); // error 及以上立即刷新
spdlog::flush\_every(std::chrono::seconds(3)); // 定时刷新
// 退出前
spdlog::shutdown();
🔴 易错 2:异步日志退出时挂起
// ❌ 错误:主线程直接退出,后台线程还在工作
int main() {
setup_async();
spdlog::info("Hello");
return 0; // 可能卡住或崩溃
}
// ✅ 正确:优雅关闭
int main() {
setup_async();
spdlog::info("Hello");
spdlog::shutdown(); // 等待后台线程完成
return 0;
}
🔴 易错 3:热路径中使用同步日志
// ❌ 错误:高频循环中用同步日志 → 严重拖慢性能
for (int i = 0; i < 1000000; ++i) {
spdlog::info("{}", i);
}
// ✅ 正确:使用异步日志
auto async_logger = get_async_logger();
for (int i = 0; i < 1000000; ++i) {
async_logger->info("{}", i);
}
🔴 易错 4:SPDLOG_ACTIVE_LEVEL 对 spdlog:: 函数无效
// ❌ 错误:以为能移除 spdlog::debug()
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO
#include <spdlog/spdlog.h>
spdlog::debug("这条还会输出"); // 无法移除!
// ✅ 正确:使用 SPDLOG_* 宏
SPDLOG_DEBUG("这条在编译时被移除"); // 完全不生成代码
🔴 易错 5:跨 DLL 传递 Logger 对象
// ❌ 错误:在 DLL 内部创建 Logger
// plugin.dll
auto logger = spdlog::basic_logger_mt("plugin", "plugin.log");
// 卸载 DLL 时可能崩溃
// ✅ 正确:由主程序创建,通过接口传递
// main.exe
auto main_logger = spdlog::basic_logger_mt("app", "app.log");
plugin->set_logger(main_logger);
🔴 易错 6:宏中的昂贵参数仍会被求值
// 对于 SPDLOG_* 宏,如果级别不满足,参数不会被求值
SPDLOG_DEBUG("复杂数据: {}", generate_large_json()); // ✅ 安全
// 但如果直接用 spdlog:: 函数,参数会立即求值
spdlog::debug("复杂数据: {}", generate_large_json()); // ❌ 即使关闭也会调用
// 对于宏,如果参数是纯函数调用,安全;但如果有副作用需注意
int counter = 0;
SPDLOG_DEBUG("counter: {}", ++counter); // 如果 debug 被关闭,++counter 不会执行
🔴 易错 7:_st 在多线程中使用
// ❌ 错误:多个线程共享同一个 _st Logger
auto logger = spdlog::stdout_color_st("console"); // 线程不安全
std::thread t1([&](){ logger->info("from t1"); });
std::thread t2([&](){ logger->info("from t2"); });
// 数据竞争 → 崩溃或乱码
// ✅ 正确:多线程用 _mt
auto logger = spdlog::stdout_color_mt("console");
六、工作原理(快速理解)
6.1 同步 vs 异步
同步:
【同步模式】
调用 logger->info()
│
▼
检查日志级别 ──不通过→ 返回
│通过
▼
格式化消息(Formatter)
│
▼
Sink 写入(I/O 操作,耗时)
│
▼
返回 ← 业务线程被阻塞
异步:
【异步模式】
调用 logger->info()
│
▼
检查日志级别 ──不通过→ 返回
│通过
▼
格式化消息(Formatter)
│
▼
放入无锁队列 ──→ 立即返回 ← 业务线程几乎无阻塞
│
│ (后台线程)
▼
从队列取出
│
▼
Sink 写入(I/O 操作)
6.2 为什么快?
- 异步非阻塞:I/O 移出业务线程
- 无锁队列:
moodycamel::ConcurrentQueue,减少竞争 - 高效格式化:
fmt 库比 iostream 快数倍 - 零拷贝:减少内存分配和数据复制
七、速查表(快速参考)
7.1 常用 API
// ===== 创建 Logger =====
auto logger = spdlog::stdout_color_mt("name"); // 彩色控制台
auto logger = spdlog::basic_logger_mt("name", "log"); // 文件
auto logger = spdlog::rotating_logger_mt("name", "log", 5*1024*1024, 3); // 滚动
auto logger = spdlog::daily_logger_mt("name", "log", 0, 0); // 每日
// ===== 获取 Logger =====
auto logger = spdlog::get("name");
// ===== 全局配置 =====
spdlog::set_level(spdlog::level::debug);
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S] [%l] %v");
spdlog::flush_on(spdlog::level::err);
spdlog::flush_every(std::chrono::seconds(3));
// ===== 记录日志 =====
logger->info("用户 {} 登录", name);
spdlog::info("全局日志");
SPDLOG_INFO("宏日志");
// ===== 异步日志 =====
spdlog::init_thread_pool(8192, 1);
auto logger = spdlog::basic_logger_mt<spdlog::async_factory>("async", "log");
spdlog::shutdown(); // 退出前必须调用
7.2 常用格式化占位符
| 占位符 | 含义 | 示例 |
|---|
%v | 消息正文 | 用户登录 |
%t | 线程 ID | 12345 |
%Y-%m-%d | 日期 | 2026-08-22 |
%H:%M:%S.%e | 时间+毫秒 | 14:30:25.123 |
%l | 级别全称 | info |
%L | 级别简称 | I |
%n | Logger 名称 | app |
%@ | 文件:行号 | main.cpp:42 |
%^ %$ | 颜色包裹 | 带颜色输出 |
八、总结(一句话记忆)
| 维度 | 一句话总结 |
|---|
| 核心部件 | Logger 接口,Sink 目的地,Formatter 格式,Registry 管家 |
| 线程安全 | 多线程用 _mt,单线程用 _st,不确定就 _mt |
| 同步/异步 | 高频用异步,低频用同步,退出前 shutdown |
| 宏 vs 函数 | 调试用宏(可移除),业务用函数(更简洁) |
| 编译控制 | SPDLOG_ACTIVE_LEVEL 移除 trace/debug |
| 刷新策略 | error 即刷新,退出前 shutdown |
| 避坑口诀 | 异步要 shutdown,热路径用异步,多线程用 _mt,调试日志用宏 |
九、进阶API接口
// ===== 1.多 Sink(同时输出到多个目标) =====
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/rotating_file_sink.h>
// 创建多个 Sink
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
"logs/app.log", 1024*1024*5, 3
);
// 组合成一个 Logger(同时输出到控制台和文件)
std::vector<spdlog::sink_ptr> sinks = {console_sink, file_sink};
auto logger = std::make_shared<spdlog::logger>("multi", sinks.begin(), sinks.end());
// 需要手动注册才能用 spdlog::get() 获取
spdlog::register_logger(logger);
// ===== 2.为不同 Sink 设置不同级别和格式 =====
// 控制台只显示 info 及以上,文件记录全部
console_sink->set_level(spdlog::level::info);
file_sink->set_level(spdlog::level::trace);
// 控制台用简洁格式,文件用详细格式
console_sink->set_pattern("[%l] %v");
file_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%t] [%l] %v");
// ===== 3.回溯日志(Backtrace) =====
// 启用回溯:缓存最近 100 条 trace 日志
logger->enable_backtrace(100);
// 在代码中埋点(不会输出,只缓存)
logger->trace("步骤1: 初始化");
logger->trace("步骤2: 处理数据");
// 发生错误时转储所有缓存的 trace
logger->error("处理失败!");
logger->dump_backtrace(); // 输出缓存的 100 条 trace
// ===== 4.自定义日志级别(level::from_str) =====
// 从字符串解析级别(方便从配置文件读取)
std::string level_str = config.get("log_level", "info");
spdlog::level::level_enum level = spdlog::level::from_str(level_str);
spdlog::set_level(level);
// 所有级别枚举值
spdlog::level::trace // 0
spdlog::level::debug // 1
spdlog::level::info // 2
spdlog::level::warn // 3
spdlog::level::err // 4
spdlog::level::critical // 5
spdlog::level::off // 6
// ===== 5.环境变量动态控制 =====
#include <spdlog/cfg/env.h>
// 从环境变量加载级别
// 启动前设置: export SPDLOG_LEVEL=debug
// 或针对特定 logger: export SPDLOG_LEVEL=logger1=debug,logger2=warn
spdlog::cfg::load_env_levels();
// 从 argv 加载(支持 --spdlog-level=debug)
spdlog::cfg::load_argv_levels(argc, argv);
// ===== 6.自定义 Sink(输出到任意目标) =====
#include <spdlog/sinks/base_sink.h>
template<typename Mutex>
class my_redis_sink : public spdlog::sinks::base_sink<Mutex> {
protected:
void sink_it_(const spdlog::details::log_msg& msg) override {
spdlog::memory_buf_t formatted;
this->formatter_->format(msg, formatted);
// 将 formatted.data() 发送到 Redis
redis_client_.rpush("logs", std::string(formatted.data(), formatted.size()));
}
void flush_() override { /* 刷新连接 */ }
private:
RedisClient redis_client_;
};
// 使用自定义 Sink
auto redis_sink = std::make_shared<my_redis_sink<std::mutex>>();
auto logger = std::make_shared<spdlog::logger>("redis_logger", redis_sink);
// ===== 7.自定义 Sink(输出到任意目标) =====
// 如果你的程序中只有一个 Sink,可以直接设为默认
auto logger = spdlog::stdout_color_mt("console");
spdlog::set_default_logger(logger);
// 之后 spdlog::info() 就会输出到这个 logger
spdlog::info("这会输出到默认 logger");
// ===== 8.线程局部 Logger(thread_local) =====
// 每个线程拥有自己的 Logger,避免锁竞争
thread_local auto logger = spdlog::stdout_color_st("thread_local");
void thread_func() {
logger->info("这条日志只属于当前线程,线程安全且无锁");
}
// ===== 9.条件日志(should_log) =====
// 在调用昂贵函数前检查级别
if (logger->should_log(spdlog::level::debug)) {
logger->debug("复杂数据: {}", generate_large_json());
}
// ===== 10. 异常处理 =====
try {
auto logger = spdlog::rotating_logger_mt("app", "logs/app.log", 1024*1024*5, 3);
} catch (const spdlog::spdlog_ex& ex) {
std::cerr << "日志初始化失败: " << ex.what() << std::endl;
}
// ===== 12. 异常处理 =====
// 固定宽度 7 个字符,左对齐
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%-7l] %v");
// 输出: [2026-08-22 14:30:25.123] [info ] 消息
// [error ] 消息
// [debug ] 消息
更高级的格式化占位符
| 占位符 | 含义 |
|---|
%s | 源文件名 |
%# | 行号 |
%! | 函数名 |
%^ %$ | 颜色范围(配合 %l 使用) |
%+ | 等同于 %Y-%m-%d %H:%M:%S.%e |
十、综合实践:生产级日志系统配置
10.1 完整示例代码
// logger_config.hpp
#pragma once
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/rotating_file_sink.h>
#include <spdlog/sinks/daily_file_sink.h>
#include <spdlog/async.h>
#include <memory>
#include <vector>
class LoggerConfig {
public:
static void init(const std::string& app_name = "app",
const std::string& log_dir = "logs",
bool enable_console = true,
bool enable_async = true) {
try {
std::vector<spdlog::sink_ptr> sinks;
// 1. 控制台 Sink(彩色输出,仅 info 及以上)
if (enable_console) {
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(spdlog::level::info);
console_sink->set_pattern("%^[%H:%M:%S.%e] [%l]%$ %v");
sinks.push_back(console_sink);
}
// 2. 滚动文件 Sink(记录全部级别,5MB × 3 个文件)
auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
log_dir + "/" + app_name + ".log",
5 * 1024 * 1024, // 5MB
3 // 保留 3 个文件
);
file_sink->set_level(spdlog::level::trace);
file_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%t] [%-7l] [%s:%#] %v");
sinks.push_back(file_sink);
// 3. 创建 Logger(异步或同步)
spdlog::logger_ptr logger;
if (enable_async) {
// 异步:队列 8192,1 个后台线程
spdlog::init_thread_pool(8192, 1);
logger = std::make_shared<spdlog::async_logger>(
app_name, sinks.begin(), sinks.end(),
spdlog::thread_pool(),
spdlog::async_overflow_policy::block
);
} else {
logger = std::make_shared<spdlog::logger>(
app_name, sinks.begin(), sinks.end()
);
}
// 4. 全局配置
logger->set_level(spdlog::level::debug);
logger->flush_on(spdlog::level::err); // error 及以上立即刷新
spdlog::flush_every(std::chrono::seconds(3)); // 定时刷新
// 5. 注册为默认 Logger
spdlog::set_default_logger(logger);
// 6. 启动日志
logger->info("日志系统初始化完成 (async: {})", enable_async);
} catch (const spdlog::spdlog_ex& ex) {
std::cerr << "日志初始化失败: " << ex.what() << std::endl;
throw;
}
}
static void shutdown() {
spdlog::info("应用关闭,日志系统即将退出");
spdlog::shutdown();
}
};
// main.cpp
#include "logger_config.hpp"
#include <spdlog/spdlog.h>
#include <thread>
#include <vector>
// 编译时控制:Release 模式移除 trace/debug
// 在 CMake 或编译命令中定义
int main(int argc, char* argv[]) {
// 初始化日志系统
LoggerConfig::init("myapp", "logs", true, true);
// 记录启动信息
spdlog::info("=== 应用启动 ===");
spdlog::info("版本: 1.0.0");
spdlog::info("参数数量: {}", argc);
// 模拟业务逻辑
spdlog::debug("开始处理任务");
// 多线程日志测试
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back([i]() {
// 使用宏(可编译时移除)
SPDLOG_DEBUG("线程 {} 开始执行", i);
for (int j = 0; j < 100; ++j) {
spdlog::info("线程 {} 处理第 {} 个任务", i, j);
}
SPDLOG_DEBUG("线程 {} 执行完成", i);
});
}
for (auto& t : threads) {
t.join();
}
// 错误日志测试
try {
throw std::runtime_error("模拟异常");
} catch (const std::exception& e) {
spdlog::error("捕获异常: {}", e.what());
spdlog::critical("严重错误,需要立即处理");
}
// 回溯日志示例
spdlog::default_logger()->enable_backtrace(50);
for (int i = 0; i < 100; ++i) {
spdlog::trace("回溯日志 {}", i);
}
spdlog::error("发生错误,转储回溯日志");
spdlog::dump_backtrace();
// 性能测试
auto start = std::chrono::steady_clock::now();
for (int i = 0; i < 10000; ++i) {
spdlog::info("性能测试消息 {}", i);
}
auto end = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
spdlog::info("10000 条异步日志耗时: {} ms", duration.count());
// 优雅关闭
LoggerConfig::shutdown();
return 0;
}
10.2 CMake 配置文件
# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(LoggerDemo)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 查找 spdlog
find_package(spdlog REQUIRED)
# 创建可执行文件
add_executable(${PROJECT_NAME} main.cpp)
# Debug 配置:保留所有日志
target_compile_definitions(${PROJECT_NAME} PRIVATE
$<$<CONFIG:Debug>:SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE>
)
# Release 配置:移除 trace/debug,优化性能
target_compile_definitions(${PROJECT_NAME} PRIVATE
$<$<CONFIG:Release>:SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO>
$<$<CONFIG:Release>:SPDLOG_CLOCK_COARSE>
$<$<CONFIG:Release>:SPDLOG_NO_THREAD_ID>
)
# 链接 spdlog
target_link_libraries(${PROJECT_NAME} PRIVATE spdlog::spdlog)
# 优化选项
if(MSVC)
target_compile_options(${PROJECT_NAME} PRIVATE /O2 /W4)
else()
target_compile_options(${PROJECT_NAME} PRIVATE -O2 -Wall -Wextra)
endif()
10.3 配置文件驱动(完整实现)
config.json 文件
{
"log": {
"level": "debug",
"async": true,
"queue_size": 8192,
"console": {
"enabled": true,
"level": "info",
"pattern": "%^[%H:%M:%S.%e] [%l]%$ %v"
},
"file": {
"enabled": true,
"path": "logs/app.log",
"max_size_mb": 5,
"max_files": 3,
"level": "trace",
"pattern": "[%Y-%m-%d %H:%M:%S.%e] [%t] [%-7l] %v"
},
"flush_on": "error",
"flush_interval_sec": 3
}
}
完整的配置文件驱动实现
// config_loader.hpp
#pragma once
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/rotating_file_sink.h>
#include <spdlog/async.h>
#include <fstream>
#include <nlohmann/json.hpp> // 需要 json 库,或者用其他 JSON 解析库
using json = nlohmann::json;
class ConfigDrivenLogger {
public:
static void init_from_file(const std::string& config_path) {
try {
// 1. 读取配置文件
std::ifstream config_file(config_path);
if (!config_file.is_open()) {
throw std::runtime_error("无法打开配置文件: " + config_path);
}
json config;
config_file >> config;
// 2. 解析日志配置
auto& log_config = config["log"];
// 3. 创建 Sinks
std::vector<spdlog::sink_ptr> sinks;
// 控制台 Sink
if (log_config["console"]["enabled"].get<bool>()) {
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(
parse_level(log_config["console"]["level"].get<std::string>())
);
console_sink->set_pattern(
log_config["console"]["pattern"].get<std::string>()
);
sinks.push_back(console_sink);
spdlog::info("控制台 Sink 已启用");
}
// 文件 Sink
if (log_config["file"]["enabled"].get<bool>()) {
auto& file_config = log_config["file"];
auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
file_config["path"].get<std::string>(),
file_config["max_size_mb"].get<int>() * 1024 * 1024,
file_config["max_files"].get<int>()
);
file_sink->set_level(
parse_level(file_config["level"].get<std::string>())
);
file_sink->set_pattern(
file_config["pattern"].get<std::string>()
);
sinks.push_back(file_sink);
spdlog::info("文件 Sink 已启用: {}", file_config["path"].get<std::string>());
}
// 4. 创建 Logger
spdlog::logger_ptr logger;
std::string logger_name = "config_logger";
if (log_config["async"].get<bool>()) {
// 异步 Logger
size_t queue_size = log_config["queue_size"].get<size_t>();
spdlog::init_thread_pool(queue_size, 1);
logger = std::make_shared<spdlog::async_logger>(
logger_name,
sinks.begin(),
sinks.end(),
spdlog::thread_pool(),
spdlog::async_overflow_policy::block
);
} else {
// 同步 Logger
logger = std::make_shared<spdlog::logger>(
logger_name,
sinks.begin(),
sinks.end()
);
}
// 5. 设置全局级别
logger->set_level(
parse_level(log_config["level"].get<std::string>())
);
// 6. 设置刷新策略
std::string flush_level = log_config["flush_on"].get<std::string>();
logger->flush_on(parse_level(flush_level));
int flush_interval = log_config["flush_interval_sec"].get<int>();
spdlog::flush_every(std::chrono::seconds(flush_interval));
// 7. 注册为默认 Logger
spdlog::set_default_logger(logger);
spdlog::info("=== 日志系统初始化完成 ===");
spdlog::info("配置来源: {}", config_path);
spdlog::info("日志级别: {}", log_config["level"].get<std::string>());
spdlog::info("异步模式: {}", log_config["async"].get<bool>() ? "开启" : "关闭");
} catch (const std::exception& ex) {
std::cerr << "日志初始化失败: " << ex.what() << std::endl;
// 降级到默认配置
fallback_to_default();
}
}
private:
static spdlog::level::level_enum parse_level(const std::string& level_str) {
if (level_str == "trace") return spdlog::level::trace;
if (level_str == "debug") return spdlog::level::debug;
if (level_str == "info") return spdlog::level::info;
if (level_str == "warn") return spdlog::level::warn;
if (level_str == "error") return spdlog::level::err;
if (level_str == "critical") return spdlog::level::critical;
if (level_str == "off") return spdlog::level::off;
return spdlog::level::info; // 默认
}
static void fallback_to_default() {
std::cerr << "使用默认日志配置" << std::endl;
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto logger = std::make_shared<spdlog::logger>("fallback", console_sink);
logger->set_level(spdlog::level::info);
spdlog::set_default_logger(logger);
spdlog::warn("使用降级日志配置(仅控制台输出)");
}
};
使用示例
// main_with_config.cpp
#include "config_loader.hpp"
#include <spdlog/spdlog.h>
int main() {
// 从配置文件初始化
ConfigDrivenLogger::init_from_file("config.json");
// 测试各级别日志
spdlog::trace("这是 trace 日志");
spdlog::debug("这是 debug 日志");
spdlog::info("这是 info 日志");
spdlog::warn("这是 warn 日志");
spdlog::error("这是 error 日志");
spdlog::critical("这是 critical 日志");
// 模拟业务逻辑
for (int i = 0; i < 100; ++i) {
spdlog::info("处理任务 {}", i);
}
// 优雅关闭
spdlog::shutdown();
return 0;
}
编译说明
# 需要安装 nlohmann-json 库
# Ubuntu/Debian:
sudo apt-get install nlohmann-json3-dev
# macOS:
brew install nlohmann-json
# 编译
g++ -std=c++17 main_with_config.cpp -o app -lspdlog -lfmt
CMake 配置
# CMakeLists.txt 添加
find_package(nlohmann_json REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE
spdlog::spdlog
nlohmann_json::nlohmann_json
)