Как перенаправить вывод qDebug, qWarning, qCritical и т. Д.?

85

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

А может есть способ Qt это сделать?

Септаграмма
источник

Ответы:

121

Вам нужно установить обработчик сообщений с помощью 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
@ Септаграмма: Совершенно верно. Вы можете добавить несколько полезных сообщений в самом обработчике; и вы можете даже вывода различных сообщений на различные файлы, основываясь на том, что вы используете 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) { ... }
Nawaz
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();
}
Sandeep Datta
источник
case QtFatalMsg: ... abort (); // он завершится перед записью журнала
raidsan 02
Запуск с QT 5, qInstallMessageHandlerследует использовать вместо qInstallMsgHandlerизменения обработчика сообщений.
SuB
Этот обработчик сообщений не является потокобезопасным. Вы потеряете сообщения журнала, если они будут отправлены двумя потоками одновременно (outFile.open () вернет false для одного из потоков). Вы можете заблокировать QMutex до того, как попытаетесь открыть файл, а затем разблокировать мьютекс после закрытия файла. Это простейший подход, но он приведет к конфликту потоков. В противном случае вам нужно будет посмотреть на потокобезопасную очередь сообщений с низкими накладными расходами ... и вам может быть лучше использовать фреймворк.
Энтони Хейворд
9

Вот рабочий пример подключения обработчика сообщений по умолчанию.

Спасибо, @Ross Rogers!

// -- 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 () в каждом событии журнала. Могу я пропустить это?
diverger
Я не рекомендую его в этой настройке, так как вы открываете файл журнала каждый раз, и поэтому он должен быть закрыт. Но вы можете изменить алгоритм таким образом, чтобы этот файл журнала открывался только один раз при запуске приложения. Таким образом, вам нужно будет закрыть его только один раз при выходе из приложения.
Neurotransmitter
1
Благодаря! Это очень полезно.
Аарон
Этот обработчик сообщений не является потокобезопасным. Вы потеряете сообщения журнала, если они будут отправлены двумя потоками одновременно (outFile.open () вернет false для одного из потоков). Вы можете заблокировать QMutex перед попыткой открыть файл, а затем разблокировать мьютекс после закрытия файла. Это простейший подход, но он приведет к конфликту потоков. В противном случае вам нужно будет посмотреть на потокобезопасную очередь сообщений с низкими накладными расходами ... и вам может быть лучше использовать фреймворк!
Энтони Хейворд
Я согласен с вами - это далеко не идеально. Но большую часть времени он выполняет свою работу. В любом случае любые модификации приветствуются!
Neurotransmitter
6

Что ж, я бы сказал, что момент, когда вам нужно перенаправить вывод отладки на что-то другое, кроме stderr, - это когда вы можете подумать о каком-нибудь инструменте ведения журнала. Если вы чувствуете, что он вам нужен, я бы рекомендовал использовать QxtLogger( «Класс QxtLogger - простой в использовании, легко расширяемый инструмент ведения журнала.» ) Из Qxtбиблиотеки.

Петр Доброгост
источник
0

Вот простой, потокобезопасный идиоматический пример Qt для входа stderrи файла:

void messageHandler (тип QtMsgType, const QMessageLogContext и контекст, const QString и сообщение)
{
    статический мьютекс QMutex;
    QMutexLocker lock (& ​​mutex);

    статический QFile logFile (LOGFILE_LOCATION);
    static 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)как описано в других ответах.

mrts
источник