blob: 5a7f8242de0febf71aaed598a9e84479e8d5103f [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
** Copyright (C) 2018 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qglobal_p.h"
#include "qlogging.h"
#include "qlogging_p.h"
#include "qlist.h"
#include "qbytearray.h"
#include "qscopeguard.h"
#include "qstring.h"
#include "qvarlengtharray.h"
#include "qdebug.h"
#include "qmutex.h"
#include <QtCore/private/qlocking_p.h>
#include "qloggingcategory.h"
#ifndef QT_BOOTSTRAPPED
#include "qelapsedtimer.h"
#include "qdatetime.h"
#include "qcoreapplication.h"
#include "qthread.h"
#include "private/qloggingregistry_p.h"
#include "private/qcoreapplication_p.h"
#include "private/qsimd_p.h"
#include <qtcore_tracepoints_p.h>
#endif
#ifdef Q_OS_WIN
#include <qt_windows.h>
#endif
#ifdef Q_CC_MSVC
#include <intrin.h>
#endif
#if QT_CONFIG(slog2)
#include <sys/slog2.h>
#endif
#if __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>
# include <syslog.h>
#endif
#if QT_CONFIG(syslog)
# include <syslog.h>
#endif
#ifdef Q_OS_UNIX
# include <sys/types.h>
# include <sys/stat.h>
# include <unistd.h>
# include "private/qcore_unix_p.h"
#endif
#ifdef Q_OS_WASM
#include <emscripten/emscripten.h>
#endif
#if QT_CONFIG(regularexpression)
# ifdef __UCLIBC__
# if __UCLIBC_HAS_BACKTRACE__
# define QLOGGING_HAVE_BACKTRACE
# endif
# elif (defined(__GLIBC__) && defined(__GLIBCXX__)) || (__has_include(<cxxabi.h>) && __has_include(<execinfo.h>))
# define QLOGGING_HAVE_BACKTRACE
# endif
#endif
#if QT_CONFIG(slog2)
extern char *__progname;
#endif
#ifndef QT_BOOTSTRAPPED
#if defined(Q_OS_LINUX) && (defined(__GLIBC__) || __has_include(<sys/syscall.h>))
# include <sys/syscall.h>
# if defined(Q_OS_ANDROID) && !defined(SYS_gettid)
# define SYS_gettid __NR_gettid
# endif
static long qt_gettid()
{
// no error handling
// this syscall has existed since Linux 2.4.11 and cannot fail
return syscall(SYS_gettid);
}
#elif defined(Q_OS_DARWIN)
# include <pthread.h>
static int qt_gettid()
{
// no error handling: this call cannot fail
__uint64_t tid;
pthread_threadid_np(NULL, &tid);
return tid;
}
#elif defined(Q_OS_FREEBSD_KERNEL) && defined(__FreeBSD_version) && __FreeBSD_version >= 900031
# include <pthread_np.h>
static int qt_gettid()
{
return pthread_getthreadid_np();
}
#else
static QT_PREPEND_NAMESPACE(qint64) qt_gettid()
{
QT_USE_NAMESPACE
return qintptr(QThread::currentThreadId());
}
#endif
#ifdef QLOGGING_HAVE_BACKTRACE
# include <qregularexpression.h>
# include <cxxabi.h>
# include <execinfo.h>
#endif
#endif // !QT_BOOTSTRAPPED
#include <cstdlib>
#include <algorithm>
#include <memory>
#include <vector>
#include <stdio.h>
QT_BEGIN_NAMESPACE
#if !defined(Q_CC_MSVC)
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)
{
if (msgType == QtFatalMsg)
return true;
if (msgType == QtCriticalMsg) {
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.loadRelaxed() && fatalCriticals.fetchAndAddRelaxed(-1) == 1;
}
if (msgType == QtWarningMsg || msgType == QtCriticalMsg) {
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.loadRelaxed() && fatalWarnings.fetchAndAddRelaxed(-1) == 1;
}
return false;
}
static bool isDefaultCategory(const char *category)
{
return !category || strcmp(category, "default") == 0;
}
/*!
Returns true if writing to \c stderr is supported.
\internal
\sa stderrHasConsoleAttached()
*/
static bool systemHasStderr()
{
#if defined(Q_OS_WINRT)
return false; // WinRT has no stderr
#endif
return true;
}
/*!
Returns true if writing to \c stderr will end up in a console/terminal visible to the user.
This is typically the case if the application was started from the command line.
If the application is started without a controlling console/terminal, but the parent
process reads \c stderr and presents it to the user in some other way, the parent process
may override the detection in this function by setting the QT_ASSUME_STDERR_HAS_CONSOLE
environment variable to \c 1.
\note Qt Creator does not implement a pseudo TTY, nor does it launch apps with
the override environment variable set, but it will read stderr and print it to
the user, so in effect this function cannot be used to conclude that stderr
output will _not_ be visible to the user, as even if this function returns false,
the output might still end up visible to the user. For this reason, we don't guard
the stderr output in the default message handler with stderrHasConsoleAttached().
\internal
\sa systemHasStderr()
*/
static bool stderrHasConsoleAttached()
{
static const bool stderrHasConsoleAttached = []() -> bool {
if (!systemHasStderr())
return false;
if (qEnvironmentVariableIntValue("QT_LOGGING_TO_CONSOLE")) {
fprintf(stderr, "warning: Environment variable QT_LOGGING_TO_CONSOLE is deprecated, use\n"
"QT_ASSUME_STDERR_HAS_CONSOLE and/or QT_FORCE_STDERR_LOGGING instead.\n");
return true;
}
if (qEnvironmentVariableIntValue("QT_ASSUME_STDERR_HAS_CONSOLE"))
return true;
#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
return GetConsoleWindow();
#elif defined(Q_OS_UNIX)
# ifndef _PATH_TTY
# define _PATH_TTY "/dev/tty"
# endif
// If we can open /dev/tty, we have a controlling TTY
int ttyDevice = -1;
if ((ttyDevice = qt_safe_open(_PATH_TTY, O_RDONLY)) >= 0) {
qt_safe_close(ttyDevice);
return true;
} else if (errno == ENOENT || errno == EPERM || errno == ENXIO) {
// Fall back to isatty for some non-critical errors
return isatty(STDERR_FILENO);
} else {
return false;
}
#else
return false; // No way to detect if stderr has a console attached
#endif
}();
return stderrHasConsoleAttached;
}
namespace QtPrivate {
/*!
Returns true if logging \c stderr should be ensured.
This is normally the case if \c stderr has a console attached, but may be overridden
by the user by setting the QT_FORCE_STDERR_LOGGING environment variable to \c 1.
\internal
\sa stderrHasConsoleAttached()
*/
bool shouldLogToStderr()
{
static bool forceStderrLogging = qEnvironmentVariableIntValue("QT_FORCE_STDERR_LOGGING");
return forceStderrLogging || stderrHasConsoleAttached();
}
} // QtPrivate
using namespace QtPrivate;
/*!
\class QMessageLogContext
\inmodule QtCore
\brief The QMessageLogContext class provides additional information about a log message.
\since 5.0
The class provides information about the source code location a qDebug(), qInfo(), qWarning(),
qCritical() or qFatal() message was generated.
\note By default, this information is recorded only in debug builds. You can overwrite
this explicitly by defining \c QT_MESSAGELOGCONTEXT or \c{QT_NO_MESSAGELOGCONTEXT}.
\sa QMessageLogger, QtMessageHandler, qInstallMessageHandler()
*/
/*!
\class QMessageLogger
\inmodule QtCore
\brief The QMessageLogger class generates log messages.
\since 5.0
QMessageLogger is used to generate messages for the Qt logging framework. Usually one uses
it through qDebug(), qInfo(), qWarning(), qCritical, or qFatal() functions,
which are actually macros: For example qDebug() expands to
QMessageLogger(__FILE__, __LINE__, Q_FUNC_INFO).debug()
for debug builds, and QMessageLogger(0, 0, 0).debug() for release builds.
One example of direct use is to forward errors that stem from a scripting language, e.g. QML:
\snippet code/qlogging/qlogging.cpp 1
\sa QMessageLogContext, qDebug(), qInfo(), qWarning(), qCritical(), qFatal()
*/
#if defined(Q_CC_MSVC) && defined(QT_DEBUG) && defined(_DEBUG) && defined(_CRT_ERROR)
static inline void convert_to_wchar_t_elided(wchar_t *d, size_t space, const char *s) noexcept
{
size_t len = qstrlen(s);
if (len + 1 > space) {
const size_t skip = len - space + 4; // 4 for "..." + '\0'
s += skip;
len -= skip;
for (int i = 0; i < 3; ++i)
*d++ = L'.';
}
while (len--)
*d++ = *s++;
*d++ = 0;
}
#endif
/*!
\internal
*/
Q_NEVER_INLINE
static QString qt_message(QtMsgType msgType, const QMessageLogContext &context, const char *msg, va_list ap)
{
QString buf = QString::vasprintf(msg, ap);
qt_message_print(msgType, context, buf);
return buf;
}
#undef qDebug
/*!
Logs a debug message specified with format \a msg. Additional
parameters, specified by \a msg, may be used.
\sa qDebug()
*/
void QMessageLogger::debug(const char *msg, ...) const
{
va_list ap;
va_start(ap, msg); // use variable arg list
const QString message = qt_message(QtDebugMsg, context, msg, ap);
va_end(ap);
if (isFatal(QtDebugMsg))
qt_message_fatal(QtDebugMsg, context, message);
}
#undef qInfo
/*!
Logs an informational message specified with format \a msg. Additional
parameters, specified by \a msg, may be used.
\sa qInfo()
\since 5.5
*/
void QMessageLogger::info(const char *msg, ...) const
{
va_list ap;
va_start(ap, msg); // use variable arg list
const QString message = qt_message(QtInfoMsg, context, msg, ap);
va_end(ap);
if (isFatal(QtInfoMsg))
qt_message_fatal(QtInfoMsg, context, message);
}
/*!
\typedef QMessageLogger::CategoryFunction
This is a typedef for a pointer to a function with the following
signature:
\snippet code/qlogging/qlogging.cpp 2
A function which this signature is generated by Q_DECLARE_LOGGING_CATEGORY,
Q_LOGGING_CATEGORY.
\since 5.3
*/
/*!
Logs a debug message specified with format \a msg for the context \a cat.
Additional parameters, specified by \a msg, may be used.
\since 5.3
\sa qCDebug()
*/
void QMessageLogger::debug(const QLoggingCategory &cat, const char *msg, ...) const
{
if (!cat.isDebugEnabled())
return;
QMessageLogContext ctxt;
ctxt.copyContextFrom(context);
ctxt.category = cat.categoryName();
va_list ap;
va_start(ap, msg); // use variable arg list
const QString message = qt_message(QtDebugMsg, ctxt, msg, ap);
va_end(ap);
if (isFatal(QtDebugMsg))
qt_message_fatal(QtDebugMsg, ctxt, message);
}
/*!
Logs a debug message specified with format \a msg for the context returned
by \a catFunc. Additional parameters, specified by \a msg, may be used.
\since 5.3
\sa qCDebug()
*/
void QMessageLogger::debug(QMessageLogger::CategoryFunction catFunc,
const char *msg, ...) const
{
const QLoggingCategory &cat = (*catFunc)();
if (!cat.isDebugEnabled())
return;
QMessageLogContext ctxt;
ctxt.copyContextFrom(context);
ctxt.category = cat.categoryName();
va_list ap;
va_start(ap, msg); // use variable arg list
const QString message = qt_message(QtDebugMsg, ctxt, msg, ap);
va_end(ap);
if (isFatal(QtDebugMsg))
qt_message_fatal(QtDebugMsg, ctxt, message);
}
#ifndef QT_NO_DEBUG_STREAM
/*!
Logs a debug message using a QDebug stream
\sa qDebug(), QDebug
*/
QDebug QMessageLogger::debug() const
{
QDebug dbg = QDebug(QtDebugMsg);
QMessageLogContext &ctxt = dbg.stream->context;
ctxt.copyContextFrom(context);
return dbg;
}
/*!
Logs a debug message into category \a cat using a QDebug stream.
\since 5.3
\sa qCDebug(), QDebug
*/
QDebug QMessageLogger::debug(const QLoggingCategory &cat) const
{
QDebug dbg = QDebug(QtDebugMsg);
if (!cat.isDebugEnabled())
dbg.stream->message_output = false;
QMessageLogContext &ctxt = dbg.stream->context;
ctxt.copyContextFrom(context);
ctxt.category = cat.categoryName();
return dbg;
}
/*!
Logs a debug message into category returned by \a catFunc using a QDebug stream.
\since 5.3
\sa qCDebug(), QDebug
*/
QDebug QMessageLogger::debug(QMessageLogger::CategoryFunction catFunc) const
{
return debug((*catFunc)());
}
/*!
\internal
Returns a QNoDebug object, which is used to ignore debugging output.
\sa QNoDebug, qDebug()
*/
QNoDebug QMessageLogger::noDebug() const noexcept
{
return QNoDebug();
}
#endif
/*!
Logs an informational message specified with format \a msg for the context \a cat.
Additional parameters, specified by \a msg, may be used.
\since 5.5
\sa qCInfo()
*/
void QMessageLogger::info(const QLoggingCategory &cat, const char *msg, ...) const
{
if (!cat.isInfoEnabled())
return;
QMessageLogContext ctxt;
ctxt.copyContextFrom(context);
ctxt.category = cat.categoryName();
va_list ap;
va_start(ap, msg); // use variable arg list
const QString message = qt_message(QtInfoMsg, ctxt, msg, ap);
va_end(ap);
if (isFatal(QtInfoMsg))
qt_message_fatal(QtInfoMsg, ctxt, message);
}
/*!
Logs an informational message specified with format \a msg for the context returned
by \a catFunc. Additional parameters, specified by \a msg, may be used.
\since 5.5
\sa qCInfo()
*/
void QMessageLogger::info(QMessageLogger::CategoryFunction catFunc,
const char *msg, ...) const
{
const QLoggingCategory &cat = (*catFunc)();
if (!cat.isInfoEnabled())
return;
QMessageLogContext ctxt;
ctxt.copyContextFrom(context);
ctxt.category = cat.categoryName();
va_list ap;
va_start(ap, msg); // use variable arg list
const QString message = qt_message(QtInfoMsg, ctxt, msg, ap);
va_end(ap);
if (isFatal(QtInfoMsg))
qt_message_fatal(QtInfoMsg, ctxt, message);
}
#ifndef QT_NO_DEBUG_STREAM
/*!
Logs an informational message using a QDebug stream.
\since 5.5
\sa qInfo(), QDebug
*/
QDebug QMessageLogger::info() const
{
QDebug dbg = QDebug(QtInfoMsg);
QMessageLogContext &ctxt = dbg.stream->context;
ctxt.copyContextFrom(context);
return dbg;
}
/*!
Logs an informational message into the category \a cat using a QDebug stream.
\since 5.5
\sa qCInfo(), QDebug
*/
QDebug QMessageLogger::info(const QLoggingCategory &cat) const
{
QDebug dbg = QDebug(QtInfoMsg);
if (!cat.isInfoEnabled())
dbg.stream->message_output = false;
QMessageLogContext &ctxt = dbg.stream->context;
ctxt.copyContextFrom(context);
ctxt.category = cat.categoryName();
return dbg;
}
/*!
Logs an informational message into category returned by \a catFunc using a QDebug stream.
\since 5.5
\sa qCInfo(), QDebug
*/
QDebug QMessageLogger::info(QMessageLogger::CategoryFunction catFunc) const
{
return info((*catFunc)());
}
#endif
#undef qWarning
/*!
Logs a warning message specified with format \a msg. Additional
parameters, specified by \a msg, may be used.
\sa qWarning()
*/
void QMessageLogger::warning(const char *msg, ...) const
{
va_list ap;
va_start(ap, msg); // use variable arg list
const QString message = qt_message(QtWarningMsg, context, msg, ap);
va_end(ap);
if (isFatal(QtWarningMsg))
qt_message_fatal(QtWarningMsg, context, message);
}
/*!
Logs a warning message specified with format \a msg for the context \a cat.
Additional parameters, specified by \a msg, may be used.
\since 5.3
\sa qCWarning()
*/
void QMessageLogger::warning(const QLoggingCategory &cat, const char *msg, ...) const
{
if (!cat.isWarningEnabled())
return;
QMessageLogContext ctxt;
ctxt.copyContextFrom(context);
ctxt.category = cat.categoryName();
va_list ap;
va_start(ap, msg); // use variable arg list
const QString message = qt_message(QtWarningMsg, ctxt, msg, ap);
va_end(ap);
if (isFatal(QtWarningMsg))
qt_message_fatal(QtWarningMsg, ctxt, message);
}
/*!
Logs a warning message specified with format \a msg for the context returned
by \a catFunc. Additional parameters, specified by \a msg, may be used.
\since 5.3
\sa qCWarning()
*/
void QMessageLogger::warning(QMessageLogger::CategoryFunction catFunc,
const char *msg, ...) const
{
const QLoggingCategory &cat = (*catFunc)();
if (!cat.isWarningEnabled())
return;
QMessageLogContext ctxt;
ctxt.copyContextFrom(context);
ctxt.category = cat.categoryName();
va_list ap;
va_start(ap, msg); // use variable arg list
const QString message = qt_message(QtWarningMsg, ctxt, msg, ap);
va_end(ap);
if (isFatal(QtWarningMsg))
qt_message_fatal(QtWarningMsg, ctxt, message);
}
#ifndef QT_NO_DEBUG_STREAM
/*!
Logs a warning message using a QDebug stream
\sa qWarning(), QDebug
*/
QDebug QMessageLogger::warning() const
{
QDebug dbg = QDebug(QtWarningMsg);
QMessageLogContext &ctxt = dbg.stream->context;
ctxt.copyContextFrom(context);
return dbg;
}
/*!
Logs a warning message into category \a cat using a QDebug stream.
\sa qCWarning(), QDebug
*/
QDebug QMessageLogger::warning(const QLoggingCategory &cat) const
{
QDebug dbg = QDebug(QtWarningMsg);
if (!cat.isWarningEnabled())
dbg.stream->message_output = false;
QMessageLogContext &ctxt = dbg.stream->context;
ctxt.copyContextFrom(context);
ctxt.category = cat.categoryName();
return dbg;
}
/*!
Logs a warning message into category returned by \a catFunc using a QDebug stream.
\since 5.3
\sa qCWarning(), QDebug
*/
QDebug QMessageLogger::warning(QMessageLogger::CategoryFunction catFunc) const
{
return warning((*catFunc)());
}
#endif
#undef qCritical
/*!
Logs a critical message specified with format \a msg. Additional
parameters, specified by \a msg, may be used.
\sa qCritical()
*/
void QMessageLogger::critical(const char *msg, ...) const
{
va_list ap;
va_start(ap, msg); // use variable arg list
const QString message = qt_message(QtCriticalMsg, context, msg, ap);
va_end(ap);
if (isFatal(QtCriticalMsg))
qt_message_fatal(QtCriticalMsg, context, message);
}
/*!
Logs a critical message specified with format \a msg for the context \a cat.
Additional parameters, specified by \a msg, may be used.
\since 5.3
\sa qCCritical()
*/
void QMessageLogger::critical(const QLoggingCategory &cat, const char *msg, ...) const
{
if (!cat.isCriticalEnabled())
return;
QMessageLogContext ctxt;
ctxt.copyContextFrom(context);
ctxt.category = cat.categoryName();
va_list ap;
va_start(ap, msg); // use variable arg list
const QString message = qt_message(QtCriticalMsg, ctxt, msg, ap);
va_end(ap);
if (isFatal(QtCriticalMsg))
qt_message_fatal(QtCriticalMsg, ctxt, message);
}
/*!
Logs a critical message specified with format \a msg for the context returned
by \a catFunc. Additional parameters, specified by \a msg, may be used.
\since 5.3
\sa qCCritical()
*/
void QMessageLogger::critical(QMessageLogger::CategoryFunction catFunc,
const char *msg, ...) const
{
const QLoggingCategory &cat = (*catFunc)();
if (!cat.isCriticalEnabled())
return;
QMessageLogContext ctxt;
ctxt.copyContextFrom(context);
ctxt.category = cat.categoryName();
va_list ap;
va_start(ap, msg); // use variable arg list
const QString message = qt_message(QtCriticalMsg, ctxt, msg, ap);
va_end(ap);
if (isFatal(QtCriticalMsg))
qt_message_fatal(QtCriticalMsg, ctxt, message);
}
#ifndef QT_NO_DEBUG_STREAM
/*!
Logs a critical message using a QDebug stream
\sa qCritical(), QDebug
*/
QDebug QMessageLogger::critical() const
{
QDebug dbg = QDebug(QtCriticalMsg);
QMessageLogContext &ctxt = dbg.stream->context;
ctxt.copyContextFrom(context);
return dbg;
}
/*!
Logs a critical message into category \a cat using a QDebug stream.
\since 5.3
\sa qCCritical(), QDebug
*/
QDebug QMessageLogger::critical(const QLoggingCategory &cat) const
{
QDebug dbg = QDebug(QtCriticalMsg);
if (!cat.isCriticalEnabled())
dbg.stream->message_output = false;
QMessageLogContext &ctxt = dbg.stream->context;
ctxt.copyContextFrom(context);
ctxt.category = cat.categoryName();
return dbg;
}
/*!
Logs a critical message into category returned by \a catFunc using a QDebug stream.
\since 5.3
\sa qCCritical(), QDebug
*/
QDebug QMessageLogger::critical(QMessageLogger::CategoryFunction catFunc) const
{
return critical((*catFunc)());
}
#endif
#undef qFatal
/*!
Logs a fatal message specified with format \a msg. Additional
parameters, specified by \a msg, may be used.
\sa qFatal()
*/
void QMessageLogger::fatal(const char *msg, ...) const noexcept
{
QString message;
va_list ap;
va_start(ap, msg); // use variable arg list
QT_TERMINATE_ON_EXCEPTION(message = qt_message(QtFatalMsg, context, msg, ap));
va_end(ap);
qt_message_fatal(QtFatalMsg, context, message);
}
/*!
\internal
*/
Q_AUTOTEST_EXPORT QByteArray qCleanupFuncinfo(QByteArray info)
{
// Strip the function info down to the base function name
// note that this throws away the template definitions,
// the parameter types (overloads) and any const/volatile qualifiers.
if (info.isEmpty())
return info;
int pos;
// Skip trailing [with XXX] for templates (gcc), but make
// sure to not affect Objective-C message names.
pos = info.size() - 1;
if (info.endsWith(']') && !(info.startsWith('+') || info.startsWith('-'))) {
while (--pos) {
if (info.at(pos) == '[')
info.truncate(pos);
}
}
// operator names with '(', ')', '<', '>' in it
static const char operator_call[] = "operator()";
static const char operator_lessThan[] = "operator<";
static const char operator_greaterThan[] = "operator>";
static const char operator_lessThanEqual[] = "operator<=";
static const char operator_greaterThanEqual[] = "operator>=";
// canonize operator names
info.replace("operator ", "operator");
// remove argument list
forever {
int parencount = 0;
pos = info.lastIndexOf(')');
if (pos == -1) {
// Don't know how to parse this function name
return info;
}
// find the beginning of the argument list
--pos;
++parencount;
while (pos && parencount) {
if (info.at(pos) == ')')
++parencount;
else if (info.at(pos) == '(')
--parencount;
--pos;
}
if (parencount != 0)
return info;
info.truncate(++pos);
if (info.at(pos - 1) == ')') {
if (info.indexOf(operator_call) == pos - (int)strlen(operator_call))
break;
// this function returns a pointer to a function
// and we matched the arguments of the return type's parameter list
// try again
info.remove(0, info.indexOf('('));
info.chop(1);
continue;
} else {
break;
}
}
// find the beginning of the function name
int parencount = 0;
int templatecount = 0;
--pos;
// make sure special characters in operator names are kept
if (pos > -1) {
switch (info.at(pos)) {
case ')':
if (info.indexOf(operator_call) == pos - (int)strlen(operator_call) + 1)
pos -= 2;
break;
case '<':
if (info.indexOf(operator_lessThan) == pos - (int)strlen(operator_lessThan) + 1)
--pos;
break;
case '>':
if (info.indexOf(operator_greaterThan) == pos - (int)strlen(operator_greaterThan) + 1)
--pos;
break;
case '=': {
int operatorLength = (int)strlen(operator_lessThanEqual);
if (info.indexOf(operator_lessThanEqual) == pos - operatorLength + 1)
pos -= 2;
else if (info.indexOf(operator_greaterThanEqual) == pos - operatorLength + 1)
pos -= 2;
break;
}
default:
break;
}
}
while (pos > -1) {
if (parencount < 0 || templatecount < 0)
return info;
char c = info.at(pos);
if (c == ')')
++parencount;
else if (c == '(')
--parencount;
else if (c == '>')
++templatecount;
else if (c == '<')
--templatecount;
else if (c == ' ' && templatecount == 0 && parencount == 0)
break;
--pos;
}
info = info.mid(pos + 1);
// remove trailing '*', '&' that are part of the return argument
while ((info.at(0) == '*')
|| (info.at(0) == '&'))
info = info.mid(1);
// we have the full function name now.
// clean up the templates
while ((pos = info.lastIndexOf('>')) != -1) {
if (!info.contains('<'))
break;
// find the matching close
int end = pos;
templatecount = 1;
--pos;
while (pos && templatecount) {
char c = info.at(pos);
if (c == '>')
++templatecount;
else if (c == '<')
--templatecount;
--pos;
}
++pos;
info.remove(pos, end - pos + 1);
}
return info;
}
// tokens as recognized in QT_MESSAGE_PATTERN
static const char categoryTokenC[] = "%{category}";
static const char typeTokenC[] = "%{type}";
static const char messageTokenC[] = "%{message}";
static const char fileTokenC[] = "%{file}";
static const char lineTokenC[] = "%{line}";
static const char functionTokenC[] = "%{function}";
static const char pidTokenC[] = "%{pid}";
static const char appnameTokenC[] = "%{appname}";
static const char threadidTokenC[] = "%{threadid}";
static const char qthreadptrTokenC[] = "%{qthreadptr}";
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 ifInfoTokenC[] = "%{if-info}";
static const char ifWarningTokenC[] = "%{if-warning}";
static const char ifCriticalTokenC[] = "%{if-critical}";
static const char ifFatalTokenC[] = "%{if-fatal}";
static const char endifTokenC[] = "%{endif}";
static const char emptyTokenC[] = "";
static const char defaultPattern[] = "%{if-category}%{category}: %{endif}%{message}";
struct QMessagePattern {
QMessagePattern();
~QMessagePattern();
void setPattern(const QString &pattern);
// 0 terminated arrays of literal tokens / literal or placeholder tokens
std::unique_ptr<std::unique_ptr<const char[]>[]> literals;
std::unique_ptr<const char*[]> tokens;
QList<QString> timeArgs; // timeFormats in sequence of %{time
#ifndef QT_BOOTSTRAPPED
QElapsedTimer timer;
#endif
#ifdef QLOGGING_HAVE_BACKTRACE
struct BacktraceParams {
QString backtraceSeparator;
int backtraceDepth;
};
QVector<BacktraceParams> backtraceArgs; // backtrace argumens in sequence of %{backtrace
#endif
bool fromEnvironment;
static QBasicMutex mutex;
};
#ifdef QLOGGING_HAVE_BACKTRACE
Q_DECLARE_TYPEINFO(QMessagePattern::BacktraceParams, Q_MOVABLE_TYPE);
#endif
QBasicMutex QMessagePattern::mutex;
QMessagePattern::QMessagePattern()
{
#ifndef QT_BOOTSTRAPPED
timer.start();
#endif
const QString envPattern = QString::fromLocal8Bit(qgetenv("QT_MESSAGE_PATTERN"));
if (envPattern.isEmpty()) {
setPattern(QLatin1String(defaultPattern));
fromEnvironment = false;
} else {
setPattern(envPattern);
fromEnvironment = true;
}
}
QMessagePattern::~QMessagePattern()
= default;
void QMessagePattern::setPattern(const QString &pattern)
{
timeArgs.clear();
#ifdef QLOGGING_HAVE_BACKTRACE
backtraceArgs.clear();
#endif
// scanner
QList<QString> lexemes;
QString lexeme;
bool inPlaceholder = false;
for (int i = 0; i < pattern.size(); ++i) {
const QChar c = pattern.at(i);
if ((c == QLatin1Char('%'))
&& !inPlaceholder) {
if ((i + 1 < pattern.size())
&& pattern.at(i + 1) == QLatin1Char('{')) {
// beginning of placeholder
if (!lexeme.isEmpty()) {
lexemes.append(lexeme);
lexeme.clear();
}
inPlaceholder = true;
}
}
lexeme.append(c);
if ((c == QLatin1Char('}') && inPlaceholder)) {
// end of placeholder
lexemes.append(lexeme);
lexeme.clear();
inPlaceholder = false;
}
}
if (!lexeme.isEmpty())
lexemes.append(lexeme);
// tokenizer
std::vector<std::unique_ptr<const char[]>> literalsVar;
tokens.reset(new const char*[lexemes.size() + 1]);
tokens[lexemes.size()] = nullptr;
bool nestedIfError = false;
bool inIf = false;
QString error;
for (int i = 0; i < lexemes.size(); ++i) {
const QString lexeme = lexemes.at(i);
if (lexeme.startsWith(QLatin1String("%{"))
&& lexeme.endsWith(QLatin1Char('}'))) {
// placeholder
if (lexeme == QLatin1String(typeTokenC)) {
tokens[i] = typeTokenC;
} else if (lexeme == QLatin1String(categoryTokenC))
tokens[i] = categoryTokenC;
else if (lexeme == QLatin1String(messageTokenC))
tokens[i] = messageTokenC;
else if (lexeme == QLatin1String(fileTokenC))
tokens[i] = fileTokenC;
else if (lexeme == QLatin1String(lineTokenC))
tokens[i] = lineTokenC;
else if (lexeme == QLatin1String(functionTokenC))
tokens[i] = functionTokenC;
else if (lexeme == QLatin1String(pidTokenC))
tokens[i] = pidTokenC;
else if (lexeme == QLatin1String(appnameTokenC))
tokens[i] = appnameTokenC;
else if (lexeme == QLatin1String(threadidTokenC))
tokens[i] = threadidTokenC;
else if (lexeme == QLatin1String(qthreadptrTokenC))
tokens[i] = qthreadptrTokenC;
else if (lexeme.startsWith(QLatin1String(timeTokenC))) {
tokens[i] = timeTokenC;
int spaceIdx = lexeme.indexOf(QChar::fromLatin1(' '));
if (spaceIdx > 0)
timeArgs.append(lexeme.mid(spaceIdx + 1, lexeme.length() - spaceIdx - 2));
else
timeArgs.append(QString());
} else if (lexeme.startsWith(QLatin1String(backtraceTokenC))) {
#ifdef QLOGGING_HAVE_BACKTRACE
tokens[i] = backtraceTokenC;
QString backtraceSeparator = QStringLiteral("|");
int backtraceDepth = 5;
static const QRegularExpression depthRx(QStringLiteral(" depth=(?|\"([^\"]*)\"|([^ }]*))"));
static const QRegularExpression separatorRx(QStringLiteral(" separator=(?|\"([^\"]*)\"|([^ }]*))"));
QRegularExpressionMatch m = depthRx.match(lexeme);
if (m.hasMatch()) {
int depth = m.capturedRef(1).toInt();
if (depth <= 0)
error += QLatin1String("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);
BacktraceParams backtraceParams;
backtraceParams.backtraceDepth = backtraceDepth;
backtraceParams.backtraceSeparator = backtraceSeparator;
backtraceArgs.append(backtraceParams);
#else
error += QLatin1String("QT_MESSAGE_PATTERN: %{backtrace} is not supported by this Qt build\n");
tokens[i] = "";
#endif
}
#define IF_TOKEN(LEVEL) \
else if (lexeme == QLatin1String(LEVEL)) { \
if (inIf) \
nestedIfError = true; \
tokens[i] = LEVEL; \
inIf = true; \
}
IF_TOKEN(ifCategoryTokenC)
IF_TOKEN(ifDebugTokenC)
IF_TOKEN(ifInfoTokenC)
IF_TOKEN(ifWarningTokenC)
IF_TOKEN(ifCriticalTokenC)
IF_TOKEN(ifFatalTokenC)
#undef IF_TOKEN
else if (lexeme == QLatin1String(endifTokenC)) {
tokens[i] = endifTokenC;
if (!inIf && !nestedIfError)
error += QLatin1String("QT_MESSAGE_PATTERN: %{endif} without an %{if-*}\n");
inIf = false;
} else {
tokens[i] = emptyTokenC;
error += QStringLiteral("QT_MESSAGE_PATTERN: Unknown placeholder %1\n")
.arg(lexeme);
}
} else {
char *literal = new char[lexeme.size() + 1];
strncpy(literal, lexeme.toLatin1().constData(), lexeme.size());
literal[lexeme.size()] = '\0';
literalsVar.emplace_back(literal);
tokens[i] = literal;
}
}
if (nestedIfError)
error += QLatin1String("QT_MESSAGE_PATTERN: %{if-*} cannot be nested\n");
else if (inIf)
error += QLatin1String("QT_MESSAGE_PATTERN: missing %{endif}\n");
if (!error.isEmpty())
qt_message_print(error);
literals.reset(new std::unique_ptr<const char[]>[literalsVar.size() + 1]);
std::move(literalsVar.begin(), literalsVar.end(), &literals[0]);
}
#if defined(QLOGGING_HAVE_BACKTRACE) && !defined(QT_BOOTSTRAPPED)
// make sure the function has "Message" in the name so the function is removed
#if ((defined(Q_CC_GNU) && defined(QT_COMPILER_SUPPORTS_SIMD_ALWAYS)) || __has_attribute(optimize)) \
&& !defined(Q_CC_INTEL) && !defined(Q_CC_CLANG)
// force skipping the frame pointer, to save the backtrace() function some work
__attribute__((optimize("omit-frame-pointer")))
#endif
static QStringList backtraceFramesForLogMessage(int frameCount)
{
QStringList result;
if (frameCount == 0)
return result;
// 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 library name (without the path) and the function name.
// This code is protected by QMessagePattern::mutex so it is thread safe on all compilers
static const QRegularExpression rx(QStringLiteral("^(?:[^(]*/)?([^(/]+)\\(([^+]*)(?:[\\+[a-f0-9x]*)?\\) \\[[a-f0-9x]*\\]$"));
QVarLengthArray<void*, 32> buffer(8 + frameCount);
int n = backtrace(buffer.data(), buffer.size());
if (n > 0) {
int numberPrinted = 0;
for (int i = 0; i < n && numberPrinted < frameCount; ++i) {
QScopedPointer<char*, QScopedPointerPodDeleter> strings(backtrace_symbols(buffer.data() + i, 1));
QString trace = QString::fromLatin1(strings.data()[0]);
QRegularExpressionMatch m = rx.match(trace);
if (m.hasMatch()) {
QString library = m.captured(1);
QString function = m.captured(2);
// skip the trace from QtCore that are because of the qDebug itself
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 (function.isEmpty()) {
result.append(QLatin1Char('?') + library + QLatin1Char('?'));
} else {
result.append(function);
}
} else {
if (numberPrinted == 0) {
// innermost, unknown frames are usually the logging framework itself
continue;
}
result.append(QStringLiteral("???"));
}
numberPrinted++;
}
}
return result;
}
static QString formatBacktraceForLogMessage(const QMessagePattern::BacktraceParams backtraceParams,
const char *function)
{
QString backtraceSeparator = backtraceParams.backtraceSeparator;
int backtraceDepth = backtraceParams.backtraceDepth;
QStringList frames = backtraceFramesForLogMessage(backtraceDepth);
if (frames.isEmpty())
return QString();
// if the first frame is unknown, replace it with the context function
if (function && frames.at(0).startsWith(QLatin1Char('?')))
frames[0] = QString::fromUtf8(qCleanupFuncinfo(function));
return frames.join(backtraceSeparator);
}
#endif // QLOGGING_HAVE_BACKTRACE && !QT_BOOTSTRAPPED
Q_GLOBAL_STATIC(QMessagePattern, qMessagePattern)
/*!
\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;
const auto locker = qt_scoped_lock(QMessagePattern::mutex);
QMessagePattern *pattern = qMessagePattern();
if (!pattern) {
// after destruction of static QMessagePattern instance
message.append(str);
return message;
}
bool skip = false;
#ifndef QT_BOOTSTRAPPED
int timeArgsIdx = 0;
#ifdef QLOGGING_HAVE_BACKTRACE
int backtraceArgsIdx = 0;
#endif
#endif
// we do not convert file, function, line literals to local encoding due to overhead
for (int i = 0; pattern->tokens[i]; ++i) {
const char *token = pattern->tokens[i];
if (token == endifTokenC) {
skip = false;
} else if (skip) {
// we skip adding messages, but we have to iterate over
// timeArgsIdx and backtraceArgsIdx anyway
#ifndef QT_BOOTSTRAPPED
if (token == timeTokenC)
timeArgsIdx++;
#ifdef QLOGGING_HAVE_BACKTRACE
else if (token == backtraceTokenC)
backtraceArgsIdx++;
#endif
#endif
} else if (token == messageTokenC) {
message.append(str);
} else if (token == categoryTokenC) {
message.append(QLatin1String(context.category));
} else if (token == typeTokenC) {
switch (type) {
case QtDebugMsg: message.append(QLatin1String("debug")); break;
case QtInfoMsg: message.append(QLatin1String("info")); break;
case QtWarningMsg: message.append(QLatin1String("warning")); break;
case QtCriticalMsg:message.append(QLatin1String("critical")); break;
case QtFatalMsg: message.append(QLatin1String("fatal")); break;
}
} else if (token == fileTokenC) {
if (context.file)
message.append(QLatin1String(context.file));
else
message.append(QLatin1String("unknown"));
} else if (token == lineTokenC) {
message.append(QString::number(context.line));
} else if (token == functionTokenC) {
if (context.function)
message.append(QString::fromLatin1(qCleanupFuncinfo(context.function)));
else
message.append(QLatin1String("unknown"));
#ifndef QT_BOOTSTRAPPED
} else if (token == pidTokenC) {
message.append(QString::number(QCoreApplication::applicationPid()));
} else if (token == appnameTokenC) {
message.append(QCoreApplication::applicationName());
} else if (token == threadidTokenC) {
// print the TID as decimal
message.append(QString::number(qt_gettid()));
} else if (token == qthreadptrTokenC) {
message.append(QLatin1String("0x"));
message.append(QString::number(qlonglong(QThread::currentThread()->currentThread()), 16));
#ifdef QLOGGING_HAVE_BACKTRACE
} else if (token == backtraceTokenC) {
QMessagePattern::BacktraceParams backtraceParams = pattern->backtraceArgs.at(backtraceArgsIdx);
backtraceArgsIdx++;
message.append(formatBacktraceForLogMessage(backtraceParams, context.function));
#endif
} else if (token == timeTokenC) {
QString timeFormat = pattern->timeArgs.at(timeArgsIdx);
timeArgsIdx++;
if (timeFormat == QLatin1String("process")) {
quint64 ms = pattern->timer.elapsed();
message.append(QString::asprintf("%6d.%03d", uint(ms / 1000), uint(ms % 1000)));
} else if (timeFormat == QLatin1String("boot")) {
// just print the milliseconds since the elapsed timer reference
// like the Linux kernel does
QElapsedTimer now;
now.start();
uint ms = now.msecsSinceReference();
message.append(QString::asprintf("%6d.%03d", uint(ms / 1000), uint(ms % 1000)));
#if QT_CONFIG(datestring)
} else if (timeFormat.isEmpty()) {
message.append(QDateTime::currentDateTime().toString(Qt::ISODate));
} else {
message.append(QDateTime::currentDateTime().toString(timeFormat));
#endif // QT_CONFIG(datestring)
}
#endif // !QT_BOOTSTRAPPED
} else if (token == ifCategoryTokenC) {
if (isDefaultCategory(context.category))
skip = true;
#define HANDLE_IF_TOKEN(LEVEL) \
} else if (token == if##LEVEL##TokenC) { \
skip = type != Qt##LEVEL##Msg;
HANDLE_IF_TOKEN(Debug)
HANDLE_IF_TOKEN(Info)
HANDLE_IF_TOKEN(Warning)
HANDLE_IF_TOKEN(Critical)
HANDLE_IF_TOKEN(Fatal)
#undef HANDLE_IF_TOKEN
} else {
message.append(QLatin1String(token));
}
}
return message;
}
#if !QT_DEPRECATED_SINCE(5, 0)
// make sure they're defined to be exported
typedef void (*QtMsgHandler)(QtMsgType, const char *);
Q_CORE_EXPORT QtMsgHandler qInstallMsgHandler(QtMsgHandler);
#endif
static void qDefaultMsgHandler(QtMsgType type, const char *buf);
static void qDefaultMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &buf);
// pointer to QtMsgHandler debug handler (without context)
static QBasicAtomicPointer<void (QtMsgType, const char*)> msgHandler = Q_BASIC_ATOMIC_INITIALIZER(nullptr);
// pointer to QtMessageHandler debug handler (with context)
static QBasicAtomicPointer<void (QtMsgType, const QMessageLogContext &, const QString &)> messageHandler = Q_BASIC_ATOMIC_INITIALIZER(nullptr);
// ------------------------ 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 (shouldLogToStderr())
return false; // Leave logging up to stderr handler
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 bool systemd_default_message_handler(QtMsgType type,
const QMessageLogContext &context,
const QString &message)
{
if (shouldLogToStderr())
return false; // Leave logging up to stderr handler
QString formattedMessage = qFormatLogMessage(type, context, message);
int priority = LOG_INFO; // Informational
switch (type) {
case QtDebugMsg:
priority = LOG_DEBUG; // Debug-level messages
break;
case QtInfoMsg:
priority = LOG_INFO; // Informational conditions
break;
case QtWarningMsg:
priority = LOG_WARNING; // Warning conditions
break;
case QtCriticalMsg:
priority = LOG_CRIT; // Critical conditions
break;
case QtFatalMsg:
priority = LOG_ALERT; // Action must be taken immediately
break;
}
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 bool syslog_default_message_handler(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
if (shouldLogToStderr())
return false; // Leave logging up to stderr handler
QString formattedMessage = qFormatLogMessage(type, context, message);
int priority = LOG_INFO; // Informational
switch (type) {
case QtDebugMsg:
priority = LOG_DEBUG; // Debug-level messages
break;
case QtInfoMsg:
priority = LOG_INFO; // Informational conditions
break;
case QtWarningMsg:
priority = LOG_WARNING; // Warning conditions
break;
case QtCriticalMsg:
priority = LOG_CRIT; // Critical conditions
break;
case QtFatalMsg:
priority = LOG_ALERT; // Action must be taken immediately
break;
}
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 bool android_default_message_handler(QtMsgType type,
const QMessageLogContext &context,
const QString &message)
{
if (shouldLogToStderr())
return false; // Leave logging up to stderr handler
QString formattedMessage = qFormatLogMessage(type, context, message);
android_LogPriority priority = ANDROID_LOG_DEBUG;
switch (type) {
case QtDebugMsg: priority = ANDROID_LOG_DEBUG; break;
case QtInfoMsg: priority = ANDROID_LOG_INFO; break;
case QtWarningMsg: priority = ANDROID_LOG_WARN; break;
case QtCriticalMsg: priority = ANDROID_LOG_ERROR; break;
case QtFatalMsg: priority = ANDROID_LOG_FATAL; break;
};
__android_log_print(priority, qPrintable(QCoreApplication::applicationName()), "%s\n", qPrintable(formattedMessage));
return true; // Prevent further output to stderr
}
#endif //Q_OS_ANDROID
#ifdef Q_OS_WIN
static bool win_message_handler(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
if (shouldLogToStderr())
return false; // Leave logging up to stderr handler
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
#ifdef Q_OS_WASM
static bool wasm_default_message_handler(QtMsgType type,
const QMessageLogContext &context,
const QString &message)
{
if (shouldLogToStderr())
return false; // Leave logging up to stderr handler
QString formattedMessage = qFormatLogMessage(type, context, message);
int emOutputFlags = (EM_LOG_CONSOLE | EM_LOG_DEMANGLE);
QByteArray localMsg = message.toLocal8Bit();
switch (type) {
case QtDebugMsg:
break;
case QtInfoMsg:
break;
case QtWarningMsg:
emOutputFlags |= EM_LOG_WARN;
break;
case QtCriticalMsg:
emOutputFlags |= EM_LOG_ERROR;
break;
case QtFatalMsg:
emOutputFlags |= EM_LOG_ERROR;
}
emscripten_log(emOutputFlags, "%s\n", qPrintable(formattedMessage));
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 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 (formattedMessage.isNull())
return;
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)
handledStderr |= AppleUnifiedLogger::messageHandler(type, context, message);
# elif defined Q_OS_WASM
handledStderr |= wasm_default_message_handler(type, context, message);
# endif
#endif
if (!handledStderr)
stderr_message_handler(type, context, message);
}
/*!
\internal
*/
static void qDefaultMsgHandler(QtMsgType type, const char *buf)
{
QMessageLogContext emptyContext;
qDefaultMessageHandler(type, emptyContext, QString::fromLocal8Bit(buf));
}
#if defined(Q_COMPILER_THREAD_LOCAL)
static thread_local bool msgHandlerGrabbed = false;
static bool grabMessageHandler()
{
if (msgHandlerGrabbed)
return false;
msgHandlerGrabbed = true;
return true;
}
static void ungrabMessageHandler()
{
msgHandlerGrabbed = false;
}
#else
static bool grabMessageHandler() { return true; }
static void ungrabMessageHandler() { }
#endif // (Q_COMPILER_THREAD_LOCAL)
static void qt_message_print(QtMsgType msgType, const QMessageLogContext &context, const QString &message)
{
#ifndef QT_BOOTSTRAPPED
Q_TRACE(qt_message_print, msgType, context.category, context.function, context.file, context.line, message);
// qDebug, qWarning, ... macros do not check whether category is enabledgc
if (msgType != QtFatalMsg && isDefaultCategory(context.category)) {
if (QLoggingCategory *defaultCategory = QLoggingCategory::defaultCategory()) {
if (!defaultCategory->isEnabled(msgType))
return;
}
}
#endif
// prevent recursion in case the message handler generates messages
// itself, e.g. by using Qt API
if (grabMessageHandler()) {
const auto ungrab = qScopeGuard([]{ ungrabMessageHandler(); });
auto oldStyle = msgHandler.loadAcquire();
auto newStye = messageHandler.loadAcquire();
// prefer new message handler over the old one
if (newStye || !oldStyle) {
(newStye ? newStye : qDefaultMessageHandler)(msgType, context, message);
} else {
(oldStyle ? oldStyle : qDefaultMsgHandler)(msgType, message.toLocal8Bit().constData());
}
} else {
fprintf(stderr, "%s\n", message.toLocal8Bit().constData());
}
}
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 (!shouldLogToStderr()) {
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)
wchar_t contextFileL[256];
// we probably should let the compiler do this for us, by declaring QMessageLogContext::file to
// be const wchar_t * in the first place, but the #ifdefery above is very complex and we
// wouldn't be able to change it later on...
convert_to_wchar_t_elided(contextFileL, sizeof contextFileL / sizeof *contextFileL,
context.file);
// get the current report mode
int reportMode = _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_WNDW);
_CrtSetReportMode(_CRT_ERROR, reportMode);
int ret = _CrtDbgReportW(_CRT_ERROR, contextFileL, context.line, _CRT_WIDE(QT_VERSION_STR),
reinterpret_cast<const wchar_t *>(message.utf16()));
if ((ret == 0) && (reportMode & _CRTDBG_MODE_WNDW))
return; // ignore
else if (ret == 1)
_CrtDbgBreak();
#else
Q_UNUSED(context);
Q_UNUSED(message);
#endif
#ifdef Q_OS_WIN
// std::abort() in the MSVC runtime will call _exit(3) if the abort
// behavior is _WRITE_ABORT_MSG - see also _set_abort_behavior(). This is
// the default for a debug-mode build of the runtime. Worse, MinGW's
// std::abort() implementation (in msvcrt.dll) is basically a call to
// _exit(3) too. Unfortunately, _exit() and _Exit() *do* run the static
// destructors of objects in DLLs, a violation of the C++ standard (see
// [support.start.term]). So we bypass std::abort() and directly
// terminate the application.
# if defined(Q_CC_MSVC) && !defined(Q_CC_INTEL)
if (IsProcessorFeaturePresent(PF_FASTFAIL_AVAILABLE))
__fastfail(FAST_FAIL_FATAL_APP_EXIT);
# else
RaiseFailFastException(nullptr, nullptr, 0);
# endif
// Fallback
TerminateProcess(GetCurrentProcess(), STATUS_FATAL_APP_EXIT);
// Tell the compiler the application has stopped.
Q_UNREACHABLE_IMPL();
#else // !Q_OS_WIN
std::abort();
#endif
}
/*!
\internal
*/
void qt_message_output(QtMsgType msgType, const QMessageLogContext &context, const QString &message)
{
qt_message_print(msgType, context, message);
if (isFatal(msgType))
qt_message_fatal(msgType, context, message);
}
void qErrnoWarning(const char *msg, ...)
{
// qt_error_string() will allocate anyway, so we don't have
// to be careful here (like we do in plain qWarning())
QString error_string = qt_error_string(-1); // before vasprintf changes errno/GetLastError()
va_list ap;
va_start(ap, msg);
QString buf = QString::vasprintf(msg, ap);
va_end(ap);
buf += QLatin1String(" (") + error_string + QLatin1Char(')');
QMessageLogContext context;
qt_message_output(QtCriticalMsg, context, buf);
}
void qErrnoWarning(int code, const char *msg, ...)
{
// qt_error_string() will allocate anyway, so we don't have
// to be careful here (like we do in plain qWarning())
va_list ap;
va_start(ap, msg);
QString buf = QString::vasprintf(msg, ap);
va_end(ap);
buf += QLatin1String(" (") + qt_error_string(code) + QLatin1Char(')');
QMessageLogContext context;
qt_message_output(QtCriticalMsg, context, buf);
}
/*!
\typedef QtMsgHandler
\relates <QtGlobal>
\deprecated
This is a typedef for a pointer to a function with the following
signature:
\snippet code/src_corelib_global_qglobal.cpp 7
This typedef is deprecated, you should use QtMessageHandler instead.
\sa QtMsgType, QtMessageHandler, qInstallMsgHandler(), qInstallMessageHandler()
*/
/*!
\typedef QtMessageHandler
\relates <QtGlobal>
\since 5.0
This is a typedef for a pointer to a function with the following
signature:
\snippet code/src_corelib_global_qglobal.cpp 49
\sa QtMsgType, qInstallMessageHandler()
*/
/*!
\fn QtMessageHandler qInstallMessageHandler(QtMessageHandler handler)
\relates <QtGlobal>
\since 5.0
Installs a Qt message \a handler which has been defined
previously. Returns a pointer to the previous message handler.
The message handler is a function that prints out debug messages,
warnings, critical and fatal error messages. The Qt library (debug
mode) contains hundreds of warning messages that are printed
when internal errors (usually invalid function arguments)
occur. Qt built in release mode also contains such warnings unless
QT_NO_WARNING_OUTPUT and/or QT_NO_DEBUG_OUTPUT have been set during
compilation. If you implement your own message handler, you get total
control of these messages.
The default message handler prints the message to the standard
output under X11 or to the debugger under Windows. If it is a
fatal message, the application aborts immediately.
Only one message handler can be defined, since this is usually
done on an application-wide basis to control debug output.
To restore the message handler, call \c qInstallMessageHandler(0).
Example:
\snippet code/src_corelib_global_qglobal.cpp 23
\sa QtMessageHandler, QtMsgType, qDebug(), qInfo(), qWarning(), qCritical(), qFatal(),
{Debugging Techniques}
*/
/*!
\fn QtMsgHandler qInstallMsgHandler(QtMsgHandler handler)
\relates <QtGlobal>
\deprecated
Installs a Qt message \a handler which has been defined
previously. This method is deprecated, use qInstallMessageHandler
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(), qInfo(), qWarning(), qCritical(),
and qFatal(). The category logging output of qCDebug(), qCInfo(),
qCWarning(), and qCCritical() is formatted, too.
Following placeholders are supported:
\table
\header \li Placeholder \li Description
\row \li \c %{appname} \li QCoreApplication::applicationName()
\row \li \c %{category} \li Logging category
\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 The system-wide ID of current thread (if it can be obtained)
\row \li \c %{qthreadptr} \li A pointer to the current QThread (result of QThread::currentThread())
\row \li \c %{type} \li "debug", "warning", "critical" or "fatal"
\row \li \c %{time process} \li time of the message, in seconds since the process started (the token "process" is literal)
\row \li \c %{time boot} \li the time of the message, in seconds since the system boot if that
can be determined (the token "boot" is literal). If the time since boot could not be obtained,
the output is indeterminate (see QElapsedTimer::msecsSinceReference()).
\row \li \c %{time [format]} \li system time when the message occurred, formatted by
passing the \c format to \l QDateTime::toString(). If the format is
not specified, the format of Qt::ISODate is used.
\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}, \c %{if-info}
\c %{if-warning}, \c %{if-critical} or \c %{if-fatal} followed by an \c %{endif}.
What is inside the \c %{if-*} and \c %{endif} will only be printed if the type matches.
Finally, text inside \c %{if-category} ... \c %{endif} is only printed if the category
is not the default one.
Example:
\snippet code/src_corelib_global_qlogging.cpp 0
The default \a pattern is "%{if-category}%{category}: %{endif}%{message}".
The \a pattern can also be changed at runtime by setting the QT_MESSAGE_PATTERN
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}
*/
QtMessageHandler qInstallMessageHandler(QtMessageHandler h)
{
const auto old = messageHandler.fetchAndStoreOrdered(h);
if (old)
return old;
else
return qDefaultMessageHandler;
}
QtMsgHandler qInstallMsgHandler(QtMsgHandler h)
{
const auto old = msgHandler.fetchAndStoreOrdered(h);
if (old)
return old;
else
return qDefaultMsgHandler;
}
void qSetMessagePattern(const QString &pattern)
{
const auto locker = qt_scoped_lock(QMessagePattern::mutex);
if (!qMessagePattern()->fromEnvironment)
qMessagePattern()->setPattern(pattern);
}
/*!
Copies context information from \a logContext into this QMessageLogContext.
Returns a reference to this object.
Note that the version is \b not copied, only the context information.
\internal
*/
QMessageLogContext &QMessageLogContext::copyContextFrom(const QMessageLogContext &logContext) noexcept
{
this->category = logContext.category;
this->file = logContext.file;
this->line = logContext.line;
this->function = logContext.function;
return *this;
}
/*!
\fn QMessageLogger::QMessageLogger()
Constructs a default QMessageLogger. See the other constructors to specify
context information.
*/
/*!
\fn QMessageLogger::QMessageLogger(const char *file, int line, const char *function)
Constructs a QMessageLogger to record log messages for \a file at \a line
in \a function. The is equivalent to QMessageLogger(file, line, function, "default")
*/
/*!
\fn QMessageLogger::QMessageLogger(const char *file, int line, const char *function, const char *category)
Constructs a QMessageLogger to record \a category messages for \a file at \a line
in \a function.
*/
/*!
\fn void QMessageLogger::noDebug(const char *, ...) const
\internal
Ignores logging output
\sa QNoDebug, qDebug()
*/
/*!
\fn QMessageLogContext::QMessageLogContext()
\internal
Constructs a QMessageLogContext
*/
/*!
\fn QMessageLogContext::QMessageLogContext(const char *fileName, int lineNumber, const char *functionName, const char *categoryName)
\internal
Constructs a QMessageLogContext with for file \a fileName at line
\a lineNumber, in function \a functionName, and category \a categoryName.
*/
QT_END_NAMESPACE