Reputation: 751
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?
Upvotes: 1
Views: 1036
Reputation: 98425
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();
}
Upvotes: 3