cortiq-gateway 0.2.38

Universal LLM gateway with intelligent routing and an embedded multilingual admin console
//! In-memory ring of recent log lines, so the admin console can show logs to
//! users who never see the terminal (Docker, autostart, double-clicked exe).
//! Fed by a second `tracing` layer writing plain text (no ANSI) into the ring
//! while the normal stdout layer stays colored.

use std::collections::VecDeque;
use std::io;
use std::sync::{Arc, Mutex, OnceLock};

const CAP: usize = 500;

fn ring() -> &'static Arc<Mutex<VecDeque<String>>> {
    static RING: OnceLock<Arc<Mutex<VecDeque<String>>>> = OnceLock::new();
    RING.get_or_init(|| Arc::new(Mutex::new(VecDeque::with_capacity(CAP))))
}

/// Last `limit` lines, oldest first.
pub fn snapshot(limit: usize) -> Vec<String> {
    let g = ring().lock().unwrap();
    g.iter()
        .skip(g.len().saturating_sub(limit))
        .cloned()
        .collect()
}

pub fn push_line(line: &str) {
    let line = line.trim_end();
    if line.is_empty() {
        return;
    }
    let mut g = ring().lock().unwrap();
    if g.len() >= CAP {
        g.pop_front();
    }
    g.push_back(line.to_string());
}

/// `MakeWriter` for the ring layer: buffers partial writes until a newline.
#[derive(Clone, Default)]
pub struct RingMakeWriter;

pub struct RingWriter {
    buf: String,
}

impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for RingMakeWriter {
    type Writer = RingWriter;
    fn make_writer(&'a self) -> Self::Writer {
        RingWriter { buf: String::new() }
    }
}

impl io::Write for RingWriter {
    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
        self.buf.push_str(&String::from_utf8_lossy(data));
        while let Some(idx) = self.buf.find('\n') {
            let line: String = self.buf.drain(..=idx).collect();
            push_line(&line);
        }
        Ok(data.len())
    }
    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

impl Drop for RingWriter {
    fn drop(&mut self) {
        if !self.buf.is_empty() {
            push_line(&self.buf.clone());
        }
    }
}