spdlog高性能日志库
一、spdlog 概述
1. 定位与作用
spdlog 是一款高性能 C++ 日志库,旨在为开发者提供高效、灵活的日志记录解决方案。其核心作用包括:
- 追踪程序状态:记录程序运行时的关键节点、变量状态等信息。
- 故障排查:在问题发生时提供详细的现场数据(如调用栈、错误参数),辅助定位 bug。
- 性能分析:通过日志分析程序的性能瓶颈(如耗时操作、资源竞争)。
- 系统监控:记录潜在故障信号(如内存泄漏、异常流量),支持预警机制。
2. 核心优势
- 极致性能:
- 零成本抽象:通过模板和内联函数实现,仅在日志级别启用时执行实际记录操作。
- 异步日志:将日志消息异步提交到线程池处理,避免阻塞主线程。
- 高效格式化:集成 fmt 库,支持快速字符串格式化,减少 CPU 开销。
- 低资源占用:内存管理优化,高负载下仍保持稳定。
- 灵活配置:支持多日志级别(trace/debug/info/warn/error/critical)、多输出目标(控制台、文件、远程服务器)及自定义格式。
二、spdlog 核心组件与架构
1. 关键组件
组件 | 功能描述 |
---|---|
Logger | 日志记录的入口,负责生成日志消息,关联 Sink 和 Formatter。 |
Sink | 定义日志输出目标(如文件、控制台、网络),支持自定义输出逻辑。 |
Formatter | 负责将日志消息格式化为指定结构(如 JSON、纯文本),支持自定义格式字符串。 |
Async Logger | 通过线程池异步处理日志消息,减少主线程延迟。 |
Registry | 全局管理所有 Logger,支持通过名称注册和获取 Logger,方便全局访问。 |
2. 处理流程
- 同步日志:Logger 直接调用 Sink 的接口,将格式化后的消息写入目标(如文件 I/O)。
- 异步日志:
- Logger 将消息放入异步队列,由线程池中的工作线程负责处理。
- 线程池默认配置:1 个线程,队列大小 8192,可通过
queue_size
和n_threads
调整。 - 队列满时策略:
block
(阻塞等待)或overrun_oldest
(丢弃最旧消息)。
三、快速上手:从安装到基本使用
1. 安装步骤
# 克隆仓库
git clone https://github.com/gabime/spdlog.git
cd spdlog# 编译与安装(需提前安装 CMake)
mkdir build && cd build
cmake ..
make -j # 并行编译加速
sudo make install # 安装到系统路径(默认 /usr/local)
2. 基本使用示例
步骤 1:包含头文件与链接库
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h> // 带颜色的控制台输出
// 链接时需指定 spdlog 库(如 -lspdlog -lspdlog_stdout)
步骤 2:创建 Logger 并记录日志
// 创建同步 Logger(输出到控制台,带颜色)
auto console_logger = spdlog::stdout_color_mt("console_logger");
console_logger->set_level(spdlog::level::info); // 设置日志级别
console_logger->info("Hello, spdlog!"); // 记录 info 级日志
console_logger->warn("This is a warning!"); // 记录 warn 级日志// 创建异步 Logger(输出到文件,异步处理)
auto file_logger = spdlog::create_async<spdlog::sinks::simple_file_sink_mt>("file_logger", "app.log");
file_logger->error("Error occurred at line {}!", __LINE__); // 带格式化参数
步骤 3:注册 Logger(全局访问)
spdlog::register_logger(console_logger); // 注册到 Registry
auto global_logger = spdlog::get("console_logger"); // 全局获取
global_logger->debug("This is a debug message.");
四、进阶用法:异步日志与自定义配置
1. 异步日志深度配置
// 手动创建线程池(自定义线程数和队列大小)
auto thread_pool = std::make_shared<spdlog::async_factory::thread_pool>(4, 8192); // 4 个工作线程,队列大小 8192// 创建带自定义线程池的异步 Logger
auto async_logger = spdlog::async_factory::create_async<spdlog::sinks::rotating_file_sink_mt>("async_file", "async.log", 1024*1024, 3, thread_pool); // 分割文件,最大 1MB,保留 3 个备份// 设置队列满策略(默认 block,可选 overrun_oldest)
async_logger->set_async_mode(8192, spdlog::async_overflow_policy::overrun_oldest);
2. 自定义输出格式
// 定义格式模板(包含时间、级别、文件名、行号)
console_logger->set_pattern("[%Y-%m-%d %H:%M:%S] [%^%l%$] [%f:%l] %v");
// 输出示例:[2025-05-20 14:30:00] [info] [main.cpp:42] Hello, spdlog!// 扩展格式标识(如源文件路径)
console_logger->set_pattern("[%P] %v"); // %P 表示源文件完整路径
3. 自定义 Sink
// 实现自定义 Sink(继承 spdlog::sink)
class my_network_sink : public spdlog::sink {
public:void log(const spdlog::details::log_msg& msg) override {// 解析日志消息(msg.payload() 为格式化后的字符串)// 发送到网络服务器的逻辑}void flush() override { /* 可选:刷新缓冲区 */ }
};// 注册自定义 Sink
auto custom_logger = spdlog::create("custom_logger", std::make_shared<my_network_sink>());
五、高级特性:刷新策略与线程安全
1. 刷新策略
- 手动刷新:
logger->flush();
立即触发异步线程刷新缓冲区。 - 条件刷新:当记录指定级别日志时自动刷新(如错误日志):
logger->flush_on(spdlog::level::err); // 记录 error/critical 时自动刷新
- 间隔刷新:定期自动刷新(需确保 Logger 线程安全):
spdlog::flush_every(std::chrono::seconds(5)); // 每 5 秒全局刷新一次
2. 多线程注意事项
- 异步日志线程安全:异步 Logger 内部通过线程池处理,多线程并发调用
log()
是安全的。 - 输出顺序:多线程异步日志无法保证严格顺序(因线程池调度不确定),如需有序日志需使用同步模式或自定义序列化机制。
六、具体使用案例
1.dlog.h
#ifndef LOG_H
#define LOG_H
#include <vector>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/daily_file_sink.h>
#include <spdlog/async.h>
#include "spdlog/sinks/stdout_color_sinks.h"
#ifndef SPDLOG_TRACE_ON
#define SPDLOG_TRACE_ON
#endif#ifndef SPDLOG_DEBUG_ON
#define SPDLOG_DEBUG_ON
#endifclass DLog{
public:static DLog* GetInstance(){static DLog dlogger;return &dlogger;}std::shared_ptr<spdlog::logger> getLogger(){return log_;}static void SetLevel(char *log_level);private:DLog(){//创建一个包含多个日志的sink列表std::vector<spdlog::sink_ptr> sinkList;#if 1 //输出日志到控制台auto consoleSink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>(); //输出到控制台带颜色支持consoleSink->set_level(level_);consoleSink->set_pattern("[%Y-%m-%d %H:%M:%S.%e][thread %t][%@,%!][%l] : %v"); //这个是日志的输出格式sinkList.push_back(consoleSink);#endif//输出日志到文件auto dailySink = std::make_shared<spdlog::sinks::daily_file_sink_mt>("logs/daily.log", 23, 59);dailySink->set_level(level_);dailySink->set_pattern("[%Y-%m-%d %H:%M:%S.%e][thread %t][%@,%!][%l] : %v");sinkList.push_back(dailySink);log_ = std::make_shared<spdlog::logger>("both", begin(sinkList), end(sinkList)); //创建一个新的 spdlog::logger 对象,名称为 "both",并将 sinkList 中的所有 sink 传递给它。这将使得日志同时输出到控制台和文件中。//注册到全局管理器中spdlog::register_logger(log_);//每隔一秒刷新一次spdlog::flush_every(std::chrono::seconds(1)); //`std::chrono::seconds(1)` 表示一个持续时间为1秒的时间间隔}~DLog(){}
private:std::shared_ptr<spdlog::logger> log_;static spdlog::level::level_enum level_;
};//用定义的方式调用C++标准库spdlog里面的接口,记录不同级别的日志信息
#define LogTrace(...) SPDLOG_LOGGER_CALL(DLog::GetInstance()->getLogger().get(), spdlog::level::trace, __VA_ARGS__)
#define LogDebug(...) SPDLOG_LOGGER_CALL(DLog::GetInstance()->getLogger().get(), spdlog::level::debug, __VA_ARGS__)
#define LogInfo(...) SPDLOG_LOGGER_CALL(DLog::GetInstance()->getLogger().get(), spdlog::level::info, __VA_ARGS__)
#define LogWarn(...) SPDLOG_LOGGER_CALL(DLog::GetInstance()->getLogger().get(), spdlog::level::warn, __VA_ARGS__)
#define LogError(...) SPDLOG_LOGGER_CALL(DLog::GetInstance()->getLogger().get(), spdlog::level::err, __VA_ARGS__)
#define LogCritical(...) SPDLOG_LOGGER_CALL(DLog::GetInstance()->getLogger().get(), spdlog::level::critical, __VA_ARGS__)
#endif
2.dlog.c
#include "dlog.h"spdlog::level::level_enum DLog::level_ = spdlog::level::info; // 默认使用info级别void DLog::SetLevel(char *log_level) {printf("SetLevel log_level:%s\n", log_level);fflush(stdout);if(strcmp(log_level, "trace") == 0) {level_ = spdlog::level::trace;}else if(strcmp(log_level, "debug") == 0) {level_ = spdlog::level::debug;}else if(strcmp(log_level, "info") == 0) {level_ = spdlog::level::info;}else if(strcmp(log_level, "warn") == 0) {level_ = spdlog::level::warn;}else if(strcmp(log_level, "err") == 0) {level_ = spdlog::level::err;}else if(strcmp(log_level, "critical") == 0) {level_ = spdlog::level::critical;}else if(strcmp(log_level, "off") == 0) {level_ = spdlog::level::off;} else {printf("level: %s is invalid\n", log_level);}
}