Skip to content

Latest commit

 

History

History
467 lines (352 loc) · 36.4 KB

File metadata and controls

467 lines (352 loc) · 36.4 KB

Sprint 2 — Problems — Logger — Solution


🎯 Задача по разработке потокобезопасного логгера с variadic template, Singleton-доступом, макросом LOG, датированными файлами и поддержкой тестового timestamp

📌 Что именно нужно реализовать в задаче logger внутри sprint2/problems/logger/solution 📖 Полное описание задачи и основного поведения логгера 🧠 Почему этот логгер важен для backend-сервера, многопоточности, тестирования и дальнейшей поддержки проекта ✅ Итог
Потокобезопасный логгер с поддержкой variadic template Разработать потокобезопасный логгер с поддержкой variadic template Логгер должен удобно принимать любое количество аргументов разных типов, писать timestamp, выбирать файл по дате и быть безопасным при работе из разных потоков Задача реализована
Вызов через Singleton Логгер должен позволять писать Logger::GetInstance().Log("User entered numbers "sv, x, " and "sv, y); Такой вызов даёт доступ к логгеру из любого места программы без передачи объекта через все классы Singleton-доступ есть
Вызов через макрос Логгер также должен позволять писать LOG("User entered numbers "sv, x, " and "sv, y); Макрос сокращает синтаксис и делает запись логов удобнее Макрос LOG есть
Logger::GetInstance().Log("User entered numbers "sv, x, " and "sv, y);
LOG("User entered numbers "sv, x, " and "sv, y);

📂 Полный путь проекта logger/solution внутри второго спринта

📌 Что именно указывается в этом разделе README для удобной навигации по проекту 📖 Абсолютный путь к папке готового решения задачи logger 🧠 Зачем этот путь нужен при открытии файла, сборке проекта, запуске бинарника и проверке результата ✅ Итог
Полный путь проекта /home/ubuntu/cppbackend/sprint2/problems/logger/solution По этому пути находится готовое решение задачи, README.md, CMakeLists.txt, conanfile.txt, main.cpp и my_logger.h Путь проекта зафиксирован
/home/ubuntu/cppbackend/sprint2/problems/logger/solution

📋 Требования к логгеру, которые должны быть выполнены в этом решении

📌 Номер требования к логгеру 📖 Что именно должен уметь логгер по условию и по реализации 🧠 Почему это требование важно для реального логирования и автотестов ✅ Итог
1 Принимать произвольное количество аргументов Это делает логгер удобным для сообщений из строк, чисел и других типов без ручной склейки Выполнено
2 Выводить каждый аргумент через operator<< Это позволяет использовать стандартный потоковый вывод C++ Выполнено
3 Перед сообщением писать временную метку Timestamp нужен, чтобы понимать время события Выполнено
4 Поддерживать SetTimestamp для тестирования Тесты могут задавать фиксированное время и проверять имена файлов и строки лога Выполнено
5 Писать в /var/log/sample_log_YYYY_MM_DD.log Логи раскладываются по файлам на основе даты Выполнено
6 Использовать дату из SetTimestamp При ручном timestamp логгер должен выбирать файл по этой дате Выполнено
7 Менять файл при смене даты Если дата изменилась, лог должен пойти в новый файл Выполнено
8 Быть потокобезопасным Несколько потоков могут писать лог, менять timestamp и переоткрывать файл Выполнено
1. принимать произвольное количество аргументов
2. выводить каждый аргумент через operator<<
3. перед сообщением писать временную метку
4. поддерживать SetTimestamp для тестирования
5. писать в /var/log/sample_log_YYYY_MM_DD.log
6. использовать дату из SetTimestamp
7. менять файл при смене даты
8. быть потокобезопасным

📁 Структура проекта solution для задачи logger

📁 Элемент структуры проекта 📖 Что находится внутри 🧠 Какую роль играет в решении задачи logger ✅ Итог
CMakeLists.txt Файл сборки проекта Описывает сборку решения через CMake Сборка настроена
conanfile.txt Файл зависимостей Conan Используется при установке зависимостей через conan install Зависимости подключаются
main.cpp Демонстрационный или тестовый код запуска Использует логгер и позволяет проверить работу бинарника Точка запуска есть
my_logger.h Основная реализация логгера Содержит Singleton Logger, LOG, SetTimestamp, Log, mutex и работу с файлами Логгер реализован
README.md Документация решения Описывает задачу, требования, код, сборку, запуск и проверки Документация готова
solution/
├── CMakeLists.txt
├── conanfile.txt
├── main.cpp
├── my_logger.h
└── README.md

💡 Главная идея решения: метод Log реализован через variadic template

