agentguard/commands/
log.rs1use 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}