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}