You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言中的递归日志问题(即日志函数自身的日志记录)

解决自定义日志框架的递归问题与平台无关替代方案

核心问题分析

你的递归问题根源很明确:Log函数内部处理失败时(比如CreateNNEvent失败),如果调用Log本身输出错误,会触发无限递归——因为调用Log又会重复执行整个流程,再次走到错误处理分支,循环往复。之前的递归检测和临时缓冲没解决根本问题,是因为没切断错误路径里的递归触发点。

平台无关的解决方案

要彻底解决,需要两个关键改动:

  1. 线程安全的递归防护标记:用C11标准的_Thread_local(兼容大多数现代编译器,包括GCC、Clang、MSVC)记录当前线程是否正在执行Log函数,避免递归调用。
  2. 极简应急输出函数:实现一个不依赖任何日志框架的基础输出函数,直接写标准错误流(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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 16:55:24