I am using qDebug()
, qInfo()
and so on in combination of qInstallMessageHandler
to write my logfiles
. I also get an output on my batch screen, when executing my application.
I only found QT_NO_DEBUG_OUTPUT
, but I want to toggle this configuration while run-time. Is there a way to prevent Qt from writing to std output?
sadly no, you only get access to the messages, but cannot prevent from beeing written to std output.
That's false in Qt 5 at the very least. The message printing code looks as follows: you can clearly see that only the message handler is being used:
if (grabMessageHandler()) {
// prefer new message handler over the old one
if (msgHandler.load() == qDefaultMsgHandler
|| messageHandler.load() != qDefaultMessageHandler) {
(*messageHandler.load())(msgType, context, message);
} else {
(*msgHandler.load())(msgType, message.toLocal8Bit().constData());
}
ungrabMessageHandler();
} else {
fprintf(stderr, "%s\n", message.toLocal8Bit().constData());
}
As you can see, the fprintf(stderr, ...)
is a fallback only if recursion is detected from within the messageHandler
itself.
Thus, all you need to prevent any debug output is to implement and set your own messageHandler
.
To completely turn off all the debug output in Qt, the following works:
#include <QtCore>
int main() {
qDebug() << "I'm not quiet at all yet";
qInstallMessageHandler(+[](QtMsgType, const QMessageLogContext &, const QString &){});
qDebug() << "I'm very, very quiet";
}
In any case, a sensible implementation of an application-wide file logger might look as follows - it doesn't recreate the QTextStream
unnecessarily; it uses QString::toUtf8()
instead, and explicitly writes line endings.
#include <QtCore>
class Logger {
static struct Data {
Logger *instance;
QtMessageHandler chainedHandler;
} d;
bool m_isOpen;
QFile m_logFile;
QtMessageHandler m_oldHandler = {};
static void handler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
if (d.instance)
d.instance->log(msg);
if (d.chainedHandler)
d.chainedHandler(type, context, msg);
}
public:
enum ChainMode { DontChain, Chain };
Logger() {
Q_ASSERT(!instance());
m_logFile.setFileName("myLog.txt");
m_isOpen = m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text);
d.instance = this;
}
~Logger() { uninstallHandler(); }
bool isOpen() const { return m_isOpen; }
void installHandler(ChainMode mode) {
Q_ASSERT(!m_oldHandler);
m_oldHandler = qInstallMessageHandler(handler);
if (mode == Chain)
d.chainedHandler = m_oldHandler;
}
void uninstallHandler() {
if (m_oldHandler) {
m_oldHandler = nullptr;
d.chainedHandler = nullptr;
qInstallMessageHandler(m_oldHandler);
}
}
/// This method is *not* thread-safe. Use with a thread-safe wrapper such as `qDebug`.
void log(const QString & msg) {
if (isOpen()) {
m_logFile.write(msg.toUtf8());
m_logFile.write("\n", 1);
}
}
/// Closes the log file early - this is mostly used for testing.
void endLog() {
uninstallHandler();
m_logFile.close();
}
static Logger *instance() { return d.instance; }
};
Logger::Data Logger::d;
template <typename T> QByteArray streamOutputFor(const T &data) {
QBuffer buf;
buf.open(QIODevice::ReadWrite | QIODevice::Text);
QTextStream s(&buf);
s << data << endl;
buf.close();
return buf.data();
}
QByteArray readEnd(const QString &fileName, int count) {
QFile file(fileName);
if (file.open(QIODevice::ReadOnly | QIODevice::Text) && file.size() >= count) {
file.seek(file.size() - count);
return file.readAll();
}
return {};
}
void test() {
auto const entry = QDateTime::currentDateTime().toString().toUtf8();
Q_ASSERT(Logger::instance()->isOpen());
qDebug() << entry.data();
Logger::instance()->endLog();
auto reference = streamOutputFor(entry.data());
auto readback = readEnd("myLog.txt", reference.size());
Q_ASSERT(!reference.isEmpty());
Q_ASSERT(readback == reference);
}
int main() {
Logger logger;
logger.installHandler(Logger::DontChain);
qDebug() << "Something or else";
test();
}