From 973aec43ea54bbf95b64fbcb636403401d1ca60e Mon Sep 17 00:00:00 2001 From: Rutger Broekhoff Date: Fri, 28 Aug 2026 18:03:05 +0200 Subject: Import from e4b104792206ee7ea64bf39c6b7d2c0c230f9d14 --- server/src/log.cppm | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 server/src/log.cppm (limited to 'server/src/log.cppm') diff --git a/server/src/log.cppm b/server/src/log.cppm new file mode 100644 index 0000000..d7e2bff --- /dev/null +++ b/server/src/log.cppm @@ -0,0 +1,148 @@ +module; + +// Seems like ADL for std::quoted is broken with +// import std; +// Might be because the _Quoted_string object is defined in +// std::__detail, which is not exported by the module. +#include + +export module routemon:log; + +import std; + +namespace routemon::log { + + export enum class level : std::uint8_t { + debug, + info, + warn, + error, + }; + + namespace { + + auto operator<<(std::ostream& os, level lvl) -> std::ostream& { + switch (lvl) { + case level::debug: os << "dbg"; break; + case level::info: os << "inf"; break; + case level::warn: os << "wrn"; break; + case level::error: os << "err"; break; + } + return os; + } + + } // namespace (unique) + + export class sink { + std::atomic lvl_; + std::ostream& os_ = std::cout; + + struct tmp_message { + level lvl; + std::string_view component; + std::string_view txt; + std::map const& attrs; + }; + + auto write(tmp_message msg) -> void { + auto sos = std::osyncstream{os_}; + sos << "[" << msg.lvl; + if (!msg.component.empty()) + sos << " " << msg.component; + sos << "] " << msg.txt; + for (auto const& [k, v] : msg.attrs) { + sos << " " << k << "=" << std::quoted(v); + } + sos << '\n'; + } + + explicit sink(level lvl) + : lvl_{lvl} + {} + + friend auto make_sink(level lvl) -> std::shared_ptr; + friend class logger; + + public: + [[nodiscard]] auto level() const -> enum level { + return lvl_; + } + + auto set_level(enum level lvl) -> void { + lvl_ = lvl; + } + }; + + export auto make_sink(level lvl) -> std::shared_ptr { + return std::shared_ptr{new sink{lvl}}; + } + + export class logger { + std::shared_ptr sink_; + std::string component_; + std::map attrs_; + + template + auto log_at(std::string_view fmt, std::format_args args) -> logger& { + if (sink_->level() <= lvl) + sink_->write(sink::tmp_message{ + .lvl = lvl, + .component = component_, + .txt = std::vformat(fmt, args), + .attrs = attrs_, + }); + return *this; + } + + public: + explicit logger(std::shared_ptr const& sink) + : sink_{sink} + { + if (!sink) { + throw std::invalid_argument{"logger sink may not be null"}; + } + } + + [[nodiscard]] auto sub(std::string_view component) const -> logger { + auto l = *this; + if (l.component_.empty()) { + l.component_ = component; + } else { + l.component_ += "."; + l.component_ += component; + } + return l; + } + + [[nodiscard]] auto with(std::string const& k, std::string&& v) const -> logger { + auto l = *this; + l.attrs_[k] = std::move(v); + return l; + } + + [[nodiscard]] auto with(std::string const& k, std::string_view v) const -> logger { + return with(k, std::string{v}); + } + + template + auto debug(std::format_string fmt, Args&&... args) -> logger& { + return log_at(fmt.get(), std::make_format_args(args...)); + } + + template + auto info(std::format_string fmt, Args&&... args) -> logger& { + return log_at(fmt.get(), std::make_format_args(args...)); + } + + template + auto warn(std::format_string fmt, Args&&... args) -> logger& { + return log_at(fmt.get(), std::make_format_args(args...)); + } + + template + auto error(std::format_string fmt, Args&&... args) -> logger& { + return log_at(fmt.get(), std::make_format_args(args...)); + } + }; + +} // namespace routemon::log -- cgit v1.3