Налаштування логування
Підтримка логування у додатку дозволяє як мінімум вирішувати такі завдання:
- фіксування дій користувача у додатку;
- фіксування виконання операцій із даними;
- фіксування критичних подій під час роботи програми.
І взагалі читання логів хвилюючий та захоплюючий процес при забезпеченні підтримки роботи програмного забезпечення.
Організацію логування подій програми я запозичив із цієї статті (https://evileg.com/ua/post/154/). Дещо модифікувавши Реалізацію обробника для того, щоб повідомлення дублювалися у вікно консолі.
Додаємо до проекту реєстрацію категорій логування через макрос.
loggingcategories.h
#ifndef LOGGINGCATEGORIES_H #define LOGGINGCATEGORIES_H #include <QLoggingCategory> Q_DECLARE_LOGGING_CATEGORY(logDebug) Q_DECLARE_LOGGING_CATEGORY(logInfo) Q_DECLARE_LOGGING_CATEGORY(logWarning) Q_DECLARE_LOGGING_CATEGORY(logCritical) #endif // LOGGINGCATEGORIES_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")
Змінюємо main.cpp.
main.cpp
#include "mainwindow.h" #include "LoggingCategories/loggingcategories.h" #include <QApplication> #include <QFile> #include <QDateTime> // Умный указатель на файл логирования static 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("iMpos.log")); // Открываем файл логирования m_logFile.data()->open(QFile::Append | QFile::Text); // Устанавливаем обработчик qInstallMessageHandler(messageHandler); qInfo(logInfo()) << "Запуск программы."; MainWindow w; w.show(); return a.exec(); } void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { // Открываем поток записи в файл QTextStream out(m_logFile.data()); QTextStream console(stdout); // Записываем дату записи out << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz "); // По типу определяем, к какому уровню относится сообщение switch (type) { #ifdef QT_DEBUG case QtInfoMsg: out << "[INF] "; console << "Info: " << msg << endl; break; case QtDebugMsg: out << "[DBG] "; console << "Debug: " << msg << endl; break; case QtWarningMsg: out << "[WRN] "; console << "Warning: " << msg << endl; break; case QtCriticalMsg: out << "[CRT] "; console << "Critical: " << msg << endl; break; case QtFatalMsg: out << "[FTL] "; console << "Fatality: " << msg << endl; break; #else 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; #endif } // Записываем в вывод категорию сообщения и само сообщение out << context.category << ": " << msg << endl; // Очищаем буферизированные данные out.flush(); console.flush(); }
Запускаємо програму. У консолі відображаються повідомлення логування.
У папці зброї проекту створився файл iMpos.log
Створення, читання бази даних налаштувань додаток
Для зберігання налаштувань програми та інших даних будемо використовувати базу даних SQLite. Як тільки починаємо використовувати бази даних, необхідно додати у файл проекту параметр qmake QT += sql.
QT += core gui sql
Додаємо до проекту клас DataBases
databases.h
#ifndef DATABASES_H #define DATABASES_H #include <QObject> class DataBases : public QObject { Q_OBJECT public: explicit DataBases(QObject *parent = nullptr); bool connectOptions(); //Подключение к базе данный опций приложения signals: public slots: }; #endif // DATABASES_H
databases.cpp
#include "databases.h" #include "LoggingCategories/loggingcategories.h" #include <QFile> #include <QSqlQuery> #include <QSqlError> #define DATABASE_NAME "iMpos.opt" #define DATABASE_HOSTNAME "iMpos" DataBases::DataBases(QObject *parent) : QObject(parent) { } bool DataBases::connectOptions() { bool result; //Проверяемналичие файла базы данных if(QFile(DATABASE_NAME).exists()){ //Файл существует создаем подключение к базе данных qInfo(logInfo()) << "Открываем файл настроек приложения."; QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE","options"); db.setHostName(DATABASE_HOSTNAME); db.setDatabaseName(DATABASE_NAME); if(db.open()){ qInfo(logInfo()) << "Файл настроек открыт успешно"; result = true; } else { qCritical(logCritical()) << "Не удалось открыть файл настроек приложения."; result = false; } } else { //Файл отсутсвует, создем базу данных QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE","options"); db.setHostName(DATABASE_HOSTNAME); db.setDatabaseName(DATABASE_NAME); if(db.open()){ QStringList listSQL; //Список запросов QSqlQuery q = QSqlQuery(db); //Создаем таблицу OPTIONS и добавлем в нее записи listSQL << "CREATE TABLE `options` (`option_id` INTEGER NOT NULL, `value` TEXT NOT NULL, `comment` TEXT, PRIMARY KEY(`option_id`))"; listSQL << "INSERT INTO `options`(`option_id`,`value`,`comment`) VALUES (1000, 'false', 'Использовать аутентификацию')"; listSQL << "INSERT INTO `options`(`option_id`,`value`,`comment`) VALUES (1010, 'false', 'Использовать привязку по региону')"; //Создаем таблицу пользователей приложения и добавляем в нее запись listSQL << "CREATE TABLE `users` ( `user_id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, `fio` TEXT NOT NULL, `password` TEXT, `isactive` TEXT NOT NULL DEFAULT 'true' )"; listSQL << "INSERT INTO `users`(`fio`,`password`) VALUES ('Администратор','masterkey')"; //Выполняем запросы for (int i =0;i<listSQL.size();++i) { if(!q.exec(listSQL.at(i))) qCritical(logCritical()) << Q_FUNC_INFO << "Не удалось выполнить запрос." << listSQL.at(i) << q.lastError().text(); } qInfo(logInfo()) << "Создан файл настроек приложения."; result = true; } else { qCritical(logCritical()) << "Не удалось создать файл настроек приложения."; result = false; } } return result; }
У main.cpp перед вивезенням гланого вікна додаємо
DataBases *db = new DataBases(); if(!db->connectOptions()){ qInfo(logInfo()) << "Аварийное завершение работы."; return 1; }
У папці збирання проекту при першому запуску з'являється файл iMpos.opt
При наступному запуску підключення до існуючої бази SQLite.
Поточний стан проекту на GitHub тут.
Архів проекту.
iMpos_ch003.zip
Я с некоторых пор перестал использовать вот такие дефайны (знаю, что в моих статьях попадаются, но это в старых статьях)
Сейчас пишу так
Header
CPP
Дело в том, что если изменить этот дефайн
То будут перекомпилироваться абсолютно все места в проекте, где он использовался. А в случае со статическими константными переменными будет перекомпилироваться только DataBaseSettings. Это даёт очень большой выигрыш по времени перекомпиляции в очень крупных проектах при частом использовании тех или иных переменных подобного рода.
Спасибо. Возьму на заметку.