Pregunta

I was trying the logger code from this link, but it gives me error. How to implement a good debug/logging feature in a project

#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

More precisely, this #define is giving errors:

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

The errors are Syntax error: if and Syntax error: else

But later, I noticed that if I move #include "logger.hpp" from main.h to main.cpp, the problem disappeared. Though 'main.h' is included many times in different places, it does contain '#pragma once'.

Any idea?

¿Fue útil?

Solución

If loglevel is known at compile time you can do the following:

template <bool>
struct LogSystem
{
    template <class T>
    LogSystem& operator << (const T &)
    {
        //ignore the input
        return (*this);
    }
};

template <>
struct LogSystem <true>
{
    template <class T>
    LogSystem& operator << (const T & v)
    {
        cout << v;
        return (*this);
    }
};

template <bool B>
LogSystem<B>    getLog()
{
    return LogSystem<B>();
}

#define log(level) getLog< (level <= loglevel) >()

if loglevel is not known at compile time:

class iLogSystem
{
public:
    virtual iLogSystem& operator << (const int &)
    {
        //empty
        return (*this);
    }
    virtual iLogSystem& operator << (const custom_type &);
    {
        return (*this);
    }
    //make functions for logging all the types you want
};

class LogSystem : public iLogSystem
{
public:
    virtual iLogSystem& operator << (const int & v)
    {
        cout << v;
        return (*this);
    }
    virtual iLogSystem& operator << (const custom_type &  q);
    {
        cout << q.toString();
        return (*this);
    }
    //make functions for logging all the types you want
};
iLogSystem& getLog(const bool l)
{
    static LogSystem actual_log;
    static iLogSystem empty_log;
    if(l)
        return &actual_log;
    return &empty_log;
}

#define log(level) getLog( level <= loglevel )

Otros consejos

Any time you want to define a macro that expands to a statement, if the definition contains any compound statements (including if/else), you should wrap the definition in do ... while (0). The enclosed code will still execute exactly once, and it can be used in any context that requires a statement.

That's the only way I know of to avoid syntax errors when the macro is used within an if/else statement, due to the use of semicolons.

So rather that this:

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

you can use this:

#define log(level) \
    do { \
        if ((level) > loglevel) ; \
        else logIt(level) \
    } while (0)

I've added parentheses around references to the macro's level parameter, to avoid any possible operator precedence problems. Note also the lack of a semicolon at the end; the semicolon will be supplied by the caller.

On the other hand, an if/else can often be replaced by the conditional (ternary) ?: operator:

#define log(level) \
    ((level) > loglevel ? 0 : logIt(level))

which allows log(level) to be used anywhere an expression can be used; that includes statement context if you add a semicolon. You might want to replace 0 by something of the type returned by logIt; if logIt is a void function, you might want:

#define log(level) \
    ((level) > loglevel ? (void)0 : logIt(level))

This all assumes that a macro is the right tool for the job. It's likely that a template (as suggested by this answer) or an inline function will do the job better and with less potential for confusion.

Can #define preprocessor directive contain if and else?

Yes.

Regarding your problem: preprocessor is dumb as a rock and performs only simple text subsitution. It is not a function, it is not a language construct, it is simple, dumb text subsitution. As a a result, this:

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

...

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

Turns into this:

if (logINFO > loglevel); // << here's your problem.
else 
    logIt(logInfo)
 << "foo " << "bar " << "baz"; 

Your problem is;. Here, semicolon indicates end of c++ if statement, so when compiler encounters else afterwards, it doesn't know what to do with it.

I noticed that if I move #include "logger.hpp" from main.h to main.cpp, the problem disappeared

C++ has "logarithm" function. Which is called log. If your other files use logarithm function, things will get very interesting, because it'll be replaced by your if/else logging code everywhere.

For example, if there's inlined logarithm code somewhere in a header, it'll turn into nonsense if you include your logger header first. For example log(6.0) + 1 will turn into log (if (6.0 > logLevel); else logIt(6.0)) + 1, which is not a valid C++ statement.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top