autopulse_utils/
logs.rs

1use anyhow::{Context, Ok};
2use serde::Deserialize;
3use std::path::PathBuf;
4use tracing_appender::non_blocking::WorkerGuard;
5use tracing_appender::rolling::RollingFileAppender;
6pub use tracing_appender::rolling::Rotation;
7use tracing_subscriber::util::SubscriberInitExt;
8use tracing_subscriber::{fmt, layer::SubscriberExt, EnvFilter};
9
10#[derive(Clone, Deserialize, Default)]
11#[serde(rename_all = "lowercase")]
12pub enum LogLevel {
13    Trace,
14    Debug,
15    #[default]
16    Info,
17    Warn,
18    Error,
19}
20
21impl std::fmt::Display for LogLevel {
22    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
23        match self {
24            Self::Trace => write!(f, "trace"),
25            Self::Debug => write!(f, "debug"),
26            Self::Info => write!(f, "info"),
27            Self::Warn => write!(f, "warn"),
28            Self::Error => write!(f, "error"),
29        }
30    }
31}
32
33impl std::str::FromStr for LogLevel {
34    type Err = anyhow::Error;
35
36    fn from_str(s: &str) -> Result<Self, Self::Err> {
37        match s {
38            "trace" => Ok(Self::Trace),
39            "debug" => Ok(Self::Debug),
40            "info" => Ok(Self::Info),
41            "warn" => Ok(Self::Warn),
42            "error" => Ok(Self::Error),
43            _ => Err(anyhow::anyhow!("Invalid log level")),
44        }
45    }
46}
47
48pub fn setup_logs(
49    log_level: &LogLevel,
50    log_file: &Option<PathBuf>,
51    log_file_rollover: Rotation,
52    api_logging: bool,
53) -> anyhow::Result<Option<WorkerGuard>> {
54    let timer = tracing_subscriber::fmt::time::OffsetTime::local_rfc_3339()
55        .context("Failed to initialize the timer")?;
56
57    let mut file_guard = None;
58
59    let mut filter =
60        EnvFilter::from_default_env().add_directive(format!("autopulse={log_level}").parse()?);
61
62    if api_logging {
63        filter = filter
64            .add_directive("actix_web=info".parse()?)
65            .add_directive("actix_server::builder=info".parse()?);
66    }
67
68    let registry = tracing_subscriber::registry().with(filter);
69
70    if let Some(log_file) = log_file {
71        let writer = RollingFileAppender::new(
72            log_file_rollover,
73            log_file
74                .parent()
75                .ok_or_else(|| anyhow::anyhow!("Failed to get parent directory of log file"))?,
76            log_file
77                .file_name()
78                .ok_or_else(|| anyhow::anyhow!("Failed to get file name of log file"))?,
79        );
80
81        let (non_blocking, guard) = tracing_appender::non_blocking(writer);
82        file_guard = Some(guard);
83
84        let file_layer = fmt::layer()
85            .with_writer(non_blocking)
86            .with_ansi(false)
87            .with_timer(timer.clone());
88
89        let registry = registry.with(file_layer);
90
91        let console_layer = fmt::layer()
92            .with_writer(std::io::stdout)
93            .with_ansi(true)
94            .with_timer(timer);
95
96        registry.with(console_layer).init();
97    } else {
98        let console_layer = fmt::layer()
99            .with_writer(std::io::stdout)
100            .with_ansi(true)
101            .with_timer(timer);
102
103        registry.with(console_layer).init();
104    }
105    Ok(file_guard)
106}