diff options
| author | Rutger Broekhoff | 2026-08-28 18:03:05 +0200 |
|---|---|---|
| committer | Rutger Broekhoff | 2026-08-28 18:03:05 +0200 |
| commit | 973aec43ea54bbf95b64fbcb636403401d1ca60e (patch) | |
| tree | 41b7911c420766a9b463245b9296f44c5bf35258 /server/src/log.cppm | |
| download | routemon-973aec43ea54bbf95b64fbcb636403401d1ca60e.tar.gz routemon-973aec43ea54bbf95b64fbcb636403401d1ca60e.zip | |
Import from e4b104792206ee7ea64bf39c6b7d2c0c230f9d14
Diffstat (limited to 'server/src/log.cppm')
| -rw-r--r-- | server/src/log.cppm | 148 |
1 files changed, 148 insertions, 0 deletions
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 @@ | |||
| 1 | module; | ||
| 2 | |||
| 3 | // Seems like ADL for std::quoted is broken with | ||
| 4 | // import std; | ||
| 5 | // Might be because the _Quoted_string object is defined in | ||
| 6 | // std::__detail, which is not exported by the module. | ||
| 7 | #include <iomanip> | ||
| 8 | |||
| 9 | export module routemon:log; | ||
| 10 | |||
| 11 | import std; | ||
| 12 | |||
| 13 | namespace routemon::log { | ||
| 14 | |||
| 15 | export enum class level : std::uint8_t { | ||
| 16 | debug, | ||
| 17 | info, | ||
| 18 | warn, | ||
| 19 | error, | ||
| 20 | }; | ||
| 21 | |||
| 22 | namespace { | ||
| 23 | |||
| 24 | auto operator<<(std::ostream& os, level lvl) -> std::ostream& { | ||
| 25 | switch (lvl) { | ||
| 26 | case level::debug: os << "dbg"; break; | ||
| 27 | case level::info: os << "inf"; break; | ||
| 28 | case level::warn: os << "wrn"; break; | ||
| 29 | case level::error: os << "err"; break; | ||
| 30 | } | ||
| 31 | return os; | ||
| 32 | } | ||
| 33 | |||
| 34 | } // namespace (unique) | ||
| 35 | |||
| 36 | export class sink { | ||
| 37 | std::atomic<enum level> lvl_; | ||
| 38 | std::ostream& os_ = std::cout; | ||
| 39 | |||
| 40 | struct tmp_message { | ||
| 41 | level lvl; | ||
| 42 | std::string_view component; | ||
| 43 | std::string_view txt; | ||
| 44 | std::map<std::string, std::string> const& attrs; | ||
| 45 | }; | ||
| 46 | |||
| 47 | auto write(tmp_message msg) -> void { | ||
| 48 | auto sos = std::osyncstream{os_}; | ||
| 49 | sos << "[" << msg.lvl; | ||
| 50 | if (!msg.component.empty()) | ||
| 51 | sos << " " << msg.component; | ||
| 52 | sos << "] " << msg.txt; | ||
| 53 | for (auto const& [k, v] : msg.attrs) { | ||
| 54 | sos << " " << k << "=" << std::quoted(v); | ||
| 55 | } | ||
| 56 | sos << '\n'; | ||
| 57 | } | ||
| 58 | |||
| 59 | explicit sink(level lvl) | ||
| 60 | : lvl_{lvl} | ||
| 61 | {} | ||
| 62 | |||
| 63 | friend auto make_sink(level lvl) -> std::shared_ptr<sink>; | ||
| 64 | friend class logger; | ||
| 65 | |||
| 66 | public: | ||
| 67 | [[nodiscard]] auto level() const -> enum level { | ||
| 68 | return lvl_; | ||
| 69 | } | ||
| 70 | |||
| 71 | auto set_level(enum level lvl) -> void { | ||
| 72 | lvl_ = lvl; | ||
| 73 | } | ||
| 74 | }; | ||
| 75 | |||
| 76 | export auto make_sink(level lvl) -> std::shared_ptr<sink> { | ||
| 77 | return std::shared_ptr<sink>{new sink{lvl}}; | ||
| 78 | } | ||
| 79 | |||
| 80 | export class logger { | ||
| 81 | std::shared_ptr<sink> sink_; | ||
| 82 | std::string component_; | ||
| 83 | std::map<std::string, std::string> attrs_; | ||
| 84 | |||
| 85 | template<log::level lvl> | ||
| 86 | auto log_at(std::string_view fmt, std::format_args args) -> logger& { | ||
| 87 | if (sink_->level() <= lvl) | ||
| 88 | sink_->write(sink::tmp_message{ | ||
| 89 | .lvl = lvl, | ||
| 90 | .component = component_, | ||
| 91 | .txt = std::vformat(fmt, args), | ||
| 92 | .attrs = attrs_, | ||
| 93 | }); | ||
| 94 | return *this; | ||
| 95 | } | ||
| 96 | |||
| 97 | public: | ||
| 98 | explicit logger(std::shared_ptr<sink> const& sink) | ||
| 99 | : sink_{sink} | ||
| 100 | { | ||
| 101 | if (!sink) { | ||
| 102 | throw std::invalid_argument{"logger sink may not be null"}; | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | [[nodiscard]] auto sub(std::string_view component) const -> logger { | ||
| 107 | auto l = *this; | ||
| 108 | if (l.component_.empty()) { | ||
| 109 | l.component_ = component; | ||
| 110 | } else { | ||
| 111 | l.component_ += "."; | ||
| 112 | l.component_ += component; | ||
| 113 | } | ||
| 114 | return l; | ||
| 115 | } | ||
| 116 | |||
| 117 | [[nodiscard]] auto with(std::string const& k, std::string&& v) const -> logger { | ||
| 118 | auto l = *this; | ||
| 119 | l.attrs_[k] = std::move(v); | ||
| 120 | return l; | ||
| 121 | } | ||
| 122 | |||
| 123 | [[nodiscard]] auto with(std::string const& k, std::string_view v) const -> logger { | ||
| 124 | return with(k, std::string{v}); | ||
| 125 | } | ||
| 126 | |||
| 127 | template<class... Args> | ||
| 128 | auto debug(std::format_string<Args...> fmt, Args&&... args) -> logger& { | ||
| 129 | return log_at<level::debug>(fmt.get(), std::make_format_args(args...)); | ||
| 130 | } | ||
| 131 | |||
| 132 | template<class... Args> | ||
| 133 | auto info(std::format_string<Args...> fmt, Args&&... args) -> logger& { | ||
| 134 | return log_at<level::info>(fmt.get(), std::make_format_args(args...)); | ||
| 135 | } | ||
| 136 | |||
| 137 | template<class... Args> | ||
| 138 | auto warn(std::format_string<Args...> fmt, Args&&... args) -> logger& { | ||
| 139 | return log_at<level::warn>(fmt.get(), std::make_format_args(args...)); | ||
| 140 | } | ||
| 141 | |||
| 142 | template<class... Args> | ||
| 143 | auto error(std::format_string<Args...> fmt, Args&&... args) -> logger& { | ||
| 144 | return log_at<level::error>(fmt.get(), std::make_format_args(args...)); | ||
| 145 | } | ||
| 146 | }; | ||
| 147 | |||
| 148 | } // namespace routemon::log | ||