llm_devices/
logging.rs

1use crate::get_target_directory;
2
3use colorful::Colorful;
4use indenter::indented;
5use std::fmt::Write;
6use std::path::PathBuf;
7use std::{fs::create_dir_all, path::Path};
8use tracing_subscriber::layer::SubscriberExt;
9
10/// Configuration for the logging system.
11///
12/// Manages log levels, file output, and logger initialization.
13#[derive(Clone, Debug)]
14pub struct LoggingConfig {
15    /// Log level threshold (ERROR, WARN, INFO, DEBUG, TRACE)
16    pub level: tracing::Level,
17
18    /// Whether logging is enabled
19    pub logging_enabled: bool,
20
21    /// Name used to identify this logger in output
22    pub logger_name: String,
23
24    /// Custom path for log files. If None, uses default path
25    pub log_path: Option<PathBuf>,
26
27    /// Guard for the tracing subscriber
28    pub _tracing_guard: Option<std::sync::Arc<tracing::subscriber::DefaultGuard>>,
29
30    /// Whether this is a build log
31    pub build_log: bool,
32}
33
34impl Default for LoggingConfig {
35    fn default() -> Self {
36        Self {
37            level: tracing::Level::INFO,
38            logging_enabled: true,
39            logger_name: "llm_interface".to_string(),
40            log_path: None,
41            _tracing_guard: None,
42            build_log: false,
43        }
44    }
45}
46
47impl LoggingConfig {
48    /// Creates a new LoggingConfig with default settings.
49    ///
50    /// Defaults to:
51    /// - INFO level
52    /// - Logging enabled
53    /// - "llm_interface" logger name
54    /// - Default log path
55    pub fn new() -> Self {
56        Default::default()
57    }
58
59    /// Initializes and starts the logger with the current configuration.
60    ///
61    /// If logging is enabled, creates log files and sets up console output.
62    /// Logs are rotated hourly and up to 6 files are kept.
63    ///
64    /// # Errors
65    ///
66    /// Returns error if:
67    /// - Log directory creation fails
68    /// - File appender creation fails
69    pub fn load_logger(&mut self) -> crate::Result<()> {
70        self._tracing_guard = if self.logging_enabled {
71            Some(std::sync::Arc::new(self.create_logger()?))
72        } else {
73            None
74        };
75
76        println!(
77            "{}",
78            format!("Starting {} Logger", self.logger_name)
79                .color(colorful::RGB::new(0, 139, 248))
80                .bold()
81        );
82
83        Ok(())
84    }
85
86    fn create_logger(&mut self) -> crate::Result<tracing::subscriber::DefaultGuard> {
87        let log_dir = if let Some(log_path) = &self.log_path {
88            log_path.clone()
89        } else {
90            let target_dir = get_target_directory()?;
91            if self.build_log {
92                target_dir.join("llm_devices_build_logs")
93            } else {
94                target_dir
95                    .parent()
96                    .map(Path::to_path_buf)
97                    .ok_or_else(|| anyhow::anyhow!("Failed to get parent directory"))?
98                    .join("llm_logs")
99            }
100        };
101
102        if !Path::new(&log_dir).exists() {
103            create_dir_all(&log_dir).expect("Failed to create log directory");
104        }
105
106        let file_appender = tracing_appender::rolling::RollingFileAppender::builder()
107            .rotation(tracing_appender::rolling::Rotation::HOURLY)
108            .max_log_files(6)
109            .filename_prefix(&self.logger_name)
110            .filename_suffix("log")
111            .build(log_dir)
112            .unwrap();
113
114        let filter = tracing_subscriber::EnvFilter::builder()
115            .with_default_directive(self.level.into())
116            .parse_lossy("");
117
118        let file_layer = tracing_subscriber::fmt::layer()
119            .pretty()
120            .with_ansi(false) // Disable ANSI codes for file output
121            .with_writer(file_appender);
122
123        let terminal_layer = tracing_subscriber::fmt::layer()
124            .compact()
125            .with_ansi(false) // Enable ANSI codes for terminal output
126            .with_writer(std::io::stdout);
127
128        let subscriber = tracing_subscriber::registry()
129            .with(filter)
130            .with(file_layer)
131            .with(terminal_layer);
132
133        Ok(tracing::subscriber::set_default(subscriber))
134    }
135}
136
137/// Trait for configuring logging behavior.
138///
139/// Provides a fluent interface for configuring logging settings.
140#[allow(dead_code)]
141pub trait LoggingConfigTrait {
142    fn logging_config_mut(&mut self) -> &mut LoggingConfig;
143
144    /// Enables or disables logging for the configuration.
145    ///
146    /// # Arguments
147    ///
148    /// * `enabled` - A boolean value where `true` enables logging and `false` disables it.
149    ///
150    /// # Returns
151    ///
152    /// Returns `Self` to allow for method chaining.
153    fn logging_enabled(mut self, enabled: bool) -> Self
154    where
155        Self: Sized,
156    {
157        self.logging_config_mut().logging_enabled = enabled;
158        self
159    }
160
161    /// Sets the name of the logger.
162    ///
163    /// This method allows you to specify a custom name for the logger, which can be useful
164    /// for identifying the source of log messages in applications with multiple components
165    /// or services.
166    ///
167    /// # Arguments
168    ///
169    /// * `logger_name` - A string-like value that can be converted into a `String`.
170    ///   This will be used as the name for the logger.
171    ///
172    /// # Returns
173    ///
174    /// Returns `Self` to allow for method chaining.
175    fn logger_name<S: Into<String>>(mut self, logger_name: S) -> Self
176    where
177        Self: Sized,
178    {
179        self.logging_config_mut().logger_name = logger_name.into();
180        self
181    }
182
183    /// Sets the path where log files will be stored.
184    ///
185    /// # Arguments
186    ///
187    /// * `path` - A path-like object that represents the directory where log files should be stored.
188    ///
189    /// # Returns
190    ///
191    /// Returns `Self` to allow for method chaining.
192    ///
193    /// # Notes
194    ///
195    /// - If no path is set, the default path is `CARGO_MANIFEST_DIRECTORY/llm_logs`.
196    fn log_path<P: AsRef<Path>>(mut self, path: P) -> Self
197    where
198        Self: Sized,
199    {
200        self.logging_config_mut().log_path = Some(path.as_ref().to_path_buf());
201        self
202    }
203
204    /// Sets the log level to TRACE.
205    ///
206    /// Use TRACE for purely "I am here!" logs. They indicate the flow of execution
207    /// without additional context.
208    ///
209    /// TRACE logs should not be used to log variables or decisions.
210    fn log_level_trace(mut self) -> Self
211    where
212        Self: Sized,
213    {
214        self.logging_config_mut().level = tracing::Level::TRACE;
215        self
216    }
217
218    /// Sets the log level to DEBUG.
219    ///
220    /// Use DEBUG to log variables or decisions. This level is appropriate for information
221    /// that is useful for debugging but not necessary for normal operation.
222    ///
223    /// # Examples
224    ///
225    /// DEBUG logs should focus on logging specific data points or choices made in the code.
226    fn log_level_debug(mut self) -> Self
227    where
228        Self: Sized,
229    {
230        self.logging_config_mut().level = tracing::Level::DEBUG;
231        self
232    }
233
234    /// Sets the log level to INFO.
235    ///
236    /// Use INFO for important runtime events that don't prevent the application from working
237    /// but are significant milestones or status updates.
238    ///
239    /// INFO logs should provide a high-level overview of the application's operation.
240    fn log_level_info(mut self) -> Self
241    where
242        Self: Sized,
243    {
244        self.logging_config_mut().level = tracing::Level::INFO;
245        self
246    }
247
248    /// Sets the log level to WARN.
249    ///
250    /// Use WARN for errors that were recovered from or potential issues that don't prevent
251    /// the application from working but might lead to problems if not addressed.
252    ///
253    /// WARN logs often indicate situations that should be monitored or addressed soon.
254    fn log_level_warn(mut self) -> Self
255    where
256        Self: Sized,
257    {
258        self.logging_config_mut().level = tracing::Level::WARN;
259        self
260    }
261
262    /// Sets the log level to ERROR.
263    ///
264    /// Use ERROR to log errors within specific tasks that cause the task to fail
265    /// but don't crash the entire application.
266    ///
267    /// ERROR logs indicate serious issues that need immediate attention but don't
268    /// necessarily stop the application.
269    fn log_level_error(mut self) -> Self
270    where
271        Self: Sized,
272    {
273        self.logging_config_mut().level = tracing::Level::ERROR;
274        self
275    }
276}
277
278/// Writes an indented line without newline.
279///
280/// # Arguments
281///
282/// * `f` - The formatter to write to
283/// * `arg` - The arguments to format and write
284///
285/// # Returns
286///
287/// std::fmt::Result indicating success or failure
288pub fn i_ln(f: &mut std::fmt::Formatter<'_>, arg: std::fmt::Arguments<'_>) -> std::fmt::Result {
289    write!(indented(f), "{}", arg)?;
290    Ok(())
291}
292
293/// Writes an indented line with newline.
294///
295/// # Arguments
296///
297/// * `f` - The formatter to write to
298/// * `arg` - The arguments to format and write
299///
300/// # Returns
301///
302/// std::fmt::Result indicating success or failure
303pub fn i_nln(f: &mut std::fmt::Formatter<'_>, arg: std::fmt::Arguments<'_>) -> std::fmt::Result {
304    writeln!(indented(f), "{}", arg)?;
305    Ok(())
306}
307
308/// Writes multiple indented lines without newlines.
309///
310/// # Arguments
311///
312/// * `f` - The formatter to write to
313/// * `args` - Array of arguments to format and write
314///
315/// # Returns
316///
317/// std::fmt::Result indicating success or failure
318pub fn i_lns(
319    f: &mut std::fmt::Formatter<'_>,
320    args: &[std::fmt::Arguments<'_>],
321) -> std::fmt::Result {
322    for arg in args {
323        write!(indented(f), "{}", arg)?;
324    }
325    Ok(())
326}
327
328/// Writes multiple indented lines with newlines.
329///
330/// # Arguments
331///
332/// * `f` - The formatter to write to
333/// * `args` - Array of arguments to format and write
334///
335/// # Returns
336///
337/// std::fmt::Result indicating success or failure
338pub fn i_nlns(
339    f: &mut std::fmt::Formatter<'_>,
340    args: &[std::fmt::Arguments<'_>],
341) -> std::fmt::Result {
342    for arg in args {
343        writeln!(indented(f), "{}", arg)?;
344    }
345    Ok(())
346}