C语言中的递归日志问题(即日志函数自身的日志记录)
解决自定义日志框架的递归问题与平台无关替代方案
核心问题分析
你的递归问题根源很明确:Log函数内部处理失败时(比如CreateNNEvent失败),如果调用Log本身输出错误,会触发无限递归——因为调用Log又会重复执行整个流程,再次走到错误处理分支,循环往复。之前的递归检测和临时缓冲没解决根本问题,是因为没切断错误路径里的递归触发点。
平台无关的解决方案
要彻底解决,需要两个关键改动:
- 线程安全的递归防护标记:用C11标准的
_Thread_local(兼容大多数现代编译器,包括GCC、Clang、MSVC)记录当前线程是否正在执行Log函数,避免递归调用。 - 极简应急输出函数:实现一个不依赖任何日志框架的基础输出函数,直接写标准错误流(
stderr),用于Log内部出错时的应急输出,完全避开递归。
代码修改示例
1. 添加递归防护和应急输出函数
#include <stdio.h> #include <stdarg.h> // 线程本地存储:标记当前线程是否正在执行Log函数,避免递归 _Thread_local static int g_log_in_progress = 0; // 应急输出函数:直接写stderr,无依赖,不触发递归 static void LogEmergency(const char* format, ...) { va_list args; va_start(args, format); // 直接用vprintf输出到标准错误流,平台无关 vfprintf(stderr, format, args); va_end(args); // 强制刷新缓冲区,确保错误信息立即输出 fflush(stderr); }
2. 修改原Log函数的错误处理逻辑
void Log(char Severity, char* str, ...) { DWORD ThreadID; va_list args; struct Event Event; unsigned long long Time; char ExpandedString[MAX_STR_LEN], err = FALSE; // 递归防护:如果当前线程正在执行Log,直接返回或用应急输出 if (g_log_in_progress) { LogEmergency("[LOG RECURSION DETECTED] Cannot log during log operation\n"); return; } // 标记当前线程进入Log流程 g_log_in_progress = 1; // Get this early so we get the best log timestamp Time = Timestamp(); // Create the log message string va_start(args, str); vsnprintf_s(ExpandedString, MAX_STR_LEN, MAX_STR_LEN, str, args); va_end(args); ThreadID = GetCurrentThreadId(); // Create the event container for putting the log data into if (CreateNNEvent(&Event) == FALSE) { // 用应急输出替代LogErr,避免递归 LogEmergency("Could not create event structure to record thread ID %lu log: [%d][%llu] %s\n", ThreadID, Severity, Time, ExpandedString); // 重置标记后返回 g_log_in_progress = 0; return; } // Below here we must now destroy this event before returning Event.WDB.Time = Time; Event.Code = EC_LOG; Event.Value.DataType = DT_STR; // 你的后续日志处理逻辑... // 重置递归标记(注意:所有退出路径都要重置,包括正常流程和其他错误分支) g_log_in_progress = 0; }
额外优化建议
- 确保所有退出路径重置标记:如果Log函数还有其他错误分支(比如Event填充失败、写入日志存储失败),也要在这些分支里重置
g_log_in_progress,避免标记一直为1导致后续日志无法输出。 - Timestamp的平台兼容:如果你的
Timestamp()函数是Windows专属的(比如用QueryPerformanceCounter),可以改成标准C的time()或clock_gettime()(Posix)+ 微调,保证平台无关性。 - 临时缓冲区的改进:如果之前的临时缓冲方案是为了批量写入,可以保留,但要确保缓冲写入的逻辑不会触发递归——比如缓冲满了写入时,先检查递归标记,必要时切换到应急输出。
内容的提问来源于stack exchange,提问作者ShoreFisher
相关产品推荐
相关产品推荐

