#include #include #include #include #include #include #include namespace { auto is_valid_log_level(modulus::log_level level) noexcept -> std::bool_t { return static_cast(level) >= static_cast(modulus::log_level::TRACE) && static_cast(level) <= static_cast(modulus::log_level::OFF); } auto should_log(modulus::log_level record_level, modulus::log_level configured_level) noexcept -> std::bool_t { assert(is_valid_log_level(record_level)); assert(is_valid_log_level(configured_level)); return configured_level != modulus::log_level::OFF && static_cast(record_level) >= static_cast(configured_level); } auto format_log_record(std::ostream &stream, const modulus::log_record &record) -> void { assert(is_valid_log_level(record.level)); assert(record.location.file_name() != nullptr); stream << '[' << modulus::to_string(record.level) << "] " << record.message << " (" << record.location.file_name() << ':' << record.location.line() << ")\n"; } auto stderr_sink(const modulus::log_record &record) -> void { assert(is_valid_log_level(record.level)); assert(record.location.file_name() != nullptr); static auto mutex = std::mutex{}; auto lock = std::lock_guard{mutex}; format_log_record(std::cerr, record); } } auto modulus::to_string(modulus::log_level level) noexcept -> std::string_view { assert(is_valid_log_level(level)); switch (level) { case modulus::log_level::TRACE: return "trace"; case modulus::log_level::DEBUG: return "debug"; case modulus::log_level::INFO: return "info"; case modulus::log_level::WARN: return "warn"; case modulus::log_level::ERROR: return "error"; case modulus::log_level::CRITICAL: return "critical"; case modulus::log_level::OFF: return "off"; } return "unknown"; } modulus::logger::logger() : sink_(stderr_sink) {} auto modulus::logger::set_level(modulus::log_level level) -> void { assert(is_valid_log_level(level)); auto lock = std::lock_guard{mutex_}; level_ = level; } auto modulus::logger::level() const -> modulus::log_level { auto lock = std::lock_guard{mutex_}; assert(is_valid_log_level(level_)); return level_; } auto modulus::logger::enabled(modulus::log_level level) const -> std::bool_t { assert(is_valid_log_level(level)); auto lock = std::lock_guard{mutex_}; return should_log(level, level_) && static_cast(sink_); } auto modulus::logger::set_sink(std::function sink) -> void { auto lock = std::lock_guard{mutex_}; sink_ = std::move(sink); } auto modulus::logger::write(modulus::log_record record) const -> void { assert(is_valid_log_level(record.level)); assert(record.location.file_name() != nullptr); assert(record.location.function_name() != nullptr); auto sink = std::function{}; { auto lock = std::lock_guard{mutex_}; if (!should_log(record.level, level_) || !sink_) { return; } sink = sink_; } assert(static_cast(sink)); auto sink_lock = std::lock_guard(sink_mutex_); sink(record); } auto modulus::logger::log(modulus::log_level level, std::string message, modulus::source_location location) const -> void { assert(is_valid_log_level(level)); assert(location.file_name() != nullptr); assert(location.function_name() != nullptr); write(modulus::log_record{ .level = level, .message = std::move(message), .location = location, }); } auto modulus::default_logger() -> modulus::logger & { static auto instance = modulus::logger{}; return instance; } auto modulus::set_default_log_level(modulus::log_level level) -> void { assert(is_valid_log_level(level)); modulus::default_logger().set_level(level); } auto modulus::is_log_enabled(modulus::log_level level) -> std::bool_t { assert(is_valid_log_level(level)); return modulus::default_logger().enabled(level); } auto modulus::log(modulus::log_level level, std::string message, modulus::source_location location) -> void { assert(is_valid_log_level(level)); assert(location.file_name() != nullptr); assert(location.function_name() != nullptr); modulus::default_logger().log(level, std::move(message), location); } auto modulus::log_trace(std::string message, modulus::source_location location) -> void { modulus::log(modulus::log_level::TRACE, std::move(message), location); } auto modulus::log_debug(std::string message, modulus::source_location location) -> void { modulus::log(modulus::log_level::DEBUG, std::move(message), location); } auto modulus::log_info(std::string message, modulus::source_location location) -> void { modulus::log(modulus::log_level::INFO, std::move(message), location); } auto modulus::log_warn(std::string message, modulus::source_location location) -> void { modulus::log(modulus::log_level::WARN, std::move(message), location); } auto modulus::log_error(std::string message, modulus::source_location location) -> void { modulus::log(modulus::log_level::ERROR, std::move(message), location); } auto modulus::log_critical(std::string message, modulus::source_location location) -> void { modulus::log(modulus::log_level::CRITICAL, std::move(message), location); }