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

6

7

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

Hama answered 14/11, 2010 at 16:22 Comment(3)
AspectC++ looks as if it might work for code which is close enough to C for most practical purposes. For example it looks as if you can join free functions (i.e. functions which aren't members of any class).Sayles
What actually are Java/.NET aspects?Buckboard
@DeadMG: en.wikipedia.org/wiki/Aspect-oriented_programming might get you started. I think it may be impossible to understand aspects without actually using them, though.Sayles
M
10

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.

Mcdade answered 14/11, 2010 at 16:48 Comment(0)
S
3

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.

Sayles answered 14/11, 2010 at 16:34 Comment(2)
The resulting code will still look from a running standpoint as C / C++ code, and that is all that I care about.Hama
@devoured elysium: fine, my point is just that if you have to use their compiler to get the extra language features, that means you aren't using whatever compiler your current toolchain provides. I don't know whether that will cause problems.Sayles
S
2

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.

Snowstorm answered 14/11, 2010 at 16:33 Comment(0)
R
1

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.

Recognizance answered 14/11, 2010 at 16:29 Comment(1)
But how to separate the business logic from the logging logic?Hama
T
1

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.

Trounce answered 14/11, 2010 at 16:47 Comment(1)
Sounds to me like you just over-used exceptions.Buckboard
F
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

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.

Fiche answered 14/11, 2010 at 22:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.