From c0d249019b098890fb8e5e9e144c2dd8029a670c Mon Sep 17 00:00:00 2001 From: Kai Koehne Date: Fri, 13 Apr 2012 13:37:56 +0200 Subject: [PATCH] Allow qDebug output to be configured by qSetMessagePattern() Add qSetMessagePattern() to configure the default message pattern. This one can still be overwritten by setting the QT_MESSAGE_PATTERN environment variable. Without this method, there's actually no way to change the default output programatically. Since QT_MESSAGE_PATTERN is evaluated when the first message arrives, setting it via e.g. qputenv might have no effect/be too late. Change-Id: I115e0c30606f128fdbf5c169a951ffa2a6a48517 Reviewed-by: Thiago Macieira --- src/corelib/global/qglobal.cpp | 33 +++++++++++ src/corelib/global/qlogging.cpp | 64 +++++++++++++++++----- src/corelib/global/qlogging.h | 2 + tests/auto/corelib/global/qlogging/app/main.cpp | 7 +++ .../auto/corelib/global/qlogging/tst_qlogging.cpp | 38 +++++++++++-- 5 files changed, 127 insertions(+), 17 deletions(-) diff --git a/src/corelib/global/qglobal.cpp b/src/corelib/global/qglobal.cpp index 7036b72..38ddd7b 100644 --- a/src/corelib/global/qglobal.cpp +++ b/src/corelib/global/qglobal.cpp @@ -3088,5 +3088,38 @@ bool QInternal::activateCallbacks(Callback cb, void **parameters) instead. \sa QtMsgHandler, qInstallMessageHandler */ +/*! + \fn void qSetMessagePattern(const QString &pattern) + \relates + \since 5.0 + + \brief Changes the output of the default message handler. + + Allows to tweak the output of qDebug(), qWarning(), qCritical() and qFatal(). + + Following placeholders are supported: + + \table + \header \li Placeholder \li Description + \row \li \c %{appname} \li QCoreApplication::applicationName() + \row \li \c %{file} \li Path to source file + \row \li \c %{function} \li Function + \row \li \c %{line} \li Line in source file + \row \li \c %{message} \li The actual message + \row \li \c %{pid} \li QCoreApplication::applicationPid() + \row \li \c %{threadid} \li ID of current thread + \row \li \c %{type} \li "debug", "warning", "critical" or "fatal" + \endtable + + The default pattern is "%{message}". + + The pattern can also be changed at runtime by setting the QT_MESSAGE_PATTERN + environment variable; if both qSetMessagePattern() is called and QT_MESSAGE_PATTERN is + set, the environment variable takes precedence. + + qSetMessagePattern() has no effect if a custom message handler is installed. + + \sa qInstallMessageHandler, Debugging Techniques + */ QT_END_NAMESPACE diff --git a/src/corelib/global/qlogging.cpp b/src/corelib/global/qlogging.cpp index 1f5b121..8726c18 100644 --- a/src/corelib/global/qlogging.cpp +++ b/src/corelib/global/qlogging.cpp @@ -45,6 +45,7 @@ #include "qstring.h" #include "qvarlengtharray.h" #include "qdebug.h" +#include "qmutex.h" #ifndef QT_BOOTSTRAPPED #include "qcoreapplication.h" #include "qthread.h" @@ -400,21 +401,53 @@ static const char appnameTokenC[] = "%{appname}"; static const char threadidTokenC[] = "%{threadid}"; static const char emptyTokenC[] = ""; +static const char defaultPattern[] = "%{message}"; + + struct QMessagePattern { QMessagePattern(); ~QMessagePattern(); + void setPattern(const QString &pattern); + // 0 terminated arrays of literal tokens / literal or placeholder tokens const char **literals; const char **tokens; + + bool fromEnvironment; + static QBasicMutex mutex; }; +QBasicMutex QMessagePattern::mutex; + QMessagePattern::QMessagePattern() + : literals(0) + , tokens(0) + , fromEnvironment(false) { - QString pattern = QString::fromLocal8Bit(qgetenv("QT_MESSAGE_PATTERN")); - if (pattern.isEmpty()) { - pattern = QLatin1String("%{message}"); + const QString envPattern = QString::fromLocal8Bit(qgetenv("QT_MESSAGE_PATTERN")); + if (envPattern.isEmpty()) { + setPattern(QLatin1String(defaultPattern)); + } else { + setPattern(envPattern); + fromEnvironment = true; } +} + +QMessagePattern::~QMessagePattern() +{ + for (int i = 0; literals[i] != 0; ++i) + delete [] literals[i]; + delete [] literals; + literals = 0; + delete [] tokens; + tokens = 0; +} + +void QMessagePattern::setPattern(const QString &pattern) +{ + delete [] tokens; + delete [] literals; // scanner QList lexemes; @@ -495,16 +528,6 @@ QMessagePattern::QMessagePattern() memcpy(literals, literalsVar.constData(), literalsVar.size() * sizeof(const char*)); } -QMessagePattern::~QMessagePattern() -{ - for (int i = 0; literals[i] != 0; ++i) - delete [] literals[i]; - delete [] literals; - literals = 0; - delete [] tokens; - tokens = 0; -} - Q_GLOBAL_STATIC(QMessagePattern, qMessagePattern) /*! @@ -515,6 +538,8 @@ Q_CORE_EXPORT QString qMessageFormatString(QtMsgType type, const QMessageLogCont { QString message; + QMutexLocker lock(&QMessagePattern::mutex); + QMessagePattern *pattern = qMessagePattern(); if (!pattern) { // after destruction of static QMessagePattern instance @@ -523,6 +548,10 @@ Q_CORE_EXPORT QString qMessageFormatString(QtMsgType type, const QMessageLogCont return message; } + // don't print anything if pattern was empty + if (pattern->tokens[0] == 0) + return message; + // we do not convert file, function, line literals to local encoding due to overhead for (int i = 0; pattern->tokens[i] != 0; ++i) { const char *token = pattern->tokens[i]; @@ -741,6 +770,14 @@ QtMsgHandler qInstallMsgHandler(QtMsgHandler h) return old; } +void qSetMessagePattern(const QString &pattern) +{ + QMutexLocker lock(&QMessagePattern::mutex); + + if (!qMessagePattern()->fromEnvironment) + qMessagePattern()->setPattern(pattern); +} + void QMessageLogContext::copy(const QMessageLogContext &logContext) { this->category = logContext.category; @@ -748,4 +785,5 @@ void QMessageLogContext::copy(const QMessageLogContext &logContext) this->line = logContext.line; this->function = logContext.function; } + QT_END_NAMESPACE diff --git a/src/corelib/global/qlogging.h b/src/corelib/global/qlogging.h index 216b847..ae388b0 100644 --- a/src/corelib/global/qlogging.h +++ b/src/corelib/global/qlogging.h @@ -168,6 +168,8 @@ Q_CORE_EXPORT QtMessageHandler qInstallMessageHandler(QtMessageHandler); typedef void (*QMessageHandler)(QtMsgType, const QMessageLogContext &, const char *); Q_CORE_EXPORT QMessageHandler qInstallMessageHandler(QMessageHandler); +Q_CORE_EXPORT void qSetMessagePattern(const QString &messagePattern); + QT_END_HEADER QT_END_NAMESPACE diff --git a/tests/auto/corelib/global/qlogging/app/main.cpp b/tests/auto/corelib/global/qlogging/app/main.cpp index dfa5231..2f5a975 100644 --- a/tests/auto/corelib/global/qlogging/app/main.cpp +++ b/tests/auto/corelib/global/qlogging/app/main.cpp @@ -51,8 +51,15 @@ int main(int argc, char **argv) QCoreApplication app(argc, argv); app.setApplicationName("tst_qlogging"); + qSetMessagePattern("[%{type}] %{message}"); + qDebug("qDebug"); qWarning("qWarning"); qCritical("qCritical"); + + qSetMessagePattern(QString()); + + qDebug("qDebug2"); + return 0; } diff --git a/tests/auto/corelib/global/qlogging/tst_qlogging.cpp b/tests/auto/corelib/global/qlogging/tst_qlogging.cpp index 1d6aa89..5474b9a 100644 --- a/tests/auto/corelib/global/qlogging/tst_qlogging.cpp +++ b/tests/auto/corelib/global/qlogging/tst_qlogging.cpp @@ -630,13 +630,16 @@ void tst_qmessagehandler::cleanupFuncinfo() void tst_qmessagehandler::qMessagePattern() { QProcess process; + const QString appExe = m_appDir + "/app"; + // + // test QT_MESSAGE_PATTERN + // QStringList environment = QProcess::systemEnvironment(); // %{file} is tricky because of shadow builds environment.prepend("QT_MESSAGE_PATTERN=\"%{type} %{appname} %{line} %{function} %{message}\""); process.setEnvironment(environment); - QString appExe = m_appDir + "/app"; process.start(appExe); QVERIFY2(process.waitForStarted(), qPrintable( QString::fromLatin1("Could not start %1: %2").arg(appExe, process.errorString()))); @@ -649,9 +652,10 @@ void tst_qmessagehandler::qMessagePattern() QVERIFY(output.contains("debug 45 T::T static constructor")); // we can't be sure whether the QT_MESSAGE_PATTERN is already destructed QVERIFY(output.contains("static destructor")); - QVERIFY(output.contains("debug tst_qlogging 54 main qDebug")); - QVERIFY(output.contains("warning tst_qlogging 55 main qWarning")); - QVERIFY(output.contains("critical tst_qlogging 56 main qCritical")); + QVERIFY(output.contains("debug tst_qlogging 56 main qDebug")); + QVERIFY(output.contains("warning tst_qlogging 57 main qWarning")); + QVERIFY(output.contains("critical tst_qlogging 58 main qCritical")); + QVERIFY(output.contains("debug tst_qlogging 62 main qDebug2")); environment = QProcess::systemEnvironment(); environment.prepend("QT_MESSAGE_PATTERN=\"PREFIX: %{unknown} %{message}\""); @@ -668,6 +672,32 @@ void tst_qmessagehandler::qMessagePattern() QVERIFY(output.contains("QT_MESSAGE_PATTERN: Unknown placeholder %{unknown}")); QVERIFY(output.contains("PREFIX: qDebug")); + + // + // test qSetMessagePattern + // + QMutableListIterator iter(environment); + while (iter.hasNext()) { + if (iter.next().startsWith("QT_MESSAGE_PATTERN")) + iter.remove(); + } + process.setEnvironment(environment); + + process.start(appExe); + QVERIFY2(process.waitForStarted(), qPrintable( + QString::fromLatin1("Could not start %1: %2").arg(appExe, process.errorString()))); + process.waitForFinished(); + + output = process.readAllStandardError(); + //qDebug() << output; + QByteArray expected = "static constructor\n" + "[debug] qDebug\n" + "[warning] qWarning\n" + "[critical] qCritical\n"; +#ifdef Q_OS_WIN + output.replace("\r\n", "\n"); +#endif + QCOMPARE(QString::fromLatin1(output), QString::fromLatin1(expected)); } QTEST_MAIN(tst_qmessagehandler) -- 2.7.4