sandbox
Loading...
Searching...
No Matches
logger.hpp
1#ifndef LIBSBX_UTILITY_LOGGER_HPP_
2#define LIBSBX_UTILITY_LOGGER_HPP_
3
4#include <iostream>
5#include <optional>
6#include <mutex>
7#include <deque>
8#include <filesystem>
9
10#include <fmt/format.h>
11
12#include <spdlog/logger.h>
13#include <spdlog/sinks/stdout_color_sinks.h>
14#include <spdlog/sinks/basic_file_sink.h>
15#include <spdlog/sinks/base_sink.h>
16
17#include <libsbx/utility/target.hpp>
18#include <libsbx/utility/string_literal.hpp>
19
20namespace sbx::utility {
21
22namespace detail {
23
24template<typename Mutex>
25class ring_buffer_sink final : public spdlog::sinks::base_sink<Mutex> {
26
27 using base = spdlog::sinks::base_sink<Mutex>;
28
29public:
30
31 struct log_line {
32 std::string text;
33 spdlog::level::level_enum level;
34 }; // struct log_line
35
36 explicit ring_buffer_sink(const std::size_t max_lines = 512)
37 : _max_lines{max_lines} {}
38
39 [[nodiscard]] auto lines() -> std::vector<log_line> {
40 auto lock = std::lock_guard<Mutex>{base::mutex_};
41
42 return {_lines.begin(), _lines.end()};
43 }
44
45 void clear() {
46 std::lock_guard<Mutex> lock(base::mutex_);
47
48 _lines.clear();
49 }
50
51protected:
52
53 void sink_it_(const spdlog::details::log_msg& msg) override {
54 spdlog::memory_buf_t formatted;
55 base::formatter_->format(msg, formatted);
56
57 auto lock = std::lock_guard<Mutex>{base::mutex_};
58 _lines.emplace_back(fmt::to_string(formatted), msg.level);
59
60 if (_lines.size() > _max_lines) {
61 _lines.pop_front();
62 }
63 }
64
65 void flush_() override {
66
67 }
68
69private:
70
71 std::size_t _max_lines;
72 std::deque<log_line> _lines;
73
74}; // class ring_buffer_sink
75
76using ring_buffer_sink_mt = ring_buffer_sink<std::mutex>;
77using ring_buffer_sink_st = ring_buffer_sink<spdlog::details::null_mutex>;
78
80
81 static auto create_logger() -> spdlog::logger {
82 auto sinks = std::vector<std::shared_ptr<spdlog::sinks::sink>>{};
83
84 sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>("./demo/logs/sbx.log", true));
85
86 if constexpr (utility::build_configuration_v == utility::build_configuration::debug) {
87 sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
88 }
89
90 sink = std::make_shared<ring_buffer_sink_st>();
91
92 sinks.push_back(sink);
93
94 auto logger = spdlog::logger{"logger", std::begin(sinks), std::end(sinks)};
95
96 logger.set_pattern("[%Y-%m-%d %H:%M:%S] [%^%l%$] %v");
97
98 if constexpr (build_configuration_v == build_configuration::debug) {
99 logger.set_level(spdlog::level::debug);
100 } else {
101 logger.set_level(spdlog::level::info);
102 }
103
104 return logger;
105 }
106
107 inline static auto sink = std::shared_ptr<ring_buffer_sink_st>{};
108 inline static auto logger = create_logger();
109
110}; // struct logger_instance
111
112inline auto instance() -> spdlog::logger& {
113 return logger_instance::logger;
114}
115
116inline auto sink() -> std::shared_ptr<ring_buffer_sink_st>& {
117 return logger_instance::sink;
118}
119
120} // namespace detail
121
122template<string_literal Tag>
123class logger {
124
125public:
126
127 template<typename... Args>
128 using format_string_type = spdlog::format_string_t<Args...>;
129
130 logger() = delete;
131
132 ~logger() = default;
133
134 template<typename... Args>
135 static auto trace(format_string_type<Args...> format, Args&&... args) -> void {
136 // [NOTE] KAJ 2023-03-20 : This should make trace and debug messages be no-ops in release builds.
137 if constexpr (utility::build_configuration_v == utility::build_configuration::debug) {
138 detail::instance().trace("[{}] : {}", Tag, fmt::format(format, std::forward<Args>(args)...));
139 }
140 }
141
142 template<typename Type>
143 static auto trace(const Type& value) -> void {
144 if constexpr (utility::build_configuration_v == utility::build_configuration::debug) {
145 detail::instance().trace("[{}] : {}", Tag, value);
146 }
147 }
148
149 template<typename... Args>
150 static auto debug(format_string_type<Args...> format, Args&&... args) -> void {
151 if constexpr (utility::build_configuration_v == utility::build_configuration::debug) {
152 detail::instance().debug("[{}] : {}", Tag, fmt::format(format, std::forward<Args>(args)...));
153 }
154 }
155
156 template<typename Type>
157 static auto debug(const Type& value) -> void {
158 if constexpr (utility::build_configuration_v == utility::build_configuration::debug) {
159 detail::instance().debug("[{}] : {}", Tag, value);
160 }
161 }
162
163 template<typename... Args>
164 static auto info(format_string_type<Args...> format, Args&&... args) -> void {
165 detail::instance().info("[{}] : {}", Tag, fmt::format(format, std::forward<Args>(args)...));
166 }
167
168 template<typename Type>
169 static auto info(const Type& value) -> void {
170 detail::instance().info("[{}] : {}", Tag, value);
171 }
172
173 template<typename... Args>
174 static auto warn(format_string_type<Args...> format, Args&&... args) -> void {
175 detail::instance().warn("[{}] : {}", Tag, fmt::format(format, std::forward<Args>(args)...));
176 }
177
178 template<typename Type>
179 static auto warn(const Type& value) -> void {
180 detail::instance().warn("[{}] : {}", Tag, value);
181 }
182
183 template<typename... Args>
184 static auto error(format_string_type<Args...> format, Args&&... args) -> void {
185 detail::instance().error("[{}] : {}", Tag, fmt::format(format, std::forward<Args>(args)...));
186 }
187
188 template<typename Type>
189 static auto error(const Type& value) -> void {
190 detail::instance().error("[{}] : {}", Tag, value);
191 }
192
193 template<typename... Args>
194 static auto critical(format_string_type<Args...> format, Args&&... args) -> void {
195 detail::instance().critical("[{}] : {}", Tag, fmt::format(format, std::forward<Args>(args)...));
196 }
197
198 template<typename Type>
199 static auto critical(const Type& value) -> void {
200 detail::instance().critical("[{}] : {}", Tag, value);
201 }
202
203}; // class logger
204
205} // namespace sbx::utility
206
207// [NOTE] KAJ 2024-01-19 : Enable formatting to underlying type for all enums
208template<typename Type>
209requires (std::is_enum_v<Type>)
210struct fmt::formatter<Type> : public fmt::formatter<std::underlying_type_t<Type>> {
211
212 using base_type = fmt::formatter<std::underlying_type_t<Type>>;
213
214 template<typename FormatContext>
215 auto format(const Type& value, FormatContext& context) -> decltype(auto) {
216 return base_type::format(static_cast<std::underlying_type_t<Type>>(value), context);
217 }
218
219}; // struct fmt::formatter
220
221// [NOTE] KAJ 2025-05-27 : Enable formatting for optionals
222template<typename Type>
223struct fmt::formatter<std::optional<Type>> : public fmt::formatter<Type> {
224
225 using base_type = fmt::formatter<Type>;
226
227 template<typename FormatContext>
228 auto format(const std::optional<Type>& value, FormatContext& context) -> decltype(auto) {
229 if (value) {
230 return base_type::format(*value, context);
231 }
232
233 return fmt::format_to(context.out(), "[empty optional]");
234 }
235
236}; // struct fmt::formatter<Type>
237
238template<>
239struct fmt::formatter<std::filesystem::path> : public fmt::formatter<std::filesystem::path::string_type> {
240
241 using base_type = fmt::formatter<std::filesystem::path::string_type>;
242
243 template<typename FormatContext>
244 auto format(const std::filesystem::path& value, FormatContext& context) -> decltype(auto) {
245 return fmt::format_to(context.out(), "{}", value.string());
246 }
247
248}; // struct fmt::formatter<Type>
249
250
251#endif // LIBSBX_UTILITY_LOGGER_HPP_
Definition: logger.hpp:123
Definition: logger.hpp:210
Definition: logger.hpp:79