📌 Главная идея реализации 📖 Какой метод используется для приёма произвольного количества аргументов 🧠 Почему variadic template подходит для логгера лучше ручной склейки строк ✅ Итог
Variadic template Метод Log реализован как template <class... Ts> void Log(const Ts&... args) Это позволяет передавать любое количество аргументов разных типов в один вызов логгера Метод Log универсален
Разные типы аргументов В один вызов можно передавать строки, числа и другие типы, поддерживающие operator<< Логгер не требует ручного std::to_string и склейки строк Интерфейс удобный
template <class... Ts>
void Log(const Ts&... args)
Это позволяет передавать любое количество аргументов разных типов.

🧵 Вывод всех аргументов через fold expression

📌 Механизм вывода аргументов 📖 Какая строка кода выводит все аргументы подряд 🧠 Как примерно раскрывается вызов LOG("x = "sv, x, ", y = "sv, y); ✅ Итог
Fold expression (log_file_ << ... << args); Вызов примерно превращается в log_file_ << "x = "sv << x << ", y = "sv << y; Все аргументы выводятся подряд
Пример вызова LOG("x = "sv, x, ", y = "sv, y); Макрос вызывает Logger::GetInstance().Log(...) Запись короткая и удобная
(log_file_ << ... << args);
LOG("x = "sv, x, ", y = "sv, y);
log_file_ << "x = "sv << x << ", y = "sv << y;

🔐 Потокобезопасность логгера через std::mutex и std::lock_guard<std::mutex>

📌 Что защищается в логгере 📖 Какой механизм синхронизации используется 🧠 Почему методы Log и SetTimestamp должны быть защищены одним mutex ✅ Итог
Общие данные логгера В логгере есть std::mutex mutex_; Общими являются timestamp, текущий файл, поток файла и логика переоткрытия Mutex добавлен
Метод Log Защищён через std::lock_guard<std::mutex> lock(mutex_); Разные потоки могут одновременно писать лог и переоткрывать файл Log потокобезопасен
Метод SetTimestamp Защищён через std::lock_guard<std::mutex> lock(mutex_); Разные потоки могут одновременно менять timestamp и писать лог SetTimestamp потокобезопасен
Одновременные операции Потоки могут писать лог, менять timestamp и переоткрывать файл Без синхронизации возможны гонки и повреждение состояния логгера Синхронизация нужна
std::mutex mutex_;
std::lock_guard<std::mutex> lock(mutex_);
писать лог
менять timestamp
переоткрывать файл

🗂 Файл лога строится по дате в формате /var/log/sample_log_YYYY_MM_DD.log

📌 Как формируется файл лога 📖 Какой шаблон имени файла используется 🧠 Почему дата в имени файла важна для разделения логов по дням ✅ Итог
Файл по дате Файл строится по дате в формате /var/log/sample_log_YYYY_MM_DD.log Каждый день может иметь свой отдельный файл логов Имена логов датированы
Пример файла /var/log/sample_log_1970_01_12.log Такой файл может появиться при timestamp, который соответствует дате 1970_01_12 Пример понятен
/var/log/sample_log_YYYY_MM_DD.log
/var/log/sample_log_1970_01_12.log

🔄 Смена файла при смене даты через OpenActualLogFile()

📌 Что происходит при смене даты 📖 Какой метод отвечает за выбор и открытие актуального файла 🧠 Почему логгер должен закрывать старый файл и открывать новый ✅ Итог
Дата изменилась Если дата изменилась, логгер закрывает старый файл и открывает новый Иначе записи за новую дату продолжали бы попадать в старый файл Смена файла работает
Метод OpenActualLogFile() Метод проверяет актуальное имя файла, закрывает старый файл при необходимости и открывает новый Это центральное место управления текущим лог-файлом Метод реализован
OpenActualLogFile()

📌 Режим открытия файла в append, чтобы старые записи сохранялись

📌 Как открывается лог-файл 📖 Какой режим открытия используется 🧠 Что означает append для логов ✅ Итог
Append-режим Файл открывается через log_file_.open(current_file_name_, std::ios::app); Файл не очищается, новые строки дописываются в конец История логов сохраняется
Смысл append Не очищать файл и дописывать новые строки в конец Это правильное поведение для логов Режим выбран правильно
log_file_.open(current_file_name_, std::ios::app);
не очищать файл
дописывать новые строки в конец

💻 Полный код my_logger.h с Singleton, макросом LOG, variadic template, SetTimestamp, датированными файлами и mutex

