spdlog 完全指南:C++ 高性能日志库深度解析

spdlog 完全指南:C++ 高性能日志库深度解析

适用版本:spdlog v1.x(主流版本)
依赖:C++11 及以上,header-only 或编译库模式
GitHubhttps://github.com/gabime/spdlog


目录

  1. 什么是 spdlog
  2. 安装与集成
  3. 核心概念
  4. 快速上手
  5. 日志级别详解
  6. Sink(输出目标)详解
  7. Logger 的创建与管理
  8. 格式化(Format)系统
  9. 异步日志(Async Logger)
  10. 日志轮转(Rotating / Daily)
  11. 多线程安全
  12. 自定义 Sink
  13. 性能调优
  14. 常见使用模式
  15. 与 CMake 集成
  16. 常见问题与踩坑
  17. 总结对比

1. 什么是 spdlog

spdlog 是一个 **C++ 高性能、零开销(zero overhead)**的日志库,由 Gabi Melman 开发并维护。它是目前 C++ 社区最流行的日志库之一,广泛用于游戏开发、后台服务、嵌入式系统等领域。

核心优势

特性 说明
Header-only 可选纯头文件模式,无需额外编译
高性能 异步模式下可达百万级 msg/s
零开销宏 关闭日志级别时,编译期直接消除,无运行时损耗
fmt 集成 使用 fmtlib(或内置 fmt)做格式化,支持 {} 风格
多 Sink 同一 logger 可同时输出到控制台、文件、网络等
线程安全 提供线程安全和非线程安全两种 logger
丰富的 Sink 内置轮转文件、每日文件、syslog、Android log 等
全局注册表 可通过名字全局访问 logger

架构概览

1
2
3
4
5
6
7
8
9
10
11
12
用户代码


Logger ←── 持有一个或多个 Sink
│ │
│ ├── stdout_color_sink (控制台彩色输出)
│ ├── basic_file_sink (基础文件)
│ ├── rotating_file_sink (轮转文件)
│ ├── daily_file_sink (每日文件)
│ └── custom_sink (自定义)

Formatter ──── 决定日志行的格式(时间、线程、级别、消息)

2. 安装与集成

方式一:Header-only(最简单)

1
2
3
4
5
# 克隆仓库
git clone https://github.com/gabime/spdlog.git

# 将 include/spdlog 目录复制到你的项目
cp -r spdlog/include/spdlog /your/project/include/

在代码中引入:

1
#include "spdlog/spdlog.h"

注意:header-only 模式每个编译单元都会实例化模板,编译速度较慢,适合小项目。

方式二:编译库模式(推荐用于大型项目)

1
2
3
4
5
mkdir build && cd build
cmake .. -DSPDLOG_BUILD_SHARED=ON # 动态库
# 或
cmake .. -DSPDLOG_BUILD_SHARED=OFF # 静态库
make && sudo make install

代码中需要在包含前定义宏:

1
2
3
// 在包含头文件前定义,告诉 spdlog 使用已编译的库
#define SPDLOG_COMPILED_LIB
#include "spdlog/spdlog.h"

方式三:vcpkg

1
vcpkg install spdlog

方式四:Conan

1
2
3
# conanfile.txt
[requires]
spdlog/1.12.0

3. 核心概念

理解 spdlog 的三个核心概念非常重要:LoggerSinkFormatter

Logger

Logger 是用户直接交互的对象,负责:

  • 接收日志消息
  • 与自身的日志级别做比较(过滤)
  • 将通过过滤的消息转发给所有 Sink
1
2
3
4
5
// 每个 logger 都有:
// 1. 名字(name)
// 2. 日志级别(level)
// 3. 一组 Sink
auto logger = std::make_shared<spdlog::logger>("my_logger", sink);

Sink

Sink 是实际执行输出的组件,是日志的”目的地”。spdlog 内置了十几种 Sink,也可以自定义。每个 Sink 也有自己独立的日志级别(进行二次过滤)。

1
2
3
Logger level = warn  →  只有 warn 及以上的消息才传给 Sink

Sink level = error → 只有 error 及以上才真正写出

Formatter

Formatter 决定日志消息的格式,支持自定义 pattern。


4. 快速上手

最简示例

1
2
3
4
5
6
7
8
9
#include "spdlog/spdlog.h"

