Sooner or later you'll want to add logging to your Qt program. What are your options? After looking around the web a few years ago, I was disappointed to find out that there was no official Qt logging library. There is some support in Qt for logging, but you won't get a logger component and it's not immediately obvious how to log into a file.
The third party support that I had found wasn't particularly encouraging:
- There was a Log4Qt based on Log4j that was at version 0.1 back then. It has since reached version 0.3 at the beginning of 2009, but there are no check-ins for the last 12 months.
- Then there was the QxtLogger class, part of the qxt library. This one looked much more promising, but the API is a bit cumbersome (you have to pass the things that you want to log as QVariants or as a QVariantList) and it was LGPL, which meant an extra dynamic library dependency.
- A multitude of non-Qt C++ loggers, heavyweights like log4cxx or Pantheios.
What I really wanted was a simple logging library that knew about Qt types that I could just drop into my project in a few cpp files and then forget about it. After reading the Qt docs, I wrote a proof of concept logger with multiple destinations and logging levels. It would use qInstallMsgHandler to set up a static function that would build a message based on the text received from qDebug(), the current date and time, the log level and send it to a list of destinations.
That was the same implementation that I cleaned up and decided to publish yesterday, only to find out in the mean time that using qInstallMsgHandler is not a good idea at all...
The simple solution that fails
The Qt docs mention that one could install a custom message handler that is called by the qDebug function family when doing the output. Basically, when you write qWarning() << "Parameter out of range:" << 1.0 << << "-" << 2.0, the custom message handler will see an array of char "Parameter out of range: 1.0 - 2.0" and the message type, which is one of debug, warning, critical and fatal.
There are two issues with this approach:
- the custom message handler must be a free function
- there are too few logging levels!
My original implementation easily solved the first problem by using a singleton logger class. The custom handler just called Logger::instance() and was itself a static member of Logger, which meant that it had access to all its member variables.
The second problem was more tricky, but I managed to get around it by writing a special character at the beginning of the log message depending on the log level. If I wanted to log an info message, I would write QLOG_INFO() << "message", which was translated into qDebug() << QsLogging::detail::infoMessage << "message". The custom handler would then remove the QsLogging::detail::infoMessage from the message and pass it on to each destination.
Simple and it worked: I could use more logging levels and log to multiple destinations with a convenient syntax. Unfortunately, this custom handler had some subtle bugs.
For starters, when you install a custom message handler, all messages from qDebug/qWarning/etc will be sent to your handler. This means that Qt's warnings or asserts would also end up in the handler and that the logger itself could trigger another log call. This could result either in a deadlock or an endless loop.
I had two choices: not use any Qt types in the custom handler and the log class or detect Qt's messages and send them to the default handler. I chose the second, only to find out that there is no default handler, or at least not always.
qInstallMsgHandler can return NULL as the old handler. The function that calls the custom message handler is named qtmessageoutput and lives in qdebug.h. The docs mention that
The default message handler prints the message to the standard output under X11 or to the debugger under Windows. If it is a fatal message, the application aborts immediately.
The defaults are coded directly in qtmessageoutput, which checks to see if there's any handler installed. My old handler was zero so I couldn't forward Qt's messages to it and I couldn't forward them to my log. Even if I could somehow access the default handler, it was not thread safe, which meant that I would have to use platform specific synchronization (QMutex would not be available).
Custom message handlers were starting to be more trouble than they were worth, so I started looking at the qDebug implementation. qDebug just returns an instance of the QDebug object, which does all the message formatting and sends the final message to qtmessageoutput when it's destroyed.
The even simpler solution
Maybe I could use QDebug to get all the formatting for free? The QDebug constructor can take a QtMsgType debug level, a QIODevice or a QString. Only when a QtMsgType is used will the QDebug destructor call qtmessageoutput.
As an example you could easily write a class MyFileLog which keeps a QFile and gives you access to a QDebug(MyFileLog::theFile) for logging. In my case, I used the QString constructor and a wrapper to make QDebug give me the formatted log message and notify the logger that it should send the message to the destinations.
The final implementation is available at my bitbucket repository under a BSD license. It features a file and debugger destination, 6 logging levels, level filtering and can be added directly into a project. Since it's based on QDebug, you can log most Qt types such as QVector or QHash directly or extend it for other types.
My conclusion is that you can use qInstallMsgHandler to control Qt's messages, but you should use something more flexible for logging. Give QsLog a try and let me know what you think. Bugs & suggestions can be submitted directly at the repository.