Все Qt-разработчики используют qDebug(), когда отлаживают приложение, написанное на Qt, но также имеются макросы qInfo(), qWarning(), qCritical() и qFatal() (который на момент написания статьи был с ошибками и не работал).
С помощью этих событий, вы можете разделить ошибки по уровням значимости и применить фильтры, для разделения того, какие ошибки нужно выводить, а какие нет.
Для перенаправления сообщений об ошибках в текстовый файл, вам необходимо установить CallBack-функцию обработчик в приложение. Для этого используется функция qInstalMessageHandler .
Сигнатура обработчика должна выглядеть следующий образом:
void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
Через эту функцию мы будем получать следующие данные:
- QtMsgType type - Тип сообщения
- QtInfoMsg
- QtDebugMsg
- QtWarningMsg
- QtCriticalMsg
- QtFatalMsg
- QMessageLogContext &context - контекст сообщения, самое полезное в котором - это категория сообщения. Это может быть полезным, когда необходимо определить местоположение сообщения в коде, то есть из каких компонентов мы получаем данные или к какому типу взаимодействия относится сообщение.
- QString &msg - передаваемое сообщение об ошибке.
Дополнительные категории - QLoggingCategory
Дополнительные категории являются объектами класса QLoggingCategory. Может быть несколько подходов к работе с категориям, например, мы можем создать категорию объекта в каком-либо классе и передать его в сообщение об ошибке:
QLoggingCategory m_category("Test"); qDebug(m_category) << "Check it";
Получим следующий вывод:
Test: Check it
Неудобство данного подхода заключается в том, что несколько классов могут иметь одни и те же категории ошибок, но в каждом классе необходимо создавать одни и те же категории. Поэтому воспользуемся другим подходом. А именно регистрацией категории через макрос.
LoggingCategories.h
Мы объявим четыре категории в данном заголовочном классе.
#ifndef LOGGER_H #define LOGGER_H #include <QLoggingCategory> Q_DECLARE_LOGGING_CATEGORY(logDebug) Q_DECLARE_LOGGING_CATEGORY(logInfo) Q_DECLARE_LOGGING_CATEGORY(logWarning) Q_DECLARE_LOGGING_CATEGORY(logCritical) #endif // LOGGER_H
LoggingCategories.cpp
И присвоим имена данным категориям
#include "LoggingCategories.h" Q_LOGGING_CATEGORY(logDebug, "Debug") Q_LOGGING_CATEGORY(logInfo, "Info") Q_LOGGING_CATEGORY(logWarning, "Warning") Q_LOGGING_CATEGORY(logCritical, "Critical")
Использование LoggingCategories
Для того, чтобы применить данные категории, вы должны подключить заголовочный файл в тот файл, где будут использоваться данные категории. И затем использовать их:
qDebug(logDebug()) << "Check it";
Получим следующий вывод:
Debug: Check it
Установка обработчика
Для демонстрации работы обработчика воспользуемся графическим приложением, в котором будет четыре кнопки.
- Debug
- Info
- Warning
- Critical
В обработчике каждой из кнопок будут вызываться соответствующие макросы логирования событий и применяться соответствующие категории, перечисленные выше. Окно приложение создано через графический дизайнер и выглядит следующим образом.
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private slots: void on_debugButton_clicked(); void on_infoButton_clicked(); void on_warningButton_clicked(); void on_criticalButton_clicked(); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h" #include "ui_mainwindow.h" #include <QDebug> #include "LoggingCategories.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_debugButton_clicked() { qDebug(logDebug()) << "Debug Button"; } void MainWindow::on_infoButton_clicked() { qInfo(logInfo()) << "Info Button"; } void MainWindow::on_warningButton_clicked() { qWarning(logWarning()) << "Warning Button"; } void MainWindow::on_criticalButton_clicked() { qCritical(logCritical()) << "Critical Button"; }
main.cpp
#include "mainwindow.h" #include <QApplication> #include <QFile> #include <QDir> #include <QScopedPointer> #include <QTextStream> #include <QDateTime> #include <QLoggingCategory> // Умный указатель на файл логирования QScopedPointer<QFile> m_logFile; // Объявляение обработчика void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg); int main(int argc, char *argv[]) { QApplication a(argc, argv); // Устанавливаем файл логирования, // внимательно сверьтесь с тем, какой используете путь для файла m_logFile.reset(new QFile("C:/example/logFile.txt")); // Открываем файл логирования m_logFile.data()->open(QFile::Append | QFile::Text); // Устанавливаем обработчик qInstallMessageHandler(messageHandler); MainWindow w; w.show(); return a.exec(); } // Реализация обработчика void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { // Открываем поток записи в файл QTextStream out(m_logFile.data()); // Записываем дату записи out << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz "); // По типу определяем, к какому уровню относится сообщение switch (type) { case QtInfoMsg: out << "INF "; break; case QtDebugMsg: out << "DBG "; break; case QtWarningMsg: out << "WRN "; break; case QtCriticalMsg: out << "CRT "; break; case QtFatalMsg: out << "FTL "; break; } // Записываем в вывод категорию сообщения и само сообщение out << context.category << ": " << msg << endl; out.flush(); // Очищаем буферизированные данные }
Итог
В результате логирования событий будет создан файл C:\example\logFile.txt (Примечание. Но только если перед запуском приложения папка C:\example уже существовала).
При нажатии кнопок в окне приложения содержимое файла будет выглядеть следующим образом:
2016-06-04 17:05:57.439 DBG Debug: Debug Button 2016-06-04 17:05:57.903 INF Info: Info Button 2016-06-04 17:05:58.367 WRN Warning: Warning Button 2016-06-04 17:05:58.815 CRT Critical: Critical Button
Скачать Qt приложение с логированием в текстовый файл
Добрый день. А как сделать, что бы ещё и в консоль выводилось?
Добрый день
Многого хотите ))) Установка обработчика же как раз для того, чтобы перенаправить вывод qDebug в файл.
Вывод здесь забирается и перенаправляется в файл. Но если так сильно хочется выводить в консоль, то используйте std::cout в обработчике сообщений.
Важно упомянуть, что QLoggingCategory в QT начиная с 5 версии.
А кто-то ещё использует Qt4? )))
Мало ли ) Мне для взаимодействия с библиотеками Octave приходится)
Это который GNU Octave? Сочувствую тогда... в Qt5 много полезный вкусностей.
Он самый
Я полистал информацию в интернетах, вроде как кто-то пытается подружить его с Qt5, но успешных результатов не нашёл. Да и на сайте как-то не заметно информации о том, что конкретно ему нужно, какая именно версия Qt требуется.
Как здесь кириллицу корректно использовать?
Фух фух фух.... не люблю я для таких вещей кириллицу использовать ;-)
А что мешает сохранить адрес дефолтного обработчика и после вывода в файл вызывать и его?
Хм.. Как-то даже не подумал ))
таки да, используется, причем активно
таки да. используется
Мне видимо нужно было добавить тег /sarcasm ?
Всем ясно, что legaci есть и будет, но это не значит, что я, например, добровольно полезу болото месить.
Вопрос- как перенести из файла .cpp
Эти записи?
Если их помещаю как private, то ошибка.
Или обработчик messageHandler должен именно вне класса быть? Хочу его методом класса сделать.
А так- работает.
Как только переношу в класс метод messageHandler, он подчеркнут ошибкой в конструкторе:
Сам метод в классе называется уже:
с расширение названия класса.
И не пойму как в класс его затулить.
Этот обработчик должен быть отдельной функцией.
Не стоит его пытаться запихать в класс.
Как бы скорее всего это возможно, но это тухлого яйца не стоит.
Отдельной ф-ей вне класса?
А если у меня еще класс будет, откуда я буду его использовать? То эту же ф-ю вызывать надо будет? Просто extern ее пробросить в то место, где вызываю?
Этот обработчик вызывается автоматически при вызове qDebug(), qWarning() и т.д.
Зачем вам вообще это куда-то пробрасывать?
Нет никакого смысла использовать класс. А даже если и будет класс, то метод должен быть статическим. Бессмысленная затея.
Это делаю в конструкторе класса. Если у меня два класса, откуда я хочу логгирование, то дважды вставляем в конструкторы инициализацию?
Или как правильней поступить?
И вопрос по приватной переменной
QLoggingCategory m_category;
где в конструкторе она инициализируется- с каждым классом так проделывать?
Это глобальный логгер, а не для отдельных классов. Он один раз устанавливается в main функции.
В статье уже есть ответ на этот вопрос
Эксперимент показал, что инициализация в конструкторе main достаточна. Дальше в конструкторе каждого класса делаю инициализацию приватной переменной m_category(" название класса:")
И уже в логгере отображается
2020-06-04 11:43:57.192 MainWindow: : Button Customers_clicked
Т.о. можно делать разделение по классам.
Разобрался, спасибо.
Отдельно отмечу, что ваш блог помогает разобраться с вопросами по Qt, отдельная благодарность)
"И присвоим имена данным категориям "
Я правильно понимаю, что такие имена документация присваивать не рекомендует?
"Avoid the category names: debug, info, warning, and critical."
Условно да, для примера не важно, но больше выглядит как набор рекомендаций, который говорит о том, что в итоге что-то может пойти не так и вы сами себе злобные Буратины.