agentguard/commands/
log.rs

1use agentguard_core::DecisionLog;
2use anyhow::Result;
3use std::path::Path;
4
5pub fn tail(
6    audit: impl AsRef<Path>,
7    n: usize,
8    principal: Option<&str>,
9    action: Option<&str>,
10    output: &str,
11) -> Result<()> {
12    let records = DecisionLog::read_all(audit.as_ref())?;
13    let mut filtered: Vec<_> = records
14        .into_iter()
15        .filter(|r| principal.map(|p| r.principal.contains(p)).unwrap_or(true))
16        .filter(|r| action.map(|a| r.action.contains(a)).unwrap_or(true))
17        .collect();
18    filtered.sort_by_key(|r| std::cmp::Reverse(r.timestamp));
19    let slice = filtered.into_iter().take(n).collect::<Vec<_>>();
20
21    if output == "json" {
22        println!("{}", serde_json::to_string_pretty(&slice)?);
23    } else {
24        for r in slice.iter().rev() {
25            let sym = if r.effect == "allow" { "✓" } else { "✗" };
26            println!(
27                "{} {} {:>5} {} {} {}",
28                r.timestamp.format("%H:%M:%S"),
29                sym,
30                r.effect.to_uppercase(),
31                r.principal,
32                r.action,
33                r.resource
34            );
35        }
36        if slice.is_empty() {
37            println!("(no matching decisions)");
38        }
39    }
40    Ok(())
41}
42
43pub fn dump(audit: impl AsRef<Path>, output: &str) -> Result<()> {
44    let records = DecisionLog::read_all(audit.as_ref())?;
45    if output == "json" {
46        println!("{}", serde_json::to_string_pretty(&records)?);
47    } else {
48        for r in &records {
49            println!("{}", serde_json::to_string_pretty(r)?);
50        }
51    }
52    Ok(())
53}