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::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()
Метод проверяет актуальное имя файла, закрывает старый файл при необходимости и открывает новый
Это центральное место управления текущим лог-файлом
Метод реализован
📌 Режим открытия файла в 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
🧪 Проверка созданных лог-файлов по датам
📌 Что проверяется
💻 Команда проверки
📖 Какой результат можно увидеть
✅ Итог
Список лог-файлов
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_);
Готово: это полноценное решение задания ✅