Создание модели для просмотра логов сервера WSUS
В предыдущих статьях мы рассмотрели создание модели для QTableView.
Сегодня мы рассмотрим создание модели для QTableView на примере парсера (далее также парсинг - разбор лог-файла) логов Microsoft Windows Server Update Services.
Создадим новый проект и назовем его QTableViewLogs
Откройте главную форму. Перетащите в нее два компонента QTableView и QTextBrowser.
в окне справа щелкните на MainWindow и выберите меню Layout -> Lay Out Vertically…
Теперь при изменении размеров окна автоматически изменяются размеры компонентов.
Изменим высоту TextBrowser, для этого выделите его и в окне свойств найдите maximumSize и установите параметр Height равным 200 и увеличьте размер самого окна.
Лог-файл WSUS
Лог-файл WSUS находится в папке C:\Program Files\Update Services\LogFiles\
Это обычный текстовый файл в кодировке UTF-8. Все поля разделены символом табуляции - \t
Структура лога следующая:
Дата\tТипСобытия\tСервис\tСобытие\tДанныеСобытия
При этом ДанныеСобытия могут занимать несколько строк.
Вот несколько строк для примера:
2021-03-24 08:02:49.190 UTC Info WsusService.16 EventLogEventReporter.ReportEvent EventId=366,Type=Information,Category=Synchronization,Message=Скачивание файла содержимого успешно завершено.
Дайджест:
Исходный файл: /c/msdownload/update/driver/drvs/2015/12/200010168_6a0afafa82a723050b887266adf8ae44caf13d1d.cab
Конечный файл: g:\wsus\WsusContent\1D\6A0AFAFA82A723050B887266ADF8AE44CAF13D1D.cab
2021-03-24 08:02:49.237 UTC Info WsusService.16 ContentSyncAgent.WakeUpWorkerThreadProc ContentSyncAgent found no more Jobs, going to Sleep for BITS Notifications
2021-03-24 08:02:49.300 UTC Info WsusService.16 ContentSyncAgent.WakeUpWorkerThreadProc Processing Item: 8dcae4cf-2f47-4145-975b-a0dcaa94f7ee, State: 10
2021-03-24 08:02:49.331 UTC Info WsusService.16 ContentSyncAgent.Download Item: 8dcae4cf-2f47-4145-975b-a0dcaa94f7ee has been submitted to BITS for Download
2021-03-24 08:02:49.378 UTC Info WsusService.16 ContentSyncAgent.WakeUpWorkerThreadProc ContentSyncAgent found no more Jobs, going to Sleep for BITS Notifications
2021-03-24 08:02:50.284 UTC Info w3wp.192 ServerImplementation.UpdateCache Database change occured; check if we need to update cache.
2021-03-24 08:02:50.284 UTC Info w3wp.653 ThreadEntry TimerQueue.FireNextTimers
2021-03-24 19:50:44.571 UTC Error w3wp.496 SyncUpdatesHelper.UpdateHardwareDriverCache SQL exception occured while trying to get Hardware changes - {1}.
в Microsoft.UpdateServices.Internal.SyncUpdatesHelper.UpdateHardwareDriverCache(Int64 lastChangeNumber, Int64 newChangeNumber, DriverUpdatesData oldData, DataAccess da)
в Microsoft.UpdateServices.Internal.SyncUpdatesHelper.InitDriverSync()
в Microsoft.UpdateServices.Internal.DataAccessCache.ResetHardwareCache()
в Microsoft.UpdateServices.Internal.DataAccessCache..ctor(ClientImplementation client)
в Microsoft.UpdateServices.Internal.DataAccessCache.GetCache(ClientImplementation client)
в Microsoft.UpdateServices.Internal.ClientImplementation..ctor(NotifyEventHandler handler, HttpContext httpContext)
в Microsoft.UpdateServices.Internal.Client.CreateClientImplementationThread()
в System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
в System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
в System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
в System.Threading.ThreadHelper.ThreadStart()
2021-03-24 19:50:44.586 UTC Error w3wp.496 ClientImplementation..ctor Error in ClientImplementation constructor: System.Data.SqlClient.SqlException (0x80131904): Время ожидания выполнения истекло. Время ожидания истекло до завершения операции, или сервер не отвечает. ---> System.ComponentModel.Win32Exception (0x80004005): Время ожидания операции истекло
в System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
в System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
в System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
в System.Data.SqlClient.SqlDataReader.TrySetMetaData(_SqlMetaDataSet metaData, Boolean moreInfo)
в System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
в System.Data.SqlClient.SqlDataReader.TryReadInternal(Boolean setTimeout, Boolean& more)
в System.Data.SqlClient.SqlDataReader.Read()
в Microsoft.UpdateServices.Internal.SyncUpdatesHelper.UpdateHardwareDriverCacheWithDBResult(DriverUpdatesData oldData, DataAccess da, DBConnection dbConnection)
в Microsoft.UpdateServices.Internal.SyncUpdatesHelper.UpdateHardwareDriverCache(Int64 lastChangeNumber, Int64 newChangeNumber, DriverUpdatesData oldData, DataAccess da)
в Microsoft.UpdateServices.Internal.SyncUpdatesHelper.InitDriverSync()
в Microsoft.UpdateServices.Internal.DataAccessCache.ResetHardwareCache()
в Microsoft.UpdateServices.Internal.DataAccessCache..ctor(ClientImplementation client)
в Microsoft.UpdateServices.Internal.DataAccessCache.GetCache(ClientImplementation client)
в Microsoft.UpdateServices.Internal.ClientImplementation..ctor(NotifyEventHandler handler, HttpContext httpContext)
ClientConnectionId:0709d2b2-23f9-4bae-ad4c-07430d855983
Error Number: -2, State: 0, Class: 11. Seconds spent in constructor: 27328,095207.
в Microsoft.UpdateServices.Internal.ClientImplementation..ctor(NotifyEventHandler handler, HttpContext httpContext)
в Microsoft.UpdateServices.Internal.Client.CreateClientImplementationThread()
в System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
в System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
в System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
в System.Threading.ThreadHelper.ThreadStart()
Далее мы будем использовать этот отрывок лога для тестирования функционала. Сохраним его в файл log.txt в папке проекта.
Класс для хранения информации лог-файла
Создадим класс для хранения полей строки нашего лог файла – WSUSLogRecord.
Заголовок:
#ifndef WSUSLOGRECORD_H
#define WSUSLOGRECORD_H
#include <qstring.h>
class WSUSLogRecord
{
public:
WSUSLogRecord();
QString getDate() const;
void setDate(const QString &value);
QString getEventType() const;
void setEventType(const QString &value);
QString getService() const;
void setService(const QString &value);
QString getEvent() const;
void setEvent(const QString &value);
QString getData() const;
void setData(const QString &value);
QString getId() const;
void setId(const QString &value);
private:
QString id;
QString date;
QString eventType;
QString service;
QString event;
QString data;
};
QDebug operator<<(QDebug debug, const WSUSLogRecord &user);
QDebug operator<<(QDebug debug, const WSUSLogRecord *user);
#endif // WSUSLOGRECORD_H
Реализация:
#include "wsuslogrecord.h"
#include <QDebugStateSaver>
WSUSLogRecord::WSUSLogRecord()
{
}
QString WSUSLogRecord::getDate() const
{
return date;
}
void WSUSLogRecord::setDate(const QString &value)
{
date = value;
}
QString WSUSLogRecord::getEventType() const
{
return eventType;
}
void WSUSLogRecord::setEventType(const QString &value)
{
eventType = value;
}
QString WSUSLogRecord::getService() const
{
return service;
}
void WSUSLogRecord::setService(const QString &value)
{
service = value;
}
QString WSUSLogRecord::getEvent() const
{
return event;
}
void WSUSLogRecord::setEvent(const QString &value)
{
event = value;
}
QString WSUSLogRecord::getData() const
{
return data;
}
void WSUSLogRecord::setData(const QString &value)
{
data = value;
}
QString WSUSLogRecord::getId() const
{
return id;
}
void WSUSLogRecord::setId(const QString &value)
{
id = value;
}
QDebug operator<<(QDebug debug, const WSUSLogRecord &rec)
{
QDebugStateSaver saver(debug);
debug.nospace() << "WSUSLogRecord( "
<< "Id: " << rec.getId() << ", "
<< "Date: " << rec.getDate() << ", "
<< "Event type: " << rec.getEventType() << ", "
<< "Service: " << rec.getService() << ", "
<< "Event: " << rec.getEvent() << ", "
<< "Datas: " << rec.getData() << ""
<< " )";
return debug;
}
QDebug operator<<(QDebug debug, const WSUSLogRecord *rec)
{
QDebugStateSaver saver(debug);
debug.nospace() << *rec;
return debug;
}
Здесь у нас простой класс для хранения информации из одной записи лог-файла.
Класс для разбора (парсинга) лог-файла
Добавим класс – WSUSLogParser
Заголовок:
#ifndef WSUSLOGPARSER_H
#define WSUSLOGPARSER_H
#include "wsuslogrecord.h"
class WSUSLogParser
{
public:
explicit WSUSLogParser(QList<WSUSLogRecord> *values);
void Load(QString fileName);
QList<WSUSLogRecord> *values;
};
#endif // WSUSLOGPARSER_H
Реализация:
#include "wsuslogparser.h"
#include <QFile>
#include <QDebug>
#include <QRegularExpression>
#include <cstdlib>
WSUSLogParser::WSUSLogParser(QList<WSUSLogRecord> *values)
{
this->values = values;
}
void WSUSLogParser::Load(QString fileName)
{
int id=0;
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
qDebug() << "Failed to open input file.";
exit(-1);
}
QRegularExpression re("^([0-9\\w\\-:. ]+)\\t(\\w+)\\t([\\w+.0-9]+)\\t([\\w+.0-9]+)\\t(.*)");
WSUSLogRecord currentRecord;
while (!file.atEnd())
{
QString line = file.readLine();
QRegularExpressionMatch match = re.match(line);
if (match.hasMatch()) {
if (!currentRecord.getDate().isEmpty())
{
values->append(currentRecord);
}
currentRecord = WSUSLogRecord();
currentRecord.setId(QString::number(id));
id++;
currentRecord.setDate(match.captured(1));
currentRecord.setEventType(match.captured(2));
currentRecord.setService(match.captured(3));
currentRecord.setEvent(match.captured(4));
currentRecord.setData(match.captured(5));
} else {
QString tmp = currentRecord.getData();
tmp = tmp + line;
currentRecord.setData(tmp);
}
}
qDebug() << values->at(0);
}
Рассмотрим код метода Load:
Сначала мы открываем текстовый файл для чтения и обязательно проверяем открылся он или нет, если нет, то роняем нашу программу с результатом выполнения -1. Конечно, нужно обрабатывать такие ошибки как следует, но в рамках данной статьи это несущественно и для экономии времени обойдемся только сообщением.
Далее мы создаем регулярное выражение для разбора строки из файла. Здесь я не буду подробно останавливаться на регулярных выражениях, это отдельная и очень обширная тема.
Затем у нас идёт цикл обработки файла - до тех пор, пока не достигнут конец файла, мы будем считывать из него строку за строкой.
Каждую строку мы проверяем на соответствие регулярному выражению, если совпадение есть, это означает, что у нужно создать новый экземпляр класса WSUSLogRecord, заполнить его поля данными и добавить в список values.
Обратите внимание, так как мы создаем экземпляр класса в блоке if, нам пришлось объявить переменную за пределами цикла, в этом случае нам нужно убедиться, что по крайней мере поле date заполнено данными иначе мы добавим в список пустую строку.
Далее мы создаем экземпляр класса WSUSLogRecord и заполняем его данными.
Если вы посмотрите на регулярное выражение, то увидите, что все поля, разделенные табуляцией, заключены в скобки:
([0-9\\w\\-:. ]+) (\\w+) ([\\w+.0-9]+) ([\\w+.0-9]+) (.*)
Когда метод match находит совпадение строки с шаблоном, он записывает результаты совпадения в массив. При этом в [0] хранится исходная строка, а начиная с первого индекса - результаты захвата, например, в нашем случае:
([0-9\\w\\-:. ]+) = match.captured(1)
(\\w+) = match.captured(2)
([\\w+.0-9]+) = match.captured(3)
([\\w+.0-9]+) = match.captured(4)
(.*) = match.captured(5)
Таким образом мы заполняем все поля экземпляра класса WSUSLogRecord.
Если же строка не совпала с шаблоном, то просто добавляем её целиком в data.
Далее мы проходим по всем строкам лог-файла и загружаем информацию в наш список - values.
Добавим в заголовок главной формы:
private:
QList<WSUSLogRecord> *values;
В конструктор главной формы добавим код:
this->values = new QList<WSUSLogRecord>();
WSUSLogParser *lp = new WSUSLogParser(this->values);
lp->Load("log.txt");
Запустим сборку Ctrl+R и в консоли получим сообщение:
Failed to open input file.
Скопируем лог-файл в место, где программа сможет его найти.
Для этого откройте папку с проектом и перейдите на уровень выше. Здесь вы найдёте папку вида:
build-QTableViewLogs-Desktop_Qt_MinGW_w64_64bit_MSYS2-Debug
Скопируйте файл log.txt в эту папку.
Запустим сборку еще раз, на этот раз в консоли получим:
WSUSLogRecord( Id: "0", Date: "2021-03-24 08:02:49.190 UTC", Event type: "Info", Service: "WsusService.16", Event: "EventLogEventReporter.ReportEvent", Datas: "EventId=366,Type=Information,Category=Synchronization,Message=Скачивание файла содержимого успешно завершено. Дайджест: \nИсходный файл: /c/msdownload/update/driver/drvs/2015/12/200010168_6a0afafa82a723050b887266adf8ae44caf13d1d.cab \nКонечный файл: g:\\wsus\\WsusContent\\1D\\6A0AFAFA82A723050B887266ADF8AE44CAF13D1D.cab\n" )
Модель для таблицы
В принципе модель ничем не отличается от той, что мы создали в предыдущей статье.
Создадим класс QTableViewModel
Заголовок:
#ifndef QTABLEVIEWMODEL_H
#define QTABLEVIEWMODEL_H
#include "wsuslogrecord.h"
#include <QModelIndex>
class QTableViewModel : public QAbstractListModel
{
public:
QTableViewModel(QObject *parent=nullptr);
int rowCount(const QModelIndex &) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
void populate(QList<WSUSLogRecord> *newValues);
void append( const WSUSLogRecord &value);
void update(int idx, const WSUSLogRecord &value);
void deleteRow(int idx);
void insertAt(int idx, const WSUSLogRecord &value);
private:
QList<WSUSLogRecord> *values;
};
#endif // QTABLEVIEWMODEL_H
Реализация:
#include "qtableviewmodel.h"
#include <QModelIndex>
#include <QDebug>
#include <QPixmap>
QTableViewModel::QTableViewModel(QObject *parent)
:QAbstractListModel(parent)
{
values = new QList<WSUSLogRecord>();
}
int QTableViewModel::rowCount(const QModelIndex &) const
{
return values->count();
}
int QTableViewModel::columnCount(const QModelIndex &) const
{
return 5;
}
QVariant QTableViewModel::data( const QModelIndex &index, int role ) const
{
QVariant value;
switch ( role )
{
case Qt::DisplayRole: //string
{
switch (index.column()) {
case 0: {
value = this->values->at(index.row()).getDate();
break;
}
case 1: {
value = this->values->at(index.row()).getEventType();
break;
}
case 2: {
value = this->values->at(index.row()).getService();
break;
}
case 3: {
value = this->values->at(index.row()).getEvent();
break;
}
case 4: {
value = this->values->at(index.row()).getData().replace("\n", " ");
break;
}
}
}
break;
case Qt::UserRole: //data
{
value = this->values->at(index.row()).getId();
}
break;
default:
break;
}
return value;
}
QVariant QTableViewModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
switch (section) {
case 0:
return QString("Date");
case 1:
return QString("Type");
case 2:
return QString("Service");
case 3:
return QString("Event");
case 4:
return QString("Datas");
}
}
return QVariant();
}
void QTableViewModel::populate(QList<WSUSLogRecord> *newValues)
{
int idx = this->values->count();
this->beginInsertRows(QModelIndex(), 1, idx);
this->values = newValues;
endInsertRows();
}
void QTableViewModel::append( const WSUSLogRecord &value)
{
int newRow = this->values->count()+1;
this->beginInsertRows(QModelIndex(), newRow, newRow);
values->append(value);
endInsertRows();
}
void QTableViewModel::update(int idx, const WSUSLogRecord &value)
{
(*this->values)[idx] = value;
QModelIndex item_idx_s = this->index(idx,0);
QModelIndex item_idx_e = this->index(idx,this->columnCount(QModelIndex()));
emit this->dataChanged(item_idx_s ,item_idx_e );
}
void QTableViewModel::deleteRow(int idx)
{
this->beginRemoveRows(QModelIndex(), idx,idx);
(*this->values).removeAt(idx);
this->endRemoveRows();
}
void QTableViewModel::insertAt(int idx, const WSUSLogRecord &value)
{
int newRow = idx;
this->beginInsertRows(QModelIndex(), newRow, newRow);
values->insert(newRow,value);
endInsertRows();
}
Единственное что мы добавили больше столбцов.
Добавим в заголовок главной формы:
private:
QTableViewModel *model;
В конструктор главной формы код:
model = new QTableViewModel();
model->populate(values);
this->ui->tableView->setModel(model);
Запустим:
Выглядит не очень красиво, давайте добавим код в конструктор главной формы:
this->ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
this->ui->tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
this->ui->tableView->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
this->ui->tableView->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
this->ui->tableView->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Stretch);
Здесь мы задаем режим масштабирования столбцов.
Для вех столбцов, кроме последнего, мы изменяем ширину столбца по ширине содержимого, последний столбец займет у нас все оставшееся свободное пространство.
Запустим:
Вывод дополнительной информации по щелчку по строке таблицы
У нас есть таблица с загруженными данными, но все данные не поместятся в столбец Datas. Именно поэтому мы добавили на главную форму виджет QTextBrowser.
Добавим слот для QTableView:
void MainWindow::on_tableView_clicked(const QModelIndex &index)
{
int curr = ui->tableView->currentIndex().row();
qDebug() << curr;
ui->textBrowser->setText(values->at(curr).getData());
}
Теперь по щелчку по строке таблицы, в нижнем поле отображается полное значение записи:
Таким образом вы можете просматривать лог сервера в удобном виде.
Заключение
Сегодня мы создали модель для просмотра в QTableView лог-файла WSUS-сервера.
Была создана форма с виджетом QTableView, для отображения списка строк и виджет QTextBrowser для отображения подробной информации.
Создан класс для хранения информации из строки лог-файла и класс для разбора лог-файла.
Мы протестировали разбор лог-файла и добавили слот для QTableView позволяющий при щелчке по строке отображать в виджете QTextBrowser дополнительную информацию из поля data.
В следующей статье мы добавим фильтрацию по столбцу Type, чтобы, например, отображать только ошибки.
Скачать исходный код проекта вы можете с GitFlic
Добавить комментарий