Як перенаправити вихід qDebug, qWarning, qCritical тощо?


84

Я використовую багато qDebug() <<операторів для виведення налагодження. Чи існує якийсь крос-платформенний спосіб перенаправлення результатів налагодження у файл, не вдаючись до сценаріїв оболонки? Я здогадуюсь, що open () і dup2 () зроблять роботу в Linux, але чи буде вона працювати, скомпільована з MinGW в Windows?

І, можливо, існує спосіб Qt це зробити?

Відповіді:


120

Вам потрібно встановити обробник повідомлень за допомогою qInstallMsgHandlerфункції, а потім ви можете використовувати QTextStreamдля написання повідомлення про налагодження у файл. Ось приклад прикладу:

#include <QtGlobal>
#include <stdio.h>
#include <stdlib.h>

void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QByteArray localMsg = msg.toLocal8Bit();
    switch (type) {
    case QtDebugMsg:
        fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtInfoMsg:
        fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtWarningMsg:
        fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtCriticalMsg:
        fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtFatalMsg:
        fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        abort();
    }
}

int main(int argc, char **argv)
{
    qInstallMessageHandler(myMessageOutput); // Install the handler
    QApplication app(argc, argv);
    ...
    return app.exec();
}

Взято з документа qInstallMsgHandler(я лише додав коментарі):

У наведеному вище прикладі функція myMessageOutputвикористовує stderrякий, можливо, ви захочете замінити іншим потоком файлів або повністю перепишіть функцію!

Після того, як ви пишете , і встановити цю функцію, всі ваші qDebug(а також qWarning, і qCriticalт.д.) повідомлення будуть перенаправлені в файл ви пишете в обробнику.


3
Гей, велике спасибі. Це не лише дозволить мені перенаправити вихідні дані налагодження у файл, але також дозволить надрукувати більше корисної інформації, наприклад
— позначку

2
@Septagram: Саме так. Ви можете додати кілька корисних повідомлень у самій вішалці; і ви можете навіть виведення різних повідомлень на різні файли, грунтуючись на тому, що ви використовуєте qDebug, qWarning, qCriticalі так далі!
— Nawaz

1
До речі, зворотний виклик, який робить фактичний результат - void myMessageOutput (тип QtMsgType, const char * msg) - в якому кодуванні він отримує повідомлення?
— Септаграма

8
Посилання на документацію та API трохи змінилися. qInstallMsgHandlerбуло припинено та замінено на qInstallMessageHandler(та сама ідея) у Qt5. Для 5.0 qInstallMsgHandlerє на qt-project.org/doc/qt-5.0/qtcore/…, і qInstallMessageHandlerтам також є. Для 5.1 qInstallMsgHandlerбув видалений повністю.
— Джейсон С

1
@Aditya: У Qt4 зворотний дзвінок приймає лише два аргументи. Тож ви можете використати це:void myMessageOutput(QtMsgType type, const char *msg) { ... }
— Наваз,

19

Від сюди все заслуга духу .

#include <QApplication>
#include <QtDebug>
#include <QFile>
#include <QTextStream>

void myMessageHandler(QtMsgType type, const QMessageLogContext &, const QString & msg)
{
    QString txt;
    switch (type) {
    case QtDebugMsg:
        txt = QString("Debug: %1").arg(msg);
        break;
    case QtWarningMsg:
        txt = QString("Warning: %1").arg(msg);
    break;
    case QtCriticalMsg:
        txt = QString("Critical: %1").arg(msg);
    break;
    case QtFatalMsg:
        txt = QString("Fatal: %1").arg(msg);
    break;
    }
    QFile outFile("log");
    outFile.open(QIODevice::WriteOnly | QIODevice::Append);
    QTextStream ts(&outFile);
    ts << txt << endl;
}

int main( int argc, char * argv[] )
{
    QApplication app( argc, argv );
    qInstallMessageHandler(myMessageHandler);   
    ...
    return app.exec();
}

case QtFatalMsg: ... abort (); // він кине роботу перед тим, як писати журнал
— raidsan

Починаючи з QT 5, qInstallMessageHandlerслід використовувати замість того, qInstallMsgHandlerщоб змінити обробник повідомлень.
— Суб

Цей обробник повідомлень не захищений від потоків. Ви втратите повідомлення журналу, якщо вони будуть надіслані двома потоками одночасно (outFile.open () поверне false для одного з потоків). Ви можете заблокувати QMutex перед спробою відкрити файл, а потім розблокувати мьютекс після закриття файлу. Це найпростіший підхід, але він запровадить суперечку між потоками. Вам потрібно буде поглянути на низько накладні потокові безпечні черги повідомлень, інакше ... і вам може бути краще використовувати фреймворк.
— Ентоні Хейворд,

9

Ось робочий приклад підключення обробника повідомлень за замовчуванням.

Дякую @ Росс Роджерс!

// -- main.cpp

// Get the default Qt message handler.
static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(0);

void myCustomMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    // Handle the messages!

    // Call the default handler.
    (*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
}

