diff options
Diffstat (limited to 'src/corelib/global/qlogging.cpp')
-rw-r--r-- | src/corelib/global/qlogging.cpp | 244 |
1 files changed, 159 insertions, 85 deletions
diff --git a/src/corelib/global/qlogging.cpp b/src/corelib/global/qlogging.cpp index 51169eb963..8b4245ccdc 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,68 @@ 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()) { + if (numberPrinted == 0 && context.function) + message += QString::fromUtf8(qCleanupFuncinfo(context.function)); + else + 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 +1292,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()) { @@ -1500,6 +1563,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}, @@ -1511,7 +1585,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}". @@ -1520,7 +1594,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} */ |