📌 Часть реализации в my_logger.h 📖 Что делает эта часть кода 🧠 Почему эта часть нужна для требований задачи logger ✅ Итог
#define LOG(...) Logger::GetInstance().Log(__VA_ARGS__) Создаёт короткий макрос для записи логов Позволяет писать LOG(...) вместо полного вызова Singleton Макрос есть
manual_ts_ Хранит ручной timestamp для тестирования Позволяет тестам управлять датой и временем логгера Тестируемость есть
GetTime() Возвращает ручной timestamp или текущее системное время Позволяет одинаково работать и в тестах, и в обычном режиме Время централизовано
GetTimeStamp() Формирует timestamp строки лога в формате %F %T Перед каждым сообщением появляется дата и время Timestamp есть
GetFileTimeStamp() Формирует дату файла в формате %Y_%m_%d Используется для имени /var/log/sample_log_YYYY_MM_DD.log Дата файла есть
MakeLogFileName() Собирает полный путь к файлу лога Возвращает путь внутри /var/log Имя файла строится
OpenActualLogFile() Открывает актуальный лог-файл и переоткрывает его при смене даты Нужен для разделения логов по датам Переоткрытие есть
GetInstance() Возвращает единственный объект Logger Реализует Singleton Singleton есть
Log Защищает mutex, открывает актуальный файл, пишет timestamp и все аргументы Это основная функция логирования Логирование работает
SetTimestamp Позволяет задать ручной timestamp Нужно для тестирования даты и имени файла Тестовый timestamp есть
#pragma once

#include <chrono>
#include <fstream>
#include <iomanip>
#include <mutex>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>

using namespace std::literals;

#define LOG(...) Logger::GetInstance().Log(__VA_ARGS__)

class Logger {
    auto GetTime() const {
        if (manual_ts_) {
            return *manual_ts_;
        }

        return std::chrono::system_clock::now();
    }

    auto GetTimeStamp() const {
        const auto now = GetTime();
        const auto time = std::chrono::system_clock::to_time_t(now);

        return std::put_time(std::localtime(&time), "%F %T");
    }

    std::string GetFileTimeStamp() const {
        const auto now = GetTime();
        const auto time = std::chrono::system_clock::to_time_t(now);

        std::ostringstream out;
        out << std::put_time(std::localtime(&time), "%Y_%m_%d");

        return out.str();
    }

    std::string MakeLogFileName() const {
        return "/var/log/sample_log_"s + GetFileTimeStamp() + ".log"s;
    }

    void OpenActualLogFile() {
        const std::string actual_file_name = MakeLogFileName();

        if (actual_file_name == current_file_name_ && log_file_.is_open()) {
            return;
        }

        if (log_file_.is_open()) {
            log_file_.close();
        }

        current_file_name_ = actual_file_name;
        log_file_.open(current_file_name_, std::ios::app);
    }

    Logger() = default;

public:
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

    Logger(Logger&&) = delete;
    Logger& operator=(Logger&&) = delete;

    static Logger& GetInstance() {
        static Logger obj;
        return obj;
    }

    template <class... Ts>
    void Log(const Ts&... args) {
        std::lock_guard<std::mutex> lock(mutex_);

        OpenActualLogFile();

        if (!log_file_) {
            return;
        }

        log_file_ << GetTimeStamp() << ": "sv;
        (log_file_ << ... << args);
        log_file_ << std::endl;
    }

    void SetTimestamp(std::chrono::system_clock::time_point ts) {
        std::lock_guard<std::mutex> lock(mutex_);
        manual_ts_ = ts;
    }

private:
    std::optional<std::chrono::system_clock::time_point> manual_ts_;
    std::ofstream log_file_;
    std::string current_file_name_;
    std::mutex mutex_;
};

🔧 Сборка решения logger/solution через Conan и CMake

📌 Шаг сборки проекта 💻 Команда, которую нужно выполнить 📖 Что делает команда ✅ Итог
Перейти в папку решения cd /home/ubuntu/cppbackend/sprint2/problems/logger/solution Открывает каталог готового решения Можно собирать
Удалить старую сборку rm -rf build Удаляет старую папку сборки Сборка будет чистой
Создать папку build mkdir build Создаёт новый каталог сборки Build создан
Перейти в build cd build Открывает папку сборки Можно запускать Conan и CMake
Установить зависимости conan install .. --build=missing Устанавливает зависимости, если их не хватает Conan-зависимости готовы
Сконфигурировать проект cmake .. Генерирует файлы сборки Конфигурация готова
Собрать проект cmake --build . Компилирует бинарник Проект собран
cd /home/ubuntu/cppbackend/sprint2/problems/logger/solution

rm -rf build
mkdir build
cd build

conan install .. --build=missing
cmake ..
cmake --build .

🔐 Права на /var/log, если программа не может писать лог-файлы в системную директорию

