QMessagePattern: add the %{threadname} placeholder

The thread name is often more meaningful than a number or pointer.

[ChangeLog][QtCore][Logging framework] QMessagePattern /
QT_MESSAGE_PATTERN now support the %{threadname} placeholder.

Change-Id: I7ad8821c8b887e428023144d6e93e20c53bf757e
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Aurélien Brooke 2025-02-12 08:59:19 +01:00
parent c8338d8e77
commit ec2e3e7ac9
3 changed files with 87 additions and 8 deletions

View File

@ -86,6 +86,8 @@ extern char *__progname;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <qt_windows.h> #include <qt_windows.h>
#include <processthreadsapi.h>
#include "qfunctionpointer.h"
#endif #endif
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@ -176,6 +178,54 @@ static bool isFatal(QtMsgType msgType)
return false; return false;
} }
#if defined(Q_OS_LINUX) || defined(Q_OS_DARWIN) || defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
static bool qt_append_thread_name_to(QString &message)
{
std::array<char, 16> name{};
if (pthread_getname_np(pthread_self(), name.data(), name.size()) == 0) {
QUtf8StringView threadName(name.data());
if (!threadName.isEmpty()) {
message.append(threadName);
return true;
}
}
return false;
}
#elif defined(Q_OS_WIN)
typedef HRESULT (WINAPI *GetThreadDescriptionFunc)(HANDLE, PWSTR *);
static bool qt_append_thread_name_to(QString &message)
{
// Once MinGW 12.0 is required for Qt, we can call GetThreadDescription directly
// instead of this runtime resolve:
static GetThreadDescriptionFunc pGetThreadDescription = []() -> GetThreadDescriptionFunc {
HMODULE hKernel = GetModuleHandleW(L"kernel32.dll");
if (!hKernel)
return nullptr;
auto funcPtr = reinterpret_cast<QFunctionPointer>(GetProcAddress(hKernel, "GetThreadDescription"));
return reinterpret_cast<GetThreadDescriptionFunc>(funcPtr);
} ();
if (!pGetThreadDescription)
return false; // Not available on this system
PWSTR description = nullptr;
HRESULT hr = pGetThreadDescription(GetCurrentThread(), &description);
std::unique_ptr<WCHAR, decltype(&LocalFree)> descriptionOwner(description, &LocalFree);
if (SUCCEEDED(hr)) {
QStringView threadName(description);
if (!threadName.isEmpty()) {
message.append(threadName);
return true;
}
}
return false;
}
#else
static bool qt_append_thread_name_to(QString &message)
{
Q_UNUSED(message)
return false;
}
#endif
#ifndef Q_OS_WASM #ifndef Q_OS_WASM
/*! /*!
@ -1059,6 +1109,7 @@ static const char functionTokenC[] = "%{function}";
static const char pidTokenC[] = "%{pid}"; static const char pidTokenC[] = "%{pid}";
static const char appnameTokenC[] = "%{appname}"; static const char appnameTokenC[] = "%{appname}";
static const char threadidTokenC[] = "%{threadid}"; static const char threadidTokenC[] = "%{threadid}";
static const char threadnameTokenC[] = "%{threadname}";
static const char qthreadptrTokenC[] = "%{qthreadptr}"; static const char qthreadptrTokenC[] = "%{qthreadptr}";
static const char timeTokenC[] = "%{time"; //not a typo: this command has arguments static const char timeTokenC[] = "%{time"; //not a typo: this command has arguments
static const char backtraceTokenC[] = "%{backtrace"; //ditto static const char backtraceTokenC[] = "%{backtrace"; //ditto
@ -1218,6 +1269,8 @@ void QMessagePattern::setPattern(const QString &pattern)
tokens[i] = appnameTokenC; tokens[i] = appnameTokenC;
else if (lexeme == QLatin1StringView(threadidTokenC)) else if (lexeme == QLatin1StringView(threadidTokenC))
tokens[i] = threadidTokenC; tokens[i] = threadidTokenC;
else if (lexeme == QLatin1StringView(threadnameTokenC))
tokens[i] = threadnameTokenC;
else if (lexeme == QLatin1StringView(qthreadptrTokenC)) else if (lexeme == QLatin1StringView(qthreadptrTokenC))
tokens[i] = qthreadptrTokenC; tokens[i] = qthreadptrTokenC;
else if (lexeme.startsWith(QLatin1StringView(timeTokenC))) { else if (lexeme.startsWith(QLatin1StringView(timeTokenC))) {
@ -1620,6 +1673,9 @@ static QString formatLogMessage(QtMsgType type, const QMessageLogContext &contex
} else if (token == threadidTokenC) { } else if (token == threadidTokenC) {
// print the TID as decimal // print the TID as decimal
message.append(QString::number(qt_gettid())); message.append(QString::number(qt_gettid()));
} else if (token == threadnameTokenC) {
if (!qt_append_thread_name_to(message))
message.append(QString::number(qt_gettid())); // fallback to the TID
} else if (token == qthreadptrTokenC) { } else if (token == qthreadptrTokenC) {
message.append("0x"_L1); message.append("0x"_L1);
message.append(QString::number(qlonglong(QThread::currentThread()->currentThread()), 16)); message.append(QString::number(qlonglong(QThread::currentThread()->currentThread()), 16));
@ -2197,6 +2253,7 @@ void qErrnoWarning(int code, const char *msg, ...)
\row \li \c %{message} \li The actual message \row \li \c %{message} \li The actual message
\row \li \c %{pid} \li QCoreApplication::applicationPid() \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 %{threadid} \li The system-wide ID of current thread (if it can be obtained)
\row \li \c %{threadname} \li The current thread name (if it can be obtained, or the thread ID, since Qt 6.10)
\row \li \c %{qthreadptr} \li A pointer to the current QThread (result of QThread::currentThread()) \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 %{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 process} \li time of the message, in seconds since the process started (the token "process" is literal)

View File

@ -3,6 +3,7 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QThread>
#ifdef Q_CC_GNU #ifdef Q_CC_GNU
#define NEVER_INLINE __attribute__((__noinline__)) #define NEVER_INLINE __attribute__((__noinline__))
@ -35,6 +36,18 @@ void MyClass::myFunction(int a)
qDebug() << "from_a_function" << a; qDebug() << "from_a_function" << a;
} }
class Thread : public QThread
{
public:
Thread() {
setObjectName("1234567890ABCDE"); // 16 bytes incl. NUL
}
protected:
void run() final {
qDebug("qDebug from another thread");
}
};
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
QCoreApplication app(argc, argv); QCoreApplication app(argc, argv);
@ -57,6 +70,10 @@ int main(int argc, char **argv)
MyClass cl; MyClass cl;
QMetaObject::invokeMethod(&cl, "mySlot1"); QMetaObject::invokeMethod(&cl, "mySlot1");
Thread thread;
thread.start();
thread.wait();
return 0; return 0;
} }

View File

@ -729,16 +729,16 @@ void tst_qmessagehandler::qMessagePattern_data()
// %{file} is tricky because of shadow builds // %{file} is tricky because of shadow builds
QTest::newRow("basic") << "%{type} %{appname} %{line} %{function} %{message}" << true << (QList<QByteArray>() QTest::newRow("basic") << "%{type} %{appname} %{line} %{function} %{message}" << true << (QList<QByteArray>()
<< "debug 14 T::T static constructor" << "debug 15 T::T static constructor"
// we can't be sure whether the QT_MESSAGE_PATTERN is already destructed // we can't be sure whether the QT_MESSAGE_PATTERN is already destructed
<< "static destructor" << "static destructor"
<< "debug tst_qlogging 35 MyClass::myFunction from_a_function 34" << "debug tst_qlogging 36 MyClass::myFunction from_a_function 34"
<< "debug tst_qlogging 45 main qDebug" << "debug tst_qlogging 58 main qDebug"
<< "info tst_qlogging 46 main qInfo" << "info tst_qlogging 59 main qInfo"
<< "warning tst_qlogging 47 main qWarning" << "warning tst_qlogging 60 main qWarning"
<< "critical tst_qlogging 48 main qCritical" << "critical tst_qlogging 61 main qCritical"
<< "warning tst_qlogging 51 main qDebug with category" << "warning tst_qlogging 64 main qDebug with category"
<< "debug tst_qlogging 55 main qDebug2"); << "debug tst_qlogging 68 main qDebug2");
QTest::newRow("invalid") << "PREFIX: %{unknown} %{message}" << false << (QList<QByteArray>() QTest::newRow("invalid") << "PREFIX: %{unknown} %{message}" << false << (QList<QByteArray>()
@ -772,6 +772,11 @@ void tst_qmessagehandler::qMessagePattern_data()
QTest::newRow("pid-tid") << "%{pid}/%{threadid}: %{message}" QTest::newRow("pid-tid") << "%{pid}/%{threadid}: %{message}"
<< true << QList<QByteArray>(); // can't match anything, just test validity << true << QList<QByteArray>(); // can't match anything, just test validity
#if defined(Q_OS_LINUX) || defined(Q_OS_DARWIN) || defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) || defined(Q_OS_WIN)
QTest::newRow("threadname") << "%[%{threadname}] %{message}"
<< true << (QList<QByteArray>()
<< "[1234567890ABCDE] qDebug from another thread");
#endif
QTest::newRow("qthreadptr") << "ThreadId:%{qthreadptr}: %{message}" QTest::newRow("qthreadptr") << "ThreadId:%{qthreadptr}: %{message}"
<< true << (QList<QByteArray>() << true << (QList<QByteArray>()
<< "ThreadId:0x"); << "ThreadId:0x");