int main() {
// 使用默认的 stdout logger
spdlog::info("Hello, spdlog! value={}", 42);
spdlog::warn("This is a warning: {:.2f}", 3.14159);
spdlog::error("Error code: {}", -1);
return 0;
}

输出:

1
2
3
[2024-01-15 10:30:00.123] [info] Hello, spdlog! value=42
[2024-01-15 10:30:00.123] [warning] This is a warning: 3.14
[2024-01-15 10:30:00.123] [error] Error code: -1

创建文件 Logger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "spdlog/spdlog.h"
#include "spdlog/sinks/basic_file_sink.h"

int main() {
// 创建一个输出到文件的 logger
auto logger = spdlog::basic_logger_mt("file_logger", "logs/app.log");

logger->info("Application started");
logger->debug("Debug message: {}", 100);
logger->error("Something went wrong!");

// 手动刷新缓冲区
logger->flush();
return 0;
}

格式化示例(fmt 风格)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "spdlog/spdlog.h"

struct Point { int x, y; };

// 为自定义类型提供 fmt formatter(spdlog v1.9+)
template<>
struct fmt::formatter<Point> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template<typename FormatContext>
auto format(const Point& p, FormatContext& ctx) {
return fmt::format_to(ctx.out(), "({}, {})", p.x, p.y);
}
};

int main() {
Point pt{3, 5};
spdlog::info("Point: {}", pt); // Point: (3, 5)
spdlog::info("Hex: {:#010x}", 255); // Hex: 0x000000ff
spdlog::info("Aligned: {:>10}", "right"); // Aligned: right
spdlog::info("Vec: {:02d}", 7); // Vec: 07
}

5. 日志级别详解

spdlog 定义了 7 个日志级别(从低到高):

1
2
3
4
5
6
7
8
9
namespace spdlog::level {
trace = 0, // SPDLOG_LEVEL_TRACE
debug = 1, // SPDLOG_LEVEL_DEBUG
info = 2, // SPDLOG_LEVEL_INFO
warn = 3, // SPDLOG_LEVEL_WARN
err = 4, // SPDLOG_LEVEL_ERROR
critical = 5, // SPDLOG_LEVEL_CRITICAL
off = 6, // 关闭所有日志
}

设置日志级别

1
2
3
4
5
6
7
8
9
10
11
12
// 设置全局(默认 logger)级别
spdlog::set_level(spdlog::level::debug);

// 设置特定 logger 级别
auto logger = spdlog::get("my_logger");
logger->set_level(spdlog::level::warn);

// 运行时检查
if (logger->should_log(spdlog::level::debug)) {
// 执行昂贵的字符串拼接
logger->debug("expensive: {}", compute_string());
}

编译期级别裁剪(零开销)

通过宏在编译期消除低级别日志,完全无运行时开销:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 在编译命令或 CMakeLists.txt 中定义:
// -DSPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO
// 则 TRACE 和 DEBUG 级别的日志在编译时被直接删除

// 必须使用宏版本才有零开销效果
SPDLOG_TRACE("This will be compiled away if level > TRACE");
SPDLOG_DEBUG("This will be compiled away if level > DEBUG");
SPDLOG_INFO("Always present in this example");
SPDLOG_WARN("Warning: {}", msg);
SPDLOG_ERROR("Error: {}", err);
SPDLOG_CRITICAL("Critical: {}", detail);

// 指定 logger 的宏版本
SPDLOG_LOGGER_DEBUG(logger, "debug msg: {}", val);
SPDLOG_LOGGER_INFO(logger, "info msg");

CMakeLists.txt 配置:

1
2
# 生产环境只保留 info 及以上,trace/debug 完全从二进制中消除
target_compile_definitions(MyApp PRIVATE SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO)

6. Sink(输出目标)详解

6.1 控制台 Sink

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "spdlog/sinks/stdout_color_sinks.h"

// 线程安全版本(mt = multi-thread)
auto console = spdlog::stdout_color_mt("console");

// 非线程安全版本(st = single-thread),性能更高
auto console_st = spdlog::stdout_color_st("console_st");

// stderr 输出
auto err_logger = spdlog::stderr_color_mt("stderr");

// 使用示例
console->info("Colored info message"); // 白色
console->warn("Colored warn message"); // 黄色
console->error("Colored error message"); // 红色

颜色对应关系:

级别 颜色
trace 白色
debug 青色
info 绿色
warn 黄色(加粗)
error 红色(加粗)
critical 白底红字

6.2 基础文件 Sink

