microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
logger.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
9#include "../reloco.hpp"
10#include "sink.hpp"
11#include "sync.hpp"
12#include <cstddef>
13#include <string_view>
14#include <utility>
15
16namespace microfmt::log {
17
42namespace detail {
43
46 [[nodiscard]] constexpr const log_msg &operator()(const log_msg &msg) const noexcept RELOCO_LIFETIMEBOUND {
47 return msg;
48 }
49};
50
51} // namespace detail
52
53template <size_t MaxSinks = 4, size_t MsgBufferCapacity = 256, typename Mutex = detail::null_mutex,
54 typename Sink = log_sink, typename Decorator = detail::identity_decorator>
55// NOLINTNEXTLINE(reloco-pointer-annotation): a logger is an owner of its sinks; the name is documented to be static
57 // Tiny RAII lock guard so this header doesn't need to pull in <mutex>
58 // merely for std::lock_guard; works with any BasicLockable Mutex,
59 // including std::mutex if the caller already includes <mutex>.
60 struct RELOCO_POINTER lock_guard {
61 explicit lock_guard(Mutex &m) noexcept : m_(m) { m_.lock(); }
62 ~lock_guard() noexcept { m_.unlock(); }
63 lock_guard(const lock_guard &) = delete;
64 lock_guard &operator=(const lock_guard &) = delete;
65
66 Mutex &m_;
67 };
68
69public:
70 explicit constexpr basic_logger(microfmt::string_view name) noexcept : name_(name) {}
71
72 template <typename... Sinks>
73 requires(sizeof...(Sinks) <= MaxSinks)
74 explicit constexpr basic_logger(microfmt::string_view name, Sinks... sinks) noexcept
75 : name_(name), sinks_{sinks...}, sink_count_(sizeof...(Sinks)) {}
76
77 bool add_sink(Sink s) noexcept {
78 lock_guard guard(mutex_);
79 if (sink_count_ >= MaxSinks)
80 return false;
81 sinks_[sink_count_++] = s;
82 return true;
83 }
84
85 void set_level(level l) noexcept { level_ = l; }
86 [[nodiscard]] constexpr level get_level() const noexcept { return level_; }
87 [[nodiscard]] constexpr microfmt::string_view name() const & noexcept RELOCO_LIFETIMEBOUND { return name_; }
88
89 [[nodiscard]] constexpr microfmt::string_view name() const && = delete;
90
91 [[nodiscard]] bool should_log(level l) const noexcept { return l >= level_; }
92
101 void log(const log_msg &msg) const noexcept {
102 if (!should_log(msg.lvl) || sink_count_ == 0) {
103 return;
104 }
105 dispatch(msg);
106 }
107
108 // NOLINTBEGIN(reloco-banned-std-type): std::source_location is the portable C++20 interop type
109 // (reloco::call_location_ref is freestanding-only)
110 template <typename... Args> void log(level lvl, microfmt::string_view fmt_str, const Args &...args) const noexcept {
111 log_impl(std::source_location::current(), lvl, fmt_str, args...);
112 }
113
114 template <typename StrProvider, typename... Args>
115 void log(level lvl, compile_string_holder<StrProvider> fmt_str, const Args &...args) const noexcept {
116 log_impl(std::source_location::current(), lvl, fmt_str, args...);
117 }
118 // NOLINTEND(reloco-banned-std-type)
119
120 template <typename... Args> void trace(microfmt::string_view fmt, const Args &...args) const noexcept {
121 log(level::trace, fmt, args...);
122 }
123
124 template <typename StrProvider, typename... Args>
125 void trace(compile_string_holder<StrProvider> fmt, const Args &...args) const noexcept {
126 log(level::trace, fmt, args...);
127 }
128
129 template <typename... Args> void debug(microfmt::string_view fmt, const Args &...args) const noexcept {
130 log(level::debug, fmt, args...);
131 }
132
133 template <typename StrProvider, typename... Args>
134 void debug(compile_string_holder<StrProvider> fmt, const Args &...args) const noexcept {
135 log(level::debug, fmt, args...);
136 }
137
138 template <typename... Args> void info(microfmt::string_view fmt, const Args &...args) const noexcept {
139 log(level::info, fmt, args...);
140 }
141
142 template <typename StrProvider, typename... Args>
143 void info(compile_string_holder<StrProvider> fmt, const Args &...args) const noexcept {
144 log(level::info, fmt, args...);
145 }
146
147 template <typename... Args> void warn(microfmt::string_view fmt, const Args &...args) const noexcept {
148 log(level::warn, fmt, args...);
149 }
150
151 template <typename StrProvider, typename... Args>
152 void warn(compile_string_holder<StrProvider> fmt, const Args &...args) const noexcept {
153 log(level::warn, fmt, args...);
154 }
155
156 template <typename... Args> void error(microfmt::string_view fmt, const Args &...args) const noexcept {
157 log(level::err, fmt, args...);
158 }
159
160 template <typename StrProvider, typename... Args>
161 void error(compile_string_holder<StrProvider> fmt, const Args &...args) const noexcept {
162 log(level::err, fmt, args...);
163 }
164
165 template <typename... Args> void critical(microfmt::string_view fmt, const Args &...args) const noexcept {
166 log(level::critical, fmt, args...);
167 }
168
169 template <typename StrProvider, typename... Args>
170 void critical(compile_string_holder<StrProvider> fmt, const Args &...args) const noexcept {
171 log(level::critical, fmt, args...);
172 }
173
174 void flush() const noexcept {
175 lock_guard guard(mutex_);
176 for (size_t i = 0; i < sink_count_; ++i) {
177 sinks_[i].flush();
178 }
179 }
180
181 // NOLINTBEGIN(reloco-banned-std-type): std::source_location is the portable C++20 interop type
182 // (reloco::call_location_ref is freestanding-only)
183 template <typename... Args>
184 void log_loc(std::source_location loc, level lvl, microfmt::string_view fmt_str, const Args &...args) const noexcept {
185 log_impl(loc, lvl, fmt_str, args...);
186 }
187
188 template <typename StrProvider, typename... Args>
189 void log_loc(std::source_location loc, level lvl, compile_string_holder<StrProvider> fmt_str,
190 const Args &...args) const noexcept {
191 log_impl(loc, lvl, fmt_str, args...);
192 }
193
194private:
195 template <typename Format, typename... Args>
196 void log_impl(std::source_location loc, level lvl, Format fmt_str, const Args &...args) const noexcept {
197 if (!should_log(lvl) || sink_count_ == 0) {
198 return;
199 }
200
201 // Format payload into internal line buffer
203 auto sink_stream = buf.as_sink();
204 format_to(sink_stream, fmt_str, args...);
205
206 log_msg msg{.logger_name = name_, .lvl = lvl, .time = microfmt::instant::now(), .payload = buf.view(), .loc = loc};
207
208 dispatch(msg);
209 }
210 // NOLINTEND(reloco-banned-std-type)
211
212protected:
214 [[nodiscard]] Decorator &decorator() & noexcept RELOCO_LIFETIMEBOUND { return decorator_; }
215 [[nodiscard]] const Decorator &decorator() const & noexcept RELOCO_LIFETIMEBOUND { return decorator_; }
216
217 [[nodiscard]] const Decorator &decorator() const && = delete;
218
224 template <typename Msg> void dispatch(const Msg &msg) const noexcept {
225 decltype(auto) decorated = decorator_(msg);
226 lock_guard guard(mutex_);
227 for (size_t i = 0; i < sink_count_; ++i) {
228 sinks_[i].log(decorated);
229 }
230 }
231
232private:
233 microfmt::string_view name_{};
234 level level_{level::info};
235 // NOLINTNEXTLINE(reloco-mutable-member): logging is logically const; sinks hold their own synchronisation
236 mutable microfmt::array<Sink, MaxSinks> sinks_{};
237 size_t sink_count_{0};
238 mutable Mutex mutex_{};
239 Decorator decorator_{};
240};
241
243
244// The process-default logger API is a nullable `logger *` by contract (nullptr = unset); callers null-check it.
245RELOCO_BEGIN_UNSAFE_CODE
246namespace detail {
247
249 static logger *instance = nullptr;
250 return instance;
251}
252
253#if defined(MICROFMT_ENABLE_DEFAULT_LOGGER) && !defined(MICROFMT_DEFAULT_LOG_SINK_BACKEND_CUSTOM)
254inline stdout_color_sink<256> &default_console_sink() noexcept {
255 static stdout_color_sink<256> sink_instance;
256 return sink_instance;
257}
258
260inline logger &built_in_default_logger() noexcept {
261 static logger instance("app", default_console_sink().as_sink());
262 return instance;
263}
264#endif
265
266} // namespace detail
267
268} // namespace microfmt::log
269
270#if defined(MICROFMT_ENABLE_DEFAULT_LOGGER) && defined(MICROFMT_DEFAULT_LOG_SINK_BACKEND_CUSTOM)
271// MICROFMT_DEFAULT_LOG_SINK_BACKEND_CUSTOM opts out of the stdout backend
272// above entirely, in favor of a platform-appropriate replacement (an OS's
273// native logging facility, an RTOS/bare-metal console, ...) supplying the
274// same `microfmt::log::detail::built_in_default_logger() noexcept ->
275// logger &` entry point via a fixed include path, exactly like reloco's
276// `detail/porting/mutex.hpp` does for `RELOCO_MUTEX_BACKEND_CUSTOM` (see
277// `reloco/mutex.hpp`). Included unconditionally at this exact point --
278// outside every namespace (the enclosing `namespace microfmt::log` is
279// closed just above, and reopened just below), exactly like reloco's own
280// fixed-path includes -- so nothing about where else this header is
281// included from matters, and the included file is free to open/close
282// `namespace microfmt::log`/`detail` itself, completely self-contained
283// (never nested inside an already-open same-named namespace, which would
284// otherwise silently declare a spurious `microfmt::log::microfmt::log`).
285// Supplying the whole function (not merely a sink helper) gives a port
286// full control over the resulting `logger` too -- its name, sink
287// count/capacity, locking policy -- exactly as it would have if it wrote
288// `built_in_default_logger()` itself. This file does not ship in this
289// repository (only `detail/porting/default_log_sink.template.hpp`, an
290// unused documentation-only scaffold, does), so you supply it yourself,
291// most conveniently through the `JPLCZ_MICROFMT_PORTING_HEADERS` CMake
292// variable (see `CMakeLists.txt`), which copies it into that exact path
293// and bakes `MICROFMT_DEFAULT_LOG_SINK_BACKEND_CUSTOM` into a generated
294// `detail/porting/microfmt_generated_config.hpp` that `microfmt_config.hpp` picks
295// up automatically for every consumer of the plain `include/` tree, not
296// merely a consumer linking the `jplcz_microfmt` CMake target -- or by
297// placing it there manually and defining the macro yourself if not using
298// CMake. See `detail/porting/README.md` and `docs/porting.md`.
299#include "../detail/porting/default_log_sink.hpp"
300#endif
301
302namespace microfmt::log {
303
305[[nodiscard]] inline logger *default_logger() noexcept {
307 if (configured != nullptr) {
308 return configured;
309 }
310
311#if defined(MICROFMT_ENABLE_DEFAULT_LOGGER)
312 return &detail::built_in_default_logger();
313#else
314 return nullptr;
315#endif
316}
317
319inline void set_default_logger(logger *instance) noexcept { detail::configured_default_logger() = instance; }
320
321template <typename... Args> inline void trace(microfmt::string_view fmt, const Args &...args) noexcept {
322 if (logger *instance = default_logger(); instance != nullptr) {
323 instance->trace(fmt, args...);
324 }
325}
326
327template <typename StrProvider, typename... Args>
328inline void trace(compile_string_holder<StrProvider> fmt, const Args &...args) noexcept {
329 if (logger *instance = default_logger(); instance != nullptr) {
330 instance->trace(fmt, args...);
331 }
332}
333
334template <typename... Args> inline void debug(microfmt::string_view fmt, const Args &...args) noexcept {
335 if (logger *instance = default_logger(); instance != nullptr) {
336 instance->debug(fmt, args...);
337 }
338}
339
340template <typename StrProvider, typename... Args>
341inline void debug(compile_string_holder<StrProvider> fmt, const Args &...args) noexcept {
342 if (logger *instance = default_logger(); instance != nullptr) {
343 instance->debug(fmt, args...);
344 }
345}
346
347template <typename... Args> inline void info(microfmt::string_view fmt, const Args &...args) noexcept {
348 if (logger *instance = default_logger(); instance != nullptr) {
349 instance->info(fmt, args...);
350 }
351}
352
353template <typename StrProvider, typename... Args>
354inline void info(compile_string_holder<StrProvider> fmt, const Args &...args) noexcept {
355 if (logger *instance = default_logger(); instance != nullptr) {
356 instance->info(fmt, args...);
357 }
358}
359
360template <typename... Args> inline void warn(microfmt::string_view fmt, const Args &...args) noexcept {
361 if (logger *instance = default_logger(); instance != nullptr) {
362 instance->warn(fmt, args...);
363 }
364}
365
366template <typename StrProvider, typename... Args>
367inline void warn(compile_string_holder<StrProvider> fmt, const Args &...args) noexcept {
368 if (logger *instance = default_logger(); instance != nullptr) {
369 instance->warn(fmt, args...);
370 }
371}
372
373template <typename... Args> inline void error(microfmt::string_view fmt, const Args &...args) noexcept {
374 if (logger *instance = default_logger(); instance != nullptr) {
375 instance->error(fmt, args...);
376 }
377}
378
379template <typename StrProvider, typename... Args>
380inline void error(compile_string_holder<StrProvider> fmt, const Args &...args) noexcept {
381 if (logger *instance = default_logger(); instance != nullptr) {
382 instance->error(fmt, args...);
383 }
384}
385
386template <typename... Args> inline void critical(microfmt::string_view fmt, const Args &...args) noexcept {
387 if (logger *instance = default_logger(); instance != nullptr) {
388 instance->critical(fmt, args...);
389 }
390}
391
392template <typename StrProvider, typename... Args>
393inline void critical(compile_string_holder<StrProvider> fmt, const Args &...args) noexcept {
394 if (logger *instance = default_logger(); instance != nullptr) {
395 instance->critical(fmt, args...);
396 }
397}
398
399} // namespace microfmt::log
400RELOCO_END_UNSAFE_CODE
Output sink that stores formatted characters in an internal fixed-size stack/inline buffer.
Definition microfmt.hpp:185
constexpr microfmt::string_view view() const &noexcept RELOCO_LIFETIMEBOUND
Returns a string view over the characters written so far.
Definition microfmt.hpp:245
RELOCO_END_UNSAFE_BUFFER_USAGE RELOCO_CONSTEXPR20 sink as_sink() &noexcept RELOCO_LIFETIMEBOUND
Creates a type-erased sink adapter pointing to this instance.
Definition microfmt.hpp:236
Definition logger.hpp:56
bool should_log(level l) const noexcept
Definition logger.hpp:91
void log_loc(std::source_location loc, level lvl, microfmt::string_view fmt_str, const Args &...args) const noexcept
Definition logger.hpp:184
void debug(compile_string_holder< StrProvider > fmt, const Args &...args) const noexcept
Definition logger.hpp:134
void info(microfmt::string_view fmt, const Args &...args) const noexcept
Definition logger.hpp:138
void flush() const noexcept
Definition logger.hpp:174
void trace(microfmt::string_view fmt, const Args &...args) const noexcept
Definition logger.hpp:120
Decorator & decorator() &noexcept RELOCO_LIFETIMEBOUND
The message decorator, e.g. to configure a metadata callback.
Definition logger.hpp:214
void info(compile_string_holder< StrProvider > fmt, const Args &...args) const noexcept
Definition logger.hpp:143
void set_level(level l) noexcept
Definition logger.hpp:85
void log(level lvl, microfmt::string_view fmt_str, const Args &...args) const noexcept
Definition logger.hpp:110
bool add_sink(Sink s) noexcept
Definition logger.hpp:77
void log(level lvl, compile_string_holder< StrProvider > fmt_str, const Args &...args) const noexcept
Definition logger.hpp:115
void error(compile_string_holder< StrProvider > fmt, const Args &...args) const noexcept
Definition logger.hpp:161
const Decorator & decorator() const &&=delete
constexpr basic_logger(microfmt::string_view name) noexcept
Definition logger.hpp:70
void critical(microfmt::string_view fmt, const Args &...args) const noexcept
Definition logger.hpp:165
void trace(compile_string_holder< StrProvider > fmt, const Args &...args) const noexcept
Definition logger.hpp:125
void critical(compile_string_holder< StrProvider > fmt, const Args &...args) const noexcept
Definition logger.hpp:170
void error(microfmt::string_view fmt, const Args &...args) const noexcept
Definition logger.hpp:156
void log(const log_msg &msg) const noexcept
Dispatches a caller-built log_msg directly to attached sinks.
Definition logger.hpp:101
void log_loc(std::source_location loc, level lvl, compile_string_holder< StrProvider > fmt_str, const Args &...args) const noexcept
Definition logger.hpp:189
constexpr microfmt::string_view name() const &&=delete
constexpr microfmt::string_view name() const &noexcept RELOCO_LIFETIMEBOUND
Definition logger.hpp:87
void debug(microfmt::string_view fmt, const Args &...args) const noexcept
Definition logger.hpp:129
void warn(microfmt::string_view fmt, const Args &...args) const noexcept
Definition logger.hpp:147
constexpr level get_level() const noexcept
Definition logger.hpp:86
void warn(compile_string_holder< StrProvider > fmt, const Args &...args) const noexcept
Definition logger.hpp:152
const Decorator & decorator() const &noexcept RELOCO_LIFETIMEBOUND
Definition logger.hpp:215
void dispatch(const Msg &msg) const noexcept
Decorates msg (see Decorator) and hands it to every sink under the logger's lock. Decoration runs bef...
Definition logger.hpp:224
ANSI-colorized stdout adapter for structured log records.
Definition sink.hpp:204
Definition fmt.hpp:17
logger *& configured_default_logger() noexcept
Definition logger.hpp:248
Definition default_log_sink.template.hpp:51
void error(microfmt::string_view fmt, const Args &...args) noexcept
Definition logger.hpp:373
void set_default_logger(logger *instance) noexcept
Sets or clears the process-default logger used by free helpers.
Definition logger.hpp:319
level
Shared log severity levels in ascending order.
Definition log_msg.hpp:22
logger * default_logger() noexcept
Returns the configured default logger, or nullptr when unset.
Definition logger.hpp:305
basic_logger< 4, 256 > logger
Definition logger.hpp:242
Definition assert.hpp:19
RELOCO_CONSTEXPR20 void format_to(const sink &out, microfmt::string_view fmt, const Args &...args) noexcept
Definition microfmt.hpp:1766
Type-erased structured logging sinks and console output.
Definition microfmt.hpp:1792
Default message decorator: forwards the record unchanged.
Definition logger.hpp:45
constexpr const log_msg & operator()(const log_msg &msg) const noexcept RELOCO_LIFETIMEBOUND
Definition logger.hpp:46
Structured log record forwarded by a logger to each sink.
Definition log_msg.hpp:69
Locking and wait-channel building blocks shared by the logging stack (basic_logger and the extended s...