fullbleed 2.2.5

Deterministic HTML/CSS-to-PDF engine in Rust for transactional document generation and AI agent workflows.
Documentation
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, BufWriter, Write};
use std::path::Path;
use std::sync::{Arc, Mutex};

#[derive(Clone)]
pub(crate) struct DebugLogger {
    inner: Arc<Mutex<DebugState>>,
}

struct DebugState {
    writer: BufWriter<File>,
    counters: HashMap<String, u64>,
}

impl DebugLogger {
    pub fn new(path: impl AsRef<Path>) -> io::Result<Self> {
        let file = File::create(path)?;
        Ok(Self {
            inner: Arc::new(Mutex::new(DebugState {
                writer: BufWriter::new(file),
                counters: HashMap::new(),
            })),
        })
    }

    pub fn log_json(&self, json: &str) {
        if let Ok(mut writer) = self.inner.lock() {
            let _ = writeln!(writer.writer, "{json}");
        }
    }

    pub fn increment(&self, key: &str, amount: u64) {
        if let Ok(mut state) = self.inner.lock() {
            let entry = state.counters.entry(key.to_string()).or_insert(0);
            *entry = entry.saturating_add(amount);
        }
    }

    pub fn emit_summary(&self, context: &str) {
        if let Ok(mut state) = self.inner.lock() {
            let mut counters: Vec<(String, u64)> = state.counters.drain().collect();
            counters.sort_by(|a, b| a.0.cmp(&b.0));
            let counts_json = if counters.is_empty() {
                "{}".to_string()
            } else {
                let mut out = String::from("{");
                for (idx, (key, value)) in counters.iter().enumerate() {
                    if idx > 0 {
                        out.push(',');
                    }
                    out.push_str(&format!("\"{}\":{}", json_escape(key), value));
                }
                out.push('}');
                out
            };
            let json = format!(
                "{{\"type\":\"debug.summary\",\"context\":\"{}\",\"counts\":{}}}",
                json_escape(context),
                counts_json
            );
            let _ = writeln!(state.writer, "{json}");
        }
    }

    pub fn flush(&self) {
        if let Ok(mut state) = self.inner.lock() {
            let _ = state.writer.flush();
        }
    }
}

pub(crate) fn json_escape(raw: &str) -> String {
    let mut out = String::with_capacity(raw.len() + 8);
    for ch in raw.chars() {
        match ch {
            '\\' => out.push_str("\\\\"),
            '"' => out.push_str("\\\""),
            '\n' => out.push_str("\\n"),
            '\r' => out.push_str("\\r"),
            '\t' => out.push_str("\\t"),
            _ => out.push(ch),
        }
    }
    out
}