Як перенаправити вихід 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.