1
2
3
4
5
6
7
#include "spdlog/sinks/basic_file_sink.h"

// 默认:追加模式
auto logger = spdlog::basic_logger_mt("basic", "logs/app.log");

// 截断模式(每次启动清空文件)
auto logger_trunc = spdlog::basic_logger_mt("basic", "logs/app.log", true);

6.3 轮转文件 Sink(Rotating)

按文件大小轮转,适合长期运行的服务:

1
2
3
4
5
6
7
8
9
#include "spdlog/sinks/rotating_file_sink.h"

// 参数:logger名, 文件路径, 单文件最大字节数, 最多保留几个文件
auto logger = spdlog::rotating_logger_mt(
"rotating",
"logs/app.log",
1024 * 1024 * 10, // 10 MB
5 // 保留 5 个文件:app.log, app.1.log, ..., app.4.log
);

轮转规则:

1
2
3
4
5
app.log       ← 当前写入
app.1.log ← 上一个
app.2.log ← 更早
app.3.log
app.4.log ← 最旧(超过后删除)

6.4 每日文件 Sink(Daily)

每天在指定时间创建新文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "spdlog/sinks/daily_file_sink.h"

// 每天 00:00 创建新日志文件,文件名包含日期
// 文件名格式:logs/app_2024-01-15.log
auto logger = spdlog::daily_logger_mt(
"daily",
"logs/app.log",
0, // 小时(0~23)
0 // 分钟(0~59)
);

// 保留最近 7 天的日志文件
auto logger7 = spdlog::daily_logger_mt("daily", "logs/app.log", 0, 0, false, 7);

6.5 系统日志 Sink(Linux syslog)

1
2
3
4
#include "spdlog/sinks/syslog_sink.h"

auto syslog_logger = spdlog::syslog_logger_mt("syslog", "my_app", LOG_PID);
syslog_logger->info("This goes to /var/log/syslog");

6.6 多 Sink 组合(Distributed Logger)

这是 spdlog 最强大的功能之一:同一个 logger 同时输出到多个目标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/rotating_file_sink.h"
#include "spdlog/sinks/basic_file_sink.h"

void setup_logger() {
// 创建各个 sink
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(spdlog::level::warn); // 控制台只显示 warn 及以上
console_sink->set_pattern("[%H:%M:%S] [%^%l%$] %v");

auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
"logs/app.log", 1024 * 1024 * 5, 3
);
file_sink->set_level(spdlog::level::trace); // 文件记录所有级别

// 组合成一个 logger
spdlog::logger logger("multi_sink", {console_sink, file_sink});
logger.set_level(spdlog::level::trace); // logger 自身级别要最低

// 注册到全局注册表
spdlog::register_logger(std::make_shared<spdlog::logger>(logger));
}

int main() {
setup_logger();
auto logger = spdlog::get("multi_sink");

logger->trace("Trace: 控制台看不到,文件能看到");
logger->warn("Warn: 控制台和文件都能看到");
logger->error("Error: 两处都显示");
}

7. Logger 的创建与管理

7.1 全局注册表

spdlog 维护一个全局的 logger 注册表,可以跨文件访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建并注册
auto logger = spdlog::basic_logger_mt("app", "logs/app.log");
// basic_logger_mt 已自动注册到全局表

// 在任意地方获取
auto logger = spdlog::get("app");
if (logger) {
logger->info("Got it!");
}

// 手动注册
auto my_logger = std::make_shared<spdlog::logger>("manual", sink);
spdlog::register_logger(my_logger);

// 注销
spdlog::drop("app");

// 注销所有
spdlog::drop_all();

7.2 默认 Logger

1
2
3
4
5
6
7
8
// 获取默认 logger(初始为 stdout_color_mt)
auto default_logger = spdlog::default_logger();

// 替换默认 logger
spdlog::set_default_logger(spdlog::basic_logger_mt("new_default", "logs/default.log"));

// 之后的全局调用使用新的默认 logger
spdlog::info("This goes to file now");

7.3 Logger 克隆

1
2
3
4
// 从已有 logger 克隆,继承所有 sink,但有独立的名字和级别
auto cloned = default_logger->clone("cloned_logger");
cloned->set_level(spdlog::level::err);
spdlog::register_logger(cloned);

8. 格式化(Format)系统

8.1 Pattern 标志

通过 set_pattern 自定义格式:

1
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%n] [%^%l%$] [%t] %v");

