第二章:日志框架选型——嵌入式环境下的三选一

做嵌入式摄像头调试,日志系统就是你的「黑匣子」。选对了,事半功倍;选错了,调试时想哭都找不到地方。

今天咱们来聊聊三个主流的C/C++日志库:log4c、spdlog、zlog。我会结合自己的实际项目经验,帮你挑出最适合嵌入式环境的那一个。

2.1 先说说我的选型标准

嵌入式环境跟PC开发不一样。资源有限,实时性要求高,有时候还得在裸机上跑。我个人习惯从这几个维度去衡量:

  • 内存占用——摄像头模组里Flash和RAM都金贵,日志库不能太胖
  • 性能开销——日志打印不能拖累图像采集的帧率
  • 异步支持——调试时最怕日志写文件卡住主循环
  • 配置灵活性——现场调试时能动态开关日志级别,不用重新编译
  • 依赖情况——最好零依赖,或者只依赖标准库

嗯,说白了就是:轻量、高效、可配置。咱们一个一个来看。

2.2 log4c——老牌选手,但有点「重」

log4c是Apache log4j的C语言移植版。我在早期一个安防摄像头项目里用过它。当时觉得功能挺全,什么日志级别、滚动文件、远程日志都有。

但实际用下来,问题不少:

  • 内存占用偏大,一个日志实例初始化就要几十KB
  • 配置依赖XML解析器,在资源受限的MCU上跑不动
  • 多线程支持不够好,曾经遇到过日志打印导致死锁的情况
避坑提醒: 我曾经在一个只有128KB RAM的STM32项目里硬塞log4c,结果系统刚启动就OOM了。后来不得不连夜重写日志模块,血的教训。

所以我的结论是:log4c适合Linux服务器端或资源充裕的嵌入式Linux平台,但不适合裸机或RTOS环境。

2.3 spdlog——现代C++的优雅选择

spdlog是我现在最常用的日志库。它用C++11写的,头文件只有几个,编译快,性能也棒。

为什么推荐它?

  • 零配置可用——几行代码就能跑起来,适合快速原型验证
  • 异步模式——日志写入独立线程,不影响主循环的实时性
  • 格式化灵活——支持fmtlib风格的格式化,比printf安全多了
  • 多后端支持——控制台、文件、syslog、甚至自定义sink

来看个实际例子。我在调试一个4K摄像头时,需要记录每帧的曝光参数:

#include <spdlog/spdlog.h>
#include <spdlog/sinks/rotating_file_sink.h>

// 创建一个滚动文件日志器,最大5MB,保留3个备份
auto logger = spdlog::rotating_logger_mt("camera_log", "logs/camera.log", 5 * 1024 * 1024, 3);

// 设置日志级别
logger->set_level(spdlog::level::debug);

// 记录每帧的曝光信息
while (capturing) {
    uint32_t exposure = get_exposure_time();
    uint16_t gain = get_gain_value();
    logger->info("Frame {}: exposure={}us, gain={}", frame_count++, exposure, gain);
    
    // 关键帧用warn级别,方便筛选
    if (exposure > 30000) {
        logger->warn("Exposure too high! Frame {}", frame_count);
    }
}

你想想看,如果不用日志库,用printf打印,调试完了还得一行行删代码。用spdlog,改个日志级别就行,多省事。

小技巧: 在嵌入式Linux上,我习惯把spdlog的异步队列大小设为1024,这样即使日志写入慢,也不会阻塞图像采集线程。

不过spdlog也有短板:它依赖C++11,如果你的编译器不支持(比如某些老旧的ARMCC),那就没法用了。

2.4 zlog——C语言下的轻量级王者

zlog是我在裸机项目里的首选。它纯C实现,没有外部依赖,编译出来才几十KB。

它的核心优势:

  • 极简API——只有几个函数,学习成本几乎为零
  • 运行时重载配置——修改配置文件就能动态调整日志级别,不用重启设备
  • 分类日志——可以按模块(如sensor、isp、ae)分别控制日志输出
  • 性能优秀——单条日志写入时间在微秒级

我记得在一个电池供电的IPCAM项目里,Flash空间只剩200KB了。log4c塞不进去,spdlog又依赖C++运行时。最后用zlog,只用了80KB就搞定了日志系统。

配置方式也很直观:

# zlog.conf 配置文件示例
[global]
buffer_min = 256
buffer_max = 2M

[categories]
camera_sensor = INFO, stdout
camera_isp = DEBUG, logfile
camera_ae = WARN, logfile

[formats]
default = "%d(%H:%M:%S) %c [%l] %m%n"

代码里调用:

#include "zlog.h"

int main() {
    int rc = zlog_init("zlog.conf");
    if (rc) {
        printf("zlog init failed\n");
        return -1;
    }
    
    zlog_category_t *sensor_log = zlog_get_category("camera_sensor");
    zlog_category_t *isp_log = zlog_get_category("camera_isp");
    
    // 不同模块用不同分类
    zlog_info(sensor_log, "Sensor initialized, ID=0x%04x", sensor_id);
    zlog_debug(isp_log, "ISP processing frame %d", frame_num);
    
    zlog_fini();
    return 0;
}
核心结论: 如果你的项目是纯C环境、资源紧张、或者需要运行时动态调整日志,zlog是最佳选择。我最近三个嵌入式项目都用的它,没出过问题。

2.5 横向对比:一张表说清楚

特性 log4c spdlog zlog
语言 C C++11 C99
内存占用 较大(~100KB+) 中等(~50KB) 极小(~20KB)
异步支持 有限 原生支持 需自行实现
运行时配置 支持(XML) 支持(编程方式) 支持(配置文件)
多线程安全 一般 优秀 良好
依赖情况 需libxml2 需C++运行时 零依赖
适用场景 Linux服务器 嵌入式Linux 裸机/RTOS/资源受限

2.6 我的最终建议

选哪个?其实没有银弹。我个人的经验是:

  • 裸机或RTOS项目(比如FreeRTOS、RT-Thread)→ 选 zlog,轻量、可控、无依赖
  • 嵌入式Linux项目(比如树莓派、全志、瑞芯微)→ 选 spdlog,现代、高效、好用
  • 老项目维护或服务器端 → 如果已经在用log4c,可以继续用,但新项目不建议入坑

对了,还有一个细节:不管你选哪个库,一定要在生产固件里把日志级别设为ERROR或更高。我曾经在出货的摄像头里忘了关DEBUG日志,结果客户反馈设备运行一段时间后变慢——查了半天,是日志文件把Flash写满了。

嗯,这就是日志框架选型的全部内容。下一章咱们聊聊日志格式怎么设计,才能让调试效率翻倍。