spdlog
- [spdlog 库:Linux Ubuntu 环境下的使用指南](#spdlog 库:Linux Ubuntu 环境下的使用指南)
spdlog 库:Linux Ubuntu 环境下的使用指南
spdlog 是一个快速、易用的 C++ 日志库,以高性能和简洁的 API 著称,广泛用于 C++ 项目的日志记录。以下是其详细介绍和使用方法。
一、spdlog 简介
spdlog 是一个 - header-only(仅头文件)的日志库,支持多种日志输出目标(控制台、文件、滚动文件等),提供丰富的日志级别和格式化选项,且性能优异(无锁设计,支持异步日志)。
二、下载与安装(Ubuntu)
spdlog 无需编译安装,只需将头文件引入项目即可。
方法 1:通过包管理器安装(推荐)
bash
sudo apt update
sudo apt install libspdlog-dev
安装后,头文件位于 `/usr/include/spdlog/,可直接在项目中引用。
方法 2:源码下载源码
bash
git clone clone git clone https://github.com/gabime/spdlog.git
cd spdlog
# 只需将 include/spdlog 目录复制到项目或系统头文件路径
sudo cp -r include/spdlog /usr/local/include/
三、核心特点
-
极致高性能
:spdlog 采用无锁设计(多线程场景下避免锁竞争开销),且内部优化了日志缓存与 I/O 调度,即使高并发、高频日志输出场景,也能减少主线程阻塞,保持低延迟响应。 -
零成本上手
:无需编译链接静态 / 动态库,仅需包含头文件即可集成,省去复杂的环境配置步骤,尤其适合快速搭建项目或轻量级开发场景。 -
异步日志解耦
:支持异步日志模式(通过独立线程处理日志写入),主线程无需等待 I/O 操作完成,从根本上避免日志记录拖慢业务逻辑执行效率。 -
灵活格式化
:内置丰富的占位符(时间戳、线程 ID、文件名、行号等),支持自定义日志格式,既能满足调试时的详细上下文需求,也能适配生产环境的精简日志规范。 -
跨平台无缝兼容
:底层通过条件编译适配不同操作系统的 I/O 接口(如 Linux 的 write、Windows 的 WriteFile),无需修改代码即可在多平台运行,降低跨平台项目的日志层适配成本。 -
易用性与扩展性兼顾
:除了 trace/debug/info 等 6 级日志级别,还支持重载 << 操作符(如 log << "value: " << x),可直接记录任意支持流输出的数据类型,同时允许自定义日志 sink(如网络输出、数据库存储),满足特殊场景需求。
四、简单用法示例
基本日志输出
cpp
#include <spdlog/spdlog.h>
int main() {
// 控制台日志
spdlog::info("这是一条 info 级别的日志");
spdlog::warn("这是一条 warn 级别的日志:{}", 123); // 支持格式化
spdlog::error("这是一条 error 级别的日志");
// 设置全局日志级别(只输出 >= 该级别的日志)
spdlog::set_level(spdlog::level::debug); // 默认为 info
spdlog::debug("这条 debug 日志会被输出");
// 关闭并释放所有日志器
spdlog::shutdown();
return 0;
}
编译运行
bash
g++ main.cpp -o main -std=c++11 # spdlog 需 C++11 及以上标准
./main
输出示例:
plaintext
[2024-09-27 15:30:00.123] [info] 这是一条 info 级别的日志
[2024-09-27 15:30:00.124] [warn] 这是一条 warn 级别的日志:123
[2024-09-27 15:30:00.124] [error] 这是一条 error 级别的日志
[2024-09-27 15:30:00.124] [debug] 这条 debug 日志会被输出
五、常用功能与函数
1. 日志器(Logger)管理
cpp
spdlog::get("logger_name"):获取指定名称的日志器。
spdlog::basic_logger_mt("logger_name", "logfile.txt"):创建多线程安全的文件日志器。
spdlog::rotating_logger_mt("logger_name", "logfile.txt", 1024*1024, 3):创建滚动日志器(文件大小达 1MB 时轮转,保留 3 个备份)。
示例:
cpp
// 创建滚动日志器
auto rotating_logger = spdlog::rotating_logger_mt("rotating_logger", "app.log", 1024*1024, 3);
rotating_logger->info("写入滚动日志文件");
2. 日志格式设置
通过 set_pattern 自定义格式,常用占位符:
%Y-%m-%d %H:%M:%S.%e
:日期时间(精确到毫秒)
%l
:日志级别(info/warn 等)
%t
:线程id
%n
:日志器名称
%v
:日志内容
示例:
cpp
// 设置全局格式
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S] [%l] - %v");
spdlog::info("自定义格式的日志");
输出:
plaintext
[2024-09-27 15:35:00] [info] - 自定义格式的日志
3. 异步日志
通过异步模式提高性能(避免 I/O 阻塞主线程):
cpp
#include <spdlog/async.h>
#include <spdlog/sinks/basic_file_sink.h>
int main() {
// 初始化异步日志(队列大小 8192,线程数 1)
spdlog::init_thread_pool(8192, 1);
// 创建异步文件日志器
auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_logger", "async.log");
async_file->info("这是一条异步日志");
spdlog::shutdown(); // 必须调用,确保队列中日志被写入
return 0;
}
4. spdlog 输出器(Sink)
在 spdlog 中,输出器(Sink)是日志的 "最终目的地载体"------ 它决定了日志会被输出到哪里(控制台、文件、系统日志等),是连接日志生成与存储 / 展示的核心组件。对于初学者,掌握输出器的基本类型、使用方法和组合逻辑,就能快速实现灵活的日志输出需求。
一、输出器的核心概念
定义:输出器是 spdlog 中负责 "写入日志" 的最小单元,每个输出器对应一个具体的输出目标(如控制台、单个文件、滚动文件)。
核心作用:解耦日志 "生成逻辑" 与 "输出逻辑"------ 日志器(Logger)负责收集日志内容,输出器负责将内容写入目标,二者配合实现 "一份日志多目标输出"(如同时输出到控制台和文件)。
线程安全:输出器名称通常带后缀 _mt(multi-thread,多线程安全)或 _st(single-thread,单线程),多线程程序必须使用 _mt 版本,避免日志错乱。
二、常用输出器类型及入门用法
spdlog 内置了多种常用输出器,覆盖绝大多数场景,以下是初学者必学的 4 种核心类型:
1. 彩色控制台输出器(stdout_color_sink_mt)
功能:将日志输出到终端,且按日志级别显示不同颜色(如 error 红色、info 绿色),方便实时调试时快速定位关键信息。
适用场景:开发阶段实时查看日志。
入门代码:
cpp
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sink_mt.h>
int main() {
// 1. 创建彩色控制台输出器(多线程安全)
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
// 2. 创建日志器,绑定输出器
auto console_logger = std::make_shared<spdlog::logger>(
"console_logger", // 日志器名称(自定义,用于后续获取)
console_sink // 绑定控制台输出器
);
// 3. 配置日志(可选:设置格式、级别)
console_logger->set_pattern("[%H:%M:%S] [%l] %v"); // 时间 + 级别 + 内容
console_logger->set_level(spdlog::level::info); // 只输出 info 及以上级别
// 4. 输出日志(会显示在控制台,带颜色)
console_logger->info("这是彩色控制台日志(info 级,绿色)");
console_logger->error("这是错误日志(error 级,红色)");
// 5. 关闭日志(确保缓存写入)
spdlog::shutdown();
return 0;
}
2. 基础文件输出器(basic_file_sink_mt)
功能:将日志写入指定文件,适合持久化存储日志(如生产环境归档)。
适用场景:需要保存日志用于后续排查问题。
注意:文件路径需确保目录存在(如 logs/app.log 需先创建 logs 文件夹),否则会报错。
入门代码:
cpp
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink_mt.h>
int main() {
try {
// 1. 创建基础文件输出器(多线程安全,日志写入 "app.log")
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("app.log");
// 2. 创建日志器,绑定文件输出器
auto file_logger = std::make_shared<spdlog::logger>("file_logger", file_sink);
// 3. 配置:输出所有级别日志(trace 最低)
file_logger->set_level(spdlog::level::trace);
// 4. 输出日志(会写入 app.log 文件)
file_logger->trace("详细调试日志(仅文件可见)");
file_logger->warn("警告日志(也写入文件)");
} catch (const spdlog::spdlog_ex& ex) {
// 捕获日志初始化失败(如文件权限不足)
std::cerr << "日志初始化失败:" << ex.what() << std::endl;
}
spdlog::shutdown();
return 0;
}
六、二次封装
1.头文件版
在大型项目中,通常会对 spdlog 进行二次封装,以统一日志配置、简化调用,并支持全局开关等功能。
封装示例(log.h 和 log.cpp)
log.h
cpp
#ifndef LOG_H // 防止头文件被重复包含(预处理指令)
#define LOG_H // 定义宏LOG_H,标识头文件已包含
#include <spdlog/spdlog.h> // 包含spdlog库的头文件,使用其日志功能
#include <string> // 包含字符串处理头文件
// 日志封装类(单例模式):确保全局只有一个日志实例,统一管理日志配置
class Log {
public:
// 初始化日志(控制台 + 文件输出)
// 参数:log_file - 日志文件路径,默认值为"app.log"
static void init(const std::string& log_file = "app.log");
// 获取单例实例:全局唯一的Log对象入口
static Log& get_instance();
// 日志输出接口(trace级别)
// 模板参数Args...:支持可变参数(如日志中包含的变量)
// 参数:fmt - 日志格式字符串(如"x = {}");args... - 格式字符串中的占位符对应的值
template<typename... Args>
void trace(const char* fmt, const Args&... args) {
logger_->trace(fmt, args...); // 调用spdlog的trace级别日志输出
}
// 日志输出接口(info级别),参数含义同上
template<typename... Args>
void info(const char* fmt, const Args&... args) {
logger_->info(fmt, args...); // 调用spdlog的info级别日志输出
}
// 日志输出接口(error级别),参数含义同上
template<typename... Args>
void error(const char* fmt, const Args&... args) {
logger_->error(fmt, args...); // 调用spdlog的error级别日志输出
}
// 其他级别(debug/warn/critical)类似...
private:
Log() = default; // 私有构造函数:禁止外部创建对象(单例模式核心)
~Log() = default; // 私有析构函数:禁止外部销毁对象
// 禁用拷贝构造和赋值操作:防止单例被复制
Log(const Log&) = delete;
Log& operator=(const Log&) = delete;
std::shared_ptr<spdlog::logger> logger_; // 日志器指针:spdlog的核心对象,负责实际日志输出
};
// 宏定义,简化调用(自动添加文件名和行号)
// __FILE__:当前源文件路径(预定义宏)
// __LINE__:当前代码行号(预定义宏)
// __VA_ARGS__:替换宏调用时传入的所有参数
#define LOG_TRACE(...) Log::get_instance().trace("[{}:{}] {}", __FILE__, __LINE__, __VA_ARGS__)
#define LOG_INFO(...) Log::get_instance().info("[{}:{}] {}", __FILE__, __LINE__, __VA_ARGS__)
#define LOG_ERROR(...) Log::get_instance().error("[{}:{}] {}", __FILE__, __LINE__, __VA_ARGS__)
#endif // LOG_H // 结束头文件保护宏
log.cpp
cpp
#include "log.h" // 包含自定义日志头文件
#include <spdlog/sinks/stdout_color_sinks.h> // 包含带颜色的控制台输出器头文件
#include <spdlog/sinks/basic_file_sink.h> // 包含文件输出器头文件
// 初始化日志函数实现
void Log::init(const std::string& log_file) {
// 多输出目标(控制台 + 文件):sinks是输出器的集合
std::vector<spdlog::sink_ptr> sinks;
// 添加带颜色的控制台输出器(_mt表示多线程安全)
// spdlog::sinks::stdout_color_sink_mt:spdlog提供的带颜色的控制台输出器类
// std::make_shared:创建智能指针(自动管理内存)
sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
// 添加文件输出器(_mt表示多线程安全)
// 参数:log_file - 日志文件路径
sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_file));
// 创建日志器(logger):管理输出器的核心对象
// 参数1:日志器名称("main_logger");参数2-3:输出器集合的起止迭代器
auto logger = std::make_shared<spdlog::logger>("main_logger", sinks.begin(), sinks.end());
// 设置日志格式
// 格式说明:
// [%Y-%m-%d %H:%M:%S.%e]:日期时间(年-月-日 时:分:秒.毫秒)
// [%l]:日志级别(如info/error)
// %v:日志内容(包括宏添加的文件名、行号和用户输入的内容)
logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v");
// 设置全局日志级别:trace(最低级别,所有日志都输出)
// 可选级别:trace < debug < info < warn < error < critical
logger->set_level(spdlog::level::trace);
// 注册为默认日志器:后续spdlog::xxx()调用会使用该日志器
spdlog::set_default_logger(logger);
}
// 获取单例实例函数实现
Log& Log::get_instance() {
static Log instance; // 静态局部变量:第一次调用时创建,全局唯一(单例核心)
// 若未初始化(logger_为空),使用spdlog的默认日志器
if (!instance.logger_) {
instance.logger_ = spdlog::default_logger();
}
return instance; // 返回唯一实例
}
使用封装后的日志库
cpp
#include "log.h"
int main() {
Log::init("my_app.log"); // 初始化日志
LOG_INFO("程序启动");
int x = 42;
LOG_DEBUG("x 的值为: {}", x);
LOG_ERROR("错误示例");
return 0;
}
2.整体版
cpp
#pragma once
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/async_logger.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
namespace LogModule
{
enum OutputMode
{
CONSOLE_ONLY,
FILE_ONLY,
BOTH
};
typedef struct LogInfo
{
OutputMode outmode = CONSOLE_ONLY;
bool is_debug_ = true;
std::string logfile_ = "logfile.txt";
bool is_async_ = false;
size_t queue_size_ = 1 << 12;
size_t thread_num_ = 1;
} LogInfo_t;
class Log
{
public:
static void Init(const LogInfo_t& loginfo = LogInfo_t())
{
logconf_ = loginfo;
create_logger();
}
static Log& GetInstance()
{
static Log log;
return log;
}
template<typename... Args>
void trace(const char* fmt, const Args&... args)
{
if(logger_)
logger_->trace(fmt, args...);
}
template<typename... Args>
void info(const char* fmt, const Args&... args)
{
if(logger_)
logger_->info(fmt, args...);
}
template<typename... Args>
void debug(const char* fmt, const Args&... args)
{
if(logger_)
logger_->debug(fmt, args...);
}
template<typename... Args>
void warn(const char* fmt, const Args&... args)
{
if(logger_)
logger_->warn(fmt, args...);
}
template<typename... Args>
void error(const char* fmt, const Args&... args)
{
if(logger_)
logger_->error(fmt, args...);
}
template<typename... Args>
void critical(const char* fmt, const Args&... args)
{
if(logger_)
logger_->critical(fmt, args...);
}
static void shutdown()
{
spdlog::shutdown();
}
private:
Log() = default;
~Log() = default;
Log& operator=(const Log&) = delete;
Log(const Log&) = delete;
static void create_logger()
{
std::vector<spdlog::sink_ptr> sinks;
if(logconf_.outmode == CONSOLE_ONLY || logconf_.outmode == BOTH)
{
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
sinks.push_back(console_sink);
}
if(logconf_.outmode == FILE_ONLY || logconf_.outmode == BOTH)
{
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(logconf_.logfile_);
sinks.push_back(file_sink);
}
spdlog::level::level_enum lenum =
logconf_.is_debug_ ? spdlog::level::trace : spdlog::level::info;
if(logconf_.is_async_)
{
spdlog::init_thread_pool(logconf_.queue_size_, logconf_.thread_num_);
logger_ = std::make_shared<spdlog::async_logger>(
"mainlog", sinks.begin(), sinks.end(),
spdlog::thread_pool(),
spdlog::async_overflow_policy::block);
}
else
{
logger_ = std::make_shared<spdlog::logger>(
"mainlog", sinks.begin(), sinks.end());
}
logger_->set_level(lenum);
spdlog::set_default_logger(logger_); // 重要:设置默认日志器
logger_->set_pattern("[%Y-%m-%d %H:%M:%S.%e][%t][%-8l]%v");
}
private:
static inline std::shared_ptr<spdlog::logger> logger_;
static inline LogInfo_t logconf_;
};
};
// // 使用简化版本的宏定义
// #define LOG_TRACE(...) LogModule::Log::GetInstance().trace(__VA_ARGS__)
// #define LOG_INFO(...) LogModule::Log::GetInstance().info(__VA_ARGS__)
// #define LOG_DEBUG(...) LogModule::Log::GetInstance().debug(__VA_ARGS__)
// #define LOG_WARN(...) LogModule::Log::GetInstance().warn(__VA_ARGS__)
// #define LOG_ERROR(...) LogModule::Log::GetInstance().error(__VA_ARGS__)
// #define LOG_CRITICAL(...) LogModule::Log::GetInstance().critical(__VA_ARGS__)
// 修改后的正确宏定义:
#define LOG_TRACE(fmt, ...) LogModule::Log::GetInstance().trace("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) LogModule::Log::GetInstance().info("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_DEBUG(fmt, ...) LogModule::Log::GetInstance().debug("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) LogModule::Log::GetInstance().warn("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) LogModule::Log::GetInstance().error("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_CRITICAL(fmt, ...) LogModule::Log::GetInstance().critical("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
七、注意事项
线程安全
:多线程环境下,使用 _mt 后缀的日志器(如 basic_logger_mt),单线程可用 _st 后缀(性能更好)。
日志级别
:发布版本建议将级别设为 info 或更高,避免 debug/trace 日志影响性能。
异步日志
:必须调用 spdlog::shutdown() 确保所有日志被写入,否则可能丢失数据。
编译选项
:需启用 C++11 及以上标准(-std=c++11),部分功能(如格式化)依赖 C++17 时需指定 -std=c++17。
性能优化
:高频日志场景建议使用异步模式,并合理设置日志级别过滤无关信息。