diff --git a/src/corelib/global/qlogging.cpp b/src/corelib/global/qlogging.cpp index 27a9f3a1792..ba7b607496d 100644 --- a/src/corelib/global/qlogging.cpp +++ b/src/corelib/global/qlogging.cpp @@ -86,6 +86,8 @@ extern char *__progname; #ifdef Q_OS_WIN #include +#include +#include "qfunctionpointer.h" #endif QT_BEGIN_NAMESPACE @@ -176,6 +178,54 @@ static bool isFatal(QtMsgType msgType) 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 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(GetProcAddress(hKernel, "GetThreadDescription")); + return reinterpret_cast(funcPtr); + } (); + if (!pGetThreadDescription) + return false; // Not available on this system + PWSTR description = nullptr; + HRESULT hr = pGetThreadDescription(GetCurrentThread(), &description); + std::unique_ptr 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 /*! @@ -1059,6 +1109,7 @@ static const char functionTokenC[] = "%{function}"; static const char pidTokenC[] = "%{pid}"; static const char appnameTokenC[] = "%{appname}"; static const char threadidTokenC[] = "%{threadid}"; +static const char threadnameTokenC[] = "%{threadname}"; static const char qthreadptrTokenC[] = "%{qthreadptr}"; static const char timeTokenC[] = "%{time"; //not a typo: this command has arguments static const char backtraceTokenC[] = "%{backtrace"; //ditto @@ -1218,6 +1269,8 @@ void QMessagePattern::setPattern(const QString &pattern) tokens[i] = appnameTokenC; else if (lexeme == QLatin1StringView(threadidTokenC)) tokens[i] = threadidTokenC; + else if (lexeme == QLatin1StringView(threadnameTokenC)) + tokens[i] = threadnameTokenC; else if (lexeme == QLatin1StringView(qthreadptrTokenC)) tokens[i] = qthreadptrTokenC; else if (lexeme.startsWith(QLatin1StringView(timeTokenC))) { @@ -1620,6 +1673,9 @@ static QString formatLogMessage(QtMsgType type, const QMessageLogContext &contex } else if (token == threadidTokenC) { // print the TID as decimal 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) { message.append("0x"_L1); 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 %{pid} \li QCoreApplication::applicationPid() \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 %{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) diff --git a/tests/auto/corelib/global/qlogging/qlogging_helper.cpp b/tests/auto/corelib/global/qlogging/qlogging_helper.cpp index 8c6dadb34d0..1254de7377e 100644 --- a/tests/auto/corelib/global/qlogging/qlogging_helper.cpp +++ b/tests/auto/corelib/global/qlogging/qlogging_helper.cpp @@ -3,6 +3,7 @@ #include #include +#include #ifdef Q_CC_GNU #define NEVER_INLINE __attribute__((__noinline__)) @@ -35,6 +36,18 @@ void MyClass::myFunction(int 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) { QCoreApplication app(argc, argv); @@ -57,6 +70,10 @@ int main(int argc, char **argv) MyClass cl; QMetaObject::invokeMethod(&cl, "mySlot1"); + Thread thread; + thread.start(); + thread.wait(); + return 0; } diff --git a/tests/auto/corelib/global/qlogging/tst_qlogging.cpp b/tests/auto/corelib/global/qlogging/tst_qlogging.cpp index 42e91726f29..bccb53defe1 100644 --- a/tests/auto/corelib/global/qlogging/tst_qlogging.cpp +++ b/tests/auto/corelib/global/qlogging/tst_qlogging.cpp @@ -729,16 +729,16 @@ void tst_qmessagehandler::qMessagePattern_data() // %{file} is tricky because of shadow builds QTest::newRow("basic") << "%{type} %{appname} %{line} %{function} %{message}" << true << (QList() - << "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 << "static destructor" - << "debug tst_qlogging 35 MyClass::myFunction from_a_function 34" - << "debug tst_qlogging 45 main qDebug" - << "info tst_qlogging 46 main qInfo" - << "warning tst_qlogging 47 main qWarning" - << "critical tst_qlogging 48 main qCritical" - << "warning tst_qlogging 51 main qDebug with category" - << "debug tst_qlogging 55 main qDebug2"); + << "debug tst_qlogging 36 MyClass::myFunction from_a_function 34" + << "debug tst_qlogging 58 main qDebug" + << "info tst_qlogging 59 main qInfo" + << "warning tst_qlogging 60 main qWarning" + << "critical tst_qlogging 61 main qCritical" + << "warning tst_qlogging 64 main qDebug with category" + << "debug tst_qlogging 68 main qDebug2"); QTest::newRow("invalid") << "PREFIX: %{unknown} %{message}" << false << (QList() @@ -772,6 +772,11 @@ void tst_qmessagehandler::qMessagePattern_data() QTest::newRow("pid-tid") << "%{pid}/%{threadid}: %{message}" << true << QList(); // 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() + << "[1234567890ABCDE] qDebug from another thread"); +#endif QTest::newRow("qthreadptr") << "ThreadId:%{qthreadptr}: %{message}" << true << (QList() << "ThreadId:0x");