【问题标题】:How to separate logging logic from business logic in a C program? And in a C++ one?如何在 C 程序中将日志记录逻辑与业务逻辑分开?在 C++ 中?
【发布时间】:2011-05-09 20:50:07
【问题描述】:

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

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

您在 C 中使用过任何类似的技术来解决这个问题吗?

我知道我可以放置一个名为 DEBUG 的标志,它可以打开或关闭,这样我就不必在每次想要显示或隐藏 printfs 时到处评论/取消评论我的整个代码。问题是我还想摆脱代码中的日志记录逻辑。

如果我用 C++ 编码而不是 C,会更好吗?

编辑

似乎有一个AspectC++,所以对于C++来说似乎有一个解决方案。 C呢?

谢谢

【问题讨论】:

  • AspectC++ 看起来好像可以用于与 C 非常接近的代码,以实现大多数实际用途。例如,您似乎可以加入免费函数(即不属于任何类的函数)。
  • 什么是 Java/.NET 方面?
  • @DeadMG: en.wikipedia.org/wiki/Aspect-oriented_programming 可能会让您入门。不过,我认为如果不实际使用它们,可能无法理解这些方面。

标签: c++ c logging aspects


【解决方案1】:

您无法真正将日志记录与您想要记录的算法分开。有策略地放置日志语句需要时间和经验。通常,代码在其整个生命周期内不断组装日志记录语句(尽管它是渐近的)。通常,日志会随着代码的发展而发展。如果算法经常改变,那么日志代码通常也会改变。

您可以做的就是尽可能不显眼记录日志。也就是说,确保日志语句始终是单行的,不会中断对算法的读取,使其他人可以在现有算法中插入额外的日志语句,而无需完全了解您的日志库等。

简而言之,像对待字符串处理一样对待日志记录:将其包装在一个不错的小库中,该库将被包含并在几乎所有地方使用,使该库快速,并使其易于使用。

【讨论】:

    【解决方案2】:

    不是真的。

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

    #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 确实声称提供了带有支持 AOP 的语言扩展的 C 和 C++ 编译器。我不知道它处于什么状态,如果您使用它,那么您当然不再真正编写 C(或 C++)了。

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

    【讨论】:

    • 从运行的角度来看,生成的代码仍然看起来像 C / C++ 代码,这就是我所关心的。
    • @devoured elysium:很好,我的意思是,如果您必须使用他们的编译器来获得额外的语言功能,这意味着您没有使用当前工具链提供的任何编译器。不知道会不会出问题。
    【解决方案3】:

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

    使用额外的隐藏verbose_level 参数可以过滤消息。

    在这种情况下,日志记录逻辑将只包含

    dprintf_cond(flags_or_verbose_level, msg, param1, param2);
    

    并且没有必要将其与其余代码分开。

    【讨论】:

      【解决方案4】:

      标志和正确的逻辑可能是更安全的方法,但您可以在编译类型时这样做。 IE。使用 #define 和 #ifdef 来包含/排除 printfs。

      【讨论】:

      • 但是如何分离业务逻辑和日志逻辑呢?
      【解决方案5】:

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

      所以我最终构建了一个小型异常处理框架,这意味着我可以将所有异常消息集中在一个类中。我会在启动时用我的(可能参数化的)消息初始化那个类,这让我可以写像throw CommunicationException(28, param1, param2)(可变参数)这样的东西。我想我会因此受到一些抨击,但它使代码无限地更具可读性。例如,唯一的危险是您可能会在不经意间抛出该异常并使用消息 #27 而不是 #28。

      【讨论】:

      • 听起来你只是过度使用了异常。
      【解决方案6】:
      #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) 的内容只是让您在使用它们时将; 放在语句之后,就像调用常规函数时一样。

      如果您不想花哨,可以取消调试级别部分。这样做只是为了让您可以根据需要更改调试/跟踪日期的级别。

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

      #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

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

      【讨论】:

        猜你喜欢
        • 2015-11-10
        • 2012-07-17
        • 2013-12-15
        • 1970-01-01
        • 1970-01-01
        • 2010-12-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多