如何在C程序中将日志记录逻辑与业务逻辑分开? 在C ++中?

我目前正在用C编码,我有很多printfs,所以我可以在某些时候跟踪我的应用程序的流程。 问题是有时我想要比其他人更多的细节,所以我通常花时间评论/取消注释我的C代码,所以我可以获得适当的输出。

使用Java或C#时,我通常可以使用Aspects将我的实现代码与日志记录逻辑分开。

你在C中使用了类似的技术来解决这个问题吗?

我知道我可以放置一个名为DEBUG的标志,可以打开或关闭,所以每次我想要显示或隐藏printfs时,我都不必全身心地评论/取消注释我的整个代码。 问题是我还想摆脱代码中的日志记录逻辑。

如果用C ++编写代码而不是CI,它会更好吗?

编辑

似乎有一个AspectC ++,所以对于C ++似乎有一个解决方案。 那C怎么样?

谢谢

IME您无法真正将日志记录与要记录的算法分开。 策略性地放置日志记录需要时间和经验 。 通常,代码会在整个生命周期内保持组合日志语句(尽管它是渐近的)。 通常, 日志记录随代码而发展 。 如果算法经常更改,通常会记录日志代码。

您可以做的是使记录尽可能不引人注目 。 也就是说,确保日志记录语句始终是单行,不会破坏读取算法,使其他人可以将其他日志记录语句插入到现有算法中,而无需完全了解您的日志记录库等。

简而言之, 像处理字符串处理一样处理日志记录 :将它包装在一个很好的小lib中,它将被包含在任何地方并使用,快速创建lib,并使其易于使用。

并不是的。

如果你有可变的宏,你可以轻松地玩这样的游戏:

#ifdef NDEBUG #define log(...) (void)0 #else #define log(...) do {printf("%s:%d: ", __FILE__, __LINE__); printf(__VA_ARGS__);} while(0) #endif 

您还可以以更精细的粒度关闭和打开日志记录:

 #define LOG_FLAGS ; #define maybe_log(FLAG, ...) do { if (FLAG&LOG_FLAGS) printf(__VA_ARGS__);} while(0) int some_function(int x, int y) { maybe_log(FUNCTION_ENTRY, "x=%d;y=%d\n", x, y); ... do something ... maybe_log(FUNCTION_EXIT, "result=%d\n", result); return result; } 

显然,这可能有点单调乏味,只允许从每个函数返回一次,因为你无法直接获得函数返回。

任何这些宏和对printf调用都可以替换为允许实际日志记录格式和目标与业务逻辑分离的东西(其他宏或可变函数调用),但是某种日志记录的事实可以“真的,真的。

aspectc.org声称提供了一个C和C ++编译器,其语言扩展支持AOP。 我不知道它处于什么状态,如果你使用它,那么你当然不再真正编写C(或C ++)了。

请记住,C ++具有多重inheritance,这有时对横切关注有帮助。 有了足够的模板,您可以做出非凡的事情,甚至可能实现自己的方法调度系统,允许某种连接点,但这是一件很重要的事情。

在GCC上,您可以使用可变参数宏: http : //gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html 。 可以使用任意数量的参数定义dprintf()

使用其他隐藏的verbose_level参数,您可以过滤消息。

在这种情况下,log loggic只包含

 dprintf_cond(flags_or_verbose_level, msg, param1, param2); 

并且不需要将其与其余代码分开。

标志和正确的逻辑可能是更安全的方法,但您可以在编译类型中执行相同操作。 IE浏览器。 使用#define和#ifdef来包含/排除printfs。

嗯,这听起来类似于我去年夏天在C ++项目中遇到的问题。 这是一个分布式的应用程序,必须绝对防弹,这导致一堆恼人的exception处理膨胀。 添加一个或两个exception时,10行函数的大小将加倍,因为每个函数都涉及从一个looongexception字符串加上任何相关参数构建一个字符串流,然后实际抛出exception可能是五行之后。

所以我最终构建了一个迷你exception处理框架,这意味着我可以将所有exception消息集中在一个类中。 我会在启动时使用我的(可能是参数化的)消息初始化该类,这允许我编写诸如throw CommunicationException(28, param1, param2) (可变参数)之类的东西。 我想我会抓住一些优势,但它使代码更具可读性。 例如,唯一的危险是你可能会无意中抛出#27而不是#28的exception。

 #ifndef DEBUG_OUT # define DBG_MGS(level, format, ...) # define DBG_SET_LEVEL(x) do{}while(0) #else extern int dbg_level; # define DBG_MSG(level, format, ...) \ do { \ if ((level) >= dbg_level) { \ fprintf(stderr, (format), ## __VA_ARGS__); \ } \ } while (0) # define DBG_SET_LEVEL(X) do { dbg_level = (X); } while (0) #endif 

__VA_ARGS__之前的##是一个GCC特定的东西,当没有实际的额外参数时, __VA_ARGS__实际上变成了正确的代码。

do { ... } while (0)东西只是为了让你放; 在您使用它们之后的声明之后,就像您在调用常规函数时所做的那样。

如果您不想那么喜欢,可以取消调试级别部分。 这样做只是为了让你可以改变你想要的调试/跟踪日期。

您可以将整个print语句转换为一个单独的函数(内联函数或常规函数),无论调试级别如何,都会调用它,并且可以在内部进行打印决定。

 #include  #include  int dbg_level = 0; void DBG_MGS(int level, const char *format, ...) { va_list ap; va_start(ap, format); if (level >= dbg_level) { vfprintf(stderr, format, ap); } va_end(ap); } 

如果您使用的是* nix系统,那么您应该查看syslog

您可能还想搜索一些跟踪库。 有一些与我概述的相似。