master
Сергей Маринкевич 2 months ago
parent 0bc8810186
commit 52f9bf2057

@ -0,0 +1,4 @@
run: all
./a.out
all:
g++ -std=c++17 -pthread main.c

@ -12,196 +12,316 @@
#include <iomanip> #include <iomanip>
#include <array> #include <array>
#include <vector> #include <vector>
#include <optional>
#include <functional>
#include <boost/format.hpp> #include <boost/format.hpp>
// Forward declarations
class DaemonLogger;
// The Severity enum is defined outside the class to be available to the template helper,
// resolving the incomplete type compilation error.
enum class Severity { Debug, Info, Warning, Error, _Count };
// Вспомогательный шаблонный класс для управления каскадными свойствами
template <typename T>
class CascadingPropertyResolver {
private:
std::mutex mtx_;
T global_default_;
std::unordered_map<std::string, T> category_defaults_;
std::unordered_map<std::string, std::unordered_map<Severity, T>> severity_specific_;
public:
void setGlobal(T value) {
std::lock_guard<std::mutex> lock(mtx_);
global_default_ = value;
}
void setForCategory(const std::string& category, T value) {
std::lock_guard<std::mutex> lock(mtx_);
category_defaults_[category] = value;
}
void setForSeverity(const std::string& category, Severity severity, T value) {
std::lock_guard<std::mutex> lock(mtx_);
severity_specific_[category][severity] = value;
}
T resolve(const std::string& category, Severity severity) {
std::lock_guard<std::mutex> lock(mtx_);
// Правило 1: Ищем [категория, уровень]
if (auto cat_it = severity_specific_.find(category); cat_it != severity_specific_.end()) {
if (auto sev_it = cat_it->second.find(severity); sev_it != cat_it->second.end()) {
return sev_it->second;
}
}
// Правило 2: Ищем [категория]
if (auto cat_it = category_defaults_.find(category); cat_it != category_defaults_.end()) {
return cat_it->second;
}
// Правило 3: Используем глобальное значение
return global_default_;
}
};
class DaemonLogger { class DaemonLogger {
public: public:
enum class Severity { // Re-expose Severity enum inside the class for clarity and namespacing,
Debug, // 0 // while the actual definition is outside.
Info, // 1 using Severity = ::Severity;
Warning, // 2
Error, // 3
_Count // 4 - для размера массива
};
private: private:
// Обертка для потокобезопасной записи в один ostream // Обертка для потокобезопасной записи в один ostream
struct SynchronizedStream { struct SynchronizedStream {
std::ostream* stream; std::ostream* stream;
std::mutex mtx; std::mutex mtx;
SynchronizedStream(std::ostream& s) : stream(&s) {}
};
// Контекст, передаваемый каждому функтору формата
struct LogContext {
const std::string& category;
Severity severity;
const std::string& location;
const std::string& user_message;
// Кэш для временной метки, чтобы не вычислять ее многократно
mutable std::optional<std::string> time_cache;
};
SynchronizedStream(std::ostream& s) : stream(&s) {} // Определяем типы для скомпилированного формата
}; using LogPieceFormatter = std::function<void(std::ostream&, const LogContext&)>;
using CompiledFormat = std::vector<LogPieceFormatter>;
public: public:
// --- Глобальная конфигурация маршрутизации --- // --- Конфигурация маршрутизации потоков ---
static void setGlobalOutputStream(std::ostream& stream) {
// Устанавливает поток по умолчанию, если другие правила не совпали stream_resolver_.setGlobal(get_or_create_synced_stream(stream));
static void setGlobalOutputStream(std::ostream& stream) { reconfigureAllLoggers();
std::lock_guard<std::mutex> lock(config_mutex_); }
global_stream_ = get_or_create_synced_stream(stream); static void setCategoryOutputStream(const std::string& category, std::ostream& stream) {
reconfigureAllLoggers(); stream_resolver_.setForCategory(category, get_or_create_synced_stream(stream));
} reconfigureLogger(category);
}
// Устанавливает поток для всех уровней в указанной категории static void setSeverityOutputStream(const std::string& category, Severity severity, std::ostream& stream) {
static void setCategoryOutputStream(const std::string& category, std::ostream& stream) { stream_resolver_.setForSeverity(category, severity, get_or_create_synced_stream(stream));
std::lock_guard<std::mutex> lock(config_mutex_); reconfigureLogger(category);
category_defaults_[category] = get_or_create_synced_stream(stream); }
if (auto it = registry_.find(category); it != registry_.end()) {
it->second->resolveOutputStreams(); // --- Конфигурация форматов ---
} static void setGlobalFormat(const std::string& format) {
} format_resolver_.setGlobal(compileFormat(format));
reconfigureAllLoggers();
// Устанавливает поток для конкретного уровня в конкретной категории }
static void setSeverityOutputStream(const std::string& category, Severity severity, std::ostream& stream) { static void setCategoryFormat(const std::string& category, const std::string& format) {
std::lock_guard<std::mutex> lock(config_mutex_); format_resolver_.setForCategory(category, compileFormat(format));
severity_specific_[category][severity] = get_or_create_synced_stream(stream); reconfigureLogger(category);
if (auto it = registry_.find(category); it != registry_.end()) { }
it->second->resolveOutputStreams(); static void setSeverityFormat(const std::string& category, Severity severity, const std::string& format) {
} format_resolver_.setForSeverity(category, severity, compileFormat(format));
} reconfigureLogger(category);
}
// Получить логгер по категории
static DaemonLogger& get(const std::string& category) { // --- Получение и настройка логгера ---
std::lock_guard<std::mutex> lock(registry_mutex_); static DaemonLogger& get(const std::string& category) {
auto it = registry_.find(category); std::lock_guard<std::mutex> lock(registry_mutex_);
if (it == registry_.end()) { if (auto it = registry_.find(category); it != registry_.end()) {
auto logger = std::unique_ptr<DaemonLogger>(new DaemonLogger(category)); return *(it->second);
logger->resolveOutputStreams(); // Разрешаем маршруты при создании }
auto [inserted_it, _] = registry_.try_emplace(category, std::move(logger)); auto logger = std::unique_ptr<DaemonLogger>(new DaemonLogger(category));
return *(inserted_it->second); logger->resolveProperties();
} auto [inserted_it, _] = registry_.try_emplace(category, std::move(logger));
return *(it->second); return *(inserted_it->second);
} }
// --- Настройка экземпляра логгера --- void setMinSeverity(Severity level) { min_severity_ = level; }
void setMinSeverity(Severity level) { min_severity_ = level; } void suppress() { suppressed_ = true; }
void suppress() { suppressed_ = true; } void unsuppress() { suppressed_ = false; }
void unsuppress() { suppressed_ = false; }
// --- Основной метод логирования ---
// --- Основной метод логирования --- template<class... Args>
template<class... Args> void log(Severity severity, const std::string& source_location, const std::string& format, Args... args) const {
void log(Severity severity, const std::string& source_location, const std::string& format, Args... args) const { if (suppressed_ || severity < min_severity_) return;
if (suppressed_ || severity < min_severity_) {
return; size_t severity_idx = static_cast<size_t>(severity);
} auto& target_stream = resolved_streams_[severity_idx];
auto& compiled_format = resolved_compiled_formats_[severity_idx];
// 1. Быстрое получение нужного потока (O(1))
auto& target_synced_stream = resolved_streams_[static_cast<size_t>(severity)]; if (!target_stream || !target_stream->stream || !compiled_format || compiled_format->empty()) return;
if (!target_synced_stream || !target_synced_stream->stream) {
return; // Маршрут не настроен / ведет в никуда // 1. Форматируем только пользовательскую часть сообщения
} std::string user_message;
try {
// 2. Форматирование сообщения (до блокировки мьютекса) user_message = (boost::format(format) % ... % args).str();
std::string user_message; } catch (const boost::io::format_error& e) {
try { user_message = "!!! LOG FORMATTING ERROR: " + std::string(e.what()) + " !!!";
user_message = (boost::format(format) % ... % args).str(); }
} catch (const boost::io::format_error& e) {
user_message = "!!! LOG FORMATTING ERROR: " + std::string(e.what()) + " !!!"; // 2. Создаем контекст
} LogContext context{category_, severity, source_location, user_message, std::nullopt};
std::ostringstream final_message; // 3. Быстро собираем итоговое сообщение, исполняя скомпилированный план
final_message << currentTime() << " [" << severityToString(severity) << "]" std::ostringstream final_message_stream;
<< " [" << category_ << "]" for (const auto& formatter : *compiled_format) {
<< " [" << source_location << "] " formatter(final_message_stream, context);
<< user_message; }
// 3. Потокобезопасная запись в целевой поток // 4. Потокобезопасно выводим результат
{ {
std::lock_guard<std::mutex> lock(target_synced_stream->mtx); std::lock_guard<std::mutex> lock(target_stream->mtx);
*target_synced_stream->stream << final_message.str() << std::endl; *target_stream->stream << final_message_stream.str() << std::endl;
} }
} }
private: private:
explicit DaemonLogger(std::string category) : category_(std::move(category)) {} explicit DaemonLogger(std::string category) : category_(std::move(category)) {}
// Разрешает и кэширует маршруты для этого экземпляра логгера void resolveProperties() {
void resolveOutputStreams() { for (size_t i = 0; i < static_cast<size_t>(Severity::_Count); ++i) {
std::lock_guard<std::mutex> lock(config_mutex_); Severity s = static_cast<Severity>(i);
for (size_t i = 0; i < static_cast<size_t>(Severity::_Count); ++i) { resolved_streams_[i] = stream_resolver_.resolve(category_, s);
Severity s = static_cast<Severity>(i); resolved_compiled_formats_[i] = format_resolver_.resolve(category_, s);
// Правило 1: Ищем [категория, уровень] }
if (auto cat_it = severity_specific_.find(category_); cat_it != severity_specific_.end()) { }
if (auto sev_it = cat_it->second.find(s); sev_it != cat_it->second.end()) {
resolved_streams_[i] = sev_it->second; // "Компилятор" строки формата в набор функторов
continue; static std::shared_ptr<const CompiledFormat> compileFormat(const std::string& format) {
} static std::mutex compile_mutex;
} static std::unordered_map<std::string, std::shared_ptr<const CompiledFormat>> cache;
// Правило 2: Ищем [категория]
if (auto cat_it = category_defaults_.find(category_); cat_it != category_defaults_.end()) { std::lock_guard<std::mutex> lock(compile_mutex);
resolved_streams_[i] = cat_it->second; if (auto it = cache.find(format); it != cache.end()) {
continue; return it->second;
} }
// Правило 3: Используем глобальный поток
resolved_streams_[i] = global_stream_; auto compiled = std::make_shared<CompiledFormat>();
} size_t last_pos = 0;
} size_t brace_pos = 0;
// --- Статические хелперы и данные --- while ((brace_pos = format.find('{', last_pos)) != std::string::npos) {
static std::shared_ptr<SynchronizedStream> get_or_create_synced_stream(std::ostream& stream) { // Добавляем литерал перед плейсхолдером
auto it = stream_registry_.find(&stream); if (brace_pos > last_pos) {
if (it != stream_registry_.end()) { compiled->emplace_back(
return it->second; [literal = format.substr(last_pos, brace_pos - last_pos)](auto& os, const auto&) {
} os << literal;
auto synced_stream = std::make_shared<SynchronizedStream>(stream); });
stream_registry_[&stream] = synced_stream; }
return synced_stream;
} size_t end_brace_pos = format.find('}', brace_pos);
if (end_brace_pos == std::string::npos) {
static void reconfigureAllLoggers() { // Если нет закрывающей скобки, то оставшаяся часть строки, включая '{',
std::lock_guard<std::mutex> lock(registry_mutex_); // будет обработана как литерал в конце функции.
for (auto const& [key, val] : registry_) { last_pos = brace_pos;
val->resolveOutputStreams(); break;
} }
}
std::string placeholder = format.substr(brace_pos + 1, end_brace_pos - brace_pos - 1);
static std::string currentTime() { if (placeholder == "date") {
auto now = std::chrono::system_clock::now(); compiled->emplace_back([](auto& os, const auto& ctx) {
auto now_c = std::chrono::system_clock::to_time_t(now); if (!ctx.time_cache) ctx.time_cache = currentTime();
std::ostringstream oss; os << *ctx.time_cache;
oss << std::put_time(std::localtime(&now_c), "%Y-%m-%d %H:%M:%S"); });
return oss.str(); } else if (placeholder == "cat") {
} compiled->emplace_back([](auto& os, const auto& ctx) { os << ctx.category; });
static std::string severityToString(Severity severity) { } else if (placeholder == "severity") {
switch (severity) { compiled->emplace_back([](auto& os, const auto& ctx) { os << severityToString(ctx.severity); });
case Severity::Debug: return "DEBUG"; } else if (placeholder == "loc") {
case Severity::Info: return "INFO "; compiled->emplace_back([](auto& os, const auto& ctx) { os << ctx.location; });
case Severity::Warning: return "WARN "; } else if (placeholder == "umsg") {
case Severity::Error: return "ERROR"; compiled->emplace_back([](auto& os, const auto& ctx) { os << ctx.user_message; });
default: return "UNKNW"; } else {
} // Неизвестный плейсхолдер - выводим как есть, включая скобки
} compiled->emplace_back(
[literal = format.substr(brace_pos, end_brace_pos - brace_pos + 1)](auto& os, const auto&) {
// --- Данные экземпляра --- os << literal;
std::string category_; });
Severity min_severity_ = Severity::Debug; }
bool suppressed_ = false;
std::array<std::shared_ptr<SynchronizedStream>, static_cast<size_t>(Severity::_Count)> resolved_streams_; last_pos = end_brace_pos + 1;
}
// --- Статические данные класса ---
inline static std::unordered_map<std::string, std::unique_ptr<DaemonLogger>> registry_;
inline static std::mutex registry_mutex_;
inline static std::mutex config_mutex_;
// Хранилище правил маршрутизации
inline static std::shared_ptr<SynchronizedStream> global_stream_;
inline static std::unordered_map<std::string, std::shared_ptr<SynchronizedStream>> category_defaults_;
inline static std::unordered_map<std::string, std::unordered_map<Severity, std::shared_ptr<SynchronizedStream>>> severity_specific_;
// Реестр ostream'ов, чтобы не создавать дубликаты SynchronizedStream для одного и того же потока
inline static std::unordered_map<std::ostream*, std::shared_ptr<SynchronizedStream>> stream_registry_;
};
// Добавляем оставшийся хвост строки
if (last_pos < format.length()) {
compiled->emplace_back(
[literal = format.substr(last_pos)](auto& os, const auto&) {
os << literal;
});
}
cache[format] = compiled;
return compiled;
}
static void reconfigureAllLoggers() {
std::lock_guard<std::mutex> lock(registry_mutex_);
for (auto const& [key, val] : registry_) val->resolveProperties();
}
static void reconfigureLogger(const std::string& category) {
std::lock_guard<std::mutex> lock(registry_mutex_);
if (auto it = registry_.find(category); it != registry_.end()) {
it->second->resolveProperties();
}
}
static std::shared_ptr<SynchronizedStream> get_or_create_synced_stream(std::ostream& stream) {
static std::mutex stream_reg_mutex;
std::lock_guard<std::mutex> lock(stream_reg_mutex);
if (auto it = stream_registry_.find(&stream); it != stream_registry_.end()) {
return it->second;
}
auto synced_stream = std::make_shared<SynchronizedStream>(stream);
stream_registry_[&stream] = synced_stream;
return synced_stream;
}
static std::string currentTime() {
auto now = std::chrono::system_clock::now();
auto now_c = std::chrono::system_clock::to_time_t(now);
std::ostringstream oss;
// std::localtime is not thread-safe on all platforms, but is often an acceptable trade-off.
// For a fully thread-safe solution, platform-specific alternatives (localtime_r, localtime_s) would be needed.
oss << std::put_time(std::localtime(&now_c), "%Y-%m-%d %H:%M:%S");
return oss.str();
}
static const char* severityToString(Severity severity) {
switch (severity) {
case Severity::Debug: return "DEBUG";
case Severity::Info: return "INFO ";
case Severity::Warning: return "WARN ";
case Severity::Error: return "ERROR";
default: return "UNKNW";
}
}
// --- Данные экземпляра ---
std::string category_;
Severity min_severity_ = Severity::Debug;
bool suppressed_ = false;
std::array<std::shared_ptr<SynchronizedStream>, static_cast<size_t>(Severity::_Count)> resolved_streams_;
std::array<std::shared_ptr<const CompiledFormat>, static_cast<size_t>(Severity::_Count)> resolved_compiled_formats_;
// --- Статические данные класса ---
inline static std::unordered_map<std::string, std::unique_ptr<DaemonLogger>> registry_;
inline static std::mutex registry_mutex_;
inline static std::unordered_map<std::ostream*, std::shared_ptr<SynchronizedStream>> stream_registry_;
inline static CascadingPropertyResolver<std::shared_ptr<SynchronizedStream>> stream_resolver_;
inline static CascadingPropertyResolver<std::shared_ptr<const CompiledFormat>> format_resolver_;
};
// --- Макросы для удобного вызова --- // --- Макросы для удобного вызова ---
#define __LOG_IMPL(category, severity, format, ...) \ #define __LOG_IMPL(category, severity, format, ...) \
do { \ do { \
std::string loc = std::string(__func__) + ":" + std::to_string(__LINE__); \ std::string loc = std::string(__func__) + ":" + std::to_string(__LINE__); \
DaemonLogger::get(category).log(severity, loc, format, ##__VA_ARGS__); \ DaemonLogger::get(category).log(severity, loc, format, ##__VA_ARGS__); \
} while (0) } while (0)
#define LOG_DEBUG(category, format, ...) __LOG_IMPL(category, DaemonLogger::Severity::Debug, format, ##__VA_ARGS__) #define LOG_DEBUG(category, format, ...) __LOG_IMPL(category, DaemonLogger::Severity::Debug, format, ##__VA_ARGS__)
#define LOG_INFO(category, format, ...) __LOG_IMPL(category, DaemonLogger::Severity::Info, format, ##__VA_ARGS__) #define LOG_INFO(category, format, ...) __LOG_IMPL(category, DaemonLogger::Severity::Info, format, ##__VA_ARGS__)
#define LOG_WARNING(category, format, ...) __LOG_IMPL(category, DaemonLogger::Severity::Warning, format, ##__VA_ARGS__) #define LOG_WARNING(category, format, ...) __LOG_IMPL(category, DaemonLogger::Severity::Warning, format, ##__VA_ARGS__)
#define LOG_ERROR(category, format, ...) __LOG_IMPL(category, DaemonLogger::Severity::Error, format, ##__VA_ARGS__) #define LOG_ERROR(category, format, ...) __LOG_IMPL(category, DaemonLogger::Severity::Error, format, ##__VA_ARGS__)

@ -1,49 +1,80 @@
#include "glogger.hpp" #include "glogger.hpp"
#include <thread> #include <thread>
#include <vector>
#include <fstream>
#include <chrono>
// Создаем файлы для логов // Создаем потоки для файлов, которые будут жить в течение всей программы
std::ofstream network_log_file("network.log"); std::ofstream general_log_file("general.log");
std::ofstream critical_errors_file("critical.log"); std::ofstream critical_errors_file("critical.log");
std::ofstream network_log_file("network.log");
void worker_thread_func(int thread_id) {
LOG_INFO("General", "Worker thread %1% started.", thread_id);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
LOG_DEBUG("Network", "heartbeat from thread %1%", thread_id);
if (thread_id % 2 == 0) {
LOG_ERROR("General", "Simulated failure in even thread %1%", thread_id);
}
LOG_INFO("General", "Worker thread %1% finished.", thread_id);
}
int main() { int main() {
// --- Настраиваем сложную маршрутизацию --- // --- 1. Настройка маршрутизации и форматов ---
// 1. По умолчанию все логи идут в стандартный вывод (консоль) // Глобальное правило по умолчанию: все, что не подошло под другие правила,
// идет в консоль в простом формате.
DaemonLogger::setGlobalOutputStream(std::cout); DaemonLogger::setGlobalOutputStream(std::cout);
DaemonLogger::setGlobalFormat("[{severity}] {cat}: {umsg}");
// 2. Все логи категории "Database", кроме ошибок, идут в /dev/null (игнорируются) // Правило для категории "General": пишем в файл general.log с полным форматом.
// Для этого создадим "пустой" поток DaemonLogger::setCategoryOutputStream("General", general_log_file);
std::ofstream null_stream; DaemonLogger::setCategoryFormat("General", "{date} [{severity}] <{loc}> {umsg}");
null_stream.setstate(std::ios_base::badbit); // Делаем поток невалидным
DaemonLogger::setCategoryOutputStream("Database", null_stream);
// 3. Все логи категории "Network" идут в свой файл network.log // Правило для категории "Network": пишем в свой файл network.log.
// Формат очень короткий, т.к. предполагается большой объем логов.
DaemonLogger::setCategoryOutputStream("Network", network_log_file); DaemonLogger::setCategoryOutputStream("Network", network_log_file);
DaemonLogger::setCategoryFormat("Network", "{date} -> {umsg}");
// 4. Но ОШИБКИ из "Network" И ОШИБКИ из "Database" идут в специальный файл critical.log // Самое специфичное правило: ошибки из категории "General" дублируются
DaemonLogger::setSeverityOutputStream("Network", DaemonLogger::Severity::Error, critical_errors_file); // в отдельный файл critical.log с особым, кричащим форматом.
DaemonLogger::setSeverityOutputStream("Database", DaemonLogger::Severity::Error, critical_errors_file); DaemonLogger::setSeverityOutputStream("General", DaemonLogger::Severity::Error, critical_errors_file);
DaemonLogger::setSeverityFormat("General", DaemonLogger::Severity::Error, "!!! CRITICAL [{cat}] {date} !!! {umsg} @ {loc}");
// --- Начинаем логирование --- // --- 2. Тестирование одиночных логов ---
LOG_INFO("Main", "Система логирования настроена. Запускаем приложение."); LOG_INFO("Main", "Система логирования настроена. Запускаем тесты."); // Попадет в std::cout
LOG_DEBUG("General", "Это отладочное сообщение для general.log"); // Попадет в general.log
LOG_WARNING("General", "Это предупреждение для general.log"); // Попадет в general.log
// Логирование из разных категорий LOG_ERROR("General", "Критическая ошибка, которая пойдет в critical.log"); // Попадет в critical.log
LOG_DEBUG("Network", "Инициализация сетевого стека..."); // Пойдет в network.log
LOG_INFO("Network", "Соединение с %1% установлено.", "192.168.1.1"); // Пойдет в network.log
LOG_INFO("Database", "Подключение к базе данных..."); // Пойдет в null_stream (никуда) LOG_INFO("Network", "New connection from 192.168.1.100"); // Попадет в network.log
LOG_WARNING("Database", "Медленный запрос обнаружен."); // Пойдет в null_stream (никуда) LOG_INFO("Network", "Packet loss detected: 5%%"); // Попадет в network.log
LOG_ERROR("Network", "Таймаут соединения с хостом %1%", "example.com"); // Пойдет в critical.log // --- 3. Тестирование изменения настроек на лету ---
LOG_ERROR("Database", "Не удалось выполнить транзакцию."); // Пойдет в critical.log LOG_INFO("Main", "Меняем уровень логирования для 'Network' на Warning");
DaemonLogger::get("Network").setMinSeverity(DaemonLogger::Severity::Warning);
LOG_INFO("Network", "Эта запись НЕ должна появиться в network.log");
LOG_WARNING("Network", "А эта запись-предупреждение ДОЛЖНА появиться.");
LOG_INFO("Scheduler", "Задача #%1% выполнена.", 123); // Правил нет, пойдет в std::cout // Возвращаем обратно для многопоточного теста
LOG_ERROR("Scheduler", "Не удалось запустить задачу."); // Правил нет, пойдет в std::cout DaemonLogger::get("Network").setMinSeverity(DaemonLogger::Severity::Debug);
// Установка минимального уровня для категории // --- 4. Тестирование многопоточной записи ---
DaemonLogger::get("Network").setMinSeverity(DaemonLogger::Severity::Warning); LOG_INFO("Main", "Запускаем 5 потоков для стресс-теста...");
LOG_INFO("Network", "Эта информация больше не будет записана."); // Не пройдет из-за min_severity
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(worker_thread_func, i + 1);
}
for (auto& t : threads) {
t.join();
}
LOG_INFO("Main", "Все потоки завершили работу. Проверьте файлы general.log, critical.log и network.log");
return 0; return 0;
} }

Loading…
Cancel
Save