alith_devices/
logging.rs

1use crate::build::get_target_directory;
2
3use indenter::indented;
4
5use std::fmt::Write;
6use std::path::PathBuf;
7use std::sync::Arc;
8use std::{fs::create_dir_all, path::Path};
9use tracing_subscriber::layer::SubscriberExt;
10
11#[derive(Clone, Debug)]
12pub struct LoggingConfig {
13    pub level: tracing::Level,
14    pub logging_enabled: bool,
15    pub logger_name: String,
16    pub log_path: Option<PathBuf>,
17    pub _tracing_guard: Option<Arc<tracing::subscriber::DefaultGuard>>,
18    pub build_log: bool,
19}
20
21impl Default for LoggingConfig {
22    fn default() -> Self {
23        Self {
24            level: tracing::Level::INFO,
25            logging_enabled: true,
26            logger_name: "llm_interface".to_string(),
27            log_path: None,
28            _tracing_guard: None,
29            build_log: false,
30        }
31    }
32}
33
34impl LoggingConfig {
35    pub fn new() -> Self {
36        Default::default()
37    }
38
39    pub fn load_logger(&mut self) -> crate::Result<()> {
40        self._tracing_guard = if self.logging_enabled {
41            Some(Arc::new(self.create_logger()?))
42        } else {
43            None
44        };
45
46        Ok(())
47    }
48
49    fn create_logger(&mut self) -> crate::Result<tracing::subscriber::DefaultGuard> {
50        let log_dir = if let Some(log_path) = &self.log_path {
51            log_path.clone()
52        } else {
53            let target_dir = get_target_directory()?;
54            if self.build_log {
55                target_dir.join("llm_devices_build_logs")
56            } else {
57                target_dir
58                    .parent()
59                    .map(Path::to_path_buf)
60                    .ok_or_else(|| anyhow::anyhow!("Failed to get parent directory"))?
61                    .join("llm_logs")
62            }
63        };
64
65        if !Path::new(&log_dir).exists() {
66            create_dir_all(&log_dir).expect("Failed to create log directory");
67        }
68
69        let file_appender = tracing_appender::rolling::RollingFileAppender::builder()
70            .rotation(tracing_appender::rolling::Rotation::HOURLY)
71            .max_log_files(6)
72            .filename_prefix(&self.logger_name)
73            .filename_suffix("log")
74            .build(log_dir)
75            .unwrap();
76
77        let filter = tracing_subscriber::EnvFilter::builder()
78            .with_default_directive(self.level.into())
79            .parse_lossy("");
80
81        let file_layer = tracing_subscriber::fmt::layer()
82            .pretty()
83            .with_ansi(false) // Disable ANSI codes for file output
84            .with_writer(file_appender);
85
86        let terminal_layer = tracing_subscriber::fmt::layer()
87            .compact()
88            .with_ansi(false) // Enable ANSI codes for terminal output
89            .with_writer(std::io::stdout);
90
91        let subscriber = tracing_subscriber::registry()
92            .with(filter)
93            .with(file_layer)
94            .with(terminal_layer);
95
96        Ok(tracing::subscriber::set_default(subscriber))
97    }
98}
99
100#[allow(dead_code)]
101pub trait LoggingConfigTrait {
102    fn logging_config_mut(&mut self) -> &mut LoggingConfig;
103
104    /// Enables or disables logging for the configuration.
105    ///
106    /// # Arguments
107    ///
108    /// * `enabled` - A boolean value where `true` enables logging and `false` disables it.
109    ///
110    /// # Returns
111    ///
112    /// Returns `Self` to allow for method chaining.
113    fn logging_enabled(mut self, enabled: bool) -> Self
114    where
115        Self: Sized,
116    {
117        self.logging_config_mut().logging_enabled = enabled;
118        self
119    }
120
121    /// Sets the name of the logger.
122    ///
123    /// This method allows you to specify a custom name for the logger, which can be useful
124    /// for identifying the source of log messages in applications with multiple components
125    /// or services.
126    ///
127    /// # Arguments
128    ///
129    /// * `logger_name` - A string-like value that can be converted into a `String`.
130    ///   This will be used as the name for the logger.
131    ///
132    /// # Returns
133    ///
134    /// Returns `Self` to allow for method chaining.
135    fn logger_name<S: Into<String>>(mut self, logger_name: S) -> Self
136    where
137        Self: Sized,
138    {
139        self.logging_config_mut().logger_name = logger_name.into();
140        self
141    }
142
143    /// Sets the path where log files will be stored.
144    ///
145    /// # Arguments
146    ///
147    /// * `path` - A path-like object that represents the directory where log files should be stored.
148    ///
149    /// # Returns
150    ///
151    /// Returns `Self` to allow for method chaining.
152    ///
153    /// # Notes
154    ///
155    /// - If no path is set, the default path is `CARGO_MANIFEST_DIRECTORY/llm_logs`.
156    fn log_path<P: AsRef<Path>>(mut self, path: P) -> Self
157    where
158        Self: Sized,
159    {
160        self.logging_config_mut().log_path = Some(path.as_ref().to_path_buf());
161        self
162    }
163
164    /// Sets the log level to TRACE.
165    ///
166    /// Use TRACE for purely "I am here!" logs. They indicate the flow of execution
167    /// without additional context.
168    ///
169    /// TRACE logs should not be used to log variables or decisions.
170    fn log_level_trace(mut self) -> Self
171    where
172        Self: Sized,
173    {
174        self.logging_config_mut().level = tracing::Level::TRACE;
175        self
176    }
177
178    /// Sets the log level to DEBUG.
179    ///
180    /// Use DEBUG to log variables or decisions. This level is appropriate for information
181    /// that is useful for debugging but not necessary for normal operation.
182    ///
183    /// # Examples
184    ///
185    /// DEBUG logs should focus on logging specific data points or choices made in the code.
186    fn log_level_debug(mut self) -> Self
187    where
188        Self: Sized,
189    {
190        self.logging_config_mut().level = tracing::Level::DEBUG;
191        self
192    }
193
194    /// Sets the log level to INFO.
195    ///
196    /// Use INFO for important runtime events that don't prevent the application from working
197    /// but are significant milestones or status updates.
198    ///
199    /// INFO logs should provide a high-level overview of the application's operation.
200    fn log_level_info(mut self) -> Self
201    where
202        Self: Sized,
203    {
204        self.logging_config_mut().level = tracing::Level::INFO;
205        self
206    }
207
208    /// Sets the log level to WARN.
209    ///
210    /// Use WARN for errors that were recovered from or potential issues that don't prevent
211    /// the application from working but might lead to problems if not addressed.
212    ///
213    /// WARN logs often indicate situations that should be monitored or addressed soon.
214    fn log_level_warn(mut self) -> Self
215    where
216        Self: Sized,
217    {
218        self.logging_config_mut().level = tracing::Level::WARN;
219        self
220    }
221
222    /// Sets the log level to ERROR.
223    ///
224    /// Use ERROR to log errors within specific tasks that cause the task to fail
225    /// but don't crash the entire application.
226    ///
227    /// ERROR logs indicate serious issues that need immediate attention but don't
228    /// necessarily stop the application.
229    fn log_level_error(mut self) -> Self
230    where
231        Self: Sized,
232    {
233        self.logging_config_mut().level = tracing::Level::ERROR;
234        self
235    }
236}
237
238pub fn i_ln(f: &mut std::fmt::Formatter<'_>, arg: std::fmt::Arguments<'_>) -> std::fmt::Result {
239    write!(indented(f), "{}", arg)?;
240    Ok(())
241}
242
243pub fn i_nln(f: &mut std::fmt::Formatter<'_>, arg: std::fmt::Arguments<'_>) -> std::fmt::Result {
244    writeln!(indented(f), "{}", arg)?;
245    Ok(())
246}
247
248pub fn i_lns(
249    f: &mut std::fmt::Formatter<'_>,
250    args: &[std::fmt::Arguments<'_>],
251) -> std::fmt::Result {
252    for arg in args {
253        write!(indented(f), "{}", arg)?;
254    }
255    Ok(())
256}
257
258pub fn i_nlns(
259    f: &mut std::fmt::Formatter<'_>,
260    args: &[std::fmt::Arguments<'_>],
261) -> std::fmt::Result {
262    for arg in args {
263        writeln!(indented(f), "{}", arg)?;
264    }
265    Ok(())
266}