summaryrefslogtreecommitdiffstats
path: root/src/corelib/global/qlogging.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/global/qlogging.cpp')
-rw-r--r--src/corelib/global/qlogging.cpp240
1 files changed, 155 insertions, 85 deletions
diff --git a/src/corelib/global/qlogging.cpp b/src/corelib/global/qlogging.cpp
index ca0fb1bb23..013efec3d5 100644
--- a/src/corelib/global/qlogging.cpp
+++ b/src/corelib/global/qlogging.cpp
@@ -1,6 +1,7 @@
/****************************************************************************
**
-** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Copyright (C) 2014 Olivier Goffart <ogoffart@woboq.com>
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtCore module of the Qt Toolkit.
@@ -48,6 +49,8 @@
#include "qmutex.h"
#include "qloggingcategory.h"
#ifndef QT_BOOTSTRAPPED
+#include "qelapsedtimer.h"
+#include "qdatetime.h"
#include "qcoreapplication.h"
#include "qthread.h"
#include "private/qloggingregistry_p.h"
@@ -70,6 +73,22 @@
# include <unistd.h>
#endif
+#if !defined QT_NO_REGULAREXPRESSION && !defined(QT_BOOTSTRAPPED)
+#ifdef __has_include
+#if __has_include(<cxxabi.h>) && __has_include(<execinfo.h>)
+#define QLOGGING_HAVE_BACKTRACE
+#endif
+#elif defined(__GLIBCXX__) && defined(__GLIBC__) // (because older version of gcc don't have __has_include)
+#define QLOGGING_HAVE_BACKTRACE
+#endif
+
+#ifdef QLOGGING_HAVE_BACKTRACE
+#include <qregularexpression.h>
+#include <cxxabi.h>
+#include <execinfo.h>
+#endif
+#endif
+
#include <stdio.h>
QT_BEGIN_NAMESPACE
@@ -171,89 +190,15 @@ static inline void convert_to_wchar_t_elided(wchar_t *d, size_t space, const cha
}
#endif
-#if !defined(QT_NO_EXCEPTIONS)
-/*!
- \internal
- Uses a local buffer to output the message. Not locale safe + cuts off
- everything after character 255, but will work in out of memory situations.
- Stop the execution afterwards.
-*/
-static void qEmergencyOut(QtMsgType msgType, const char *msg, va_list ap) Q_DECL_NOEXCEPT
-{
- char emergency_buf[256] = { '\0' };
- emergency_buf[sizeof emergency_buf - 1] = '\0';
-#if defined(Q_OS_WIN) && defined(QT_BUILD_CORE_LIB) && (defined(Q_OS_WINCE) || defined(Q_OS_WINRT)) \
- || defined(Q_CC_MSVC) && defined(QT_DEBUG) && defined(_DEBUG) && defined(_CRT_ERROR)
- wchar_t emergency_bufL[sizeof emergency_buf];
-#endif
-
- if (msg)
- qvsnprintf(emergency_buf, sizeof emergency_buf - 1, msg, ap);
-
-#if defined(Q_OS_WIN) && defined(QT_BUILD_CORE_LIB)
-# if defined(Q_OS_WINCE) || defined(Q_OS_WINRT)
- convert_to_wchar_t_elided(emergency_bufL, sizeof emergency_buf, emergency_buf);
- OutputDebugStringW(emergency_bufL);
-# else
- if (qWinLogToStderr()) {
- fprintf(stderr, "%s\n", emergency_buf);
- fflush(stderr);
- } else {
- OutputDebugStringA(emergency_buf);
- }
-# endif
-#else
- fprintf(stderr, "%s\n", emergency_buf);
- fflush(stderr);
-#endif
-
- if (isFatal(msgType)) {
-#if defined(Q_CC_MSVC) && defined(QT_DEBUG) && defined(_DEBUG) && defined(_CRT_ERROR)
- // get the current report mode
- int reportMode = _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_WNDW);
- _CrtSetReportMode(_CRT_ERROR, reportMode);
-# ifndef Q_OS_WINCE // otherwise already converted to wchar_t above
- convert_to_wchar_t_elided(emergency_bufL, sizeof emergency_buf, emergency_buf);
-# endif
- int ret = _CrtDbgReportW(_CRT_ERROR, _CRT_WIDE(__FILE__), __LINE__,
- _CRT_WIDE(QT_VERSION_STR),
- emergency_bufL);
- if (ret == 1)
- _CrtDbgBreak();
-#endif
-
-#if (defined(Q_OS_UNIX) || defined(Q_CC_MINGW))
- abort(); // trap; generates core dump
-#else
- exit(1); // goodbye cruel world
-#endif
- }
-}
-#endif
-
/*!
\internal
*/
static void qt_message(QtMsgType msgType, const QMessageLogContext &context, const char *msg,
va_list ap, QString &buf)
{
-#if !defined(QT_NO_EXCEPTIONS)
- if (std::uncaught_exception()) {
- qEmergencyOut(msgType, msg, ap);
- return;
- }
-#endif
- if (msg) {
- QT_TRY {
- buf = QString().vsprintf(msg, ap);
- } QT_CATCH(const std::bad_alloc &) {
-#if !defined(QT_NO_EXCEPTIONS)
- qEmergencyOut(msgType, msg, ap);
- // don't rethrow - we use qWarning and friends in destructors.
- return;
-#endif
- }
- }
+
+ if (msg)
+ buf = QString().vsprintf(msg, ap);
qt_message_print(msgType, context, buf);
}
@@ -834,6 +779,8 @@ static const char functionTokenC[] = "%{function}";
static const char pidTokenC[] = "%{pid}";
static const char appnameTokenC[] = "%{appname}";
static const char threadidTokenC[] = "%{threadid}";
+static const char timeTokenC[] = "%{time"; //not a typo: this command has arguments
+static const char backtraceTokenC[] = "%{backtrace"; //ditto
static const char ifCategoryTokenC[] = "%{if-category}";
static const char ifDebugTokenC[] = "%{if-debug}";
static const char ifWarningTokenC[] = "%{if-warning}";
@@ -854,9 +801,18 @@ struct QMessagePattern {
// 0 terminated arrays of literal tokens / literal or placeholder tokens
const char **literals;
const char **tokens;
+ QString timeFormat;
+#ifdef QLOGGING_HAVE_BACKTRACE
+ int backtraceDepth;
+ QString backtraceSeparator;
+#endif
bool fromEnvironment;
static QBasicMutex mutex;
+#ifndef QT_BOOTSTRAPPED
+ QElapsedTimer timer;
+ QDateTime startTime;
+#endif
};
QBasicMutex QMessagePattern::mutex;
@@ -864,8 +820,18 @@ QBasicMutex QMessagePattern::mutex;
QMessagePattern::QMessagePattern()
: literals(0)
, tokens(0)
+#ifdef QLOGGING_HAVE_BACKTRACE
+ , backtraceDepth(5)
+ , backtraceSeparator(QLatin1Char('|'))
+#endif
, fromEnvironment(false)
+#ifndef QT_BOOTSTRAPPED
+ , startTime(QDateTime::currentDateTime())
+#endif
{
+#ifndef QT_BOOTSTRAPPED
+ timer.start();
+#endif
const QString envPattern = QString::fromLocal8Bit(qgetenv("QT_MESSAGE_PATTERN"));
if (envPattern.isEmpty()) {
setPattern(QLatin1String(defaultPattern));
@@ -953,6 +919,31 @@ void QMessagePattern::setPattern(const QString &pattern)
tokens[i] = appnameTokenC;
else if (lexeme == QLatin1String(threadidTokenC))
tokens[i] = threadidTokenC;
+ else if (lexeme.startsWith(QLatin1String(timeTokenC))) {
+ tokens[i] = timeTokenC;
+ int spaceIdx = lexeme.indexOf(QChar::fromLatin1(' '));
+ if (spaceIdx > 0)
+ timeFormat = lexeme.mid(spaceIdx + 1, lexeme.length() - spaceIdx - 2);
+ } else if (lexeme.startsWith(QLatin1String(backtraceTokenC))) {
+#ifdef QLOGGING_HAVE_BACKTRACE
+ tokens[i] = backtraceTokenC;
+ QRegularExpression depthRx(QStringLiteral(" depth=(?|\"([^\"]*)\"|([^ }]*))"));
+ QRegularExpression separatorRx(QStringLiteral(" separator=(?|\"([^\"]*)\"|([^ }]*))"));
+ QRegularExpressionMatch m = depthRx.match(lexeme);
+ if (m.hasMatch()) {
+ int depth = m.capturedRef(1).toInt();
+ if (depth <= 0)
+ error += QStringLiteral("QT_MESSAGE_PATTERN: %{backtrace} depth must be a number greater than 0\n");
+ else
+ backtraceDepth = depth;
+ }
+ m = separatorRx.match(lexeme);
+ if (m.hasMatch())
+ backtraceSeparator = m.captured(1);
+#else
+ error += QStringLiteral("QT_MESSAGE_PATTERN: %{backtrace} is not supported by this Qt build\n");
+#endif
+ }
#define IF_TOKEN(LEVEL) \
else if (lexeme == QLatin1String(LEVEL)) { \
@@ -1061,10 +1052,20 @@ static void slog2_default_handler(QtMsgType msgType, const char *message)
Q_GLOBAL_STATIC(QMessagePattern, qMessagePattern)
/*!
- \internal
-*/
-Q_CORE_EXPORT QString qMessageFormatString(QtMsgType type, const QMessageLogContext &context,
- const QString &str)
+ \relates <QtGlobal>
+ \since 5.4
+
+ Generates a formatted string out of the \a type, \a context, \a str arguments.
+
+ qFormatLogMessage returns a QString that is formatted according to the current message pattern.
+ It can be used by custom message handlers to format output similar to Qt's default message
+ handler.
+
+ The function is thread-safe.
+
+ \sa qInstallMessageHandler(), qSetMessagePattern()
+ */
+QString qFormatLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &str)
{
QString message;
@@ -1122,6 +1123,64 @@ Q_CORE_EXPORT QString qMessageFormatString(QtMsgType type, const QMessageLogCont
} else if (token == threadidTokenC) {
message.append(QLatin1String("0x"));
message.append(QString::number(qlonglong(QThread::currentThread()->currentThread()), 16));
+#ifdef QLOGGING_HAVE_BACKTRACE
+ } else if (token == backtraceTokenC) {
+ QVarLengthArray<void*, 32> buffer(15 + pattern->backtraceDepth);
+ int n = backtrace(buffer.data(), buffer.size());
+ if (n > 0) {
+ QScopedPointer<char*, QScopedPointerPodDeleter> strings(backtrace_symbols(buffer.data(), n));
+ int numberPrinted = 0;
+ for (int i = 0; i < n && numberPrinted < pattern->backtraceDepth; ++i) {
+ QString trace = QString::fromLatin1(strings.data()[i]);
+ // The results of backtrace_symbols looks like this:
+ // /lib/libc.so.6(__libc_start_main+0xf3) [0x4a937413]
+ // The offset and function name are optional.
+ // This regexp tries to extract the librry name (without the path) and the function name.
+ // This code is protected by QMessagePattern::mutex so it is thread safe on all compilers
+ static QRegularExpression rx(QStringLiteral("^(?:[^(]*/)?([^(/]+)\\(([^+]*)(?:[\\+[a-f0-9x]*)?\\) \\[[a-f0-9x]*\\]$"),
+ QRegularExpression::OptimizeOnFirstUsageOption);
+
+ QRegularExpressionMatch m = rx.match(trace);
+ if (m.hasMatch()) {
+ // skip the trace from QtCore that are because of the qDebug itself
+ QString library = m.captured(1);
+ QString function = m.captured(2);
+ if (!numberPrinted && library.contains(QLatin1String("Qt5Core"))
+ && (function.isEmpty() || function.contains(QLatin1String("Message"), Qt::CaseInsensitive)
+ || function.contains(QLatin1String("QDebug")))) {
+ continue;
+ }
+
+ if (function.startsWith(QLatin1String("_Z"))) {
+ QScopedPointer<char, QScopedPointerPodDeleter> demangled(
+ abi::__cxa_demangle(function.toUtf8(), 0, 0, 0));
+ if (demangled)
+ function = QString::fromUtf8(qCleanupFuncinfo(demangled.data()));
+ }
+
+ if (numberPrinted > 0)
+ message.append(pattern->backtraceSeparator);
+
+ if (function.isEmpty())
+ message += QLatin1Char('?') + library + QLatin1Char('?');
+ else
+ message += function;
+
+ } else {
+ if (numberPrinted == 0)
+ continue;
+ message += pattern->backtraceSeparator + QLatin1String("???");
+ }
+ numberPrinted++;
+ }
+ }
+#endif
+ } else if (token == timeTokenC) {
+ quint64 ms = pattern->timer.elapsed();
+ if (pattern->timeFormat.isEmpty())
+ message.append(QString().sprintf("%6d.%03d", uint(ms / 1000), uint(ms % 1000)));
+ else
+ message.append(pattern->startTime.addMSecs(ms).toString(pattern->timeFormat));
#endif
} else if (token == ifCategoryTokenC) {
if (!context.category || (strcmp(context.category, "default") == 0))
@@ -1229,7 +1288,7 @@ static void qDefaultMessageHandler(QtMsgType type, const QMessageLogContext &con
bool toConsole;
};
- QString logMessage = qMessageFormatString(type, context, buf);
+ QString logMessage = qFormatLogMessage(type, context, buf);
#if defined(Q_OS_WIN) && defined(QT_BUILD_CORE_LIB)
if (!qWinLogToStderr()) {
@@ -1503,6 +1562,17 @@ void qErrnoWarning(int code, const char *msg, ...)
\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"
+ \row \li \c %{time} \li time of the message, in seconds since the process started
+ \row \li \c %{time format} \li system time when the message occurred, formatted by
+ passing the \c format to \l QDateTime::toString()
+ \row \li \c{%{backtrace [depth=N] [separator="..."]}} \li A backtrace with the number of frames
+ specified by the optional \c depth parameter (defaults to 5), and separated by the optional
+ \c separator parameter (defaults to "|").
+ This expansion is available only on some platforms (currently only platfoms using glibc).
+ Names are only known for exported functions. If you want to see the name of every function
+ in your application, use \c{QMAKE_LFLAGS += -rdynamic}.
+ When reading backtraces, take into account that frames might be missing due to inlining or
+ tail call optimization.
\endtable
You can also use conditionals on the type of the message using \c %{if-debug},
@@ -1514,7 +1584,7 @@ void qErrnoWarning(int code, const char *msg, ...)
Example:
\code
- QT_MESSAGE_PATTERN="[%{if-debug}D%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{file}:%{line} - %{message}"
+ QT_MESSAGE_PATTERN="[%{time yyyyMMdd h:mm:ss.zzz t} %{if-debug}D%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{file}:%{line} - %{message}"
\endcode
The default \a pattern is "%{if-category}%{category}: %{endif}%{message}".
@@ -1523,7 +1593,7 @@ void qErrnoWarning(int code, const char *msg, ...)
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.
+ Custom message handlers can use qFormatLogMessage() to take \a pattern into account.
\sa qInstallMessageHandler(), {Debugging Techniques}
*/