📌 Что сделать при проблеме записи в /var/log 💻 Команда или действие 📖 Что делает команда ✅ Итог
Обновить пакеты sudo apt update Обновляет список пакетов Система готова
Установить rsyslog sudo apt install -y rsyslog Устанавливает системный логгер и может создать группу syslog rsyslog установлен
Узнать пользователя whoami Показывает имя текущего пользователя Имя известно
Добавить пользователя в группу sudo usermod -a -G syslog ubuntu Добавляет пользователя ubuntu в группу syslog Права добавлены
Подставить правильное имя Вместо ubuntu указать имя из whoami Команда должна применяться к реальному пользователю Ошибка исключена
Переподключиться После этого переподключиться к терминалу Новые группы применятся в новой сессии Права вступят в силу
sudo apt update
sudo apt install -y rsyslog
whoami
sudo usermod -a -G syslog ubuntu
Вместо `ubuntu` указать имя из `whoami`.
После этого переподключиться к терминалу.

✅ Проверка прав записи в /var/log

📌 Что проверяется 💻 Команда проверки 📖 Что означает успешное выполнение команды ✅ Итог
Создание тестового файла touch /var/log/test_logger_permissions.log Пользователь может создать файл в /var/log Запись разрешена
Удаление тестового файла rm /var/log/test_logger_permissions.log Пользователь может удалить тестовый файл Проверка очищена
touch /var/log/test_logger_permissions.log
rm /var/log/test_logger_permissions.log

▶️ Запуск собранного решения

📌 Вариант запуска 💻 Команда запуска 📖 Когда использовать этот вариант ✅ Итог
Перейти в build cd /home/ubuntu/cppbackend/sprint2/problems/logger/solution/build Открывает папку сборки Можно запускать бинарник
Запуск из bin ./bin/hello_log Используется, если бинарник собран в build/bin Программа запускается
Запуск напрямую ./hello_log Используется, если бинарник лежит прямо в build Программа запускается
cd /home/ubuntu/cppbackend/sprint2/problems/logger/solution/build

./bin/hello_log
./hello_log

🧪 Проверка созданных лог-файлов по датам

📌 Что проверяется 💻 Команда проверки 📖 Какой результат можно увидеть ✅ Итог
Список лог-файлов ls -l /var/log/sample_log_*.log Можно увидеть файлы вида /var/log/sample_log_1970_01_12.log и /var/log/sample_log_1970_04_26.log Файлы по датам созданы
Содержимое первого файла cat /var/log/sample_log_1970_01_12.log Показывает записи за дату 1970_01_12 Лог читается
Содержимое второго файла cat /var/log/sample_log_1970_04_26.log Показывает записи за дату 1970_04_26 Лог читается
ls -l /var/log/sample_log_*.log
/var/log/sample_log_1970_01_12.log
/var/log/sample_log_1970_04_26.log
cat /var/log/sample_log_1970_01_12.log
cat /var/log/sample_log_1970_04_26.log

🏁 Итог решения задачи logger

📌 Что реализовано в решении 📖 Подробное описание результата 🧠 Почему это важно для логирования в backend-проекте ✅ Итог
Variadic template Метод Log принимает произвольное количество аргументов разных типов Логирование стало удобным и универсальным Реализовано
Fold expression (log_file_ << ... << args); выводит все аргументы подряд Не нужно вручную собирать строку Реализовано
Singleton Logger Logger::GetInstance() возвращает единственный объект логгера Логгер доступен из любого места программы Реализовано
SetTimestamp Позволяет задать ручное время для тестирования Тесты могут проверять даты и имена файлов Реализовано
Лог-файлы по датам Файл создаётся как /var/log/sample_log_YYYY_MM_DD.log Логи разделяются по дате Реализовано
Переоткрытие файла при смене даты OpenActualLogFile() открывает новый файл, если дата изменилась Записи попадают в правильный дневной лог Реализовано
std::ios::app Файл открывается в режиме append Старые записи не удаляются Реализовано
std::mutex Общий mutex защищает состояние логгера Нужен для многопоточности Реализовано
std::lock_guard Автоматически блокирует и освобождает mutex Делает синхронизацию безопасной Реализовано
Потокобезопасное логирование Log и SetTimestamp защищены mutex Потоки не ломают состояние логгера Реализовано
variadic template
fold expression
Singleton Logger
SetTimestamp
лог-файлы по датам
переоткрытие файла при смене даты
std::ios::app
std::mutex
std::lock_guard
потокобезопасное логирование

Короткий итог

Главный метод:

template <class... Ts> void Log(const Ts&... args)

Главная строка вывода: (log_file_ << ... << args);

Главный файл: /var/log/sample_log_YYYY_MM_DD.log

Главная защита: std::lock_guardstd::mutex lock(mutex_);

Готово: это полноценное решение задания ✅