MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Archivist.cpp
Go to the documentation of this file.
1#include "Archivist.hpp"
2#include "RealtimeEntry.hpp"
3
4#include "Ansi.hpp"
5#include "Sink.hpp"
6
8
9namespace MayaFlux::Journal {
10
11namespace {
12 const bool colors_enabled = AnsiColors::initialize_console_colors();
13} // namespace
14
16public:
17 static constexpr size_t RING_BUFFER_SIZE = 8192;
18 static constexpr size_t RT_RING_BUFFER_SIZE = 4096;
19 static_assert(magic_enum::enum_count<Component>() <= 64);
20 static_assert(magic_enum::enum_count<Context>() <= 64);
21
24 , m_worker_running(false)
25 , m_initialized(false)
28 {
29 for (auto& f : m_component_filters)
30 f.store(true, std::memory_order_relaxed);
31 for (auto& f : m_context_filters)
32 f.store(true, std::memory_order_relaxed);
33 }
34
36 {
37 m_accepting_entries.store(false, std::memory_order_release);
38 m_worker_running.store(false, std::memory_order_release);
39 if (m_worker_thread.joinable())
40 m_worker_thread.join();
41 }
42
43 void init()
44 {
45 if (m_initialized.exchange(true, std::memory_order_acq_rel))
46 return;
47
48 m_worker_running.store(true, std::memory_order_release);
50 std::cout << "[MayaFlux::Journal] Initialized\n";
51 }
52
53 void shutdown()
54 {
55 if (!m_initialized.load(std::memory_order_acquire))
56 return;
57 if (m_shutdown_in_progress.exchange(true, std::memory_order_acq_rel))
58 return;
59
60 m_worker_running.store(false, std::memory_order_release);
61 if (m_worker_thread.joinable())
62 m_worker_thread.join();
63
65 m_initialized.store(false, std::memory_order_release);
66 m_shutdown_in_progress.store(false, std::memory_order_release);
67 std::cout << "[MayaFlux::Journal] Shutdown\n";
68 }
69
70 void scribe(const JournalEntry& entry)
71 {
72 if (!m_accepting_entries.load(std::memory_order_acquire))
73 return;
74
75 if (!should_log(entry.severity, entry.component, entry.context))
76 return;
77
78 RealtimeEntry rt(entry.severity, entry.component, entry.context,
79 entry.message, entry.location);
80
81 while (m_push_lock.test_and_set(std::memory_order_acquire))
82 ;
83 const bool pushed = m_ring_buffer.push(rt);
84 m_push_lock.clear(std::memory_order_release);
85
86 if (!pushed)
87 m_dropped_messages.fetch_add(1, std::memory_order_relaxed);
88 }
89
90 void scribe_rt(Severity severity, Component component, Context context,
91 std::string_view message, std::source_location location)
92 {
93 if (!m_accepting_entries.load(std::memory_order_acquire))
94 return;
95
96 if (!should_log(severity, component, context))
97 return;
98
99 RealtimeEntry entry(severity, component, context, message, location);
100 if (!m_rt_ring_buffer.push(entry))
101 m_dropped_messages.fetch_add(1, std::memory_order_relaxed);
102 }
103
104 /**
105 * @brief Drain all pending ring buffer entries to sinks or console.
106 *
107 * Lock-free. Safe to call from any thread. The worker calls this on its
108 * normal cadence; error() and fatal() call it synchronously before
109 * propagating exceptions or aborting to guarantee visibility.
110 */
112 {
113 while (auto entry = m_rt_ring_buffer.pop()) {
114 if (m_sinks.empty()) {
115 write_to_console(*entry);
116 } else {
117 write_to_sinks(*entry);
118 }
119 }
120
121 while (auto entry = m_ring_buffer.pop()) {
122 if (m_sinks.empty()) {
123 write_to_console(*entry);
124 } else {
125 write_to_sinks(*entry);
126 }
127 }
128
129 const auto dropped = m_dropped_messages.exchange(0, std::memory_order_acq_rel);
130 if (dropped > 0) {
131 std::cout << "[MayaFlux::Journal] WARNING: Dropped "
132 << dropped << " log messages (buffer full)\n";
133 }
134 }
135
136 void add_sink(std::unique_ptr<Sink> sink)
137 {
138 m_sinks.push_back(std::move(sink));
139 }
140
142 {
143 m_sinks.clear();
144 }
145
147 {
148 if (sev == Severity::NONE)
149 return;
150 m_min_severity.store(sev, std::memory_order_relaxed);
151 }
152
153 void set_component_filter(Component comp, bool enabled)
154 {
155 auto comp_idx = static_cast<size_t>(comp);
156 if (comp_idx >= m_component_filters.size())
157 return;
158
159 m_component_filters[comp_idx].store(enabled, std::memory_order_release);
160 }
161
162 void set_context_filter(Context ctx, bool enabled)
163 {
164 auto ctx_idx = static_cast<size_t>(ctx);
165 if (ctx_idx >= m_context_filters.size())
166 return;
167
168 m_context_filters[ctx_idx].store(enabled, std::memory_order_release);
169 }
170
171 void set_component_includes(const std::vector<Component>& components)
172 {
173 uint64_t includes = 0;
174 for (auto component : components) {
175 auto idx = static_cast<size_t>(component);
176 if (idx < m_component_filters.size())
177 includes |= uint64_t { 1 } << idx;
178 }
179 m_component_includes.store(includes, std::memory_order_release);
180 }
181
182 void set_context_includes(const std::vector<Context>& contexts)
183 {
184 uint64_t includes = 0;
185 for (auto context : contexts) {
186 auto idx = static_cast<size_t>(context);
187 if (idx < m_context_filters.size())
188 includes |= uint64_t { 1 } << idx;
189 }
190 m_context_includes.store(includes, std::memory_order_release);
191 }
192
193private:
194 [[nodiscard]] bool should_log(Severity severity, Component component, Context context) const
195 {
196 if (severity != Severity::NONE && severity < m_min_severity.load(std::memory_order_relaxed))
197 return false;
198
199 auto comp_idx = static_cast<size_t>(component);
200 if (comp_idx >= m_component_filters.size()
201 || !m_component_filters[comp_idx].load(std::memory_order_acquire))
202 return false;
203
204 auto component_includes = m_component_includes.load(std::memory_order_acquire);
205 if (component_includes != 0 && !(component_includes & (uint64_t { 1 } << comp_idx)))
206 return false;
207
208 auto ctx_idx = static_cast<size_t>(context);
209
210 if (ctx_idx >= m_context_filters.size()
211 || !m_context_filters[ctx_idx].load(std::memory_order_acquire))
212 return false;
213
214 auto context_includes = m_context_includes.load(std::memory_order_acquire);
215 return context_includes == 0 || (context_includes & (uint64_t { 1 } << ctx_idx));
216 }
217
218 static void write_to_console(const RealtimeEntry& entry)
219 {
220 if (colors_enabled) {
221 switch (entry.severity) {
222 case Severity::TRACE:
223 std::cout << AnsiColors::Cyan;
224 break;
225 case Severity::DEBUG:
226 std::cout << AnsiColors::Blue;
227 break;
228 case Severity::INFO:
229 std::cout << AnsiColors::Green;
230 break;
231 case Severity::WARN:
232 std::cout << AnsiColors::Yellow;
233 break;
234 case Severity::ERROR:
235 std::cout << AnsiColors::BrightRed;
236 break;
237 case Severity::FATAL:
239 break;
240 case Severity::NONE:
241 default:
242 std::cout << AnsiColors::Reset;
243 break;
244 }
245 }
246
247 std::cout << "[" << Reflect::enum_to_string(entry.severity) << "]" << AnsiColors::Reset;
248 if (colors_enabled)
249 std::cout << AnsiColors::Magenta;
250 std::cout << "[" << Reflect::enum_to_string(entry.component) << "]" << AnsiColors::Reset;
251 if (colors_enabled)
252 std::cout << AnsiColors::Cyan;
253 std::cout << "[" << Reflect::enum_to_string(entry.context) << "]" << AnsiColors::Reset << " ";
254 std::cout << entry.message;
255
256 if (entry.file_name != nullptr && entry.line != 0) {
257 if (colors_enabled)
258 std::cout << AnsiColors::BrightBlue;
259 std::cout << " (" << entry.file_name << ":" << entry.line << ")" << AnsiColors::Reset;
260 }
261
262 std::cout << '\n';
263 }
264
265 void write_to_sinks(const RealtimeEntry& entry)
266 {
267 for (auto& sink : m_sinks) {
268 if (sink->is_available()) {
269 try {
270 sink->write(entry);
271 } catch (...) {
272 std::cout << "[MayaFlux::Journal] WARNING: sink threw during write, skipping\n";
273 }
274 }
275 }
276 }
277
279 {
280 m_worker_thread = std::thread([this] { worker_loop(); });
281 }
282
284 {
285 using namespace std::chrono_literals;
286 while (m_worker_running.load(std::memory_order_acquire)) {
288 std::this_thread::sleep_for(10ms);
289 }
291 }
292
293 std::vector<std::unique_ptr<Sink>> m_sinks;
294
295 std::atomic<Severity> m_min_severity;
296 std::array<std::atomic<bool>, magic_enum::enum_count<Component>()> m_component_filters {};
297 std::array<std::atomic<bool>, magic_enum::enum_count<Context>()> m_context_filters {};
298 std::atomic<uint64_t> m_component_includes { 0 };
299 std::atomic<uint64_t> m_context_includes { 0 };
300 std::atomic<bool> m_initialized;
301 std::atomic<bool> m_worker_running;
302 std::atomic<bool> m_accepting_entries;
303 std::atomic<bool> m_shutdown_in_progress;
304 std::atomic<uint64_t> m_dropped_messages { 0 };
305
306 alignas(64) std::atomic_flag m_push_lock = ATOMIC_FLAG_INIT;
309 std::thread m_worker_thread;
310};
311
313{
314 static Archivist archivist;
315 return archivist;
316}
317
319 : m_impl(std::make_unique<Impl>())
320{
321 m_impl->init();
322}
323
324Archivist::~Archivist() = default;
325
327{
328 instance().m_impl->shutdown();
329}
330
332{
333 m_impl->drain_ring_buffer();
334}
335
337 std::string_view message, std::source_location location)
338{
339 JournalEntry entry(severity, component, context, message, location);
340 m_impl->scribe(entry);
341}
342
344 std::string_view message, std::source_location location)
345{
346 m_impl->scribe_rt(severity, component, context, message, location);
347}
348
350 std::string_view message)
351{
352 JournalEntry entry(Severity::NONE, component, context, message, std::source_location {});
353 m_impl->scribe(entry);
354}
355
356void Archivist::add_sink(std::unique_ptr<Sink> sink)
357{
358 m_impl->add_sink(std::move(sink));
359}
360
362{
363 m_impl->clear_sinks();
364}
365
367{
368 m_impl->set_min_severity(min_sev);
369}
370
372{
373 m_impl->set_component_filter(comp, enabled);
374}
375
377{
378 m_impl->set_context_filter(ctx, enabled);
379}
380
381void Archivist::set_component_includes(const std::vector<Component>& components)
382{
383 m_impl->set_component_includes(components);
384}
385
386void Archivist::set_context_includes(const std::vector<Context>& contexts)
387{
388 m_impl->set_context_includes(contexts);
389}
390
391} // namespace MayaFlux::Journal
std::string severity
Definition Config.cpp:18
void set_component_includes(const std::vector< Component > &components)
std::array< std::atomic< bool >, magic_enum::enum_count< Component >()> m_component_filters
std::atomic< Severity > m_min_severity
std::atomic< bool > m_initialized
void set_component_filter(Component comp, bool enabled)
static constexpr size_t RT_RING_BUFFER_SIZE
Definition Archivist.cpp:18
bool should_log(Severity severity, Component component, Context context) const
std::array< std::atomic< bool >, magic_enum::enum_count< Context >()> m_context_filters
std::atomic< bool > m_shutdown_in_progress
std::atomic< bool > m_accepting_entries
void set_min_severity(Severity sev)
std::atomic< bool > m_worker_running
void write_to_sinks(const RealtimeEntry &entry)
Memory::MPSCQueue< RealtimeEntry, RT_RING_BUFFER_SIZE > m_rt_ring_buffer
static void write_to_console(const RealtimeEntry &entry)
Memory::LockFreeQueue< RealtimeEntry, RING_BUFFER_SIZE > m_ring_buffer
void scribe(const JournalEntry &entry)
Definition Archivist.cpp:70
void add_sink(std::unique_ptr< Sink > sink)
static constexpr size_t RING_BUFFER_SIZE
Definition Archivist.cpp:17
std::atomic< uint64_t > m_context_includes
void set_context_filter(Context ctx, bool enabled)
void drain_ring_buffer()
Drain all pending ring buffer entries to sinks or console.
void set_context_includes(const std::vector< Context > &contexts)
std::vector< std::unique_ptr< Sink > > m_sinks
std::atomic< uint64_t > m_dropped_messages
void scribe_rt(Severity severity, Component component, Context context, std::string_view message, std::source_location location)
Definition Archivist.cpp:90
std::atomic< uint64_t > m_component_includes
void scribe_simple(Component component, Context context, std::string_view message)
Log a simple message without source location information.
void add_sink(std::unique_ptr< Sink > sink)
Add a log sink for output.
void set_component_includes(const std::vector< Component > &components)
Restrict logging to these components; an empty list includes all.
void scribe_rt(Severity severity, Component component, Context context, std::string_view message, std::source_location location=std::source_location::current())
Log a message from a real-time context with the specified severity, component, and context.
void clear_sinks()
Remove all sinks.
void set_component_filter(Component comp, bool enabled)
Enable or disable logging for a specific component.
void flush()
Synchronously drain all pending ring buffer entries to sinks.
static void shutdown()
Shutdown the logging system.
void set_context_includes(const std::vector< Context > &contexts)
Restrict logging to these contexts; an empty list includes all.
static Archivist & instance()
Get the singleton instance of the Archivist.
void set_min_severity(Severity min_sev)
Set the minimum severity level for logging.
void scribe(Severity severity, Component component, Context context, std::string_view message, std::source_location location=std::source_location::current())
Log a message with the specified severity, component, and context.
std::unique_ptr< Impl > m_impl
void set_context_filter(Context ctx, bool enabled)
Enable or disable logging for a specific context.
Singleton class responsible for managing log entries.
Definition Archivist.hpp:24
Policy-driven unified circular buffer implementation.
static constexpr std::string_view Yellow
Definition Ansi.hpp:21
static constexpr std::string_view BrightBlue
Definition Ansi.hpp:31
static constexpr std::string_view White
Definition Ansi.hpp:25
static constexpr std::string_view Blue
Definition Ansi.hpp:22
static constexpr std::string_view Reset
Definition Ansi.hpp:15
static constexpr std::string_view Green
Definition Ansi.hpp:20
static constexpr std::string_view Cyan
Definition Ansi.hpp:24
static constexpr std::string_view BrightRed
Definition Ansi.hpp:28
static bool initialize_console_colors()
Definition Ansi.hpp:40
static constexpr std::string_view Magenta
Definition Ansi.hpp:23
Context
Execution contexts for log messages.
constexpr std::string_view enum_to_string(EnumType value) noexcept
Universal enum to string converter using magic_enum (original case)
A log entry structure to encapsulate log message details.
char message[MAX_MESSAGE_LENGTH]
Lightweight entry for lock-free ring buffer.