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

发布于 2024-10-02 08:13:50 字数 398 浏览 6 评论 0原文

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

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

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

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

如果用 C++ 代替 CI 进行编码,会不会更好?

编辑

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

谢谢

I am currently coding in C and I have lots of printfs so that I can track, at some times, the flow of my application. The problem is that some times I want more detail than others, so I usually spend my time commenting/uncommenting my C code, so I can get the appropriate output.

When using Java or C#, I can generally separate both my implementation code from the logging logic by using Aspects.

Is there any similar technique you use in C to get around this problem?

I know I could put a flag called DEBUG that could be either on or off, so I wouldn't have to go all around and comment/uncomment my whole code every time I want to either show or hide the printfs. The question is I'd like to also get rid of the logging logic in my code.

If instead of C I was coding in C++, would it be any better?

Edit

It seems there is an AspectC++, so for C++ there seems to be a solution. What about C?

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

地狱即天堂 2024-10-09 08:13:50

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

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

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

IME you cannot really separate logging from the algorithms that you want to log about. Placing logging statements strategically takes time and experience. Usually, the code keeps assembling logging statements over its entire lifetime (though it's asymptotic). Usually, the logging evolves with the code. If the algorithm changes often, so will usually the logging code.

What you can do is make logging as unobtrusive as possible. That is, make sure logging statements always are one-liners that do not disrupt reading the algorithm, make it so others can insert additional logging statements into an existing algorithm without having to fully understand your logging lib, etc.

In short, treat logging like you treat string handling: Wrap it in a nice little lib that will be included and used just about everywhere, make that lib fast, and make it easy to use.

爱格式化 2024-10-09 08:13:50

并不真地。

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

#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 的调用都可以替换为允许实际日志记录格式和目标与业务逻辑分离的内容(其他宏或可变函数调用),但某些事实确实不可能进行这种记录。

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

请记住,C++ 具有多重继承,这有时有助于解决横切问题。有了足够的模板,您就可以做一些了不起的事情,甚至可能实现您自己的方法调度系统,该系统允许某种连接点,但这是一件大事。

Not really.

If you have variadic macros available, you can easily play games like this:

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

You can also have logging that's turn-off-and-onable at a finer granularity:

#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;
}

Obviously this can be a bit tedious with only allowing a single return from each function, since you can't directly get at the function return.

Any of those macros and calls to printf could be replaced with something (other macros, or variadic function calls) that allows the actual logging format and target to be separated from the business logic, but the fact of some kind of logging being done can't be, really.

aspectc.org does claim to offer a C and C++ compiler with language extensions supporting AOP. I have no idea what state it's in, and if you use it then of course you're not really writing C (or C++) any more.

Remember that C++ has multiple inheritance, which is sometimes helpful with cross-cutting concerns. With enough templates you can do remarkable things, perhaps even implementing your own method dispatch system that allows some sort of join points, but it's a big thing to take on.

耳钉梦 2024-10-09 08:13:50

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

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

在这种情况下,日志逻辑将仅包含

dprintf_cond(flags_or_verbose_level, msg, param1, param2);

,并且无需将其与其余代码分开。

On GCC you could use variadic macros: http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html . It makes possible to define dprintf() with any number of parameters.

Using additional hidden verbose_level parameter you can filter the messages.

In this case the logging loggic will only contain

dprintf_cond(flags_or_verbose_level, msg, param1, param2);

and there will be no need in separating it from the rest of code.

浅忆流年 2024-10-09 08:13:50

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

A flag and proper logic is probably the safer way to do it, but you could do the same at compile type. Ie. Use #define and #ifdef to include/exclude the printfs.

几度春秋 2024-10-09 08:13:50

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

因此,我最终构建了一个小型异常处理框架,这意味着我可以将所有异常消息集中在一个类中。我会在启动时使用我的(可能是参数化的)消息初始化该类,这使我能够编写诸如 throw CommunicationException(28, param1, param2) (可变参数)之类的内容。我想我会因此受到一些批评,但它使代码的可读性无限增强。例如,唯一的危险是您可能会无意中用消息 #27 而不是 #28 抛出该异常。

Hmm, this sounds similar to a problem I encountered when working on a C++ project last summer. It was a distributed app which had to be absolutely bulletproof and this resulted in a load of annoying exception handling bloat. A 10 line function would double in size by the time you added an exception or two, because each one involved building a stringstream from a looong exception string plus any relevant parameters, and then actually throwing the exception maybe five lines later.

So I ended up building a mini exception handling framework which meant I could centralise all my exception messages inside one class. I would initialise that class with my (possibly parameterised) messages on startup, and this allowed me to write things like throw CommunicationException(28, param1, param2) (variadic arguments). I think I'll catch a bit of flak for that, but it made the code infinitely more readable. The only danger, for example, was that you could inadvertently throw that exception with message #27 rather than #28.

淡淡的优雅 2024-10-09 08:13:50
#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

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

#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

The ## before __VA_ARGS__ is a GCC specific thing that makes , __VA_ARGS__ actually turn into the right code when there are no actual extra arguments.

The do { ... } while (0) stuff is just to make you put ; after the statements when you use them, like you do when you call regular functions.

If you don't want to get as fancy you can do away with the debug level part. That just makes it so that if you want you can alter the level of debugging/tracing date you want.

You could turn the entire print statement into a separate function (either inline or a regular one) that would be called regardless of the debug level, and would make the decision as to printing or not internally.

#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);
}

If you are using a *nix system then you should have a look at syslog.

You might also want to search for some tracing libraries. There are a few that do similar things to what I have outlined.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文