diff options
Diffstat (limited to 'src/corelib/global/qlogging.cpp')
-rw-r--r-- | src/corelib/global/qlogging.cpp | 324 |
1 files changed, 215 insertions, 109 deletions
diff --git a/src/corelib/global/qlogging.cpp b/src/corelib/global/qlogging.cpp index f99675a7b9..9f1fe7cabb 100644 --- a/src/corelib/global/qlogging.cpp +++ b/src/corelib/global/qlogging.cpp @@ -63,11 +63,18 @@ #if QT_CONFIG(slog2) #include <sys/slog2.h> #endif +#if QT_HAS_INCLUDE(<paths.h>) +#include <paths.h> +#endif #ifdef Q_OS_ANDROID #include <android/log.h> #endif +#ifdef Q_OS_DARWIN +#include <QtCore/private/qcore_mac_p.h> +#endif + #if QT_CONFIG(journald) # define SD_JOURNAL_SUPPRESS_LOCATION # include <systemd/sd-journal.h> @@ -152,6 +159,21 @@ Q_NORETURN #endif static void qt_message_fatal(QtMsgType, const QMessageLogContext &context, const QString &message); static void qt_message_print(QtMsgType, const QMessageLogContext &context, const QString &message); +static void qt_message_print(const QString &message); + +static int checked_var_value(const char *varname) +{ + // qEnvironmentVariableIntValue returns 0 on both parsing failure and on + // empty, but we need to distinguish between the two for backwards + // compatibility reasons. + QByteArray str = qgetenv(varname); + if (str.isEmpty()) + return 0; + + bool ok; + int value = str.toInt(&ok, 0); + return ok ? value : 1; +} static bool isFatal(QtMsgType msgType) { @@ -159,25 +181,34 @@ static bool isFatal(QtMsgType msgType) return true; if (msgType == QtCriticalMsg) { - static bool fatalCriticals = !qEnvironmentVariableIsEmpty("QT_FATAL_CRITICALS"); - return fatalCriticals; + static QAtomicInt fatalCriticals = checked_var_value("QT_FATAL_CRITICALS"); + + // it's fatal if the current value is exactly 1, + // otherwise decrement if it's non-zero + return fatalCriticals.load() && fatalCriticals.fetchAndAddRelaxed(-1) == 1; } if (msgType == QtWarningMsg || msgType == QtCriticalMsg) { - static bool fatalWarnings = !qEnvironmentVariableIsEmpty("QT_FATAL_WARNINGS"); - return fatalWarnings; + static QAtomicInt fatalWarnings = checked_var_value("QT_FATAL_WARNINGS"); + + // it's fatal if the current value is exactly 1, + // otherwise decrement if it's non-zero + return fatalWarnings.load() && fatalWarnings.fetchAndAddRelaxed(-1) == 1; } return false; } +static bool isDefaultCategory(const char *category) +{ + return !category || strcmp(category, "default") == 0; +} + static bool willLogToConsole() { #if defined(Q_OS_WINRT) // these systems have no stderr, so always log to the system log return false; -#elif defined(QT_BOOTSTRAPPED) - return true; #else // rules to determine if we'll log preferably to the console: // 1) if QT_LOGGING_TO_CONSOLE is set, it determines behavior: @@ -195,8 +226,11 @@ static bool willLogToConsole() # ifdef Q_OS_WIN return GetConsoleWindow(); # elif defined(Q_OS_UNIX) +# ifndef _PATH_TTY +# define _PATH_TTY "/dev/tty" +# endif // if /dev/tty exists, we can only open it if we have a controlling TTY - int devtty = qt_safe_open("/dev/tty", O_RDONLY); + int devtty = qt_safe_open(_PATH_TTY, O_RDONLY); if (devtty == -1 && (errno == ENOENT || errno == EPERM || errno == ENXIO)) { // no /dev/tty, fall back to isatty on stderr return isatty(STDERR_FILENO); @@ -1183,20 +1217,10 @@ void QMessagePattern::setPattern(const QString &pattern) error += QLatin1String("QT_MESSAGE_PATTERN: %{if-*} cannot be nested\n"); else if (inIf) error += QLatin1String("QT_MESSAGE_PATTERN: missing %{endif}\n"); - if (!error.isEmpty()) { -#if defined(Q_OS_WINRT) - OutputDebugString(reinterpret_cast<const wchar_t*>(error.utf16())); - if (0) -#elif defined(Q_OS_WIN) && defined(QT_BUILD_CORE_LIB) - if (!qt_logging_to_console()) { - OutputDebugString(reinterpret_cast<const wchar_t*>(error.utf16())); - } else -#endif - { - fprintf(stderr, "%s", error.toLocal8Bit().constData()); - fflush(stderr); - } - } + + if (!error.isEmpty()) + qt_message_print(error); + literals = new const char*[literalsVar.size() + 1]; literals[literalsVar.size()] = 0; memcpy(literals, literalsVar.constData(), literalsVar.size() * sizeof(const char*)); @@ -1224,7 +1248,7 @@ static QStringList backtraceFramesForLogMessage(int frameCount) static QRegularExpression rx(QStringLiteral("^(?:[^(]*/)?([^(/]+)\\(([^+]*)(?:[\\+[a-f0-9x]*)?\\) \\[[a-f0-9x]*\\]$"), QRegularExpression::OptimizeOnFirstUsageOption); - QVarLengthArray<void*, 32> buffer(7 + frameCount); + QVarLengthArray<void*, 32> buffer(8 + frameCount); int n = backtrace(buffer.data(), buffer.size()); if (n > 0) { int numberPrinted = 0; @@ -1286,57 +1310,6 @@ static QString formatBacktraceForLogMessage(const QMessagePattern::BacktracePara } #endif // QLOGGING_HAVE_BACKTRACE && !QT_BOOTSTRAPPED -#if QT_CONFIG(slog2) -#ifndef QT_LOG_CODE -#define QT_LOG_CODE 9000 -#endif - -static void slog2_default_handler(QtMsgType msgType, const char *message) -{ - if (slog2_set_default_buffer((slog2_buffer_t)-1) == 0) { - slog2_buffer_set_config_t buffer_config; - slog2_buffer_t buffer_handle; - - buffer_config.buffer_set_name = __progname; - buffer_config.num_buffers = 1; - buffer_config.verbosity_level = SLOG2_DEBUG1; - buffer_config.buffer_config[0].buffer_name = "default"; - buffer_config.buffer_config[0].num_pages = 8; - - if (slog2_register(&buffer_config, &buffer_handle, 0) == -1) { - fprintf(stderr, "Error registering slogger2 buffer!\n"); - fprintf(stderr, "%s", message); - fflush(stderr); - return; - } - - // Set as the default buffer - slog2_set_default_buffer(buffer_handle); - } - int severity; - //Determines the severity level - switch (msgType) { - case QtDebugMsg: - severity = SLOG2_DEBUG1; - break; - case QtInfoMsg: - severity = SLOG2_INFO; - break; - case QtWarningMsg: - severity = SLOG2_NOTICE; - break; - case QtCriticalMsg: - severity = SLOG2_WARNING; - break; - case QtFatalMsg: - severity = SLOG2_ERROR; - break; - } - //writes to the slog2 buffer - slog2c(NULL, QT_LOG_CODE, severity, message); -} -#endif // slog2 - Q_GLOBAL_STATIC(QMessagePattern, qMessagePattern) /*! @@ -1454,7 +1427,7 @@ QString qFormatLogMessage(QtMsgType type, const QMessageLogContext &context, con } #endif // !QT_BOOTSTRAPPED } else if (token == ifCategoryTokenC) { - if (!context.category || (strcmp(context.category, "default") == 0)) + if (isDefaultCategory(context.category)) skip = true; #define HANDLE_IF_TOKEN(LEVEL) \ } else if (token == if##LEVEL##TokenC) { \ @@ -1486,11 +1459,80 @@ static QBasicAtomicPointer<void (QtMsgType, const char*)> msgHandler = Q_BASIC_A // pointer to QtMessageHandler debug handler (with context) static QBasicAtomicPointer<void (QtMsgType, const QMessageLogContext &, const QString &)> messageHandler = Q_BASIC_ATOMIC_INITIALIZER(qDefaultMessageHandler); +// ------------------------ Alternate logging sinks ------------------------- + +#if defined(QT_BOOTSTRAPPED) + // Boostrapped tools always print to stderr, so no need for alternate sinks +#else + +#if QT_CONFIG(slog2) +#ifndef QT_LOG_CODE +#define QT_LOG_CODE 9000 +#endif + +static bool slog2_default_handler(QtMsgType type, const QMessageLogContext &context, const QString &message) +{ + if (qt_logging_to_console()) + return false; + + QString formattedMessage = qFormatLogMessage(type, context, message); + formattedMessage.append(QLatin1Char('\n')); + if (slog2_set_default_buffer((slog2_buffer_t)-1) == 0) { + slog2_buffer_set_config_t buffer_config; + slog2_buffer_t buffer_handle; + + buffer_config.buffer_set_name = __progname; + buffer_config.num_buffers = 1; + buffer_config.verbosity_level = SLOG2_DEBUG1; + buffer_config.buffer_config[0].buffer_name = "default"; + buffer_config.buffer_config[0].num_pages = 8; + + if (slog2_register(&buffer_config, &buffer_handle, 0) == -1) { + fprintf(stderr, "Error registering slogger2 buffer!\n"); + fprintf(stderr, "%s", formattedMessage.toLocal8Bit().constData()); + fflush(stderr); + return false; + } + + // Set as the default buffer + slog2_set_default_buffer(buffer_handle); + } + int severity; + //Determines the severity level + switch (type) { + case QtDebugMsg: + severity = SLOG2_DEBUG1; + break; + case QtInfoMsg: + severity = SLOG2_INFO; + break; + case QtWarningMsg: + severity = SLOG2_NOTICE; + break; + case QtCriticalMsg: + severity = SLOG2_WARNING; + break; + case QtFatalMsg: + severity = SLOG2_ERROR; + break; + } + //writes to the slog2 buffer + slog2c(NULL, QT_LOG_CODE, severity, formattedMessage.toLocal8Bit().constData()); + + return true; // Prevent further output to stderr +} +#endif // slog2 + #if QT_CONFIG(journald) -static void systemd_default_message_handler(QtMsgType type, +static bool systemd_default_message_handler(QtMsgType type, const QMessageLogContext &context, const QString &message) { + if (qt_logging_to_console()) + return false; + + QString formattedMessage = qFormatLogMessage(type, context, message); + int priority = LOG_INFO; // Informational switch (type) { case QtDebugMsg: @@ -1510,19 +1552,26 @@ static void systemd_default_message_handler(QtMsgType type, break; } - sd_journal_send("MESSAGE=%s", message.toUtf8().constData(), + sd_journal_send("MESSAGE=%s", formattedMessage.toUtf8().constData(), "PRIORITY=%i", priority, "CODE_FUNC=%s", context.function ? context.function : "unknown", "CODE_LINE=%d", context.line, "CODE_FILE=%s", context.file ? context.file : "unknown", "QT_CATEGORY=%s", context.category ? context.category : "unknown", NULL); + + return true; // Prevent further output to stderr } #endif #if QT_CONFIG(syslog) -static void syslog_default_message_handler(QtMsgType type, const char *message) +static bool syslog_default_message_handler(QtMsgType type, const QMessageLogContext &context, const QString &message) { + if (qt_logging_to_console()) + return false; + + QString formattedMessage = qFormatLogMessage(type, context, message); + int priority = LOG_INFO; // Informational switch (type) { case QtDebugMsg: @@ -1542,15 +1591,22 @@ static void syslog_default_message_handler(QtMsgType type, const char *message) break; } - syslog(priority, "%s", message); + syslog(priority, "%s", formattedMessage.toUtf8().constData()); + + return true; // Prevent further output to stderr } #endif #if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) -static void android_default_message_handler(QtMsgType type, +static bool android_default_message_handler(QtMsgType type, const QMessageLogContext &context, const QString &message) { + if (qt_logging_to_console()) + return false; + + QString formattedMessage = qFormatLogMessage(type, context, message); + android_LogPriority priority = ANDROID_LOG_DEBUG; switch (type) { case QtDebugMsg: priority = ANDROID_LOG_DEBUG; break; @@ -1562,50 +1618,81 @@ static void android_default_message_handler(QtMsgType type, __android_log_print(priority, qPrintable(QCoreApplication::applicationName()), "%s:%d (%s): %s\n", context.file, context.line, - context.function, qPrintable(message)); + context.function, qPrintable(formattedMessage)); + + return true; // Prevent further output to stderr } #endif //Q_OS_ANDROID -/*! - \internal -*/ -static void qDefaultMessageHandler(QtMsgType type, const QMessageLogContext &context, - const QString &buf) +#ifdef Q_OS_WIN +static bool win_message_handler(QtMsgType type, const QMessageLogContext &context, const QString &message) +{ + if (qt_logging_to_console()) + return false; + + QString formattedMessage = qFormatLogMessage(type, context, message); + formattedMessage.append(QLatin1Char('\n')); + OutputDebugString(reinterpret_cast<const wchar_t *>(formattedMessage.utf16())); + + return true; // Prevent further output to stderr +} +#endif + +#endif // Bootstrap check + +// -------------------------------------------------------------------------- + +static void stderr_message_handler(QtMsgType type, const QMessageLogContext &context, const QString &message) { - QString logMessage = qFormatLogMessage(type, context, buf); + QString formattedMessage = qFormatLogMessage(type, context, message); // print nothing if message pattern didn't apply / was empty. // (still print empty lines, e.g. because message itself was empty) - if (logMessage.isNull()) + if (formattedMessage.isNull()) return; - if (!qt_logging_to_console()) { -#if defined(Q_OS_WIN) - logMessage.append(QLatin1Char('\n')); - OutputDebugString(reinterpret_cast<const wchar_t *>(logMessage.utf16())); - return; -#elif QT_CONFIG(slog2) - logMessage.append(QLatin1Char('\n')); - slog2_default_handler(type, logMessage.toLocal8Bit().constData()); - return; -#elif QT_CONFIG(journald) - systemd_default_message_handler(type, context, logMessage); - return; -#elif QT_CONFIG(syslog) - syslog_default_message_handler(type, logMessage.toUtf8().constData()); - return; -#elif defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) - android_default_message_handler(type, context, logMessage); - return; -#endif - } - fprintf(stderr, "%s\n", logMessage.toLocal8Bit().constData()); + fprintf(stderr, "%s\n", formattedMessage.toLocal8Bit().constData()); fflush(stderr); } /*! \internal */ +static void qDefaultMessageHandler(QtMsgType type, const QMessageLogContext &context, + const QString &message) +{ + bool handledStderr = false; + + // A message sink logs the message to a structured or unstructured destination, + // optionally formatting the message if the latter, and returns true if the sink + // handled stderr output as well, which will shortcut our default stderr output. + // In the future, if we allow multiple/dynamic sinks, this will be iterating + // a list of sinks. + +#if !defined(QT_BOOTSTRAPPED) +# if defined(Q_OS_WIN) + handledStderr |= win_message_handler(type, context, message); +# elif QT_CONFIG(slog2) + handledStderr |= slog2_default_handler(type, context, message); +# elif QT_CONFIG(journald) + handledStderr |= systemd_default_message_handler(type, context, message); +# elif QT_CONFIG(syslog) + handledStderr |= syslog_default_message_handler(type, context, message); +# elif defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) + handledStderr |= android_default_message_handler(type, context, message); +# elif defined(QT_USE_APPLE_UNIFIED_LOGGING) + if (__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) + handledStderr |= AppleUnifiedLogger::messageHandler(type, context, message); +# endif +#endif + + if (!handledStderr) + stderr_message_handler(type, context, message); +} + +/*! + \internal +*/ static void qDefaultMsgHandler(QtMsgType type, const char *buf) { QMessageLogContext emptyContext; @@ -1639,7 +1726,7 @@ static void qt_message_print(QtMsgType msgType, const QMessageLogContext &contex { #ifndef QT_BOOTSTRAPPED // qDebug, qWarning, ... macros do not check whether category is enabled - if (!context.category || (strcmp(context.category, "default") == 0)) { + if (isDefaultCategory(context.category)) { if (QLoggingCategory *defaultCategory = QLoggingCategory::defaultCategory()) { if (!defaultCategory->isEnabled(msgType)) return; @@ -1663,6 +1750,21 @@ static void qt_message_print(QtMsgType msgType, const QMessageLogContext &contex } } +static void qt_message_print(const QString &message) +{ +#if defined(Q_OS_WINRT) + OutputDebugString(reinterpret_cast<const wchar_t*>(message.utf16())); + return; +#elif defined(Q_OS_WIN) && !defined(QT_BOOTSTRAPPED) + if (!qt_logging_to_console()) { + OutputDebugString(reinterpret_cast<const wchar_t*>(message.utf16())); + return; + } +#endif + fprintf(stderr, "%s", message.toLocal8Bit().constData()); + fflush(stderr); +} + static void qt_message_fatal(QtMsgType, const QMessageLogContext &context, const QString &message) { #if defined(Q_CC_MSVC) && defined(QT_DEBUG) && defined(_DEBUG) && defined(_CRT_ERROR) @@ -1860,6 +1962,10 @@ void qErrnoWarning(int code, const char *msg, ...) environment variable; if both \l qSetMessagePattern() is called and QT_MESSAGE_PATTERN is set, the environment variable takes precedence. + \note The message pattern only applies to unstructured logging, such as the default + \c stderr output. Structured logging such as systemd will record the message as is, + along with as much structured information as can be captured. + Custom message handlers can use qFormatLogMessage() to take \a pattern into account. \sa qInstallMessageHandler(), {Debugging Techniques}, {QLoggingCategory} |