14

I am making a smallish project, total of about 3-4 people. I want to have a solid way of debugging the application, by logs for example. Are there any good resources on how to structure it and such? I've heard a lot from project managers that a good logging feature is critical to each project, but I'm not sure how to do it.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
KaiserJohaan
  • 9,028
  • 20
  • 112
  • 199
  • 8
    I think good logging is important (if not essential), regardless of project size. Granted, it's possibly more important for long running server processes, but it can often lead to bug solutions in client apps, especially when user feedback can be unintentionally misleading or leaves out essential details. – Mitch Wheat May 29 '11 at 14:03
  • see http://stackoverflow.com/questions/696321/best-logging-framework-for-native-c – Mitch Wheat May 29 '11 at 14:05
  • 17
    @Neil - sorry, but I would've downvoted your comment if it were possible. It is quite counter-productive and patronizing – davka May 29 '11 at 14:25
  • In my case, I never use logging, but prefer having assertions crashing the program in case of detected problem. This forces developers fixing bugs as soon as they manifest. I'm not sure logging can help you fixing problems. I think it can only be usefull to detect what did trigger a bug, which can help reproducing it and correct them. – neodelphi May 29 '11 at 15:48

3 Answers3

17

I found this Dr. Dobb's article, Logging In C++, very useful regarding this subject.

Also on Dr. Dobb's: A Highly Configurable Logging Framework In C++

If all you want is a dead simple thread safe logging class which always outputs to stderr then you could use this class I wrote:

#ifndef _LOGGER_HPP_
#define _LOGGER_HPP_

#include <iostream>
#include <sstream>

/* consider adding boost thread id since we'll want to know whose writting and
 * won't want to repeat it for every single call */

/* consider adding policy class to allow users to redirect logging to specific
 * files via the command line
 */

enum loglevel_e
    {logERROR, logWARNING, logINFO, logDEBUG, logDEBUG1, logDEBUG2, logDEBUG3, logDEBUG4};

class logIt
{
public:
    logIt(loglevel_e _loglevel = logERROR) {
        _buffer << _loglevel << " :" 
            << std::string(
                _loglevel > logDEBUG 
                ? (_loglevel - logDEBUG) * 4 
                : 1
                , ' ');
    }

    template <typename T>
    logIt & operator<<(T const & value)
    {
        _buffer << value;
        return *this;
    }

    ~logIt()
    {
        _buffer << std::endl;
        // This is atomic according to the POSIX standard
        // http://www.gnu.org/s/libc/manual/html_node/Streams-and-Threads.html
        std::cerr << _buffer.str();
    }

private:
    std::ostringstream _buffer;
};

extern loglevel_e loglevel;

#define log(level) \
if (level > loglevel) ; \
else logIt(level)

#endif

Use it like this:

// define and turn off for the rest of the test suite
loglevel_e loglevel = logERROR;

void logTest(void) {
    loglevel_e loglevel_save = loglevel;

    loglevel = logDEBUG4;

    log(logINFO) << "foo " << "bar " << "baz";

    int count = 3;
    log(logDEBUG) << "A loop with "    << count << " iterations";
    for (int i = 0; i != count; ++i)
    {
        log(logDEBUG1) << "the counter i = " << i;
        log(logDEBUG2) << "the counter i = " << i;
    }

    loglevel = loglevel_save;
}
Robert S. Barnes
  • 39,711
  • 30
  • 131
  • 179
  • Hi, I tried to use your code but the `#define log(level) \ if (level > loglevel) ; \ else logIt(level)` gives me tons of errors. It says `syntax error: if` and `syntax error: else`. Any idea? Visual Studio 2012 Express – Boyang Nov 05 '13 at 15:40
  • @CharlesW. That's really strange. I compiled it with g++ on Linux. Did you keep the `#define` statement as three separate lines? You have to make sure there is no space or anything after the backslash \ character. – Robert S. Barnes Nov 05 '13 at 18:20
  • It's very strange. I solved the issue by avoiding multiple inclusion of `logger.hpp`. I asked a separate question here http://stackoverflow.com/questions/19795038/can-define-preprocessor-directive-contain-if-and-else/19795951?noredirect=1#19795951 – Boyang Nov 05 '13 at 18:43
  • BTW should the semicolon in `if (level > loglevel) ; \ ` be replaced by empty block {}? – Boyang Nov 05 '13 at 18:46
  • BTW should the semicolon in `if (level > loglevel) ; \\` be replaced by empty block {}? If not, `log(levelDEBUG) << "test"` will become `if (logDEBUG > loglevel); else logIt(logInfo) << "foo " << "bar " << "baz";` and the else won't have a if to attach, since the semicolon closed the if, isn't it? In fact why not just do `#define log(level) if (level <= loglevel)`? I don't know much about preprocessor directives. Thanks! – Boyang Nov 05 '13 at 19:08
  • @Boyang, the block is optional, a semicolon is equivalent to an empty block. – Fabio A. Apr 26 '18 at 10:15
  • The problem with this solution is that if you're tailing the log, you wont see the output of foo bar baz until the loop starts, because it only logs on the destructor. For main programs that have an event loop, this doesn't work. You'll see logs during operation but not the "app starting up" log or similar. This should be modified or allow an option for logging immediately. – rem45acp Jan 10 '19 at 19:47
4

If you are asking about logging frameworks and you work in C++, check out Apache's log4cxx. It takes a few moments to understand the architecture, but once you did, you realize that it is a good balance of flexibility, ease of use and (as they say) performance.

log4cxx has a very flexible configuration by witch you can control, without recompiling, where the output goes to (file / rotating file / console/etc.), the debugging level of subcomponents (e.g. you want to focus on a particular class / component so you set it to DEBUG level while the rest is on INFO), format of log entries etc.

If you are asking about general guidelines on how do logging, I haven't seen such (not that I actually looked for). I think this is mainly empiric - you decide what info is needed on each logging level like INFO, DEBUG etc., and you refine it according to your and your client's needs (don't forget that your client could also be a customer of the log, depending on your project).

davka
  • 13,974
  • 11
  • 61
  • 86
2

Depends on what you mean by "logging". One form is simply to provide a method for printing the contents of some object to an output stream. For an object of type ClassName this entails writing an insertion operator for the class:

std::ostream &operator<< (std::ostream &stream, const ClassName & obj) {
   // Implementation elided
}

With this at hand you can print an object of type ClassName to an output stream. This can be quite useful, so useful that some organizations require that every class implement such a method.

David Hammen
  • 32,454
  • 9
  • 60
  • 108