How to separate logging logic from business logic in a C program? And in a C++ one?

做~自己de王妃 提交于 2019-12-03 08:14:49

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.

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.

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.

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.

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.

#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.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!