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系统
sudo apt update && sudo apt install 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"

int main() {
    // 基础日志输出
    spdlog::trace("这是TRACE级日志(最详细)");
    spdlog::debug("这是DEBUG级日志(调试信息)");
    spdlog::info("这是INFO级日志(普通信息)");
    spdlog::warn("这是WARN级日志(警告信息)");
    spdlog::error("这是ERROR级日志(错误信息)");
    spdlog::critical("这是CRITICAL级日志(严重错误)");

    return 0;
}
编译运行
# 编译
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"

int main() {
    // 设置全局日志级别为DEBUG(输出DEBUG及以上级别)
    spdlog::set_level(spdlog::level::debug);

    spdlog::debug("DEBUG日志将被输出");
    spdlog::trace("TRACE日志被屏蔽");  // 级别低于DEBUG,不输出

    return 0;
}
3.1.2 按logger实例设置级别
#include "spdlog/spdlog.h"

int main() {
    // 创建自定义logger
    auto 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日志正常输出");

    return 0;
}

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"

int main() {
    // 基础文件输出(覆盖模式,每次运行清空文件)
    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("追加到文件末尾的日志");

    return 0;
}
3.2.3 滚动文件输出(嵌入式推荐)
#include "spdlog/sinks/rotating_file_sink.h"

int main() {
    // 按文件大小滚动:单个文件最大10MB,最多保留3个备份文件
    const size_t max_file_size = 10 * 1024 * 1024;  // 10MB
    const 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分新建文件

    return 0;
}

3.3 日志格式自定义

3.3.1 全局格式设置
#include "spdlog/spdlog.h"

int main() {
    // 自定义格式:[时间] [进程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] 自定义格式的日志信息

    return 0;
}
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"

int main() {
    // 配置异步日志:队列大小8192,线程数1(嵌入式建议单线程)
    spdlog::init_thread_pool(8192, 1);
    
    // 创建文件sink
    auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/async.log", true);
    // 创建异步logger
    auto 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();
    return 0;
}
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"

int main() {
    // 创建控制台sink和滚动文件sink
    auto 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("同时输出到控制台和文件");

    return 0;
}

4.3 嵌入式串口输出(自定义sink)

#include "spdlog/spdlog.h"
#include "spdlog/sinks/base_sink.h"
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

// 自定义串口sink
class serial_sink : public spdlog::sinks::base_sink<std::mutex> {
private:
    int serial_fd_;

    // 初始化串口(嵌入式需根据硬件调整参数)
    void init_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("串口打开失败");
        }

        struct termios options;
        tcgetattr(serial_fd_, &options);
        cfsetispeed(&options, B115200);  // 波特率115200
        cfsetospeed(&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:
    void sink_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());
    }

    void flush_() override {
        tcdrain(serial_fd_);  // 等待串口数据发送完成
    }

public:
    explicit serial_sink(const std::string& port = "/dev/ttyS0") {
        init_serial(port);
    }

    ~serial_sink() override {
        if (serial_fd_ >= 0) {
            close(serial_fd_);
        }
    }
};

// 使用自定义串口sink
int main() {
    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("通过串口输出的日志信息");
    return 0;
}

五、避坑指南/常见问题

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;
  • 异步日志:日志写入操作在独立线程中执行,不阻塞主线程;
  • 滚动日志:当日志文件达到指定大小/时间时,自动创建新文件并保留历史备份。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