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 <thiago.macieira@intel.com>
bb10
Kai Koehne 2012-04-13 13:37:56 +02:00 committed by Qt by Nokia
parent e92e5fda44
commit c0d249019b
5 changed files with 127 additions and 17 deletions

View 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

View 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

View 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

View 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;
}

View 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)