不能直接用 std::cout 或 fstream 写分布式日志,因其无缓冲控制、异步刷盘、上下文注入、分级过滤、自动切分、重试压缩等能力,且无法应对网络抖动;必须分离采集/缓存/发送,采用无锁环形队列+内存映射文件兜底。

为什么不能直接用 std::cout 或 fstream 写分布式日志
因为高并发下频繁系统调用会卡死线程,fstream 没有内置缓冲区控制和异步刷盘机制;而分布式场景要求日志必须带时间戳、主机名、进程 ID、trace_id 等上下文,还要支持按 level 过滤、自动切分、失败重试和批量压缩上传——这些 std::ofstream 一个都不提供。
更关键的是:一旦网络抖动或 collector 不可用,日志不能丢,也不能阻塞业务线程。所以必须分离采集、缓存、发送三阶段,且缓存层要支持无锁环形队列 + 内存映射文件(mmap)做落盘兜底。
用 boost::lockfree::spsc_queue 做日志采集缓冲是否可行
可行,但要注意它只支持单生产者单消费者,适合“业务线程写入 → 日志线程消费”这种典型模式;但它不处理内存分配——日志消息得预分配固定大小(比如 512 字节),否则堆分配会引入锁和碎片。
- 建议用
boost::lockfree::spsc_queue<log_entry_t boost::lockfree::capacity>></log_entry_t>,其中log_entry_t在对象池里分配 - 避免在采集路径调用
std::chrono::system_clock::now(),改用线程局部的单调时钟(std::chrono::steady_clock+ 启动偏移)减少 syscall - 不要把
std::string直接塞进队列,而是用char buf[512]+uint16_t len手动序列化,避免构造/析构开销
gRPC 和 HTTP/2 哪个更适合发日志到 collector
gRPC 更合适——它原生支持流式传输(streaming RPC),能复用连接、自动压缩(gzip)、带健康探测,且 protocol buffer 序列化比 JSON 轻 3~5 倍。但要注意:默认的 grpc::ChannelArguments 不开 keepalive,长连接容易被中间设备断开。
立即学习“C++免费学习笔记(深入)”;
- 必须设置:
args.SetInt(GRPC_ARG_KEEPALIVE_TIME_MS, 30000)和args.SetInt(GRPC_ARG_KEEPALIVE_TIMEOUT_MS, 10000) - 禁用等待响应的阻塞调用,改用
WriteAsync()+CompletionQueue驱动非阻塞发送 - 批量发送时,每批不超过 1MB(避免 gRPC 单 message size 限制),并用
std::vector<:logentry></:logentry>构建 batch,而非拼接字符串
本地磁盘兜底时,mmap 日志文件怎么避免崩溃后数据错乱
核心是两段式提交:先写数据块,再原子更新 header 中的 commit offset。不能依赖 msync() 的顺序——它只保证页回写,不保证 write ordering。
- 用
open(..., O_DSYNC | O_RDWR)创建文件,避免 page cache 缓冲导致掉电丢数据 - header 区域(前 4KB)单独 mmap,含 magic number、version、committed_offset、flushed_offset 四个字段
- 每次写入前,先
memcpy到 data 区,再__atomic_store_n(&header->committed_offset, new_off, __ATOMIC_RELEASE),最后msync(header_mmap, 4096, MS_SYNC) - 启动恢复时,只信任
committed_offset,并校验每个 log entry 的 CRC32(放在 entry 尾部)
真正难的是日志回放与网络发送的协同:已 commit 但未 send 的日志,在 agent 重启后要重新加入发送队列,且不能重复投递——这需要 collector 支持幂等写入(比如用 log_id 做去重 key)。


