int main(int argc, char *argv[])
{
    qInstallMessageHandler(myCustomMessageHandler);

    QApplication a(argc, argv);

    qDebug() << "Wello Horld!";

    return 0;
}

8

Ось крос-платформне рішення для входу на консоль, якщо додаток запускався від Qt Creator, і до debug.logфайлу, коли він компілюється та запускається як автономна програма.

main.cpp :

#include <QApplication>
#include <QtGlobal>
#include <QtDebug>
#include <QTextStream>
#include <QTextCodec>
#include <QLocale>
#include <QTime>
#include <QFile>   

const QString logFilePath = "debug.log";
bool logToFile = false;
    
void customMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QHash<QtMsgType, QString> msgLevelHash({{QtDebugMsg, "Debug"}, {QtInfoMsg, "Info"}, {QtWarningMsg, "Warning"}, {QtCriticalMsg, "Critical"}, {QtFatalMsg, "Fatal"}});
    QByteArray localMsg = msg.toLocal8Bit();
    QTime time = QTime::currentTime();
    QString formattedTime = time.toString("hh:mm:ss.zzz");
    QByteArray formattedTimeMsg = formattedTime.toLocal8Bit();
    QString logLevelName = msgLevelHash[type];
    QByteArray logLevelMsg = logLevelName.toLocal8Bit();

    if (logToFile) {
        QString txt = QString("%1 %2: %3 (%4)").arg(formattedTime, logLevelName, msg,  context.file);
        QFile outFile(logFilePath);
        outFile.open(QIODevice::WriteOnly | QIODevice::Append);
        QTextStream ts(&outFile);
        ts << txt << endl;
        outFile.close();
    } else {
        fprintf(stderr, "%s %s: %s (%s:%u, %s)\n", formattedTimeMsg.constData(), logLevelMsg.constData(), localMsg.constData(), context.file, context.line, context.function);
        fflush(stderr);
    }

    if (type == QtFatalMsg)
        abort();
}

int main(int argc, char *argv[])
{
    QByteArray envVar = qgetenv("QTDIR");       //  check if the app is ran in Qt Creator

    if (envVar.isEmpty())
        logToFile = true;

    qInstallMessageHandler(customMessageOutput); // custom message handler for debugging

    QApplication a(argc, argv);
    // ...and the rest of 'main' follows

Форматування журналу виконується QString("%1 %2: %3 (%4)").arg...(для файлу) та fprintf(stderr, "%s %s: %s (%s:%u, %s)\n"...(для консолі).

Натхнення: https://gist.github.com/polovik/10714049 .


Я бачу, що ви викликаєте "outFile.close ()" у кожній події журналу. Чи можу я це опустити?
— розбіжник

Я не рекомендую його в цьому налаштуванні, оскільки ви щоразу відкриваєте файл журналу, тому його слід закрити. Але ви можете змінити алгоритм таким чином, що файл журналу відкривається лише один раз у ініціалізації програми. Таким чином, вам потрібно буде закрити його лише один раз, коли програма виходить із програми.
— Нейромедіатор

1
Дякую! Це дуже корисно.
— Аарон

Цей обробник повідомлень не захищений від потоків. Ви втратите повідомлення журналу, якщо вони будуть надіслані двома потоками одночасно (outFile.open () поверне false для одного з потоків). Ви можете заблокувати QMutex перед спробою відкрити файл, а потім розблокувати мьютекс після закриття файлу. Це найпростіший підхід, але він запровадить суперечку між потоками. Вам потрібно буде поглянути на низькопорожні потокові безпечні черги повідомлень, інакше ... і вам може бути краще використовувати фреймворк!
— Ентоні Хейворд,

Я з вами згоден - це далеко не ідеально. Але це робить свою роботу більшу частину часу. У будь-якому випадку, будь-які моди вітаються!
— Нейромедіатор

6

Ну, я б сказав, що момент, коли вам потрібно перенаправити результат налагодження на щось інше, ніж stderr, це коли ви можете подумати про якийсь інструмент ведення журналу. Якщо ви відчуваєте, що вам потрібен, я б рекомендував використовувати QxtLogger( "Клас QxtLogger - простий у використанні, простий у розширенні інструмент ведення журналу". ) З Qxtбібліотеки.


0

Ось простий, безпечний для потоку ідіоматичний приклад Qt для реєстрації як у stderrфайлі, так і у файлі:

void messageHandler (тип QtMsgType, const QMessageLogContext і контекст, const QString і повідомлення)
{
    статичний мьютекс QMutex;
    Блокування QMutexLocker (& mutex);

    статичний QFile logFile (LOGFILE_LOCATION);
    статичний bool logFileIsOpen = logFile.open (QIODevice :: Append | QIODevice :: Text);

    std :: cerr << qPrintable (qFormatLogMessage (тип, контекст, повідомлення)) << std :: endl;

    if (logFileIsOpen) {
        logFile.write (qFormatLogMessage (тип, контекст, повідомлення) .toUtf8 () + '\ n');
        logFile.flush ();
    }
}

Встановіть його, qInstallMessageHandler(messageHandler)як описано в інших відповідях.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.