跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

c++如何将所有日志写入内存缓冲区并在崩溃时导出【实战】

崩溃时日志易丢失,须用预分配环形缓冲区+信号拦截+原子操作+write系统调用实现无锁安全日志;单条日志≤4KB,导出前需按\n截取完整行。 崩溃时没日志?得用双缓冲+信号拦截 直接写磁盘的日志在进程崩溃瞬间大概率丢失,因为
std::ofstream
或
fprintf
的缓冲未刷新、文件句柄可能已损坏。必须把日志先攒在内存里,再通过
signal
捕获(如
SIGSEGV
、
SIGABRT
)触发强制导出。但注意:信号处理函数里只能调用异步信号安全函数,
std::string
、
std::vector
、
new
、
malloc
全部禁止——这意味着你不能在信号处理函数里做任何动态内存分配或 STL 操作。 用预分配的环形缓冲区 + 原子索引控制写入 避免运行时分配,整个缓冲区必须在程序启动时静态/全局分配。推荐用
std::array
或裸
char buffer[N]
,配合两个原子整数记录写入位置和已提交长度:
std::atomic write_pos{0}
:当前可写起始偏移(多线程写入时用
fetch_add
)
std::atomic commit_len{0}
:已完整写入且可被导出的日志字节数(仅由写入线程更新) 每次写入前检查剩余空间:
if (write_pos.load() + len > buffer.size())
→ 回绕或丢弃(不阻塞) 写入后用
commit_len.store(write_pos.load())
标记完成(确保内存序为
memory_order_release
) 示例关键片段:
static char g_log_buffer[1024 * 1024]; static std::atomic g_write_pos{0}; static std::atomic g_commit_len{0};

void safe_log(const char* msg) { size_t pos = g_write_pos.fetch_add(len, std::memory_order_relaxed); if (pos + len >= sizeof(g_log_buffer)) { // 回绕:从头开始,覆盖旧日志 pos = 0; g_write_pos.store(len, std::memory_order_relaxed); } memcpy(g_log_buffer + pos, msg, len); g_commit_len.store(pos + len, std::memory_order_release); // 标记有效终点 }

信号处理函数里只能用
write()
导出,且必须重入安全
signal()
处理器中禁止调用
fopen
、
fprintf
、
std::cout
、
std::string::c_str()
等——它们内部可能锁、分配、调用非 async-signal-safe 函数。唯一可靠方式是用系统调用
write()
直接写文件描述符: C知道 CSDN推出的一款AI技术问答工具 下载 立即学习 “ C++免费学习笔记(深入) ”; 提前用
open()
打开日志文件(如
log_fd = open("crash.log", O_WRONLY | O_CREAT | O_APPEND, 0644)
),并在
main()
开头完成 信号处理器中只做三件事:读
g_commit_len
、计算有效日志范围、调用
write(log_fd, ...)
必须用
volatile sig_atomic_t
标记信号标志位(如
volatile sig_atomic_t g_crashed = 0
),避免编译器优化掉轮询判断 不要在 handler 中调用
exit()
或
abort()
—— 它们本身可能触发二次崩溃;导出后用
_exit(128)
(不刷 stdio 缓冲、不调析构)立即终止 多线程写入需规避锁,但要注意内存可见性 多个线程并发调用
safe_log()
时,不能用互斥锁(
pthread_mutex_lock
在信号上下文中不安全,且可能死锁)。环形缓冲区靠原子索引 + 无锁写入即可,但有两点硬约束: 单条日志必须短于缓冲区一半(否则回绕时无法保证原子写入),建议限制单条 ≤ 4KB 写入内容需是纯 ASCII 或 UTF-8 字节流,避免宽字符、locale 依赖操作 若需格式化(如带时间戳、线程ID),必须在进入
safe_log()
前完成,并存为 C 风格字符串(
snprintf(buf, sizeof(buf), "[%ld][%d] %s", time(NULL), gettid(), msg)
),再传入 导出时,
g_commit_len.load(std::memory_order_acquire)
必须配对
memory_order_release
,否则可能读到中间态数据 真正棘手的是:崩溃可能发生在日志写入一半时(比如
memcpy
被中断)。所以导出逻辑要跳过不完整行——按换行符切分,只保存以
\n
结尾的完整行。这需要在信号 handler 中手动扫描,不能依赖
strtok
。

相关文章