4

I am working on a logger framework for QT applications. I am not using QMessageLogger directly because of understanding and learning purposes. There is one thing about one QMessageLogger functionality that I would really like to have in my logger but I dont know how does it work. Lets take for example the qDebug macro:

#define qDebug QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).debug

One can call this function in 2 ways: 1st way:

qDebug("abc = %u", abc);

2nd way:

qDebug() << "abc = " << abc;

I am looking at the library code, but I cannot quite understand how is it implemented that one can work with QMessageLogger by using va_args as well as some stream object. How can I achieve such effect? I would really appreciate all help, would be grateful for an example.

Here is my print method body. I need to achieve simmilar functionality with the "stream" way:

/*!
 * \brief Adds the log line to the print queue.
 * \param lvl: Log level of the line.
 * \param text: Formatted input for va_list.
 */
void CBcLogger::print(MLL::ELogLevel lvl, const char* text, ...)
{
    // check if logger initialized
    if (!m_loggerStarted)
        return;

    // check if log level sufficient
    if (lvl > m_setLogLvl)
        return;

    logLine_t logline;
    logline.loglvl = lvl;
    logline.datetime = QDateTime::currentDateTime();

    va_list argptr;
    va_start(argptr, text);

    char* output = NULL;
    if (vasprintf(&output, text, argptr))
    {
        logline.logstr = output;
        delete output;
    }

    va_end(argptr);
    emit addNewLogLine(logline);
}
Łukasz Przeniosło
  • 2,725
  • 5
  • 38
  • 74

1 Answers1

7

First, you need to understand what is the following

QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).debug

The above line constructs a QMessageLogger instance and immediately accesses its debug member. Since it is a macro, it's also important what you write in code right after it.

If you look at what QMessageLogger::debug is, you'll see four overloads, and the first two of them are pertinent to your question:

void debug(const char *msg, ...) const Q_ATTRIBUTE_FORMAT_PRINTF(2, 3);
QDebug debug() const;
QDebug debug(const QLoggingCategory &cat) const;
QDebug debug(CategoryFunction catFunc) const;

Now the matter should be simple. If you call qDebug("abc = %u", abc), you're calling the first overload, and the expanded macro is as follows:

QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).debug("abc = %u", abc)

which is more or less equal to

QMessageLogger temp(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC);
temp.debug("abc = %u", abc);

In the second case you're calling an overload that returns a QDebug object. QDebug has overloaded operator<<. The expanded macro is as follows:

QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).debug() << "abc = " << abc;

which is more or less equal to

QMessageLogger temp(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC);
QDebug anotherTemp = temp.debug();
anotherTemp << "abc = " << abc;

Here's a simple implementation of such logger:

void addNewLogLine(char const* ptr){
    cout << "addNewLogLine: " << ptr << endl;
}
struct LoggerHelper
{
    std::stringstream s;

    explicit LoggerHelper()=default;
    LoggerHelper(LoggerHelper&&) = default;

    ~LoggerHelper(){
        auto str = s.str();
        addNewLogLine(str.c_str());
    }

    template<typename T>
    LoggerHelper& operator<<(T const& val){
        s << val;
        return *this;
    }
};

struct Logger
{
    void operator()(char const* fmt, ...) const {
        char* buf;
        va_list args;
        va_start(args, fmt);
        vasprintf(&buf, fmt, args);
        va_end(args);
        addNewLogLine(buf);
        free(buf);
    }

    LoggerHelper operator()() const {
        return LoggerHelper{};
    }
};

demo

Several notes:

  • I adhered to your interface, but personally, I'd use variadic templates instead of va_args
  • you're supposed to free the buffer returned by vasprintf. free is not interchangeable with delete or delete[]
  • I used std::stringstream, but changing it to QTextStream or any other should be simple enough
  • You don't need to implement helper as a separate class if you're okay with allowing log << "foo" << "bar" syntax as opposed to log() << "foo" << "bar"
krzaq
  • 16,240
  • 4
  • 46
  • 61
  • So if my standard method is: `void print(MLL::ELogLevel lvl, const char* text, ...);`, should I add an overload, looking maybe like this: `QTextStream print(MLL::ELogLevel lvl) const;` ? – Łukasz Przeniosło Oct 31 '16 at 22:30
  • @ŁukaszPrzeniosło I kinda missed the second part of the question. I'll update it. You can do what you did, but tbh I'd just overload `operator()` in my logger, as well as `operator<<` – krzaq Oct 31 '16 at 22:34
  • I would appreciate it. Because for now I am not sure how to do it the way I showed, because in the pint function I check if logger is initialised and if not i return. But here I have to return a `QTextStream` so I cant return ie. 0. – Łukasz Przeniosło Oct 31 '16 at 22:37
  • I have added my current `print` method to the question. – Łukasz Przeniosło Oct 31 '16 at 22:40
  • @ŁukaszPrzeniosło added an example, pointed out a bug in your code ;) – krzaq Oct 31 '16 at 23:13
  • I didnt know about the free vs delete problem, thank you. I will try to implement your example. Thank you a lot. – Łukasz Przeniosło Oct 31 '16 at 23:21
  • @ŁukaszPrzeniosło let me know if you have problems :) – krzaq Oct 31 '16 at 23:22
  • I did run a a slight problem... The general concept you introduced works, but I am having problems implementing it into my current system. Here are my classes declarations and an example main file: https://pastebin.com/R3vYkwX4 . At line 85 compiller will call an error, because the struct `LoggerHelper` is not known yet. Also for some reason he doesnt allow me to do forward declaration. This is the last thing I need to do. I would appreciate help. – Łukasz Przeniosło Nov 01 '16 at 00:09
  • 1
    @ŁukaszPrzeniosło http://pastebin.com/HV8hP2J4 line 73 fwd declaration, 87 changed op() to just declaration inside the class, 130 defined op() – krzaq Nov 01 '16 at 00:13
  • Damn you are fast... :I will check this 1st thing in the morning because I had to close the pc now. Thank you! – Łukasz Przeniosło Nov 01 '16 at 00:16
  • I cannot figure out how to call the created operator through the instance. It should be something like this I believe `CBcLogger::instance()(MLL::ELogLevel::LInfo) << "test 2";` but I cannot get it to compile. – Łukasz Przeniosło Nov 01 '16 at 02:26
  • 1
    Ok, I think I managed to do this. I added an override print function that looks like this: `LoggerHelper CBcLogger::print(MLL::ELogLevel lvl) const { return LoggerHelper(lvl); }`. I am not sure this is the right way and I am sure I dont understand this fully yet, but I am happy it works. Thank you very much @krzaq – Łukasz Przeniosło Nov 01 '16 at 02:41