В данном проекте сборка осуществляется с помощью CMake, а самих файлов CMakeLists.txt несколько. В корневом файле заданы глобальные настройки и подключены вложенные директории tests и src.
- В src/ скрипт описывает сборку библиотеки в двух вариантах (статическая и динамическая) и многопоточных приложений, которые в свою очередь подключают и используют эти библиотеки. Вся компиляция проводится из исходных файлов
- В tests скрипт описывает сборку юнит-тестов с помощью библиотеки GTest.
- Для начала, необходимо склонировать исходный код в систему
git clone git@github.com:exodess/infotecs_cpp_developer.git- Перейти в новую директорию
cd infotecs_cpp_developer- После этого нужно собрать проект (вместо
buildможно указать другое название директори, куда вы хотите сохранить сборку)
cmake -S . build - Затем перейти в директорию сборки
cd build- Выполнить установку
make- Готово. Теперь можете запускать и тестировать работоспособность проекта
./tests/TestLogger # запуск тестов
./src/CheckLibApp <file> <default_level> # запуск приложения из части 2
./src/CollectStatApp <connection_parameters> N T # запуск приложения из части 3Генерация документации
make docs # запуск в папке buildДокументация будет формироваться в корне проекта, в папке. Для просмотра необходимо открыть файл docs/html/index.html в браузере
Также при запуске тестов необходимо завершить работу приложений CheckLibApp и CollectStatApp, чтобы они проходили без ошибок

В соответствие с ТЗ была разработана библиотека для записи текстовых сообщений в журнал.
В качестве журнала используются файл и сокет, интерфейсы логгирования не отличаются.

Класс Logger является основным классом библиотеки.
Для поддержки разного типа логгирования (в файл и сокет) был реализован абстрактный класс BWriter,
который представляет интерфейс записи сообщений в журнал. Классы, наследуемые от этого интерфейса,
реализуют конкретный способ записи (в файл или в сокет). Это позволяет гибко управлять и, по возможности,
дополнять библиотеку, ведь непосредственная IO логика вынесена в отдельные классы,
которые придерживаются описанного интерфейса.
При создании логгера в конструктор принимается в качестве аргумента имя журнала и минимальный уровень важности сообщения.
Внутри конструктора происходит анализ имени журнала и выбор того, какой тип он представляет - файл или сокет.
Если журнал - это сокет, то "записыватель" _writer создается как объект класса SocketWriter.
Параметры подключения указываются в виде <IP-адрес>:<Port>, например, 127.0.0.1:8080
Если журнал не подходит под сокет, то он создается как объект класса FileWriter.
Внутри класса Logger, кроме _writer поля, через которое происходит запись текущего сообщения в логгере, также хранится static словарь всех объектов BWriter, ассоциированных с именем журнала. Это сделано для того, чтобы при повторном открытии того же журнала в другом логгере не создавать объект BWriter, а использовать уже ранее созданный. За счет хранения в виде умных указателель обеспечивается free-leaked.
Важно, что для реализации логики библиотеки используются исключения, которые потом будут перехвачены в бизнес логике.
В части 2 было реализовано консольное многопоточное приложение CheckLibApp для проверки библиотеки записи сообщений в журнал из первой части.
Запуск приложения происходит по следующему шаблону:
./src/CheckLibApp <file_or_socket_setting> <minimum_level>
- <file_or_socket_setting>: Указывает журнал для сохранения логов.
Важно, что приложение может записывать как в файл (например,
file.log), так и в сокет (например,127.0.0.1:8080), но без активного приложения из части 3 он работать не будет. - <minimum_level>: Уровень важности сообщения по умолчанию. При создании логгера это значение используется как минимальный уровень важности, которое должно иметь сообщение, чтобы оно было записано в журнал
Сначала приложение принимает имя журнала и уровень важности по умолчанию. Если один или оба из этих параметров не будет получен, то программа завершит свою работу.
Дальше приложение циклично просит пользователя ввести сообщение и его уровень важности. Если сообщение не будет получено, то программа спросит, точно ли пользователь хочет завершить работу приложения. При утвердительном ответе программа завершиться, при отрицательном продолжит работу.
Если пользователь не укажет уровень важности сообщения, то будет использовано значение по умолчанию, указанное вначале.
Если пользователь захочет записать сообщение, у котого уровень важности ниже минимального, то программа предложит заменить значение уровня на новое. Если ответ утвердительный, то программа запросит новое значение, если отрицательное, то продолжит работу дальше. Если пользователь при утвердительном ответе введет корректное значение (от 0 до 2), то это значение уровня важности будет использоваться по умолчанию. Иначе будет использовано старое значение.
Программа также способна обрабатывать некорректный ввод: когда пользатель вводит уровень важности и указывает значение, не входящее в промежуток от 0 до 2, то во всех случаях (кроме смены уровня по умолчанию) будет использовано самое близкое значение - если указано отрицательное значение, то 0, если положительное и больше 2, то 2.
Главный класс приложения - CheckLibApp. Внутри него находится логгер, через который происходит запись логов в журнал. Запись логов осуществляется асинхронно, передача данных потокобезопасна за счет того, что в BWriter-наследуемых классах запись в методе Write() синхронизируется через мьютекс.
Приложение корректно обрабатывает исключения, которые возникают в библиотеке логгирования.
Но для реализации бизнес-логики исключения не используются: все методы объявлены noexcept
При указании параметров подключения сокета без запущенного приложения из части 3, будет выведена ошибка, и программа завершится

