Skip to main content

polydat_core/library/
log_levels.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! `log_debug` / `log_info` / `log_warn` / `log_error` — pass-through
5//! logging node functions (SRD-66 §"Surface 5").
6//!
7//! Each takes one wire input, emits a single diag line at the named
8//! level containing the value's display form, and returns the input
9//! unchanged. The pass-through return value lets workloads insert
10//! logging into a binding chain without restructuring:
11//!
12//! ```yaml
13//! result: |
14//!   has_sai := log_info(regex_match(body, "..."))
15//! ```
16//!
17//! Probe phases run rarely and gate downstream dispatch — surfacing
18//! the detected facts at session start without a custom readout is
19//! the load-bearing use case.
20//!
21//! Diag emission routes through [`crate::library::support::audit`] so the
22//! host's installed audit sink forwards every line to its own logger
23//! alongside the rest of the run trace. With no sink installed (unit
24//! tests, dryrun, pre-init) lines fall back to stderr.
25
26use crate::ast::Value;
27
28fn log_at(level: crate::library::support::audit::LogLevel, fn_name: &str, value: &Value) {
29    let msg = format!("{fn_name}: {}", value.to_display_string());
30    crate::library::support::audit::log(level, &msg);
31}
32
33#[crate::polydat_node(category = Diagnostic, purity = SideChannel(LogBuffer))]
34fn log_debug(value: Value) -> Value {
35    log_at(
36        crate::library::support::audit::LogLevel::Debug,
37        "log_debug",
38        &value,
39    );
40    value
41}
42
43#[crate::polydat_node(category = Diagnostic, purity = SideChannel(LogBuffer))]
44fn log_info(value: Value) -> Value {
45    log_at(
46        crate::library::support::audit::LogLevel::Info,
47        "log_info",
48        &value,
49    );
50    value
51}
52
53#[crate::polydat_node(category = Diagnostic, purity = SideChannel(LogBuffer))]
54fn log_warn(value: Value) -> Value {
55    log_at(
56        crate::library::support::audit::LogLevel::Warn,
57        "log_warn",
58        &value,
59    );
60    value
61}
62
63#[crate::polydat_node(category = Diagnostic, purity = SideChannel(LogBuffer))]
64fn log_error(value: Value) -> Value {
65    log_at(
66        crate::library::support::audit::LogLevel::Error,
67        "log_error",
68        &value,
69    );
70    value
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use crate::ast::{PolydatNode, PortType, Slot};
77
78    #[test]
79    fn log_debug_passthrough() {
80        let node = LogDebug::new(PortType::Str);
81        let mut out = [Value::None];
82        node.eval(&[Value::Str("hello".into())], &mut out);
83        assert_eq!(out[0].as_str(), "hello");
84    }
85
86    #[test]
87    fn log_info_passthrough() {
88        let node = LogInfo::new(PortType::Bool);
89        let mut out = [Value::None];
90        node.eval(&[Value::Bool(true)], &mut out);
91        assert!(out[0].as_bool());
92    }
93
94    #[test]
95    fn log_warn_passthrough() {
96        let node = LogWarn::new(PortType::U64);
97        let mut out = [Value::None];
98        node.eval(&[Value::U64(42)], &mut out);
99        assert_eq!(out[0].as_u64(), 42);
100    }
101
102    #[test]
103    fn log_error_passthrough() {
104        let node = LogError::new(PortType::F64);
105        let mut out = [Value::None];
106        node.eval(&[Value::F64(1.5)], &mut out);
107        assert_eq!(out[0].as_f64(), 1.5);
108    }
109
110    #[test]
111    fn log_node_meta_has_one_input_one_output() {
112        let node = LogInfo::new(PortType::Str);
113        assert_eq!(node.meta().ins.len(), 1);
114        assert_eq!(node.meta().outs.len(), 1);
115        assert_eq!(node.meta().name, "log_info");
116    }
117
118    #[test]
119    fn log_info_meta_tracks_constructor_port_type() {
120        let node = LogInfo::new(PortType::Bool);
121        assert_eq!(node.meta().outs[0].typ, PortType::Bool);
122        if let Slot::Wire(p) = &node.meta().ins[0] {
123            assert_eq!(p.typ, PortType::Bool);
124        } else {
125            panic!("expected Slot::Wire");
126        }
127    }
128
129    #[test]
130    fn log_purity_is_side_channel() {
131        use crate::ast::{Purity, SideChannelSink};
132        let node = LogInfo::new(PortType::U64);
133        let p = node.purity();
134        assert!(matches!(
135            p,
136            Purity::SideChannel {
137                sink: SideChannelSink::LogBuffer
138            }
139        ));
140    }
141}