agentguard_telemetry/sinks/
stdout.rs

1//! Stdout sink — prints events as pretty JSON for local debugging.
2
3use crate::sink::{Sink, SinkError, SinkEvent};
4use async_trait::async_trait;
5use std::io::Write;
6use std::sync::Mutex;
7
8/// Sink that writes each event to stdout, pretty-printed.
9///
10/// The internal `Mutex` serializes writes so output doesn't interleave.
11pub struct StdoutSink {
12    inner: Mutex<std::io::Stdout>,
13}
14
15impl StdoutSink {
16    pub fn new() -> Self {
17        Self {
18            inner: Mutex::new(std::io::stdout()),
19        }
20    }
21}
22
23impl Default for StdoutSink {
24    fn default() -> Self {
25        Self::new()
26    }
27}
28
29#[async_trait]
30impl Sink for StdoutSink {
31    fn name(&self) -> &str {
32        "stdout"
33    }
34
35    async fn emit(&self, event: &SinkEvent) -> Result<(), SinkError> {
36        let json = serde_json::to_string_pretty(event)?;
37        // ponytail: a poisoned mutex would normally panic the
38        // thread; recover by acquiring the inner state anyway
39        // (the data is still consistent — only the recovery
40        // metadata was lost).
41        let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
42        writeln!(guard, "{}", json)?;
43        guard.flush()?;
44        Ok(())
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[tokio::test]
53    async fn stdout_sink_serializes_event() {
54        let sink = StdoutSink::new();
55        let event = SinkEvent::decision("allow", "alice", "x", "y", vec![], vec![], 100);
56        // Smoke test: just ensure it doesn't error.
57        sink.emit(&event).await.unwrap();
58    }
59}