microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
sink.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 "../formatters/ansi.hpp"
10#include "../formatters/reloco.hpp"
11#include "../sinks/stdio.hpp"
12#include "log_msg.hpp"
13#include <cstddef>
14#include <string_view>
15#include <type_traits>
16
17namespace microfmt::log {
18
19// ============================================================================
20// Traits-Based Log Sink Provider Pattern
21// ============================================================================
22//
23// Mirrors reloco's context_type-based provider pattern (see reloco's
24// `docs/extending.md`, and `microfmt::address_space_ref`/
25// `microfmt::sink_provider_ref` for the same shape elsewhere in this
26// library): an empty tag selects a `log_sink_traits<Tag>` specialization
27// declaring a `context_type` and a mandatory `log` operation (plus an
28// optional `flush`); `log_sink` is the type-erased, two-word handle built
29// from a tag and (for stateful backends) a context reference. No virtual
30// interfaces, no allocation, and no runtime registration are involved.
31
46template <typename Tag> struct log_sink_traits;
47
48namespace detail {
49
50template <typename Tag, typename = void> struct has_log_sink_flush : std::false_type {};
51
52template <typename Tag>
53struct has_log_sink_flush<Tag, std::void_t<decltype(log_sink_traits<Tag>::flush(
54 std::declval<value_ref<typename log_sink_traits<Tag>::context_type>>()))>>
55 : std::true_type {};
56
57template <typename Tag, typename = void> struct has_stateless_log_sink_flush : std::false_type {};
58
59template <typename Tag>
60struct has_stateless_log_sink_flush<Tag, std::void_t<decltype(log_sink_traits<Tag>::flush())>> : std::true_type {};
61
62} // namespace detail
63
73class MICROFMT_API_CLASS RELOCO_POINTER log_sink {
74public:
78 struct vtable {
80 void (*log)(void *ctx, const log_msg &msg) noexcept = nullptr;
85 void (*flush)(void *ctx) noexcept = nullptr;
86 };
87
91 constexpr log_sink() noexcept = default;
92
99 template <typename Tag, typename Traits = log_sink_traits<Tag>,
100 std::enable_if_t<std::is_void_v<typename Traits::context_type>, int> = 0>
101 constexpr explicit log_sink(Tag, level lvl = level::trace) noexcept : vtbl_(&s_vtbl<Tag>), lvl_(lvl) {}
102
112 template <typename Tag, typename Context, typename Traits = log_sink_traits<Tag>,
113 std::enable_if_t<!std::is_void_v<typename Traits::context_type> &&
114 std::is_convertible_v<Context *, typename Traits::context_type *>,
115 int> = 0>
116 constexpr log_sink(Tag, Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS,
117 level lvl = level::trace) noexcept
118 : ctx_(&ctx), vtbl_(&s_vtbl<Tag>), lvl_(lvl) {}
119
120 // Refuses to bind to a temporary context, which would leave ctx_ dangling
121 // the moment this constructor returns.
122 template <typename Tag, typename Context, std::enable_if_t<!std::is_lvalue_reference_v<Context>, int> = 0>
123 constexpr log_sink(Tag, Context &&, level = level::trace) = delete;
124
129 void log(const log_msg &msg) noexcept {
130 if (vtbl_ != nullptr && msg.lvl >= lvl_) {
131 vtbl_->log(ctx_.get(), msg);
132 }
133 }
134
138 [[nodiscard]] constexpr bool can_flush() const noexcept { return vtbl_ != nullptr && vtbl_->flush != nullptr; }
139
143 void flush() noexcept {
144 if (can_flush()) {
145 vtbl_->flush(ctx_.get());
146 }
147 }
148
149 void set_level(level l) noexcept { lvl_ = l; }
150 [[nodiscard]] constexpr bool should_log(level l) const noexcept { return l >= lvl_; }
151
155 [[nodiscard]] constexpr explicit operator bool() const noexcept { return vtbl_ != nullptr; }
156
157private:
158 template <typename Tag> static void log_entry(void *ctx, const log_msg &msg) noexcept {
159 using context_type = typename log_sink_traits<Tag>::context_type;
160 if constexpr (std::is_void_v<context_type>) {
161 (void)ctx;
163 } else {
164 auto &typed = *static_cast<context_type *>(ctx);
165 log_sink_traits<Tag>::log(value_ref<context_type>(typed), msg);
166 }
167 }
168
169 template <typename Tag> [[nodiscard]] static constexpr auto flush_entry() noexcept {
170 using context_type = typename log_sink_traits<Tag>::context_type;
171 if constexpr (std::is_void_v<context_type>) {
172 if constexpr (detail::has_stateless_log_sink_flush<Tag>::value) {
173 return +[](void *) noexcept { log_sink_traits<Tag>::flush(); };
174 } else {
175 return static_cast<void (*)(void *) noexcept>(nullptr);
176 }
177 } else if constexpr (detail::has_log_sink_flush<Tag>::value) {
178 return +[](void *ctx) noexcept {
179 auto &typed = *static_cast<context_type *>(ctx);
180 log_sink_traits<Tag>::flush(value_ref<context_type>(typed));
181 };
182 } else {
183 return static_cast<void (*)(void *) noexcept>(nullptr);
184 }
185 }
186
187 template <typename Tag> static constexpr vtable s_vtbl{&log_entry<Tag>, flush_entry<Tag>()};
188
189 value_ptr<void> ctx_{};
190 const vtable *vtbl_{nullptr};
191 level lvl_{level::trace};
192};
193
194// ============================================================================
195// Built-in Standard Sinks
196// ============================================================================
197
198// ANSI Color Console Sink (stdout / stderr)
199
201template <std::size_t LineBufCap> struct stdout_color_sink_tag {};
202
204template <size_t LineBufCap = 256> class stdout_color_sink {
205public:
206 stdout_color_sink() noexcept = default;
207
208 [[nodiscard]] log_sink as_sink() & noexcept RELOCO_LIFETIMEBOUND {
210 }
211
212 void log_impl(const log_msg &msg) noexcept {
214 auto out = buf.as_sink();
215
216 // Timestamp: [HH:MM:SS.mmm]
217 microfmt::format_to(out, "[{}] ", msg.time);
218
219 // Logger Tag
220 if (!msg.logger_name.empty()) {
221 microfmt::format_to(out, "[{}] ", msg.logger_name);
222 }
223
224 // Colorized Level Tag
225 switch (msg.lvl) {
226 case level::trace:
227 microfmt::format_to(out, "[{}] ", ansi::gray("TRACE"));
228 break;
229 case level::debug:
230 microfmt::format_to(out, "[{}] ", ansi::cyan("DEBUG"));
231 break;
232 case level::info:
233 microfmt::format_to(out, "[{}] ", ansi::green("INFO "));
234 break;
235 case level::warn:
236 microfmt::format_to(out, "[{}] ", ansi::yellow("WARN "));
237 break;
238 case level::err:
239 microfmt::format_to(out, "[{}] ", ansi::red("ERROR"));
240 break;
241 case level::critical:
243 out, "[{}] ",
245 break;
246 case level::off:
247 default:
248 break;
249 }
250
251 // Message payload + newline
252 microfmt::format_to(out, "{}\n", msg.payload);
253
254 // Output to direct stdout file descriptor
255 auto term = stdout_sink();
256 term.write(buf.view());
257 }
258};
259
260template <std::size_t LineBufCap> struct log_sink_traits<stdout_color_sink_tag<LineBufCap>> {
262
263 static void log(value_ref<context_type> ctx, const log_msg &msg) noexcept { ctx->log_impl(msg); }
264};
265
266} // namespace microfmt::log
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
Type-erased, two-word handle to a log sink backend, plus the severity threshold below which records a...
Definition sink.hpp:73
void log(const log_msg &msg) noexcept
Emits msg to the underlying backend, honoring both the record's own level check and this sink's confi...
Definition sink.hpp:129
constexpr bool can_flush() const noexcept
Reports whether the bound backend supports flush.
Definition sink.hpp:138
constexpr log_sink(Tag, Context &&, level=level::trace)=delete
constexpr log_sink() noexcept=default
Constructs an empty (invalid) handle.
void set_level(level l) noexcept
Definition sink.hpp:149
void flush() noexcept
Flushes buffered output. A no-op when can_flush is false.
Definition sink.hpp:143
constexpr log_sink(Tag, Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS, level lvl=level::trace) noexcept
Constructs a handle for a stateful log sink tag.
Definition sink.hpp:116
constexpr bool should_log(level l) const noexcept
Definition sink.hpp:150
ANSI-colorized stdout adapter for structured log records.
Definition sink.hpp:204
stdout_color_sink() noexcept=default
log_sink as_sink() &noexcept RELOCO_LIFETIMEBOUND
Definition sink.hpp:208
void log_impl(const log_msg &msg) noexcept
Definition sink.hpp:212
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Structured log-record types and severity levels.
constexpr styled_view< T > gray(const T &val) noexcept
Definition ansi.hpp:185
constexpr styled_view< T > styled(const T &val, style st) noexcept
Definition ansi.hpp:153
Definition default_log_sink.template.hpp:51
level
Shared log severity levels in ascending order.
Definition log_msg.hpp:22
RELOCO_END_UNSAFE_CODE sink stdout_sink() noexcept
Definition stdio.hpp:36
RELOCO_CONSTEXPR20 void format_to(const sink &out, microfmt::string_view fmt, const Args &...args) noexcept
Definition microfmt.hpp:1766
Definition ansi.hpp:66
Structured log record forwarded by a logger to each sink.
Definition log_msg.hpp:69
Virtual table of log sink operations.
Definition sink.hpp:78
static void log(value_ref< context_type > ctx, const log_msg &msg) noexcept
Definition sink.hpp:263
Static customization point describing a log sink backend.
Definition sink.hpp:46
Tag selecting stdout_color_sink<LineBufCap> as a log_sink backend.
Definition sink.hpp:201