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

时间:2010-11-14 16:22:43

标签: c++ c logging aspects

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

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

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

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

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

修改

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

由于

6 个答案:

答案 0 :(得分:10)

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

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

简而言之, 对待日志就像对待字符串处理一样 :将它包装在一个很好的小lib中,它将被包含在几乎无处不在的地方,快速创建lib,并使其易于使用。

答案 1 :(得分:5)

不是。

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

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

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

#define LOG_FLAGS <something>;

#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 ++有多重继承,这有时对横切关注有帮助。有了足够的模板,你可以做出非凡的事情,甚至可能实现你自己的方法调度系统,允许某种连接点,但这是一件很重要的事情。

答案 2 :(得分:4)

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

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

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

dprintf_cond(flags_or_verbose_level, msg, param1, param2);

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:0)

#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 <stdarg.h>
#include <stdio.h>

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

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