spdlog日志库嵌入式Linux C++使用指南
spdlog日志库嵌入式Linux C++使用指南
文档信息
| 项目 | 内容 |
|---|---|
| 适配spdlog版本 | v1.12.0 |
| 适用场景 | ARM架构Linux系统、C++11及以上、嵌入式应用日志输出 |
| 前置依赖 | CMake 3.10+、C++11编译器、Linux glibc 2.28+ |
一、工具介绍
1.1 工具定位
spdlog是一款高性能、头文件优先的C++日志库,支持同步/异步日志输出,无第三方依赖,适配嵌入式Linux资源受限场景,可快速集成到C++项目中实现灵活的日志管理。
1.2 核心特性
- 零外部依赖,纯头文件+源码集成,适配嵌入式交叉编译;
- 支持TRACE/DEBUG/INFO/WARN/ERROR/CRITICAL 6级日志级别;
- 多输出目标:控制台、文件、滚动文件(按大小/时间分割);
- 线程安全设计,支持多线程环境下日志输出;
- 自定义日志格式(时间、进程ID、线程ID、日志级别等);
- 低内存占用,适配嵌入式闪存/内存受限场景。
1.3 对比优势(嵌入式场景)
| 对比项 | spdlog | log4cpp |
|---|---|---|
| 依赖 | 无 | 依赖log4j、apr等库 |
| 编译体积 | 小(仅需编译核心源码) | 大(依赖库编译后体积大) |
| 内存占用 | 低(支持按需加载) | 高(初始化加载全量组件) |
| 嵌入式适配性 | 优(头文件优先,支持交叉编译) | 差(依赖复杂,需手动移植) |
二、快速集成
2.1 集成方式1:源码集成(嵌入式推荐)
步骤1:下载源码
git clone https://github.com/gabime/spdlog.git cd spdlog &&git checkout v1.12.0 # 固定版本,避免兼容性问题步骤2:项目集成
将spdlog源码目录下的include/spdlog文件夹拷贝到项目的include路径下;
将src/目录下的所有.cpp文件拷贝到项目的third_party/spdlog目录下(仅需编译一次)。
步骤3:CMakeLists.txt配置
# 1. 添加头文件路径 include_directories(${PROJECT_SOURCE_DIR}/include) # 2. 添加spdlog源码编译 file(GLOB SPDLOG_SRC ${PROJECT_SOURCE_DIR}/third_party/spdlog/*.cpp) add_library(spdlog STATIC ${SPDLOG_SRC}) # 3. 链接spdlog库到项目 add_executable(your_project main.cpp) target_link_libraries(your_project spdlog pthread) # 依赖pthread(线程安全) # 4. 指定C++标准 set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) 2.2 集成方式2:包管理集成(桌面Linux)
# Ubuntu/Debian系统sudoapt update &&sudoaptinstall libspdlog-dev # CMakeLists.txt配置 find_package(spdlog REQUIRED) add_executable(your_project main.cpp) target_link_libraries(your_project spdlog::spdlog)2.3 验证集成
测试代码(test_spdlog.cpp)
#include"spdlog/spdlog.h"intmain(){// 基础日志输出 spdlog::trace("这是TRACE级日志(最详细)"); spdlog::debug("这是DEBUG级日志(调试信息)"); spdlog::info("这是INFO级日志(普通信息)"); spdlog::warn("这是WARN级日志(警告信息)"); spdlog::error("这是ERROR级日志(错误信息)"); spdlog::critical("这是CRITICAL级日志(严重错误)");return0;}编译运行
# 编译 g++ test_spdlog.cpp -o test -std=c++11 -lspdlog -pthread # 运行(默认输出INFO及以上级别日志) ./test # 输出结果:# [2025-12-23 15:30:00.123] [info] 这是INFO级日志(普通信息)# [2025-12-23 15:30:00.123] [warn] 这是WARN级日志(警告信息)# [2025-12-23 15:30:00.123] [error] 这是ERROR级日志(错误信息)# [2025-12-23 15:30:00.123] [critical] 这是CRITICAL级日志(严重错误)三、基础使用
3.1 日志级别控制
3.1.1 全局级别设置
#include"spdlog/spdlog.h"intmain(){// 设置全局日志级别为DEBUG(输出DEBUG及以上级别) spdlog::set_level(spdlog::level::debug); spdlog::debug("DEBUG日志将被输出"); spdlog::trace("TRACE日志被屏蔽");// 级别低于DEBUG,不输出return0;}3.1.2 按logger实例设置级别
#include"spdlog/spdlog.h"intmain(){// 创建自定义loggerauto logger = spdlog::get("my_logger");if(!logger){ logger = spdlog::stdout_color_mt("my_logger");// 彩色控制台输出}// 单独设置该logger的级别为WARN logger->set_level(spdlog::level::warn); logger->info("INFO日志被屏蔽"); logger->warn("WARN日志正常输出");return0;}3.2 输出目标配置
3.2.1 控制台输出
// 普通控制台(无颜色)auto console_logger = spdlog::stdout_logger_mt("console");// 彩色控制台(推荐,不同级别日志显示不同颜色)auto color_console = spdlog::stdout_color_mt("color_console"); color_console->info("彩色INFO日志"); color_console->error("彩色ERROR日志");3.2.2 文件输出
#include"spdlog/sinks/basic_file_sink.h"intmain(){// 基础文件输出(覆盖模式,每次运行清空文件)auto file_logger = spdlog::basic_logger_mt("file_logger","logs/app.log"); file_logger->info("写入文件的日志信息");// 追加模式(保留历史日志)auto append_file = spdlog::basic_logger_mt("append_file","logs/app.log",true); append_file->info("追加到文件末尾的日志");return0;}3.2.3 滚动文件输出(嵌入式推荐)
#include"spdlog/sinks/rotating_file_sink.h"intmain(){// 按文件大小滚动:单个文件最大10MB,最多保留3个备份文件const size_t max_file_size =10*1024*1024;// 10MBconst size_t max_files =3;auto rotating_logger = spdlog::rotating_logger_mt("rotating_logger","logs/rotating.log", max_file_size, max_files);// 按时间滚动(每天生成一个新文件)#include"spdlog/sinks/daily_file_sink.h"auto daily_logger = spdlog::daily_logger_mt("daily_logger","logs/daily.log",0,0);// 每天0点0分新建文件return0;}3.3 日志格式自定义
3.3.1 全局格式设置
#include"spdlog/spdlog.h"intmain(){// 自定义格式:[时间] [进程ID:线程ID] [日志级别] 日志内容 spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%P:%t] [%l] %v"); spdlog::info("自定义格式的日志信息");// 输出示例:[2025-12-23 15:40:00.123] [1234:5678] [info] 自定义格式的日志信息return0;}3.3.2 格式占位符说明
| 占位符 | 含义 | 示例 |
|---|---|---|
| %Y-%m-%d | 年-月-日 | 2025-12-23 |
| %H:%M:%S.%e | 时:分:秒.毫秒 | 15:40:00.123 |
| %P | 进程ID | 1234 |
| %t | 线程ID | 5678 |
| %l | 日志级别(小写) | info、error |
| %L | 日志级别(大写) | INFO、ERROR |
| %v | 日志内容 | 自定义日志信息 |
四、高级特性
4.1 异步日志配置(嵌入式避坑)
4.1.1 基础异步配置
#include"spdlog/spdlog.h"#include"spdlog/async.h"#include"spdlog/sinks/basic_file_sink.h"intmain(){// 配置异步日志:队列大小8192,线程数1(嵌入式建议单线程) spdlog::init_thread_pool(8192,1);// 创建文件sinkauto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/async.log",true);// 创建异步loggerauto async_logger = std::make_shared<spdlog::async_logger>("async_logger", file_sink, spdlog::thread_pool(), spdlog::async_overflow_policy::block);// 注册并设置为默认logger spdlog::register_logger(async_logger); spdlog::set_default_logger(async_logger);// 异步输出日志(不阻塞主线程)for(int i =0; i <1000;++i){ spdlog::info("异步日志输出:{}", i);}// 程序退出前刷新日志(避免丢失) spdlog::shutdown();return0;}4.1.2 嵌入式避坑点
- 队列大小不宜过大(建议8192~16384),避免占用过多内存;
- 异步溢出策略选择
block(阻塞)而非overrun_oldest(覆盖旧日志),防止关键日志丢失; - 必须调用
spdlog::shutdown()刷新队列,否则程序退出时未写入的日志会丢失。
4.2 多sink组合输出
#include"spdlog/spdlog.h"#include"spdlog/sinks/stdout_color_sinks.h"#include"spdlog/sinks/rotating_file_sink.h"intmain(){// 创建控制台sink和滚动文件sinkauto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>("logs/multi_sink.log",10*1024*1024,3);// 组合sink到同一个logger std::vector<spdlog::sink_ptr> sinks{console_sink, file_sink};auto multi_sink_logger = std::make_shared<spdlog::logger>("multi_sink", sinks.begin(), sinks.end());// 注册并使用 spdlog::register_logger(multi_sink_logger); multi_sink_logger->info("同时输出到控制台和文件");return0;}4.3 嵌入式串口输出(自定义sink)
#include"spdlog/spdlog.h"#include"spdlog/sinks/base_sink.h"#include<unistd.h>#include<fcntl.h>#include<termios.h>// 自定义串口sinkclassserial_sink:public spdlog::sinks::base_sink<std::mutex>{private:int serial_fd_;// 初始化串口(嵌入式需根据硬件调整参数)voidinit_serial(const std::string& port){ serial_fd_ =open(port.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);if(serial_fd_ <0){throw std::runtime_error("串口打开失败");}structtermios options;tcgetattr(serial_fd_,&options);cfsetispeed(&options, B115200);// 波特率115200cfsetospeed(&options, B115200); options.c_cflag |=(CLOCAL | CREAD);// 本地连接、启用接收 options.c_cflag &=~PARENB;// 无校验位 options.c_cflag &=~CSTOPB;// 1位停止位 options.c_cflag &=~CSIZE; options.c_cflag |= CS8;// 8位数据位tcsetattr(serial_fd_, TCSANOW,&options);}protected:voidsink_it_(const spdlog::details::log_msg& msg) override {// 格式化日志内容 spdlog::memory_buf_t formatted; spdlog::sinks::base_sink<std::mutex>::formatter_->format(msg, formatted);// 写入串口write(serial_fd_, formatted.data(), formatted.size());}voidflush_() override {tcdrain(serial_fd_);// 等待串口数据发送完成}public:explicitserial_sink(const std::string& port ="/dev/ttyS0"){init_serial(port);}~serial_sink() override {if(serial_fd_ >=0){close(serial_fd_);}}};// 使用自定义串口sinkintmain(){auto serial_sink_ptr = std::make_shared<serial_sink>();auto serial_logger = std::make_shared<spdlog::logger>("serial_logger", serial_sink_ptr); spdlog::register_logger(serial_logger); serial_logger->info("通过串口输出的日志信息");return0;}五、避坑指南/常见问题
5.1 中文乱码问题
现象
日志中的中文显示为乱码(如????)。
解决方案
- 编译时添加编码参数:
-fexec-charset=UTF-8 -finput-charset=UTF-8; - 确保嵌入式终端/串口工具的编码为UTF-8;
- 自定义格式时避免使用非UTF-8字符集。
示例CMake配置
add_compile_options(-fexec-charset=UTF-8 -finput-charset=UTF-8) 5.2 同步日志阻塞主线程
现象
高频率日志输出时,主线程卡顿(如嵌入式实时控制场景)。
解决方案
- 切换为异步日志模式(参考4.1节);
- 减少低级别日志输出(如关闭DEBUG/TRACE级);
- 增大日志输出缓冲区(通过
set_pattern添加缓冲配置)。
5.3 编译报错:nullptr未定义
错误信息
error: ‘nullptr’ was not declared in this scope 解决方案
- 编译时指定C++11及以上标准:
-std=c++11; - CMake中添加
set(CMAKE_CXX_STANDARD 11)。
5.4 日志文件占满闪存
现象
嵌入式设备闪存被日志文件占满,导致系统异常。
解决方案
- 使用滚动文件日志(按大小/时间分割,参考3.2.3节);
- 限制日志总大小(如最多保留3个10MB文件);
- 定期清理过期日志(通过脚本或定时器)。
六、附录
6.1 常用API速查表
| API | 功能描述 |
|---|---|
| spdlog::set_level() | 设置全局日志级别 |
| spdlog::set_pattern() | 设置全局日志格式 |
| spdlog::get(logger_name) | 获取已注册的logger实例 |
| spdlog::shutdown() | 刷新日志队列并释放资源 |
| logger->set_level() | 设置单个logger的日志级别 |
| logger->flush() | 手动刷新日志到输出目标 |
6.2 参考链接
- spdlog官方文档:https://github.com/gabime/spdlog
- spdlog嵌入式适配案例:https://github.com/gabime/spdlog/tree/v1.12.0/example
- C++11线程安全指南:https://en.cppreference.com/w/cpp/thread
6.3 术语解释
- Sink:日志输出目标(如控制台、文件、串口);
- Logger:日志器实例,关联一个或多个Sink;
- 异步日志:日志写入操作在独立线程中执行,不阻塞主线程;
- 滚动日志:当日志文件达到指定大小/时间时,自动创建新文件并保留历史备份。