agent_trace/
observability.rs1use anyhow::{Context, Result};
2use std::io::{self, Write};
3use std::path::Path;
4use tracing_subscriber::EnvFilter;
5
6pub trait CliOutput: Send + Sync {
11 fn line(&self, message: &str) -> Result<()>;
12 fn warn(&self, message: &str) -> Result<()>;
13 fn error(&self, message: &str) -> Result<()>;
14 fn raw_stdout(&self, content: &str) -> Result<()>;
15}
16
17#[derive(Debug, Clone, Copy)]
18pub struct TerminalOutput {
19 quiet: bool,
20}
21
22impl TerminalOutput {
23 pub fn new(quiet: bool) -> Self {
24 Self { quiet }
25 }
26}
27
28impl CliOutput for TerminalOutput {
29 fn line(&self, message: &str) -> Result<()> {
30 if self.quiet {
31 return Ok(());
32 }
33 let mut stdout = io::stdout().lock();
34 writeln!(stdout, "{message}").context("writing stdout")
35 }
36
37 fn warn(&self, message: &str) -> Result<()> {
38 let mut stderr = io::stderr().lock();
39 writeln!(stderr, "{message}").context("writing stderr")
40 }
41
42 fn error(&self, message: &str) -> Result<()> {
43 let mut stderr = io::stderr().lock();
44 writeln!(stderr, "{message}").context("writing stderr")
45 }
46
47 fn raw_stdout(&self, content: &str) -> Result<()> {
48 let mut stdout = io::stdout().lock();
49 stdout
50 .write_all(content.as_bytes())
51 .context("writing raw stdout")?;
52 stdout.flush().context("flushing stdout")
53 }
54}
55
56#[derive(Debug, Clone, Copy)]
57pub struct NoopOutput;
58
59impl CliOutput for NoopOutput {
60 fn line(&self, _message: &str) -> Result<()> {
61 Ok(())
62 }
63
64 fn warn(&self, _message: &str) -> Result<()> {
65 Ok(())
66 }
67
68 fn error(&self, _message: &str) -> Result<()> {
69 Ok(())
70 }
71
72 fn raw_stdout(&self, _content: &str) -> Result<()> {
73 Ok(())
74 }
75}
76
77pub fn init_tracing(verbosity: u8) -> Result<()> {
78 let default_level = match verbosity {
79 0 => "warn",
80 1 => "info",
81 _ => "debug",
82 };
83 let filter =
84 EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(default_level));
85
86 tracing_subscriber::fmt()
87 .with_writer(io::stderr)
88 .with_env_filter(filter)
89 .try_init()
90 .map_err(|e| anyhow::anyhow!("initializing tracing subscriber: {e}"))
91}
92
93pub fn format_permission_denied(path: &Path, reason: &str) -> String {
94 format!("Permission denied: {} — {}", path.display(), reason)
95}