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}