You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

328 lines
14 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <ostream>
#include <sstream>
#include <chrono>
#include <iomanip>
#include <array>
#include <vector>
#include <optional>
#include <functional>
#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 {
public:
// Re-expose Severity enum inside the class for clarity and namespacing,
// while the actual definition is outside.
using Severity = ::Severity;
private:
// Обертка для потокобезопасной записи в один ostream
struct SynchronizedStream {
std::ostream* stream;
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;
};
// Определяем типы для скомпилированного формата
using LogPieceFormatter = std::function<void(std::ostream&, const LogContext&)>;
using CompiledFormat = std::vector<LogPieceFormatter>;
public:
// --- Конфигурация маршрутизации потоков ---
static void setGlobalOutputStream(std::ostream& stream) {
stream_resolver_.setGlobal(get_or_create_synced_stream(stream));
reconfigureAllLoggers();
}
static void setCategoryOutputStream(const std::string& category, std::ostream& stream) {
stream_resolver_.setForCategory(category, get_or_create_synced_stream(stream));
reconfigureLogger(category);
}
static void setSeverityOutputStream(const std::string& category, Severity severity, std::ostream& stream) {
stream_resolver_.setForSeverity(category, severity, get_or_create_synced_stream(stream));
reconfigureLogger(category);
}
// --- Конфигурация форматов ---
static void setGlobalFormat(const std::string& format) {
format_resolver_.setGlobal(compileFormat(format));
reconfigureAllLoggers();
}
static void setCategoryFormat(const std::string& category, const std::string& format) {
format_resolver_.setForCategory(category, compileFormat(format));
reconfigureLogger(category);
}
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_);
if (auto it = registry_.find(category); it != registry_.end()) {
return *(it->second);
}
auto logger = std::unique_ptr<DaemonLogger>(new DaemonLogger(category));
logger->resolveProperties();
auto [inserted_it, _] = registry_.try_emplace(category, std::move(logger));
return *(inserted_it->second);
}
void setMinSeverity(Severity level) { min_severity_ = level; }
void suppress() { suppressed_ = true; }
void unsuppress() { suppressed_ = false; }
// --- Основной метод логирования ---
template<class... Args>
void log(Severity severity, const std::string& source_location, const std::string& format, Args... args) const {
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];
if (!target_stream || !target_stream->stream || !compiled_format || compiled_format->empty()) return;
// 1. Форматируем только пользовательскую часть сообщения
std::string user_message;
try {
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};
// 3. Быстро собираем итоговое сообщение, исполняя скомпилированный план
std::ostringstream final_message_stream;
for (const auto& formatter : *compiled_format) {
formatter(final_message_stream, context);
}
// 4. Потокобезопасно выводим результат
{
std::lock_guard<std::mutex> lock(target_stream->mtx);
*target_stream->stream << final_message_stream.str() << std::endl;
}
}
private:
explicit DaemonLogger(std::string category) : category_(std::move(category)) {}
void resolveProperties() {
for (size_t i = 0; i < static_cast<size_t>(Severity::_Count); ++i) {
Severity s = static_cast<Severity>(i);
resolved_streams_[i] = stream_resolver_.resolve(category_, s);
resolved_compiled_formats_[i] = format_resolver_.resolve(category_, s);
}
}
// "Компилятор" строки формата в набор функторов
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;
std::lock_guard<std::mutex> lock(compile_mutex);
if (auto it = cache.find(format); it != cache.end()) {
return it->second;
}
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) {
// Добавляем литерал перед плейсхолдером
if (brace_pos > last_pos) {
compiled->emplace_back(
[literal = format.substr(last_pos, brace_pos - last_pos)](auto& os, const auto&) {
os << literal;
});
}
size_t end_brace_pos = format.find('}', brace_pos);
if (end_brace_pos == std::string::npos) {
// Если нет закрывающей скобки, то оставшаяся часть строки, включая '{',
// будет обработана как литерал в конце функции.
last_pos = brace_pos;
break;
}
std::string placeholder = format.substr(brace_pos + 1, end_brace_pos - brace_pos - 1);
if (placeholder == "date") {
compiled->emplace_back([](auto& os, const auto& ctx) {
if (!ctx.time_cache) ctx.time_cache = currentTime();
os << *ctx.time_cache;
});
} else if (placeholder == "cat") {
compiled->emplace_back([](auto& os, const auto& ctx) { os << ctx.category; });
} else if (placeholder == "severity") {
compiled->emplace_back([](auto& os, const auto& ctx) { os << severityToString(ctx.severity); });
} else if (placeholder == "loc") {
compiled->emplace_back([](auto& os, const auto& ctx) { os << ctx.location; });
} else if (placeholder == "umsg") {
compiled->emplace_back([](auto& os, const auto& ctx) { os << ctx.user_message; });
} else {
// Неизвестный плейсхолдер - выводим как есть, включая скобки
compiled->emplace_back(
[literal = format.substr(brace_pos, end_brace_pos - brace_pos + 1)](auto& os, const auto&) {
os << literal;
});
}
last_pos = end_brace_pos + 1;
}
// Добавляем оставшийся хвост строки
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, ...) \
do { \
std::string loc = std::string(__func__) + ":" + std::to_string(__LINE__); \
DaemonLogger::get(category).log(severity, loc, format, ##__VA_ARGS__); \
} while (0)
#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_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__)