Всі 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
Установка обробника
Для демонстрації роботи обробника скористаємося графічним додатком, де буде чотири кнопки.
- Відлагоджувати
- Інформація
- Увага
- Критично
В обробнику кожної з кнопок будуть викликатись відповідні макроси логування подій та застосовуватись відповідні категорії, перераховані вище. Вікно програма створена через графічний дизайнер і виглядає наступним чином.
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."
Условно да, для примера не важно, но больше выглядит как набор рекомендаций, который говорит о том, что в итоге что-то может пойти не так и вы сами себе злобные Буратины.