完整的 Pattern 标志表:

标志 含义 示例
%v 日志消息内容 Hello World
%t 线程 ID 12345
%P 进程 ID 9876
%n Logger 名称 my_logger
%l 日志级别(小写) info
%L 日志级别(单字母) I
%^ 颜色范围开始
%$ 颜色范围结束
%Y 年(4位) 2024
%m 月(2位) 01
%d 日(2位) 15
%H 小时(24时制) 14
%M 分钟 30
%S 05
%e 毫秒 123
%f 微秒 123456
%F 纳秒 123456789
%E Unix 时间戳(秒) 1705300200
%s 源文件名(需宏) main.cpp
%# 源文件行号(需宏) 42
%! 函数名(需宏) main
%@ 文件名:行号 main.cpp:42

8.2 常用格式示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 简洁格式
spdlog::set_pattern("%H:%M:%S [%l] %v");
// 10:30:05 [info] Hello

// 带颜色的格式
spdlog::set_pattern("%^[%H:%M:%S] [%l]%$ %v");

// 详细格式(适合调试)
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%n] [%^%-8l%$] [tid:%t] %v");
// [2024-01-15 10:30:05.123] [app] [info ] [tid:12345] Hello

// 带源代码位置(需配合 SPDLOG_LOGGER_* 宏)
spdlog::set_pattern("[%l] [%s:%#] %v");
// [info] [main.cpp:42] Hello

8.3 自定义格式化 Flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 自定义 flag,例如 %* 打印星号
class my_flag_formatter : public spdlog::custom_flag_formatter {
public:
void format(const spdlog::details::log_msg&,
const std::tm&,
spdlog::memory_buf_t& dest) override {
std::string stars(3, '*');
dest.append(stars.data(), stars.data() + stars.size());
}

std::unique_ptr<custom_flag_formatter> clone() const override {
return spdlog::details::make_unique<my_flag_formatter>();
}
};

// 注册并使用
auto formatter = std::make_unique<spdlog::pattern_formatter>();
formatter->add_flag<my_flag_formatter>('*').set_pattern("[%*] %v");
spdlog::set_formatter(std::move(formatter));

spdlog::info("test"); // 输出: [***] test

9. 异步日志(Async Logger)

异步模式将日志写入操作移到后台线程,大幅降低调用线程的延迟。

9.1 基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h"

int main() {
// 初始化异步线程池
// 参数1: 队列大小(必须是 2 的幂)
// 参数2: 后台线程数
spdlog::init_thread_pool(8192, 1);

// 创建异步 logger
auto async_logger = spdlog::basic_logger_async<spdlog::async_factory>(
// 或者使用 create_async 工厂函数:
);

// 更常用的方式:
auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>(
"async_logger", "logs/async.log"
);

for (int i = 0; i < 100000; i++) {
async_file->info("Async message #{}", i);
}

// 程序退出前等待队列清空
spdlog::shutdown();
return 0;
}

9.2 溢出策略

1
2
3
4
5
6
// 策略1:阻塞(block)- 队列满时调用线程等待(默认)
auto logger = spdlog::create_async<spdlog::sinks::stdout_color_sink_mt>("async");

// 策略2:丢弃(overrun_oldest)- 队列满时丢弃最旧的消息
auto logger_overrun = spdlog::create_async_nb<spdlog::sinks::stdout_color_sink_mt>("async_nb");
// nb = non-blocking

9.3 异步原理

1
2
3
4
5
6
7
调用线程                   后台线程池
│ │
log("msg")
│──→ 加入环形队列 ──────────→│ 取出消息
(无锁 SPSC/MPSC) │ 调用 Sink 写入
│ 立即返回 │ 磁盘/网络 IO
│ │

关键参数建议:

1
2
3
4
5
6
7
// 队列大小:根据业务峰值设置,必须是 2 的幂
// 内存占用 ≈ queue_size * sizeof(log_msg) ≈ queue_size * 256B
// 8192 ≈ 2MB,适合大多数场景
spdlog::init_thread_pool(8192, 1);

// 线程数:通常 1 个线程足够,除非有多个高频 sink
spdlog::init_thread_pool(8192, 2);

9.4 手动创建异步 Logger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "spdlog/async.h"

auto tp = std::make_shared<spdlog::details::thread_pool>(8192, 1);

auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
"logs/async.log", 1024*1024*5, 3
);

