Skip to main content

aperture_cli/cli/
tracing_init.rs

1//! Tracing/logging initialization for the CLI.
2
3use tracing_subscriber::EnvFilter;
4
5/// Wrapper type to write logs to file or stderr.
6struct FileOrStderr {
7    file: Option<std::sync::Mutex<std::fs::File>>,
8}
9
10impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for FileOrStderr {
11    type Writer = Box<dyn std::io::Write + 'a>;
12
13    fn make_writer(&'a self) -> Self::Writer {
14        self.file
15            .as_ref()
16            .and_then(|mutex| mutex.lock().ok())
17            .and_then(|file| file.try_clone().ok())
18            .map_or_else(
19                || Box::new(std::io::stderr()) as Self::Writer,
20                |cloned| Box::new(cloned) as Self::Writer,
21            )
22    }
23}
24
25/// Initialize tracing-subscriber for request/response logging.
26pub fn init_tracing(verbosity: u8) {
27    use std::fs::OpenOptions;
28    use std::sync::Mutex;
29    use tracing_subscriber::fmt::format::FmtSpan;
30    use tracing_subscriber::layer::SubscriberExt;
31    use tracing_subscriber::util::SubscriberInitExt;
32
33    let log_level_str = if verbosity > 0 {
34        match verbosity {
35            1 => "debug".to_string(),
36            _ => "trace".to_string(),
37        }
38    } else {
39        std::env::var("APERTURE_LOG").unwrap_or_else(|_| "error".to_string())
40    };
41
42    let env_filter = EnvFilter::try_new(&log_level_str)
43        .or_else(|_| EnvFilter::try_new("error"))
44        .unwrap_or_else(|_| EnvFilter::new("error"));
45
46    let log_format = std::env::var("APERTURE_LOG_FORMAT")
47        .map_or_else(|_| "text".to_string(), |s| s.to_lowercase());
48
49    if log_format != "json" && log_format != "text" {
50        eprintln!(
51            "Warning: Unrecognized APERTURE_LOG_FORMAT '{log_format}'. Valid values: 'json', 'text'. Using 'text'."
52        );
53    }
54
55    let writer = std::env::var("APERTURE_LOG_FILE").ok().map_or_else(
56        || FileOrStderr { file: None },
57        |path| match OpenOptions::new().create(true).append(true).open(&path) {
58            Ok(file) => FileOrStderr {
59                file: Some(Mutex::new(file)),
60            },
61            Err(e) => {
62                eprintln!("Warning: Could not open log file '{path}': {e}. Using stderr.");
63                FileOrStderr { file: None }
64            }
65        },
66    );
67
68    if log_format == "json" {
69        let json_layer = tracing_subscriber::fmt::layer()
70            .json()
71            .with_span_list(false)
72            .with_target(true)
73            .with_thread_ids(false)
74            .with_line_number(true)
75            .with_writer(writer);
76        tracing_subscriber::registry()
77            .with(env_filter)
78            .with(json_layer)
79            .init();
80    } else {
81        let fmt_layer = tracing_subscriber::fmt::layer()
82            .pretty()
83            .with_span_events(FmtSpan::CLOSE)
84            .with_target(false)
85            .with_thread_ids(false)
86            .with_line_number(false)
87            .with_writer(writer);
88        tracing_subscriber::registry()
89            .with(env_filter)
90            .with(fmt_layer)
91            .init();
92    }
93}