Allow qDebug output to be configured by qSetMessagePattern()
authorKai Koehne <kai.koehne@nokia.com>
Fri, 13 Apr 2012 11:37:56 +0000 (13:37 +0200)
committerQt by Nokia <qt-info@nokia.com>
Tue, 24 Apr 2012 15:12:46 +0000 (17:12 +0200)
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 <thiago.macieira@intel.com>
src/corelib/global/qglobal.cpp
src/corelib/global/qlogging.cpp
src/corelib/global/qlogging.h
tests/auto/corelib/global/qlogging/app/main.cpp
tests/auto/corelib/global/qlogging/tst_qlogging.cpp

index 7036b72..38ddd7b 100644 (file)
@@ -3088,5 +3088,38 @@ bool QInternal::activateCallbacks(Callback cb, void **parameters)
     instead.
     \sa QtMsgHandler, qInstallMessageHandler
 */
+/*!
+    \fn void qSetMessagePattern(const QString &pattern)
+    \relates <QtGlobal>
+    \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
index 1f5b121..8726c18 100644 (file)
@@ -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<QString> 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
index 216b847..ae388b0 100644 (file)
@@ -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
 
index dfa5231..2f5a975 100644 (file)
@@ -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;
 }
index 1d6aa89..5474b9a 100644 (file)
@@ -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<QString> 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)