1use crate::log_level::LogLevel;
18use crate::sharded_queue::ShardedQueue;
19#[cfg(not(miri))]
20use crate::sink::PlatformSink;
21use crate::tui::TuiMetrics;
22#[cfg(not(miri))]
23use crate::tui::spawn_tui_thread;
24use std::fmt;
25use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
26use std::sync::{Arc, LazyLock, Mutex};
27use std::thread;
28#[cfg(not(miri))]
29use std::time::Duration;
30
31const RING_BUFFER_CAPACITY: usize = 65_536;
33
34#[cfg(not(miri))]
36const MAX_DRAIN_BATCH_SIZE: usize = 64;
37
38#[derive(Debug, Clone)]
43pub struct LogEvent {
44 pub level: LogLevel,
46 pub level_num: u8,
48 pub log: crate::log::Log,
50}
51
52pub struct LockFreeEngine {
57 queue: Arc<ShardedQueue>,
67 shutdown_flag: Arc<AtomicBool>,
69 metrics: Arc<TuiMetrics>,
71 filter_level: AtomicU8,
73 flusher_thread_handle: Option<thread::Thread>,
75 flusher_join: Mutex<Option<thread::JoinHandle<()>>>,
77}
78
79impl fmt::Debug for LockFreeEngine {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 f.debug_struct("LockFreeEngine")
82 .field("queue", &self.queue)
83 .field("shutdown_flag", &self.shutdown_flag)
84 .field("metrics", &self.metrics)
85 .field("filter_level", &self.filter_level)
86 .field(
87 "flusher_thread_handle",
88 &self
89 .flusher_thread_handle
90 .as_ref()
91 .map(thread::Thread::id),
92 )
93 .finish_non_exhaustive()
94 }
95}
96
97pub static ENGINE: LazyLock<LockFreeEngine> =
99 LazyLock::new(|| LockFreeEngine::new(RING_BUFFER_CAPACITY));
100
101impl LockFreeEngine {
102 #[must_use]
108 pub fn new(capacity: usize) -> Self {
109 let queue = Arc::new(ShardedQueue::new(capacity));
110 let shutdown_flag = Arc::new(AtomicBool::new(false));
111 let metrics = Arc::new(TuiMetrics::default());
112 let filter_level = AtomicU8::new(0); #[cfg(not(miri))]
117 let flusher_handle = {
118 let flusher_queue = queue.clone();
119 let flusher_shutdown = shutdown_flag.clone();
120
121 let handle = thread::Builder::new()
123 .name("rlg-flusher".into())
124 .spawn(move || {
125 use std::io::Write;
126 let mut sink = PlatformSink::native();
127 let mut fmt_buf = Vec::with_capacity(512);
128
129 loop {
130 let mut batch: [Option<LogEvent>;
131 MAX_DRAIN_BATCH_SIZE] =
132 std::array::from_fn(|_| None);
133 let mut count = 0;
134 while count < MAX_DRAIN_BATCH_SIZE {
135 match flusher_queue.pop() {
136 Some(event) => {
137 batch[count] = Some(event);
138 count += 1;
139 }
140 None => break,
141 }
142 }
143 for event in batch.iter().flatten() {
144 fmt_buf.clear();
145 let _ = writeln!(fmt_buf, "{}", &event.log);
146 sink.emit(event.level.as_str(), &fmt_buf);
147 }
148
149 if flusher_shutdown.load(Ordering::Relaxed)
150 && flusher_queue.is_empty()
151 {
152 break;
153 }
154
155 thread::park_timeout(Duration::from_millis(5));
157 }
158 })
159 .expect(
160 "Failed to spawn rlg-flusher background thread",
161 );
162
163 if std::env::var("RLG_TUI").is_ok_and(|v| v == "1") {
165 spawn_tui_thread(
166 metrics.clone(),
167 shutdown_flag.clone(),
168 );
169 }
170
171 Some(handle)
172 };
173
174 #[cfg(miri)]
175 let flusher_handle: Option<thread::JoinHandle<()>> = None;
176
177 let flusher_thread_handle =
178 flusher_handle.as_ref().map(|h| h.thread().clone());
179
180 Self {
181 queue,
182 shutdown_flag,
183 metrics,
184 filter_level,
185 flusher_thread_handle,
186 flusher_join: Mutex::new(flusher_handle),
187 }
188 }
189
190 pub fn ingest(&self, event: LogEvent) {
195 if event.level_num < self.filter_level.load(Ordering::Acquire) {
196 return;
197 }
198
199 self.metrics.inc_events();
200 self.metrics.inc_level(event.level);
201
202 if event.level_num >= LogLevel::ERROR.to_numeric() {
203 self.metrics.inc_errors();
204 }
205
206 if let Err(rejected) = self.queue.push(event) {
213 self.metrics.inc_dropped();
214 let mut to_push = rejected;
215 for _ in 0..3 {
216 let _ = self.queue.pop_local();
217 match self.queue.push(to_push) {
218 Ok(()) => break,
219 Err(e) => to_push = e,
220 }
221 }
222 }
223
224 if let Some(thread) = &self.flusher_thread_handle {
226 thread.unpark();
227 }
228 }
229
230 pub fn set_filter(&self, level: u8) {
232 self.filter_level.store(level, Ordering::Release);
233 }
234
235 #[must_use]
237 pub fn filter_level(&self) -> u8 {
238 self.filter_level.load(Ordering::Relaxed)
239 }
240
241 pub fn inc_format(&self, format: crate::log_format::LogFormat) {
243 self.metrics.inc_format(format);
244 }
245
246 pub fn inc_spans(&self) {
248 self.metrics.inc_spans();
249 }
250
251 pub fn dec_spans(&self) {
253 self.metrics.dec_spans();
254 }
255
256 #[must_use]
258 pub fn active_spans(&self) -> usize {
259 self.metrics.active_spans.load(Ordering::Relaxed)
260 }
261
262 pub fn apply_config(&self, config: &crate::config::Config) {
268 self.set_filter(config.log_level.to_numeric());
269 }
270
271 pub fn shutdown(&self) {
276 self.shutdown_flag.store(true, Ordering::SeqCst);
277 if let Some(thread) = &self.flusher_thread_handle {
279 thread.unpark();
280 }
281 if let Ok(mut guard) = self.flusher_join.lock()
282 && let Some(handle) = guard.take()
283 {
284 let _ = handle.join();
285 }
286 }
287}
288
289#[derive(Debug, Clone, Copy)]
291pub struct FastSerializer;
292
293impl FastSerializer {
294 pub fn append_u64(buf: &mut Vec<u8>, val: u64) {
296 let mut buffer = itoa::Buffer::new();
297 buf.extend_from_slice(buffer.format(val).as_bytes());
298 }
299
300 pub fn append_f64(buf: &mut Vec<u8>, val: f64) {
302 let mut buffer = ryu::Buffer::new();
303 buf.extend_from_slice(buffer.format(val).as_bytes());
304 }
305}
306
307#[cfg(test)]
308#[cfg_attr(miri, allow(unused_imports))]
309mod tests {
310 use super::*;
311 use crate::LogLevel;
312 use crate::log::Log;
313
314 fn make_event(level: LogLevel) -> LogEvent {
315 LogEvent {
316 level,
317 level_num: level.to_numeric(),
318 log: Log::build(level, "test"),
319 }
320 }
321
322 #[test]
323 #[cfg_attr(miri, ignore)]
324 fn fast_serializer_round_trip() {
325 let mut buf = Vec::new();
326 FastSerializer::append_u64(&mut buf, 1234);
327 FastSerializer::append_f64(&mut buf, 3.5);
328 assert_eq!(buf, b"12343.5");
329 }
330
331 #[test]
332 #[cfg_attr(miri, ignore)]
333 fn ingest_overfills_small_queue_and_drops() {
334 let engine = LockFreeEngine::new(1);
337 for _ in 0..32 {
338 engine.ingest(make_event(LogLevel::INFO));
339 }
340 engine.shutdown();
341 }
342
343 #[test]
344 #[cfg_attr(miri, ignore)]
345 fn ingest_under_concurrent_overfill_hits_retry_err_branch() {
346 let engine = Arc::new(LockFreeEngine::new(1));
351 let mut handles = Vec::new();
352 for _ in 0..8 {
353 let e = engine.clone();
354 handles.push(thread::spawn(move || {
355 for _ in 0..2_000 {
356 e.ingest(make_event(LogLevel::INFO));
357 }
358 }));
359 }
360 for h in handles {
361 h.join().unwrap();
362 }
363 engine.shutdown();
364 }
365
366 #[test]
367 #[cfg_attr(miri, ignore)]
368 fn ingest_below_filter_short_circuits() {
369 let engine = LockFreeEngine::new(8);
370 engine.set_filter(LogLevel::ERROR.to_numeric());
371 engine.ingest(make_event(LogLevel::DEBUG));
373 engine.shutdown();
374 }
375
376 #[test]
377 #[cfg_attr(miri, ignore)]
378 fn engine_records_errors_and_spans() {
379 let engine = LockFreeEngine::new(8);
380 engine.ingest(make_event(LogLevel::ERROR));
381 engine.inc_format(crate::log_format::LogFormat::JSON);
382 engine.inc_spans();
383 engine.inc_spans();
384 assert_eq!(engine.active_spans(), 2);
385 engine.dec_spans();
386 assert_eq!(engine.active_spans(), 1);
387 engine.shutdown();
388 }
389
390 #[test]
391 #[cfg_attr(miri, ignore)]
392 fn shutdown_is_idempotent() {
393 let engine = LockFreeEngine::new(4);
394 engine.ingest(make_event(LogLevel::INFO));
395 engine.shutdown();
396 engine.shutdown();
399 }
400
401 #[test]
402 fn apply_config_updates_filter() {
403 let engine = LockFreeEngine::new(4);
404 let cfg = crate::config::Config {
405 log_level: LogLevel::WARN,
406 ..crate::config::Config::default()
407 };
408 engine.apply_config(&cfg);
409 assert_eq!(engine.filter_level(), LogLevel::WARN.to_numeric());
410 engine.shutdown();
411 }
412}