Если при запуске приложения не было указано никаких параметров, то программа их запрашивает у пользователя

Если и в этот раз пользователь не укажет параметры, то программа завершит работу.

Если количество аргументов будет больше, чем нужно, то программа сразу же завершает программу

Важно! При смене уровня важности эта настройка применяется только к следующему сообщению

В части 3 было реализовано консольное многопоточное приложение для сбора статистик по данным из сокета (от библиотеки логгирования из части 1)
Запуск приложения происходит по следующему шаблону:
./src/CollectStatApp <connection_parameters> N T
- <connection_parameters>: Параметры подключения сокета в виде <IP-адрес>:<Порт>
- N: количество сообщений, через которые будет выводиться статистика
- T: таймаут в секундах, по истечению которого будет выводиться статистика, при условии, что она изменилась с последнего вывода
Параметры N и T должны быть больше 0
Сначала приложение принимает параметры подключениия и значения N и T. Если какое-то значение не будет указано через аргументы командной строки, то программа запросит их напрямую у пользователя. Если что-то не все равно не будет получено, то программа завершит свою работу.
Дальше приложение будет ожидать сообщения от клиентов, которым может выступить приложение CheckLibApp, запущенное с именем журнала, пригодным для подключения сокета. Каждое принятое сообщение будет выведено в терминал.
При достижении N-ого сообщения будет выведена статистика, которая описана в ТЗ. Также через каждые T секунд также будет выводиться статистика, при условии, что с последнего вывода она была обновлена (т.е с последнего вывода пришло новое сообщение).
Класс Server реализует создание серверной части с указанными параметрами подключения, а также прослушивание сети на предмет подключения к нему клиентов. Сетевое взаимодействие построено на сокетах.
Главный класс приложения - CollectStatApp. Вся логика сетевого взаимодействия вынесена в класс Server, что позволяет внутри не осложнять код класса приложения, а пользоваться дополнительным слоем абстракции в виде класса сервера.
Внутри приложения также реализован функция таймера. В ней через каждые T секунд вызывается метод для вывода статистики. Сам таймер при инициализации программы помещается в отдельный поток, чтобы не блокировать работу основного потока.
В методе exec(), который играет роль главного цикла приложения, происходит прослушивание соединения средствами класса Server. Когда соединение установлено, то программа начинает считывать данные, которые посылает клиент.
Для каждого клиентского узла создается отдельный поток, в котором происходит чтение приходящих данных, обновление статистики и вывод ее в консоль, если принятое сообщение - N-ое по списку. Все потоки, созданные для чтения данных, хранятся в одном списке, чтобы потом в деструкторе высвободить ресурсы через join().
Для обеспечения корректной работы внутри многопоточной программы используются две atomic переменные типа bool. Одна из них сигнализирует всем созданным потокам о завершении работы, когда произойдет ошибка, а вторая показывает, обновлена ли статистика или нет.
Если при запуске не указаны параметры, то программа запросит их у пользователя

Работа приложения (слева запущено CheckLibApp, справа - CollectStatApp):

Приложение также сигнализирует о подключении и отключении клиента




