Четверг, 01 апреля 2021 17:38

Создание модели для просмотра логов сервера WSUS

Россия
Оцените материал
(0 голосов)

Создание модели для просмотра логов сервера 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);

Запустим:

2021-03-31_10-59-50.png

Выглядит не очень красиво, давайте добавим код в конструктор главной формы:

    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);

Здесь мы задаем режим масштабирования столбцов.

Для вех столбцов, кроме последнего, мы изменяем ширину столбца по ширине содержимого, последний столбец займет у нас все оставшееся свободное пространство.

Запустим:

2021-03-31_11-05-45.png

Вывод дополнительной информации по щелчку по строке таблицы

У нас есть таблица с загруженными данными, но все данные не поместятся в столбец 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());   

}

Теперь по щелчку по строке таблицы, в нижнем поле отображается полное значение записи:

2021-03-31_11-11-03.png

Таким образом вы можете просматривать лог сервера в удобном виде.

Заключение

Сегодня мы создали модель для просмотра в QTableView лог-файла WSUS-сервера.

Была создана форма с виджетом QTableView, для отображения списка строк и виджет QTextBrowser для отображения подробной информации.

Создан класс для хранения информации из строки лог-файла и класс для разбора лог-файла.

Мы протестировали разбор лог-файла и добавили слот для QTableView позволяющий при щелчке по строке отображать в виджете QTextBrowser дополнительную информацию из поля data.

В следующей статье мы добавим фильтрацию по столбцу Type, чтобы, например, отображать только ошибки.

Скачать исходный код проекта вы можете с Github.

Прочитано 95 раз Последнее изменение Четверг, 01 апреля 2021 21:34