|
|
|
@ -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__)
|
|
|
|
|