endpoint_libs/libs/
log.rs

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