auto logger = std::make_shared<spdlog::async_logger>(
"async",
file_sink,
tp,
spdlog::async_overflow_policy::block // 或 overrun_oldest
);

spdlog::register_logger(logger);

10. 日志轮转(Rotating / Daily)

10.1 轮转文件详解

1
2
3
4
5
6
7
8
9
10
11
12
#include "spdlog/sinks/rotating_file_sink.h"

// 创建轮转 sink
auto rotating_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
"logs/app.log", // 基础文件名
1024 * 1024 * 100, // 100MB 触发轮转
10, // 保留 10 个旧文件
false // rotate_on_open: 启动时是否轮转
);

// 手动触发轮转
rotating_sink->rotate_now();

10.2 每日文件详解

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "spdlog/sinks/daily_file_sink.h"

// 每天 02:30 创建新文件
auto daily_sink = std::make_shared<spdlog::sinks::daily_file_sink_mt>(
"logs/app.log", // 基础文件名(实际文件名会加日期)
2, // 小时
30, // 分钟
false, // truncate
30 // 保留最近 30 天
);

// 自定义文件名格式
// 可以通过继承 daily_file_sink 来自定义

生成的文件名格式:logs/app_2024-01-15.log


11. 多线程安全

spdlog 的所有内置 sink 都有线程安全(_mt)和非线程安全(_st)两个版本:

1
2
3
4
5
// mt (multi-thread) - 线程安全,内部有互斥锁
auto mt_logger = spdlog::basic_logger_mt("mt", "logs/mt.log");

// st (single-thread) - 无锁,更高性能,只能在单线程中使用
auto st_logger = spdlog::basic_logger_st("st", "logs/st.log");

选择原则

1
2
3
多线程环境 → 使用 _mt 版本
单线程环境 → 使用 _st 版本(性能更好)
异步模式 → 后台线程是单线程,sink 用 _st;但注意队列本身是线程安全的

多线程示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <thread>
#include "spdlog/spdlog.h"
#include "spdlog/sinks/rotating_file_sink.h"

void worker(int id, std::shared_ptr<spdlog::logger> logger) {
for (int i = 0; i < 1000; i++) {
logger->info("Thread {} iteration {}", id, i);
}
}

int main() {
auto logger = spdlog::rotating_logger_mt("mt", "logs/mt.log", 1024*1024, 3);

std::vector<std::thread> threads;
for (int i = 0; i < 10; i++) {
threads.emplace_back(worker, i, logger);
}
for (auto& t : threads) t.join();

spdlog::drop_all();
return 0;
}

12. 自定义 Sink

当内置 Sink 无法满足需求时(如发送到数据库、消息队列、Webhook),可以自定义:

12.1 继承 base_sink

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "spdlog/sinks/base_sink.h"
#include <mutex>
#include <vector>

// 将日志存入内存(用于测试)
template<typename Mutex>
class memory_sink : public spdlog::sinks::base_sink<Mutex> {
public:
std::vector<std::string>& messages() { return messages_; }

protected:
void sink_it_(const spdlog::details::log_msg& msg) override {
// 格式化消息
spdlog::memory_buf_t formatted;
spdlog::sinks::base_sink<Mutex>::formatter_->format(msg, formatted);
messages_.emplace_back(fmt::to_string(formatted));
}

void flush_() override {
// 可以在这里做持久化
}

private:
std::vector<std::string> messages_;
};

// 类型别名
using memory_sink_mt = memory_sink<std::mutex>;
using memory_sink_st = memory_sink<spdlog::details::null_mutex>;

12.2 HTTP Sink 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "spdlog/sinks/base_sink.h"

template<typename Mutex>
class http_sink : public spdlog::sinks::base_sink<Mutex> {
public:
explicit http_sink(std::string url) : url_(std::move(url)) {}

protected:
void sink_it_(const spdlog::details::log_msg& msg) override {
spdlog::memory_buf_t formatted;
this->formatter_->format(msg, formatted);
std::string log_str = fmt::to_string(formatted);

// 这里用你的 HTTP 库发送请求
// send_http_post(url_, log_str);
}

void flush_() override {}

private:
std::string url_;
};

using http_sink_mt = http_sink<std::mutex>;

12.3 使用自定义 Sink

