为什么以及如何重定向调试语句?

时间:2011-12-30 06:27:53

标签: c linux macros output-redirect

我想知道为什么首选将调试语句重定向到stderr,就像在这里完成一样:

#ifdef DEBUG_TEST
  #define DEBUG_TEST 1
  #else
  #define DEBUG_TEST 0
  #endif

  #define DEBUG_PRINT(fmt, ...) \
             do { if (DEBUG_TEST) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)

另外:我们如何将这些调试语句重定向到具有该文件时间戳的单独日志文件?我想在我的代码中使用宏来做到这一点。

平台:Linux,gcc编译器

4 个答案:

答案 0 :(得分:3)

在stdout上使用stderr的好处是,如果要将输出重定向到文件或将其(使用管道)流式传输到另一个进程,则调试消息不会妨碍。

如果要将stderr重定向到Unix上的文件,可以像这样运行程序:

./program 2>logfile

答案 1 :(得分:2)

我可以举两个例子说明你为什么这样做:你可以轻松地将标准输出重定向到文件,同时在你的终端中保持标准错误(这似乎正是你想要在第二个问题中做的) 。您的工具可能会突出显示发送到stderr的消息,使其更容易立即发现。

您可以使用命令行版本重定向stderr,正如相同的建议。要获取文件名中的时间戳,您可以在运行程序时执行以下操作:

./program 2>"logfile-`date`.txt"

如果你想在程序本身中这样做,一种方法是简单地使用fopen打开另一个文件并写入。以下是您可以使用的完整工作示例:

#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

#define DEBUG_TEST true

FILE *debug_file;

#define DEBUG_PRINT(fmt, ...) \
        do { if (DEBUG_TEST) fprintf(debug_file, fmt, ##__VA_ARGS__); } while (false);



int main()
{
        time_t timestamp = time(NULL);
        char * s = malloc(snprintf(NULL, 0, "debug-%d.txt", timestamp));
        sprintf(s, "debug-%d.txt", timestamp);

        debug_file=fopen(s, "w");

        DEBUG_PRINT("YEAH\n");

        fclose(debug_file);

        return EXIT_SUCCESS;
}

答案 2 :(得分:1)

您向stderr而不是stdout报告调试信息的原因之一,因为stdout可能会在管道中发送,而您的诊断将与实际数据一起使用,从而混淆了后续阶段管道。

如果您可能想要重定向输出,或添加时间戳(或PID或任何其他信息),请不要直接使用fprintf()。调用您自己设计的功能,以您想要的方式处理您想要的信息。

因此,你的宏可能是:

extern void dbg_print(const char *fmt, ...);

#define DEBUG_PRINT(fmt, ...) \
         do { if (DEBUG_TEST) dbg_print(fmt, __VA_ARGS__); } while (0)

或者:

extern void dbg_print(const char *func, const char *file, int line, const char *fmt, ...);

#define DEBUG_PRINT(fmt, ...) \
         do { if (DEBUG_TEST) dbg_print(__func__, __FILE__, __LINE__, fmt, __VA_ARGS__); } while (0)

这包括信息中的功能名称,文件名和行号

例如,我有一个适度复杂的包。其中一个核心内部例程是:

/* err_stdio - report error via stdio */
static void err_stdio(FILE *fp, int flags, int errnum, const char *format, va_list args)
{
    if ((flags & ERR_NOARG0) == 0)
        fprintf(fp, "%s: ", arg0);
    if (flags & ERR_STAMP)
    {
        char timbuf[32];
        fprintf(fp, "%s - ", err_time(timbuf, sizeof(timbuf)));
    }
    if (flags & ERR_PID)
        fprintf(fp, "pid=%d: ", (int)getpid());
    vfprintf(fp, format, args);
    if (flags & ERR_ERRNO)
        fprintf(fp, "error (%d) %s\n", errnum, strerror(errnum));
}

调试包装器可以使用适当的标志调用该函数,并生成所需的输出。系统的其他部分控制使用的文件流(stderr是默认值,但是有一个函数可以将输出重定向到任何其他流),依此类推。

如果您通过直接在调试宏中使用fprintf()来限制自己,那么您将无法使用fprintf()可以执行的操作,或者重新编译所有内容。

另请参阅我对'C #define macro for debug printing'的回答,了解有关调试宏以及如何使用它们的更多信息(尽管看起来好像你已经采用了我在船上所说的内容)。

答案 3 :(得分:1)

原谅,如果我错了,但似乎没有人提到缓冲。

默认情况下,stdout在大多数平台上都是行缓冲的,而stderr是无缓冲的。基本上这意味着默认情况下写入stdout会写入内存并以块的形式写入文件或控制台,因为逐个字符输出是slooooow。

如果你正在调试你不希望消息出现得太晚,那么你实际上是要打印它(如果程序崩溃或卡在无限循环中,甚至可能永远不会打印出来(这可能真的很乱)当你认为它在错误的地方循环或崩溃时,你起来了吗?你通常不介意速度命中。理论上它可能会因为时间和同步错误而产生差异但是那些很难捕获/调试那些无论如何。

TL; DR调试打印到stderr而不是标准输出(其他人可能建议记录到文件而不是允许运行时选项,如启用/禁用打印文件,样式,阈值等)。