《C++ spdlog高性能日志库快速上手》
一、为什么选择 spdlog?
在介绍使用方法之前,先了解一下 spdlog 的核心优势:
- 极致性能:采用高效的日志处理机制,对主线程影响极小
- 仅头文件:无需编译链接,直接包含即可使用,简化集成流程
- 丰富功能:支持多种日志级别、输出目标和格式化选项
- 线程安全:提供线程安全版本的日志器,适合多线程环境
- 跨平台:完美支持 Windows、Linux 和 macOS 等主流操作系统
- 现代化 API:使用 C++11 及以上特性,语法简洁直观
这些特性使 spdlog 成为从小型工具到大型企业级应用的理想选择。
二、第一步:安装 spdlog
spdlog 的安装非常简单,有多种方式可供选择:
方法 1:通过包管理器(推荐)
对于 Linux 或 macOS 用户,可以直接使用系统包管理器安装:
# Ubuntu/Debian
sudo apt-get install libspdlog-dev# macOS (使用Homebrew)
brew install spdlog# Windows (使用vcpkg)
vcpkg install spdlog
方法 2:源码安装
如果需要使用最新版本或特定版本,可以从 GitHub 获取源码:
# 克隆仓库
git clone https://github.com/gabime/spdlog.git# 进入目录
cd spdlog# 创建构建目录
mkdir build && cd build# 编译安装
cmake .. && make -j && sudo make install
方法 3:直接集成到项目
对于小型项目,也可以直接将 spdlog 源码复制到项目中:
- 下载 spdlog 源码并解压
- 将include目录下的spdlog文件夹复制到你的项目 include 目录
- 在代码中直接#include <spdlog/spdlog.h>即可使用
三、入门示例
让我们从一个简单的示例开始,感受 spdlog 的基本用法:
#include <spdlog/spdlog.h>int main() {// 基本日志输出spdlog::info("欢迎使用spdlog日志库!");spdlog::warn("这是一条警告信息");spdlog::error("这是一条错误信息");// 带参数的日志int user_id = 123;std::string user_name = "张三";spdlog::info("用户 {} (ID: {}) 登录成功", user_name, user_id);// 日志级别控制spdlog::set_level(spdlog::level::debug); // 设置最低日志级别为debugspdlog::debug("调试信息:用户登录流程开始");spdlog::set_level(spdlog::level::info); // 设置最低日志级别为infospdlog::debug("这条调试信息不会被输出"); // 因为级别低于当前设置// 关闭所有日志器spdlog::shutdown();return 0;
}
把它看成有不同级别的printf输出一样
四、核心功能讲解
1. 日志级别
spdlog 定义了 6 种日志级别,从低到高分别是:
级别 | 功能 |
---|---|
trace | 最详细的日志,通常用于开发调试 |
debug | 调试信息,用于开发阶段 |
info | 普通信息,记录正常的运行状态 |
warn | 警告信息,表示可能存在问题但不影响运行 |
error | 错误信息,表示发生了错误 |
critical | 严重错误信息,表示发生了致命错误 |
可以通过spdlog::set_level()设置全局日志级别,也可以为每个日志器单独设置级别。
就像上面代码中一样:
spdlog::set_level(spdlog::level::debug); //设置最低日志级别为debug
spdlog::debug("调试信息:用户登录流程开始"); //也可作为输出工具
2. 输出目标(Sinks)
spdlog 支持将日志输出到多种目标,常见的包括:
目标 | 功能 |
---|---|
控制台 | (带或不带颜色) |
文件 | (单个文件或轮转文件) |
系统日志 | (如 Linux 的 syslog) |
下面是一个多目标输出的示例:
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/rotating_file_sink.h>int main() {// 1. 创建控制台日志器(带颜色)auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();console_sink->set_level(spdlog::level::warn); // 控制台只输出警告及以上级别// 2. 创建基本文件日志器auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("app.log");file_sink->set_level(spdlog::level::info); // 文件输出info及以上级别// 3. 创建轮转文件日志器// 单个文件最大1MB,最多保留3个备份auto rotating_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>("rotating.log", 1024*1024, 3);rotating_sink->set_level(spdlog::level::trace); // 轮转日志记录所有级别// 创建一个多目标日志器spdlog::logger logger("multi_sink_logger", {console_sink, file_sink, rotating_sink});logger.set_level(spdlog::level::trace); // 日志器本身的最低级别// 测试不同级别的日志logger.trace("这是一条trace级别的日志"); // 只输出到轮转日志logger.debug("这是一条debug级别的日志"); // 只输出到轮转日志logger.info("这是一条info级别的日志"); // 输出到文件和轮转日志logger.warn("这是一条warn级别的日志"); // 输出到所有三个目标logger.error("这是一条error级别的日志"); // 输出到所有三个目标logger.critical("这是一条critical级别的日志"); // 输出到所有三个目标spdlog::shutdown();return 0;
}
控制台:
文件:
[2025-09-16 15:24:22.518] [multi_sink_logger] [info] 这是一条info级别的日志
[2025-09-16 15:24:22.518] [multi_sink_logger] [warning] 这是一条warn级别的日志
[2025-09-16 15:24:22.518] [multi_sink_logger] [error] 这是一条error级别的日志
[2025-09-16 15:24:22.518] [multi_sink_logger] [critical] 这是一条critical级别的日志
轮转日志:
[2025-09-16 15:24:22.518] [multi_sink_logger] [trace] 这是一条trace级别的日志
[2025-09-16 15:24:22.518] [multi_sink_logger] [debug] 这是一条debug级别的日志
[2025-09-16 15:24:22.518] [multi_sink_logger] [info] 这是一条info级别的日志
[2025-09-16 15:24:22.518] [multi_sink_logger] [warning] 这是一条warn级别的日志
[2025-09-16 15:24:22.518] [multi_sink_logger] [error] 这是一条error级别的日志
[2025-09-16 15:24:22.518] [multi_sink_logger] [critical] 这是一条critical级别的日志
3. 日志格式化
spdlog 允许你自定义日志的输出格式,包括时间、日志级别、文件名、行号等信息:
格式标记 | 解释 |
---|---|
[%Y-%m-%d %H:%M:%S.%e] | 输出日期时间,精确到毫秒。%Y 是四位年份,%m 是月份,%d 是日期,%H 是小时(24 小时制),%M 是分钟,%S 是秒,%e 是毫秒。 |
[%n] | 输出日志器名称。在 spdlog 中,每个日志器可以被命名,方便区分不同模块或功能的日志。 |
[%^%l%$] | 输出日志级别,且带颜色。%l 表示日志级别(如 info、warn、error 等);%^ 和 %$ 是颜色控制标记,spdlog 会根据不同日志级别自动赋予不同颜色(比如 error 级日志通常显示为红色)。 |
[%s:%#] | 输出文件名和行号。%s 是产生日志的源文件名称,%# 是该行日志对应的代码行号,便于快速定位日志产生的位置。 |
%v | 输出日志消息内容,也就是你通过 logger->info(“消息内容”) 等接口传递的具体日志文本。 |
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>int main()
{// 设置全局的刷新策略// 每秒刷新spdlog::flush_every(std::chrono::seconds(1));// 遇到debug以上等级的日志立即刷新spdlog::flush_on(spdlog::level::level_enum::debug);// 设置全局的日志输出等级 -- 无所谓 -- 每个日志器可以独立进行设置spdlog::set_level(spdlog::level::level_enum::debug);// 创建同步日志器(标准输出/文件) -- 工厂接口默认创建的就是同步日志器auto logger = spdlog::basic_logger_mt("file-logger", "sync.log");// 设置日志器的刷新策略,以及设置日志器的输出等级// 如果以设置全局就不需要// logger->flush_on(spdlog::level::level_enum::debug);// logger->set_level(spdlog::level::level_enum::debug);// 设置日志输出格式logger->set_pattern("[%n][%H:%M:%S][%7l] %v");// 进行简单的日志输出logger->trace("你好! ", "小明");logger->debug("你好! ", "小明");logger->info("你好! ", "小明");logger->warn("你好! ", "小明");logger->error("你好! ", "小明");spdlog::shutdown();return 0;
}
运行后,你将看到sync.log的日志输出:
[file-logger][16:03:24][ debug] 你好!
[file-logger][16:03:24][ info] 你好!
[file-logger][16:03:24][warning] 你好!
[file-logger][16:03:24][ error] 你好!
4. 异步日志
对于高性能要求的场景,spdlog 提供了异步日志功能,可以避免日志 IO 操作阻塞主线程:
#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/sinks/basic_file_sink.h>int main() {// 初始化异步日志设置// 队列大小为8192,1个后台线程处理日志spdlog::init_thread_pool(8192, 1);// 创建异步日志器auto async_file_logger = spdlog::basic_logger_mt<spdlog::async_factory>("async_logger", "async_log.log");// 使用异步日志器for (int i = 0; i < 100; ++i) {async_file_logger->info("异步日志消息 {}", i);}// 异步日志需要手动刷新或等待后台线程处理// shutdown()会等待所有日志处理完成spdlog::shutdown();return 0;
}
async_log.log中的一部分:
五、实际项目中的最佳实践
1.对spdlog进行二次封装
- 通过 LOG_INFO 等宏直接输出日志,无需每次手动调用 logger->info()
- 隐藏了 spdlog 的底层接口,新开发者无需深入学习 spdlog 即可上手
- 在多人协作项目中,封装能强制统一日志格式、输出目标和级别定义
- 如果未来需要替换日志库(如从 spdlog 换成其他库),封装层可以隔离底层实现
#pragma once
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/async.h>
#include <iostream>// mode - 运行模式: true-发布模式; false调试模式
// 定义命名空间 bite_im
namespace bite_im{
std::shared_ptr<spdlog::logger> g_default_logger;// 初始化日志器
// 参数:
// - mode: 运行模式(false=调试模式,true=发布模式)
// - file: 发布模式下的日志文件路径
// - level: 日志输出级别
void init_logger(bool mode, const std::string &file, int32_t level)
{if (mode == false) {// 调试模式:输出到带颜色的控制台,最低日志级别(trace)g_default_logger = spdlog::stdout_color_mt("default-logger");g_default_logger->set_level(spdlog::level::level_enum::trace);g_default_logger->flush_on(spdlog::level::level_enum::trace);}else {// 发布模式:输出到文件,日志级别由参数指定g_default_logger = spdlog::basic_logger_mt("default-logger", file);g_default_logger->set_level((spdlog::level::level_enum)level);g_default_logger->flush_on((spdlog::level::level_enum)level);}// 设置日志格式:[日志器名称][时间][线程ID][级别]消息内容g_default_logger->set_pattern("[%n][%H:%M:%S][%t][%-8l]%v");
}// 日志宏定义,自动添加文件名和行号
#define LOG_TRACE(format, ...) bite_im::g_default_logger->trace(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_DEBUG(format, ...) bite_im::g_default_logger->debug(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_INFO(format, ...) bite_im::g_default_logger->info(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_WARN(format, ...) bite_im::g_default_logger->warn(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_ERROR(format, ...) bite_im::g_default_logger->error(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_FATAL(format, ...) bite_im::g_default_logger->critical(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
}
## 是令牌连接符,主要作用是将宏中的两个令牌(token)拼接成一个新的令牌。在你提供的日志宏中,##VA_ARGS 的作用是处理可变参数为空的情况,避免编译错误。
(format, …) 是 C++ 可变参数宏的语法,专门用于处理 “日志格式字符串 + 动态参数” 的场景,核心作用是让日志宏既能接收固定的格式字符串,又能灵活接收任意数量、任意类型的额外参数。
主函数使用:
#include "logger.hpp"int main() {// 初始化调试模式日志bite_im::init_logger(false, "", 0);// 输出不同级别的日志LOG_INFO("应用程序启动");LOG_DEBUG("用户ID: {}", 12345);LOG_WARN("磁盘空间不足");LOG_ERROR("连接数据库失败");return 0;
}
2. 线程安全考虑
spdlog 提供了两种日志器:
多线程版本:使用_mt后缀的创建函数(如stdout_color_mt)
单线程版本:使用_st后缀的创建函数(如stdout_color_st)
// 1. 创建控制台日志器(带颜色)auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
在多线程环境中,务必使用多线程版本的日志器,以确保线程安全。