В прошлой статье мы рассмотрели процедуры извлечения информации из текстовых файлов, представляющих собой информацию от программы отслеживания действий пользователя, а в этот раз рассмотрим как собрать описанное в целостную систему и добавить немного анализирующих блоков.
Определим папку, в которой будут размещены текстовые файлы с логами, переменную-накопитель с базой пользовательской информации, счетчик-идентификатор, а также подготовим переменную под файл подозрительных действий:
// путь к папке с логами enum string logFileDirectory = `D:\Logs\`; // счетчик по генерации идентификаторов int counter; // общая база пользователей UserInfo[string] usersBase; // файл подозрительных действий File suspiciousActionFile;
Создадим базу, заполнив переменную usersBase с помощью описанных ранее процедур:
// создать базу на основе всех файлов из папки dirEntries(logFileDirectory, SpanMode.shallow) .filter!(a => a.isFile) .each!(a => processFile(a, usersBase, counter));
То как работает, эта конструкция было рассказано в одном из наших рецептов.
Теперь, когда у нас есть совокупная информация по каждому пользователю, то можно пройти по базе данных с помощью простого цикла:
foreach (userName; usersBase.keys) // работаем со сгенерированной базой { // формирование нужных структур }
Итак, каждый ключ – это фамилия-имя пользователя, согласно некоторым соображениям, нужно сформировать два файла, которые необходимо заполнить несколько отличающейся информацией (а заодно подготовим структуры данных):
// файлы сводки и статистики File summaryFile, statisticFile; // сводка по пользователю Summary summary; // статистика на пользователя Statistic statistic;
Выполним самые простые действия: откроем на запись файл подозрительных действий (он общий для всех пользователей), извлечем из базы пользователя по фамилии-имени (не забывайте, сейчас мы рассматриваем внутренности цикла!), список его действий, выделим только подозрительные и посчитаем общее количество действий:
suspiciousActionFile.open(logFileDirectory ~ `suspicious_action.txt`, `a`); auto user = usersBase[userName]; // конкретный пользователь auto actions = user.actions; // действия пользователя auto suspiciousActions = actions .filter!(a => a.suspicioned); // подозрительные действия auto numberOfAction = actions.length; // общее количество действий
Количество подозрительных действий можно вычислить так:
auto numberOfSuspicious = suspiciousActions .filter!(a => a.suspicioned) .array .length; // количество подозрительных действий
А время последнего действия можно получить, если отсортировать все действия по времени, взять первый элемент и получить значение свойства time:
auto lastTime = actions .sort!((a,b) => (a.time > b.time)) .front .time; // время последнего действия
Осуществим наполнение структур, представляющих собой файлы с данными статистики и сводки:
// заполнить краткую сводку summary.username = userName; summary.numberOfSuspicious = numberOfSuspicious; summary.timeOfLastAction = lastTime; // заполнить статистику statistic.id = user.id; statistic.numberOfActions = numberOfAction; statistic.numberOfSuspicious = numberOfSuspicious; statistic.timeOfLastAction = lastTime;
А теперь запишем каждую из этих структур в свой файл (в этом нам поможет заблаговременно перегруженные методы toString):
// Запись в файл сводки summaryFile.open(logFileDirectory ~ userName ~ `_summary.txt`, `w`); summaryFile.write(summary); summaryFile.close; // Запись в файл статистики statisticFile.open(logFileDirectory ~ userName ~ `_statistic.txt`, `w`); statisticFile.write(statistic); statisticFile.close;
Можно, наконец, записать все подозрительные действия:
// Запись в файл подозрительных действий suspiciousActions.each!(a => suspiciousActionFile.writeln(a.time, " ", a.description));
На этом основной цикл окончен, и почти все задачи выполнены – остается только закрыть файл подозрительных действий:
suspiciousActionFile.close;
На этом описание программы закончено, можно свести код из обоих статей в один единственный файл, и после запуска генератора, указания пути к папке с логами, можно запускать файл с помощью rdmd. Результатом работы программы будет несколько файлов вида фамилия_имя_statistic.txt, фамилия_имя_summary.txt и suspicious_action.txt, которые будут содержать всю нужную информацию.
[accordion][panel intro=»К примеру, результат работы программы для вышеприведенных данных (спойлер)»]
Файл Vasiliyi Shandybin_statistic.txt
Identifier : 2
Number of action: 11400
Number of suspicious: 6859
Time of last action : 2021-Nov-28 09:15:00
Файл Vasiliyi Shandybin_summary.txt
Name : Vasiliyi Shandybin
Number of suspicious: 6859
Time of last action : 2021-Nov-28 09:15:00
Файл suspicious_action.txt
2021-Nov-28 09:56:54 Leave building
2021-Nov-26 05:08:10 Exploites vulnerability
2021-Nov-24 17:15:39 Exploites vulnerability
2021-Nov-24 06:04:27 Catch the fire
2021-Nov-24 00:47:26 Destroy PC
2021-Nov-23 06:41:44 Destroy PC
2021-Nov-23 02:16:26 Destroy PC
2021-Nov-21 09:11:21 Exploites vulnerability
2021-Nov-21 06:58:02 Leave building[/panel][/accordion]
[accordion][panel intro=»Полный код примера (конечно же под спойлером).»]
import std.algorithm; import std.array; import std.conv; import std.datetime; import std.file; import std.format; import std.range; import std.stdio; import std.string; // Информация по текущему пользователю struct UserInfo { int id; string name; Action[] actions; } // Информация по конкретным записям из файла struct UserEntry { string name; Action action; } // Информация о действии struct Action { DateTime time; string description; bool suspicioned; } // Короткая сводка на юзера struct Summary { string username; int numberOfSuspicious; DateTime timeOfLastAction; string toString() { return format("Name : %s\nNumber of suspicious: %s\nTime of last action : %s", username, numberOfSuspicious, timeOfLastAction ); } } // Статистика по пользователю struct Statistic { int id; int numberOfActions; int numberOfSuspicious; DateTime timeOfLastAction; string toString() { return format("Identifier : %s\nNumber of action: %s\nNumber of suspicious: %s\nTime of last action : %s", id, numberOfActions, numberOfSuspicious, timeOfLastAction ); } } // Извлечь дату из строки final DateTime parseDateFrom(string line) { auto dateTimeBlocks = line[1..$-1].split; auto date = dateTimeBlocks[0].split("/"); auto day = to!int(date[0]); auto month = to!int(date[1]); auto year = to!int(date[2]); auto time = dateTimeBlocks[1].split(":"); auto hour = to!int(time[0]); auto minute = to!int(time[1]); auto second = to!int(time[2]); return DateTime(year, month, day, hour, minute, second); } // Извлечь текущую информацию final UserEntry parseLine(string line) { UserEntry userEntry; auto lineBlocks = line.split(" "); // значащие единицы строки auto date = parseDateFrom(lineBlocks[0] ~ " " ~ lineBlocks[1]); // дата и время userEntry.name = lineBlocks[2] ~ " " ~ lineBlocks[3]; // имя пользователя auto description = to!string(lineBlocks[5..$].join(" ")); // описание действия // определение степени легальности действия switch (description) { case "Enter in system", "Create documents", "Exit from system", "Copy documents", "Remove documents": userEntry.action = Action(date, description, false); break; default: userEntry.action = Action(date, description, true); break; } return userEntry; } // Обработка одного файла final void processFile(string fileName, ref UserInfo[string] usersBase, ref int counter) { final void addInDictionary(UserEntry userEntry, ref UserInfo[string] usersBase, ref int counter) { if (userEntry.name in usersBase) // если пользователь есть в базе, // то обновить информацию, иначе создать новую запись в базе { UserInfo userInfo; userInfo = usersBase[userEntry.name]; userInfo.actions ~= userEntry.action; usersBase[userEntry.name] = userInfo; } else { UserInfo userInfo; userInfo.id = counter; userInfo.name = userEntry.name; userInfo.actions ~= userEntry.action; usersBase[userEntry.name] = userInfo; counter++; } } // создать базу на основе записей из файла (cast(string) std.file.read(fileName)) .splitLines .map!(a => parseLine(a)) .each!(a => addInDictionary(a, usersBase, counter)); } void main() { // путь к папке с логами enum string logFileDirectory = `D:\Logs\`; // счетчик по генерацию идентификаторов int counter; // общая база пользователей UserInfo[string] usersBase; // файл подозрительных действий File suspiciousActionFile; // создать базу на основе всех файлов из папки dirEntries(logFileDirectory, SpanMode.shallow) .filter!(a => a.isFile) .each!(a => processFile(a, usersBase, counter)); foreach (userName; usersBase.keys) // работаем со сгенерированной базой { // файлы сводки и статистики File summaryFile, statisticFile; // сводка по пользователю Summary summary; // статистика на пользователя Statistic statistic; suspiciousActionFile.open(logFileDirectory ~ `suspicious_action.txt`, `w`); auto user = usersBase[userName]; // конкретный юзер auto actions = user.actions; // действия юзера auto suspiciousActions = actions .filter!(a => a.suspicioned); // подозрительные действия auto numberOfAction = actions.length; // общее количество действий auto numberOfSuspicious = suspiciousActions .filter!(a => a.suspicioned) .array .length; // количество подозрительных действий auto lastTime = actions .sort!((a,b) => (a.time > b.time)) .front .time; // время последнего действия // заполнить краткую сводку summary.username = userName; summary.numberOfSuspicious = numberOfSuspicious; summary.timeOfLastAction = lastTime; // заполнить статистику statistic.id = user.id; statistic.numberOfActions = numberOfAction; statistic.numberOfSuspicious = numberOfSuspicious; statistic.timeOfLastAction = lastTime; // Запись в файл сводки summaryFile.open(logFileDirectory ~ userName ~ `_summary.txt`, `w`); summaryFile.write(summary); summaryFile.close; // Запись в файл статистики statisticFile.open(logFileDirectory ~ userName ~ `_statistic.txt`, `w`); statisticFile.write(statistic); statisticFile.close; // Запись в файл подозрительных действий suspiciousActions.each!(a => suspiciousActionFile.writeln(a.time, " ", a.description)); } suspiciousActionFile.close; }
[/panel][/accordion]
Таким образом, D очень удобен не только для системного и прикладного программирования, но и для скриптового тоже. Ряд возможностей D позволяет с комфортом решать повседневные и рутинные задачи, не отвлекаясь на другие языки программирования. Сочетание функционального стиля с императивным порой жутко выглядит и вполне может испортить самый качественный код, но иногда это очень действенный способ решения какой-либо мелкой внезапно возникшей задачи.
В общем, используйте D для автоматизации своей работы, и старайтесь писать код аккуратно, а не так как порой на работе пишем его мы.