1
2
3
4
5
6
7
8
9
10
11
12
13
int main() {
auto mem_sink = std::make_shared<memory_sink_mt>();
auto logger = std::make_shared<spdlog::logger>("test", mem_sink);
spdlog::register_logger(logger);

logger->info("test message 1");
logger->warn("test message 2");

// 访问存储的消息
for (const auto& msg : mem_sink->messages()) {
std::cout << msg;
}
}

13. 性能调优

13.1 基准测试数据(官方)

模式 吞吐量
同步(stdout,单线程) ~300 万 msg/s
同步(文件,单线程) ~150 万 msg/s
异步(文件,单线程) ~300 万 msg/s
异步(文件,多线程) 线性扩展

13.2 优化策略

1. 编译期裁剪(最重要)

1
2
3
4
# 生产环境关闭 trace/debug,零运行时开销
target_compile_definitions(App PRIVATE
SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO
)

2. 使用异步模式

1
2
3
4
spdlog::init_thread_pool(65536, 1);  // 队列大一点
auto logger = spdlog::rotating_logger_mt<spdlog::async_factory>(
"async", "logs/app.log", 1024*1024*100, 10
);

3. 减少刷新频率

1
2
3
4
5
6
// 默认每条消息不强制刷新
// 设置定时刷新(每3秒刷一次)
spdlog::flush_every(std::chrono::seconds(3));

// 或只在 error 以上级别时刷新
logger->flush_on(spdlog::level::err);

4. 使用 st 版本(单线程场景)

1
2
// 单线程场景下,st 比 mt 快约 20%
auto logger = spdlog::basic_logger_st("fast", "logs/fast.log");

5. 避免在热路径上构造临时字符串

1
2
3
4
5
6
7
8
// BAD: 即使级别过滤,string 已经构造了
logger->debug("Result: " + std::to_string(expensive_call()));

// GOOD: 使用 fmt,级别不够时不会格式化
logger->debug("Result: {}", expensive_call());

// BEST: 使用零开销宏
SPDLOG_LOGGER_DEBUG(logger, "Result: {}", expensive_call());

6. 批量日志时使用 should_log 守卫

1
2
3
4
5
if (logger->should_log(spdlog::level::debug)) {
// 构造昂贵的调试信息
auto report = generate_debug_report();
logger->debug("{}", report);
}

14. 常见使用模式

14.1 单例 Logger 管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// logger_manager.h
#pragma once
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/rotating_file_sink.h"

class LoggerManager {
public:
static LoggerManager& instance() {
static LoggerManager inst;
return inst;
}

void init(const std::string& log_dir, spdlog::level::level_enum level) {
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(spdlog::level::warn);

auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
log_dir + "/app.log", 1024*1024*50, 5
);
file_sink->set_level(level);

auto logger = std::make_shared<spdlog::logger>(
"app", spdlog::sinks_init_list{console_sink, file_sink}
);
logger->set_level(level);
logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%-8l%$] [%t] %v");

spdlog::set_default_logger(logger);
spdlog::register_logger(logger);
}

void shutdown() {
spdlog::shutdown();
}

private:
LoggerManager() = default;
};

// 使用
#define LOG_INFO(...) SPDLOG_INFO(__VA_ARGS__)
#define LOG_WARN(...) SPDLOG_WARN(__VA_ARGS__)
#define LOG_ERROR(...) SPDLOG_ERROR(__VA_ARGS__)
#define LOG_DEBUG(...) SPDLOG_DEBUG(__VA_ARGS__)

14.2 模块化 Logger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 为不同模块创建独立的 logger,方便调试
class NetworkModule {
public:
NetworkModule() {
logger_ = spdlog::get("network");
if (!logger_) {
logger_ = spdlog::basic_logger_mt("network", "logs/network.log");
}
}

void connect(const std::string& host) {
logger_->info("Connecting to {}", host);
}

private:
std::shared_ptr<spdlog::logger> logger_;
};

14.3 条件日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 限流:每 N 次只记录一次
void process_request() {
static std::atomic<int> count{0};
if (++count % 1000 == 0) {
spdlog::info("Processed {} requests", count.load());
}
// ...
}

// 采样日志
void high_freq_operation(int id) {
if (id % 100 == 0) { // 1% 采样
spdlog::debug("Sample: id={}", id);
}
}

14.4 结构化日志(JSON 风格)

1
2
3
4
5
// 将日志格式化为 JSON,方便 ELK 等日志系统解析
spdlog::set_pattern(R"({"time":"%Y-%m-%dT%H:%M:%S.%e","level":"%l","msg":"%v"})");

