microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
ring_buffer_sink_protocol_sink.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2// SPDX-License-Identifier: BSD-2-Clause
3
4#pragma once
5
6#include <algorithm>
7#include <cstring>
9#include <reloco/ring_buffer.hpp>
10
11namespace microfmt::log {
12
17struct alignas(4) ring_buffer_log_header {
18 uint16_t payload_len{}; // Max 65,535 bytes
19 uint8_t logger_name_len{}; // Max 64 bytes
20 uint8_t lvl{}; // Severity level encoded as an 8-bit integer
21};
22
23class ring_buffer_log_sink;
24
26
29
30 static void log(value_ref<context_type> ctx, const log_msg &msg) noexcept;
31};
32
37class RELOCO_POINTER ring_buffer_log_sink {
38public:
39 explicit constexpr ring_buffer_log_sink(
40 reloco::detail::unowned_ring_base<char> &ring RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
41 : ring_(&ring) {}
42
43 [[nodiscard]] log_sink as_sink() noexcept RELOCO_LIFETIMEBOUND { return log_sink(ring_buffer_log_sink_tag{}, *this); }
44
45 // bridges to a C/platform API (FILE*, syslog, android log, raw chunk storage) that takes raw pointers.
46 RELOCO_BEGIN_UNSAFE_CODE
47 void log_impl(const log_msg &msg) const noexcept {
48 // Enforce limits via truncation
49 const std::size_t name_len = std::min<std::size_t>(msg.logger_name.size(), 64);
50 const std::size_t payload_len =
51 std::min<std::size_t>(msg.payload.size(), static_cast<size_t>(std::numeric_limits<uint16_t>::max()));
52
53 // Pack the 4-byte header
54 const ring_buffer_log_header hdr{static_cast<uint16_t>(payload_len), static_cast<uint8_t>(name_len),
55 static_cast<uint8_t>(msg.lvl)};
56
57 // Validator derives total_bytes dynamically!
58 auto validator = [](const ring_buffer_log_header &h) -> std::pair<bool, std::size_t> {
59 return {true, sizeof(ring_buffer_log_header) + h.logger_name_len + h.payload_len};
60 };
61
62 const auto spans = array{reloco::span<const char>(msg.logger_name.data(), name_len),
63 reloco::span<const char>(msg.payload.data(), payload_len)};
64
65 // Write atomically and evict oldest if necessary
66 std::ignore = ring_->try_write_frame_evicting(hdr, spans, validator);
67 }
69
70private:
71 reloco::detail::unowned_ring_base<char> *ring_{};
72};
73
74inline void log_sink_traits<ring_buffer_log_sink_tag>::log(const value_ref<context_type> ctx,
75 const log_msg &msg) noexcept {
76 ctx->log_impl(msg);
77}
78
82struct RELOCO_POINTER split_string_view {
83 reloco::span<const char> first;
84 reloco::span<const char> second;
85
86 [[nodiscard]] constexpr std::size_t size() const noexcept { return first.size() + second.size(); }
87};
88
92class RELOCO_POINTER RELOCO_CONSUMABLE(unconsumed) log_record_tx {
93 reloco::detail::unowned_ring_base<char> *ring_{nullptr};
94 level lvl_{level::trace};
95 split_string_view logger_name_{};
96 split_string_view payload_{};
97 std::size_t total_bytes_{0};
98
99 friend class ring_buffer_log_reader;
100
101 log_record_tx(reloco::detail::unowned_ring_base<char> *ring RELOCO_LIFETIMEBOUND, level lvl, split_string_view name,
102 split_string_view payload, std::size_t total_bytes) noexcept RELOCO_RETURN_TYPESTATE(unconsumed)
103 : ring_(ring), lvl_(lvl), logger_name_(name), payload_(payload), total_bytes_(total_bytes) {}
104
105public:
106 // Creates an empty/exhausted transaction state
107 constexpr log_record_tx() noexcept RELOCO_RETURN_TYPESTATE(consumed) = default;
108
109 log_record_tx(const log_record_tx &) = delete;
110 log_record_tx &operator=(const log_record_tx &) = delete;
111
112 log_record_tx(log_record_tx &&other) noexcept RELOCO_RETURN_TYPESTATE(unconsumed)
113 : ring_(other.ring_), lvl_(other.lvl_), logger_name_(other.logger_name_), payload_(other.payload_),
114 total_bytes_(other.total_bytes_) {
115 other.ring_ = nullptr; // Steal ownership
116 }
117
118 ~log_record_tx() = default; // Implicit rollback on destruction
119
120 [[nodiscard]] constexpr explicit operator bool() const noexcept RELOCO_TEST_TYPESTATE(unconsumed) {
121 return ring_ != nullptr;
122 }
123
124 [[nodiscard]] constexpr bool is_known() const noexcept RELOCO_TEST_TYPESTATE(unconsumed) { return ring_ != nullptr; }
125
126 [[nodiscard]] level get_level() const noexcept RELOCO_CALLABLE_WHEN(unconsumed) { return lvl_; }
127 [[nodiscard]] split_string_view logger_name() const noexcept RELOCO_CALLABLE_WHEN(unconsumed) { return logger_name_; }
128 [[nodiscard]] split_string_view payload() const noexcept RELOCO_CALLABLE_WHEN(unconsumed) { return payload_; }
129
133 void commit() noexcept RELOCO_SET_TYPESTATE(consumed) {
134 if (ring_ != nullptr) {
135 ring_->consume(total_bytes_);
136 ring_ = nullptr;
137 }
138 }
139
143 void rollback() noexcept RELOCO_SET_TYPESTATE(consumed) { ring_ = nullptr; }
144
145 [[nodiscard]] log_record_tx &as_known() & noexcept RELOCO_SET_TYPESTATE(unconsumed) {
146 RELOCO_ASSERT(ring_, "Record consumed");
147 return *this;
148 }
149
150 [[nodiscard]] const log_record_tx &as_known() const & noexcept RELOCO_SET_TYPESTATE(unconsumed) {
151 RELOCO_ASSERT(ring_, "Record consumed");
152 return *this;
153 }
154
155 [[nodiscard]] log_record_tx &&as_known() && noexcept RELOCO_SET_TYPESTATE(unconsumed) {
156 RELOCO_ASSERT(ring_, "Record consumed");
157 return std::move(*this);
158 }
159};
160
165class RELOCO_POINTER ring_buffer_log_reader : public reloco::iterator_adaptor<ring_buffer_log_reader, log_record_tx> {
166public:
167 using item_type = log_record_tx;
168
169 explicit constexpr ring_buffer_log_reader(
170 reloco::detail::unowned_ring_base<char> &ring RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
171 : ring_(&ring) {}
172
177 [[nodiscard]] reloco::optional<log_record_tx> try_read() & noexcept {
178 constexpr std::size_t header_elems = sizeof(ring_buffer_log_header);
179
180 if (ring_->size() < header_elems) {
181 return {};
182 }
183
184 auto hdr_opt = ring_->try_peek_object<ring_buffer_log_header>();
185 if (!hdr_opt) {
186 return {};
187 }
188
189 const std::size_t total_bytes = header_elems + hdr_opt->logger_name_len + hdr_opt->payload_len;
190
191 // Self-heal from massive corruption dynamically
192 if (total_bytes > ring_->capacity() || hdr_opt->logger_name_len > 64) {
193 ring_->clear();
194 return {};
195 }
196
197 // Wait for the full frame to arrive
198 if (ring_->size() < total_bytes) {
199 return {};
200 }
201
202 // Read slices exactly up to total_bytes
203 auto [p1, p2] = ring_->read_slices(total_bytes);
204
205 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
206
207 // Helper to perfectly slice out split spans using mathematical offsets
208 const auto slice_spans = [p1, p2](std::size_t offset, std::size_t len) {
209 const std::size_t p1_avail = (offset < p1.size()) ? p1.size() - offset : 0;
210 const std::size_t out1_len = std::min(len, p1_avail);
211
212 const std::size_t out2_len = len - out1_len;
213 const std::size_t s2_offset = (offset > p1.size()) ? offset - p1.size() : 0;
214
215 return split_string_view{reloco::span<const char>(p1.data() + offset, out1_len),
216 reloco::span<const char>(p2.data() + s2_offset, out2_len)};
217 };
218
219 RELOCO_END_UNSAFE_BUFFER_USAGE;
220
221 return log_record_tx(ring_, static_cast<level>(hdr_opt->lvl), slice_spans(header_elems, hdr_opt->logger_name_len),
222 slice_spans(header_elems + hdr_opt->logger_name_len, hdr_opt->payload_len), total_bytes);
223 }
224
228 [[nodiscard]] reloco::optional<log_record_tx> next_impl() noexcept { return try_read(); }
229
230private:
231 reloco::detail::unowned_ring_base<char> *ring_{};
232};
233
234} // namespace microfmt::log
235
237 static constexpr void parse(microfmt::format_parse_context &) noexcept {}
238
239 static void format(const microfmt::log::split_string_view &val, const microfmt::sink &out) noexcept {
240 if (!val.first.empty()) {
241 out.write(microfmt::string_view(val.first.data(), val.first.size()));
242 }
243 if (!val.second.empty()) {
244 out.write(microfmt::string_view(val.second.data(), val.second.size()));
245 }
246 }
247};
Definition microfmt.hpp:881
Type-erased, two-word handle to a log sink backend, plus the severity threshold below which records a...
Definition sink.hpp:73
Pull-based reader for extracting structured logs via RAII transactions. Natively supports the reloco/...
Definition ring_buffer_sink_protocol_sink.hpp:165
reloco::optional< log_record_tx > try_read() &noexcept
Attempts to extract the next log frame.
Definition ring_buffer_sink_protocol_sink.hpp:177
constexpr ring_buffer_log_reader(reloco::detail::unowned_ring_base< char > &ring RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
Definition ring_buffer_sink_protocol_sink.hpp:169
log_record_tx item_type
Definition ring_buffer_sink_protocol_sink.hpp:167
reloco::optional< log_record_tx > next_impl() noexcept
The magic hook that fuels the reloco::iterator_adaptor pipeline!
Definition ring_buffer_sink_protocol_sink.hpp:228
A log sink that serializes structured logs into a byte-oriented ring_buffer. Uses try_write_frame_evi...
Definition ring_buffer_sink_protocol_sink.hpp:37
constexpr ring_buffer_log_sink(reloco::detail::unowned_ring_base< char > &ring RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
Definition ring_buffer_sink_protocol_sink.hpp:39
log_sink as_sink() noexcept RELOCO_LIFETIMEBOUND
Definition ring_buffer_sink_protocol_sink.hpp:43
RELOCO_BEGIN_UNSAFE_CODE void log_impl(const log_msg &msg) const noexcept
Definition ring_buffer_sink_protocol_sink.hpp:47
Definition default_log_sink.template.hpp:51
class RELOCO_POINTER RELOCO_CONSUMABLE(unconsumed) log_record_tx
RAII Transaction holding an unconsumed log record.
Definition ring_buffer_sink_protocol_sink.hpp:92
level
Shared log severity levels in ascending order.
Definition log_msg.hpp:22
Definition assert.hpp:19
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
Type-erased structured logging sinks and console output.
static void format(const microfmt::log::split_string_view &val, const microfmt::sink &out) noexcept
Definition ring_buffer_sink_protocol_sink.hpp:239
static constexpr void parse(microfmt::format_parse_context &) noexcept
Definition ring_buffer_sink_protocol_sink.hpp:237
Definition microfmt.hpp:985
Structured log record forwarded by a logger to each sink.
Definition log_msg.hpp:69
Static customization point describing a log sink backend.
Definition sink.hpp:46
Binary framing header. Embedding the logger_name directly guarantees we only have one dynamic payload...
Definition ring_buffer_sink_protocol_sink.hpp:17
uint8_t logger_name_len
Definition ring_buffer_sink_protocol_sink.hpp:19
uint8_t lvl
Definition ring_buffer_sink_protocol_sink.hpp:20
uint16_t payload_len
Definition ring_buffer_sink_protocol_sink.hpp:18
Definition ring_buffer_sink_protocol_sink.hpp:25
Represents string data that may cross the circular array boundary.
Definition ring_buffer_sink_protocol_sink.hpp:82
reloco::span< const char > second
Definition ring_buffer_sink_protocol_sink.hpp:84
constexpr std::size_t size() const noexcept
Definition ring_buffer_sink_protocol_sink.hpp:86
reloco::span< const char > first
Definition ring_buffer_sink_protocol_sink.hpp:83