endpoint_libs/libs/
log.rs

1use std::path::PathBuf;
2use std::str::FromStr;
3use std::sync::Arc;
4
5use eyre::eyre;
6use serde::{Deserialize, Serialize};
7use tracing::{level_filters::LevelFilter, Level};
8use tracing_subscriber::fmt;
9use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
10use tracing_subscriber::util::SubscriberInitExt;
11use tracing_subscriber::Layer;
12use tracing_subscriber::{registry, EnvFilter};
13
14#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
15#[serde(rename_all = "lowercase")]
16pub enum LogLevel {
17    #[default]
18    Off,
19    Error,
20    Warn,
21    Info,
22    Debug,
23    Trace,
24    Detail,
25}
26
27impl From<LogLevel> for LevelFilter {
28    fn from(value: LogLevel) -> Self {
29        match value {
30            LogLevel::Error => LevelFilter::ERROR,
31            LogLevel::Warn => LevelFilter::WARN,
32            LogLevel::Info => LevelFilter::INFO,
33            LogLevel::Debug => LevelFilter::DEBUG,
34            LogLevel::Trace => LevelFilter::TRACE,
35            LogLevel::Detail => LevelFilter::TRACE,
36            LogLevel::Off => LevelFilter::OFF,
37        }
38    }
39}
40
41impl From<LogLevel> for Level {
42    fn from(value: LogLevel) -> Self {
43        match value {
44            LogLevel::Error => Level::ERROR,
45            LogLevel::Warn => Level::WARN,
46            LogLevel::Info => Level::INFO,
47            LogLevel::Debug => Level::DEBUG,
48            LogLevel::Trace => Level::TRACE,
49            LogLevel::Off => Level::TRACE,
50            LogLevel::Detail => Level::TRACE,
51        }
52    }
53}
54
55impl FromStr for LogLevel {
56    type Err = eyre::Error;
57    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
58        match s.to_ascii_lowercase().as_ref() {
59            "error" => Ok(LogLevel::Error),
60            "warn" => Ok(LogLevel::Warn),
61            "info" => Ok(LogLevel::Info),
62            "debug" => Ok(LogLevel::Debug),
63            "trace" => Ok(LogLevel::Trace),
64            "detail" => Ok(LogLevel::Detail),
65            "off" => Ok(LogLevel::Off),
66            _ => Err(eyre!("Invalid log level: {}", s)),
67        }
68    }
69}
70
71fn build_env_filter(log_level: LogLevel) -> eyre::Result<EnvFilter> {
72    let level: Level = log_level.into();
73    let mut filter = EnvFilter::from_default_env().add_directive(level.into());
74    if log_level != LogLevel::Detail {
75        filter = filter
76            .add_directive("tungstenite::protocol=debug".parse()?)
77            .add_directive("tokio_postgres::connection=debug".parse()?)
78            .add_directive("tokio_util::codec::framed_impl=debug".parse()?)
79            .add_directive("tokio_tungstenite=debug".parse()?)
80            .add_directive("h2=info".parse()?)
81            .add_directive("rustls::client::hs=info".parse()?)
82            .add_directive("rustls::client::tls13=info".parse()?)
83            .add_directive("hyper::client=info".parse()?)
84            .add_directive("hyper::proto=info".parse()?)
85            .add_directive("mio=info".parse()?)
86            .add_directive("want=info".parse()?)
87            .add_directive("sqlparser=info".parse()?);
88    }
89    Ok(filter)
90}
91
92pub enum LoggingGuard {
93    NonBlocking(tracing_appender::non_blocking::WorkerGuard, PathBuf),
94    StdoutWithPath(Option<PathBuf>),
95}
96impl LoggingGuard {
97    pub fn get_file(&self) -> Option<PathBuf> {
98        match self {
99            LoggingGuard::NonBlocking(_guard, path) => Some(path.clone()),
100            LoggingGuard::StdoutWithPath(path) => path.clone(),
101        }
102    }
103}
104pub fn setup_logs(
105    log_level: LogLevel,
106    log_dir_and_file_prefix: Option<(PathBuf, &str, Option<LogLevel>)>,
107) -> eyre::Result<()> {
108    let filter = build_env_filter(log_level)?;
109
110    let stdout_layer: tracing_subscriber::filter::Filtered<
111        fmt::Layer<registry::Registry>,
112        EnvFilter,
113        registry::Registry,
114    > = fmt::layer()
115        .with_thread_names(true)
116        .with_line_number(true)
117        .with_filter(filter);
118
119    if let Some((log_dir, file_prefix, file_log_level)) = log_dir_and_file_prefix {
120        let file_filter = if let Some(file_log_level) = file_log_level {
121            build_env_filter(file_log_level)?
122        } else {
123            build_env_filter(log_level)?
124        };
125
126        registry()
127            .with(stdout_layer)
128            .with(
129                fmt::layer()
130                    .with_thread_names(true)
131                    .with_line_number(true)
132                    .with_ansi(false)
133                    .with_writer(tracing_appender::rolling::hourly(log_dir, file_prefix))
134                    .with_filter(file_filter),
135            )
136            .init();
137    } else {
138        registry().with(stdout_layer).init();
139    }
140
141    Ok(())
142}
143
144#[derive(Clone)]
145pub struct DynLogger {
146    logger: Arc<dyn Fn(&str) + Send + Sync>,
147}
148impl DynLogger {
149    pub fn new(logger: Arc<dyn Fn(&str) + Send + Sync>) -> Self {
150        Self { logger }
151    }
152    pub fn empty() -> Self {
153        Self {
154            logger: Arc::new(|_| {}),
155        }
156    }
157    pub fn log(&self, msg: impl AsRef<str>) {
158        (self.logger)(msg.as_ref())
159    }
160}
161
162/// actually test writing, there is no direct way to check if the application has the ownership or the write access
163pub fn can_create_file_in_directory(directory: &str) -> bool {
164    let test_file_path: String = format!("{directory}/test_file.txt");
165    match std::fs::File::create(&test_file_path) {
166        Ok(file) => {
167            // File created successfully; remove it after checking
168            drop(file);
169            if let Err(err) = std::fs::remove_file(&test_file_path) {
170                eprintln!("Error deleting test file: {err}");
171            }
172            true
173        }
174        Err(_) => false,
175    }
176}