Skip to main content

tatara_lisp_script/stdlib/
log.rs

1//! Structured logging to stderr. Level-aware — respects TATARA_LOG env
2//! variable (debug | info | warn | error). Default level is info.
3//!
4//!   (log-debug MSG)
5//!   (log-info  MSG)
6//!   (log-warn  MSG)
7//!   (log-error MSG)
8//!
9//! Each emits a timestamped, level-prefixed line. Scripts that want
10//! structured JSON should use (json-stringify …) and (eprint-line).
11
12use std::sync::Arc;
13
14use tatara_lisp_eval::{Arity, Interpreter, Value};
15
16use crate::script_ctx::ScriptCtx;
17use crate::stdlib::env::str_arg;
18
19const LEVEL_DEBUG: u8 = 0;
20const LEVEL_INFO: u8 = 1;
21const LEVEL_WARN: u8 = 2;
22const LEVEL_ERROR: u8 = 3;
23
24pub fn install(interp: &mut Interpreter<ScriptCtx>) {
25    for (name, lvl, label) in [
26        ("log-debug", LEVEL_DEBUG, "DEBUG"),
27        ("log-info", LEVEL_INFO, "INFO"),
28        ("log-warn", LEVEL_WARN, "WARN"),
29        ("log-error", LEVEL_ERROR, "ERROR"),
30    ] {
31        interp.register_fn(
32            name,
33            Arity::Exact(1),
34            move |args: &[Value], _ctx: &mut ScriptCtx, sp| {
35                let msg = str_arg(&args[0], name, sp)?;
36                if lvl >= current_level() {
37                    eprintln!("[{}] {} {}", now_hms(), label, msg);
38                }
39                Ok(Value::Nil)
40            },
41        );
42    }
43}
44
45fn current_level() -> u8 {
46    match std::env::var("TATARA_LOG")
47        .ok()
48        .as_deref()
49        .unwrap_or("info")
50    {
51        "debug" => LEVEL_DEBUG,
52        "warn" => LEVEL_WARN,
53        "error" => LEVEL_ERROR,
54        _ => LEVEL_INFO,
55    }
56}
57
58fn now_hms() -> String {
59    // HH:MM:SS (local-less; the timestamp is a relative breadcrumb, not a
60    // precise wall-clock for audit). For audit, use (now-rfc3339) from time.
61    let secs = std::time::SystemTime::now()
62        .duration_since(std::time::UNIX_EPOCH)
63        .map(|d| d.as_secs())
64        .unwrap_or(0);
65    let h = (secs / 3600) % 24;
66    let m = (secs / 60) % 60;
67    let s = secs % 60;
68    Arc::<str>::from(format!("{h:02}:{m:02}:{s:02}")).to_string()
69}