C++使用easylogging++多进程写日志丢失消息如何解决
问题根因
日志丢失、内容错位、空行问题核心是跨进程并发写入无互斥保护,和线程安全配置无关:
- 你在Log.cpp中封装的
std::mutex是进程内锁,仅能保护单进程下多线程的写入顺序,不同独立进程的锁内存空间完全隔离,无法拦截跨进程并发写操作。 - easylogging++自带的
ELPP_THREAD_SAFE宏仅实现单进程内的线程安全,默认未开启多进程写同一文件的兼容逻辑。 - 配置中开启了
MAX_LOG_FILE_SIZE按大小切分日志,多进程同时触发大小校验、文件重命名/截断操作时,会直接打乱文件写入偏移,这是空行、内容串行的主要诱因。 - 即使关闭按大小切分,默认文件写入未走操作系统层原子追加逻辑时,两个进程同时写入会互相覆盖对应偏移位置的磁盘内容,你看到计数290的日志丢失就是被另一进程的写入覆盖导致。
- 单进程多线程场景下你的进程内锁可以正常工作,所以不会出现丢日志问题,和你观察到的现象完全吻合。
解决方案
按改动量从小到大排序,可根据生产环境实际情况选择:
方案1:开启库自带多进程安全模式(改动最小)
easylogging++原生提供多进程写同一日志文件的安全支持,不需要修改上层业务调用逻辑,仅需调整编译配置和日志参数即可:
- 在项目预编译宏列表中新增两个宏定义,必须保证宏在引入
easylogging++.h前生效,不要在代码局部定义:ELPP_FORCE_USE_STD_THREAD ELPP_MULTI_PROCESS_SAFE - 修改日志配置,关闭多进程场景下有竞态的按大小切日志逻辑,保留已有的按日期建目录的切分方式即可:
* GLOBAL: FORMAT = "%datetime %msg" FILENAME = "C:/logs/%datetime{%Y-%M-%d}/msgs.log" ENABLED = true TO_FILE = true TO_STANDARD_OUTPUT = false SUBSECOND_PRECISION = 6 PERFORMANCE_TRACKING = false MAX_LOG_FILE_SIZE = 0 ## 关闭按大小切分,避免多进程切文件竞态 - 删除Log.cpp中自己封装的
std::mutex相关加解锁逻辑:开启多进程安全宏后,库内部会自动通过文件锁实现跨进程互斥,同时保证单进程内线程安全,额外加锁只会带来不必要的性能损耗。 - 配置默认日志器每次写完主动刷盘,避免进程异常崩溃时缓冲区内容丢失:
// 初始化日志后加一行即可 el::Loggers::getLogger("default")->setFlushThreshold(el::Level::Info, 1);
方案2:自定义跨进程文件锁(适配旧版本库)
如果你使用的easylogging++版本较低,没有内置多进程安全支持,可将现有进程内锁替换为跨进程文件锁兜底:
- 单独创建一个固定路径的锁文件(比如日志目录下的
.log.lock),不要直接用日志文件当锁,避免切日志时锁失效。 - 锁使用操作系统原生文件锁实现:Linux下用
flock,Windows下用LockFile,这类锁会在进程退出时自动释放,不会出现死锁。 - 同样需要关闭按大小切日志的配置,关闭性能跟踪特性。
Linux下的参考实现:
#include <sys/file.h> #include <fcntl.h> #include <unistd.h> static int lock_fd = open("/your/log/path/.log.lock", O_RDWR | O_CREAT, 0666); void log_info(std::string s) { flock(lock_fd, LOCK_EX); // 加跨进程排他锁 LOG(INFO) << s; el::Loggers::getLogger("default")->flush(); // 主动刷盘 flock(lock_fd, LOCK_UN); // 释放锁 } void log_error(std::string s) { flock(lock_fd, LOCK_EX); LOG(ERROR) << s; el::Loggers::getLogger("default")->flush(); flock(lock_fd, LOCK_UN); }
方案3:架构层面规避多进程写同一文件(长期最稳妥)
如果后续允许做架构调整,优先从根源上避免多进程直接竞争写同一个磁盘文件:
- 最轻量的调整是给每个进程的日志文件名加进程ID后缀,输出独立日志文件,排查问题时通过多文件检索命令聚合即可,性能比跨进程加锁高30%以上。
- 如果有统一日志收集需求,可以单独部署轻量日志进程,所有业务进程通过本地套接字将日志发送给该进程,由其单线程写入磁盘,完全不存在竞态问题,性能和可靠性都更高。
避坑说明
- 不要认为给文件加
O_APPEND打开模式就可以解决问题:O_APPEND仅能保证单次write调用大小不超过文件系统管道缓冲区时的原子性,easylogging++默认写日志可能拆分多次write调用,叠加日志切分逻辑,依然会出现内容覆盖、丢失问题。 - 跨进程锁场景下必须关闭异步日志、性能跟踪这类后台异步写特性,这类特性的内存队列是进程内独有的,跨进程下无法保证写入顺序,反而会加剧日志丢失问题。
- 不要用共享内存、命名信号量这类机制做日志锁,进程异常退出时这类锁可能无法自动释放,会导致所有业务进程阻塞在写日志逻辑上,用文件锁是最安全的选择。
内容的提问来源于stack exchange,提问作者Fnr
相关产品推荐
相关产品推荐