spdlog::info("User login: user_id={} ip={}", user_id, ip);
// 输出: {"time":"2024-01-15T10:30:05.123","level":"info","msg":"User login: user_id=123 ip=192.168.1.1"}

15. 与 CMake 集成

15.1 FetchContent(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cmake_minimum_required(VERSION 3.14)
project(MyApp)

include(FetchContent)

FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.13.0
)
FetchContent_MakeAvailable(spdlog)

add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE spdlog::spdlog)

# 可选:设置编译期日志级别
target_compile_definitions(MyApp PRIVATE
SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG
)

15.2 find_package(系统安装)

1
2
find_package(spdlog REQUIRED)
target_link_libraries(MyApp PRIVATE spdlog::spdlog)

15.3 add_subdirectory

1
2
add_subdirectory(third_party/spdlog)
target_link_libraries(MyApp PRIVATE spdlog::spdlog)

16. 常见问题与踩坑

Q1: 日志不输出怎么办?

1
2
3
4
5
6
7
8
9
10
11
12
13
// 检查1:logger 级别是否过高
logger->set_level(spdlog::level::trace); // 临时调低测试

// 检查2:sink 级别是否过高
sink->set_level(spdlog::level::trace);

// 检查3:是否使用了零开销宏但没开启对应级别
// 必须设置 SPDLOG_ACTIVE_LEVEL
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE
SPDLOG_TRACE("This needs SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE");

// 检查4:缓冲区未刷新
logger->flush();

Q2: 程序崩溃时日志丢失?

1
2
3
4
5
6
7
8
9
10
11
// 方法1:遇到 error 自动刷新
logger->flush_on(spdlog::level::err);

// 方法2:注册信号处理器
#include <csignal>
void signal_handler(int signal) {
spdlog::shutdown(); // 刷新并关闭所有 logger
exit(signal);
}
signal(SIGSEGV, signal_handler);
signal(SIGABRT, signal_handler);

Q3: 异步 logger 程序退出时丢消息?

1
2
3
4
5
6
7
8
int main() {
// ... 初始化异步 logger ...

// 程序退出前必须调用 shutdown!
// 它会等待队列中所有消息被处理完毕
spdlog::shutdown();
return 0;
}

Q4: 多个翻译单元都引入 spdlog,编译很慢?

1
2
3
4
// 使用编译库模式:
// 1. cmake 构建时开启 SPDLOG_BUILD_SHARED 或 SPDLOG_BUILD_STATIC
// 2. 在每个 .cpp 文件第一次包含前定义 SPDLOG_COMPILED_LIB
// 或者统一在 precompiled header 中定义

Q5: fmt 版本冲突?

spdlog 内置了 bundled fmt(位于 include/spdlog/fmt/),如果项目中已有 fmt:

1
2
3
4
# 使用外部 fmt
find_package(fmt REQUIRED)
target_compile_definitions(spdlog INTERFACE SPDLOG_FMT_EXTERNAL)
target_link_libraries(spdlog INTERFACE fmt::fmt)

Q6: Windows 中文乱码?

1
2
3
4
5
// Windows 控制台 UTF-8 支持
#ifdef _WIN32
#include <windows.h>
SetConsoleOutputCP(CP_UTF8);
#endif

Q7: 日志文件权限问题(Linux)

1
2
3
# 确保日志目录存在且有写权限
mkdir -p /var/log/myapp
chown myuser:myuser /var/log/myapp

17. 总结对比

spdlog vs 其他日志库

特性 spdlog log4cpp Boost.Log glog
header-only
异步支持
格式化(fmt) ✅ 原生
性能 ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐ ⭐⭐⭐
易用性 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐ ⭐⭐⭐
依赖 仅 fmt Boost gflags
C++ 标准 C++11+ C++03+ C++11+ C++11+

适用场景

场景 推荐配置
开发调试 stdout_color_mt + trace 级别
单机后台服务 rotating_file_mt + 异步
高频交易/游戏 async + overrun_oldest 策略
微服务 结构化 JSON 格式 + ELK
嵌入式 st 版本 + 关闭 trace/debug
单元测试 自定义 memory_sink

参考资源


spdlog 完全指南:C++ 高性能日志库深度解析
https://www.psnow.sbs/2026/03/24/spdlog-完全指南:C-高性能日志库深度解析/
作者
Psnow
发布于
2026年3月24日
许可协议