多文件C程序,如何最好地实现可选日志记录?

多文件C程序,如何最好地实现可选日志记录?,c,debugging,C,Debugging,我有一个多文件C程序。我希望用户能够在运行时指定不同的调试级别 实现这一点的最佳方式是什么 我想导出一个debug(level,“message”)类型的函数,并在任何地方使用。有更好的/其他想法吗?log4j有一个非常好的C端口。我使用两个密切相关的调试系统(在一个标题中声明,用于歇斯底里的理由)。较简单的一个具有单个调试级别和printf类函数,这些函数采用调试级别,并且仅在调试级别设置足够高时才发出输出。更复杂的调试子系统提供了不同的调试子系统,每个子系统的行为与更简单的子系统类似(因此,

我有一个多文件C程序。我希望用户能够在运行时指定不同的调试级别

实现这一点的最佳方式是什么


我想导出一个debug(level,“message”)类型的函数,并在任何地方使用。有更好的/其他想法吗?

log4j有一个非常好的C端口。

我使用两个密切相关的调试系统(在一个标题中声明,用于歇斯底里的理由)。较简单的一个具有单个调试级别和printf类函数,这些函数采用调试级别,并且仅在调试级别设置足够高时才发出输出。更复杂的调试子系统提供了不同的调试子系统,每个子系统的行为与更简单的子系统类似(因此,例如,我可以在与输入调试、规则调试或…)不同的级别进行宏调试

您的问题没有解决的另一个问题是如何在运行时启用调试。我一直使用命令行选项——通常使用“
-d
”进行“3级基本调试”,使用“
-d nn
”进行nn级调试。或者,对于复杂系统:“
-D输入=3,宏=5,规则=1
”。拥有具有相同语义的环境变量并不困难

从实现这些功能的标题:

/*
** Usage:  TRACE((level, fmt, ...))
** "level" is the debugging level which must be operational for the output
** to appear. "fmt" is a printf format string. "..." is whatever extra
** arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
** -- See chapter 8 of 'The Practice of Programming', by Kernighan and Pike.
*/
#ifdef DEBUG
#define TRACE(x)    db_print x
#else
#define TRACE(x)    do { if (0) db_print x; } while (0)
#endif /* DEBUG */

#ifndef lint
#ifdef DEBUG
/* This string can't be made extern - multiple definition in general */
static const char jlss_id_debug_enabled[] = "@(#)*** DEBUG ***";
#endif /* DEBUG */
#ifdef MAIN_PROGRAM
const char jlss_id_debug_h[] = "@(#)$Id: debug.h,v 3.6 2008/02/11 06:46:37 jleffler Exp $";
#endif /* MAIN_PROGRAM */
#endif /* lint */

#include <stdio.h>

extern int      db_getdebug(void);
extern int      db_newindent(void);
extern int      db_oldindent(void);
extern int      db_setdebug(int level);
extern int      db_setindent(int i);
extern void     db_print(int level, const char *fmt,...);
extern void     db_setfilename(const char *fn);
extern void     db_setfileptr(FILE *fp);
extern FILE    *db_getfileptr(void);

/* Semi-private function */
extern const char *db_indent(void);

/**************************************\
** MULTIPLE DEBUGGING SUBSYSTEMS CODE **
\**************************************/

/*
** Usage:  MDTRACE((subsys, level, fmt, ...))
** "subsys" is the debugging system to which this statement belongs.
** The significance of the subsystems is determined by the programmer,
** except that the functions such as db_print refer to subsystem 0.
** "level" is the debugging level which must be operational for the
** output to appear. "fmt" is a printf format string. "..." is
** whatever extra arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
*/
#ifdef DEBUG
#define MDTRACE(x)  db_mdprint x
#else
#define MDTRACE(x)  do { if (0) db_mdprint x; } while (0)
#endif /* DEBUG */

extern int      db_mdgetdebug(int subsys);
extern int      db_mdparsearg(char *arg);
extern int      db_mdsetdebug(int subsys, int level);
extern void     db_mdprint(int subsys, int level, const char *fmt,...);
extern void     db_mdsubsysnames(char const * const *names);
/*
**用法:跟踪((级别、fmt等))
**“级别”是调试级别,对于输出必须是可操作的
**出现。“fmt”是一个printf格式字符串。“…”是额外的
**fmt需要的参数(可能没有)。
**非调试宏意味着代码已验证,但从未调用过。
**--参见Kernighan和Pike的《编程实践》第8章。
*/
#ifdef调试
#定义跟踪(x)db_打印x
#否则
#定义跟踪(x)do{if(0)db_print x;}while(0)
#endif/*调试*/
#ifndef皮棉
#ifdef调试
/*一般来说,无法将此字符串设置为外部多个定义*/
静态常量字符jlss_id_debug_enabled[]=“@(#))***debug***”;
#endif/*调试*/
#ifdef主程序
const char jlss_id_debug_h[]=“@(#)$id:debug.h,v3.6 2008/02/11 06:46:37 jleffler Exp$”;
#endif/*主程序*/
#endif/*lint*/
#包括
外部内部db_getdebug(无效);
外部内部db_新缩进(无效);
外部内部数据库(无效);
外部内部数据库_setdebug(内部级别);
外部数据集缩进(内部i);
外部无效db_打印(内部级别,常量字符*fmt,…);
外部无效db_setfilename(const char*fn);
外部无效db_setfileptr(文件*fp);
外部文件*db_getfileptr(无效);
/*半私有函数*/
外部常量字符*db_缩进(无效);
/**************************************\
**多调试子系统代码**
\**************************************/
/*
**用法:MDTRACE((子系统、级别、fmt等))
**“subsys”是该语句所属的调试系统。
**子系统的重要性由程序员确定,
**除了db_print等功能指的是子系统0。
**“级别”是调试级别,对于
**要显示的输出。“fmt”是一个printf格式字符串。“…”是
**fmt需要的任何额外参数(可能没有)。
**非调试宏意味着代码已验证,但从未调用过。
*/
#ifdef调试
#定义MDTRACE(x)db\u mdprint x
#否则
#定义MDTRACE(x)do{if(0)db_mdprint x;}while(0)
#endif/*调试*/
外部int db_mdgetdebug(int subsys);
extern int db_mdparsearg(char*arg);
外部int db_mdsetdebug(int子系统,int级);
外部无效db_mdprint(整数子系统、整数级别、常量字符*fmt等);
外部无效数据库名称(字符常量*常量*名称);
在Windows(以及整个Microsoft)中,我们广泛使用(ETW)。ETW是一种高效的静态日志记录机制。NT内核和许多组件都经过了很好的测试。ETW有很多优点:

  • 任何ETW事件提供程序都可以在运行时动态启用/禁用-无需重新启动或进程重新启动。大多数ETW提供程序对单个事件或事件组提供细粒度控制
  • 事件数据的格式化不是在运行时完成的(这可能非常昂贵)。它在事件跟踪进行后处理时完成
  • ETW是该问题的基本机制。如果您正确构建了您的工具,您可以免费获得应用程序级日志记录
  • 您的组件可以支持非常细粒度的事件启用,可以是单独的、按级别的,也可以是分组的(或任何组合的)。您还可以在我们的代码中放置多个提供者
  • 来自任何提供者(最重要的是内核)的事件都可以合并到一个跟踪中,这样所有事件都可以关联起来
  • 合并的轨迹可以从框中复制出来,并使用符号进行完全处理
  • NT内核样本配置文件中断可以生成ETW事件-这会产生一个非常轻的样本配置文件,可以随时使用
  • 在Vista和Windows Server 2008上,记录事件是无锁且完全支持多核的,每个处理器上的线程都可以独立记录事件,而不需要在事件之间进行同步
  • ETW在注销日志时也很有效——它只是一个简单的布尔检查(ETW会执行此操作,或者您可以显式执行此操作)。注意,这不需要内核模式转换。这一切都是在过程中完成的
这对我们非常有价值,也可以用于您的Windows代码-ETW可由任何组件使用-包括用户模式、驱动程序和其他内核组件

许多人喜欢在程序或组件运行时观察日志输出。使用ETW很容易做到这一点。我们经常做的是编写一个流式ETW消费者。我没有将printfs放在代码中,而是将ETW事件放在有趣的地方。当我的组件运行时,我可以随时运行我的ETW观察程序——观察程序接收事件并显示它们、统计它们或执行其他有趣的操作
#define LOG_FATAL    (1)
#define LOG_ERR      (2)
#define LOG_WARN     (3)
#define LOG_INFO     (4)
#define LOG_DBG      (5)

#define LOG(level, ...) do {  \
                            if (level <= debug_level) { \
                                fprintf(dbgstream,"%s:%d:", __FILE__, __LINE__); \
                                fprintf(dbgstream, __VA_ARGS__); \
                                fprintf(dbgstream, "\n"); \
                                fflush(dbgstream); \
                            } \
                        } while (0)
extern FILE *dbgstream;
extern int  debug_level;