cubecl_runtime/config/
logger.rs

1use super::GlobalConfig;
2use crate::config::{
3    autotune::AutotuneLogLevel, compilation::CompilationLogLevel, memory::MemoryLogLevel,
4    profiling::ProfilingLogLevel, streaming::StreamingLogLevel,
5};
6use alloc::{string::ToString, sync::Arc, vec::Vec};
7use core::fmt::Display;
8use hashbrown::HashMap;
9
10#[cfg(std_io)]
11use std::{
12    fs::{File, OpenOptions},
13    io::{BufWriter, Write},
14    path::PathBuf,
15};
16
17/// Configuration for logging in CubeCL, parameterized by a log level type.
18///
19/// Note that you can use multiple loggers at the same time.
20#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
21#[serde(bound = "")]
22pub struct LoggerConfig<L: LogLevel> {
23    /// Path to the log file, if file logging is enabled (requires `std` feature).
24    #[serde(default)]
25    #[cfg(std_io)]
26    pub file: Option<PathBuf>,
27
28    /// Whether to append to the log file (true) or overwrite it (false). Defaults to true.
29    ///
30    /// ## Notes
31    ///
32    /// This parameter might get ignored based on other loggers config.
33    #[serde(default = "append_default")]
34    pub append: bool,
35
36    /// Whether to log to standard output.
37    #[serde(default)]
38    pub stdout: bool,
39
40    /// Whether to log to standard error.
41    #[serde(default)]
42    pub stderr: bool,
43
44    /// Optional crate-level logging configuration (e.g., info, debug, trace).
45    #[serde(default)]
46    pub log: Option<LogCrateLevel>,
47
48    /// The log level for this logger, determining verbosity.
49    #[serde(default)]
50    pub level: L,
51}
52
53impl<L: LogLevel> Default for LoggerConfig<L> {
54    fn default() -> Self {
55        Self {
56            #[cfg(std_io)]
57            file: None,
58            append: true,
59            #[cfg(feature = "autotune-checks")]
60            stdout: true,
61            #[cfg(not(feature = "autotune-checks"))]
62            stdout: false,
63            stderr: false,
64            log: None,
65            level: L::default(),
66        }
67    }
68}
69
70/// Log levels using the `log` crate.
71///
72/// This enum defines verbosity levels for crate-level logging.
73#[derive(
74    Clone, Copy, Debug, Default, serde::Serialize, serde::Deserialize, Hash, PartialEq, Eq,
75)]
76pub enum LogCrateLevel {
77    /// Logs informational messages.
78    #[default]
79    #[serde(rename = "info")]
80    Info,
81
82    /// Logs debugging messages.
83    #[serde(rename = "debug")]
84    Debug,
85
86    /// Logs trace-level messages.
87    #[serde(rename = "trace")]
88    Trace,
89}
90
91impl LogLevel for u32 {}
92
93fn append_default() -> bool {
94    true
95}
96
97/// Trait for types that can be used as log levels in `LoggerConfig`.
98pub trait LogLevel:
99    serde::de::DeserializeOwned + serde::Serialize + Clone + Copy + core::fmt::Debug + Default
100{
101}
102
103/// Central logging utility for CubeCL, managing multiple log outputs.
104#[derive(Debug)]
105pub struct Logger {
106    /// Collection of logger instances (file, stdout, stderr, or crate-level).
107    loggers: Vec<LoggerKind>,
108
109    /// Indices of loggers used for compilation logging.
110    compilation_index: Vec<usize>,
111
112    /// Indices of loggers used for profiling logging.
113    profiling_index: Vec<usize>,
114
115    /// Indices of loggers used for autotuning logging.
116    autotune_index: Vec<usize>,
117
118    /// Indices of loggers used for streaming logging.
119    streaming_index: Vec<usize>,
120
121    /// Indices of loggers used for memory logging.
122    memory_index: Vec<usize>,
123
124    /// Global configuration for logging settings.
125    pub config: Arc<GlobalConfig>,
126}
127
128impl Default for Logger {
129    fn default() -> Self {
130        Self::new()
131    }
132}
133
134impl Logger {
135    /// Creates a new `Logger` instance based on the global configuration.
136    ///
137    /// Initializes loggers for compilation, profiling, and autotuning based on the settings in
138    /// `GlobalConfig`.
139    ///
140    /// Note that creating a logger is quite expensive.
141    pub fn new() -> Self {
142        let config = GlobalConfig::get();
143        let mut loggers = Vec::new();
144        let mut compilation_index = Vec::new();
145        let mut profiling_index = Vec::new();
146        let mut autotune_index = Vec::new();
147        let mut streaming_index = Vec::new();
148        let mut memory_index = Vec::new();
149
150        #[derive(Hash, PartialEq, Eq)]
151        enum LoggerId {
152            #[cfg(std_io)]
153            File(PathBuf),
154            #[cfg(feature = "std")]
155            Stdout,
156            #[cfg(feature = "std")]
157            Stderr,
158            LogCrate(LogCrateLevel),
159        }
160
161        let mut logger2index = HashMap::<LoggerId, usize>::new();
162
163        fn new_logger<S: Clone, ID: Fn(S) -> LoggerId, LG: Fn(S) -> LoggerKind>(
164            setting_index: &mut Vec<usize>,
165            loggers: &mut Vec<LoggerKind>,
166            logger2index: &mut HashMap<LoggerId, usize>,
167            state: S,
168            func_id: ID,
169            func_logger: LG,
170        ) {
171            let id = func_id(state.clone());
172
173            if let Some(index) = logger2index.get(&id) {
174                setting_index.push(*index);
175            } else {
176                let logger = func_logger(state);
177                let index = loggers.len();
178                logger2index.insert(id, index);
179                loggers.push(logger);
180                setting_index.push(index);
181            }
182        }
183
184        fn register_logger<L: LogLevel>(
185            #[allow(unused_variables)] kind: &LoggerConfig<L>, // not used in no-std
186            #[allow(unused_variables)] append: bool,           // not used in no-std
187            level: Option<LogCrateLevel>,
188            setting_index: &mut Vec<usize>,
189            loggers: &mut Vec<LoggerKind>,
190            logger2index: &mut HashMap<LoggerId, usize>,
191        ) {
192            #[cfg(std_io)]
193            if let Some(file) = &kind.file {
194                new_logger(
195                    setting_index,
196                    loggers,
197                    logger2index,
198                    (file, append),
199                    |(file, _append)| LoggerId::File(file.clone()),
200                    |(file, append)| LoggerKind::File(FileLogger::new(file, append)),
201                );
202            }
203
204            #[cfg(feature = "std")]
205            if kind.stdout {
206                new_logger(
207                    setting_index,
208                    loggers,
209                    logger2index,
210                    (),
211                    |_| LoggerId::Stdout,
212                    |_| LoggerKind::Stdout,
213                );
214            }
215
216            #[cfg(feature = "std")]
217            if kind.stderr {
218                new_logger(
219                    setting_index,
220                    loggers,
221                    logger2index,
222                    (),
223                    |_| LoggerId::Stderr,
224                    |_| LoggerKind::Stderr,
225                );
226            }
227
228            if let Some(level) = level {
229                new_logger(
230                    setting_index,
231                    loggers,
232                    logger2index,
233                    level,
234                    LoggerId::LogCrate,
235                    LoggerKind::Log,
236                );
237            }
238        }
239
240        if let CompilationLogLevel::Disabled = config.compilation.logger.level {
241        } else {
242            register_logger(
243                &config.compilation.logger,
244                config.compilation.logger.append,
245                config.compilation.logger.log,
246                &mut compilation_index,
247                &mut loggers,
248                &mut logger2index,
249            )
250        }
251
252        if let ProfilingLogLevel::Disabled = config.profiling.logger.level {
253        } else {
254            register_logger(
255                &config.profiling.logger,
256                config.profiling.logger.append,
257                config.profiling.logger.log,
258                &mut profiling_index,
259                &mut loggers,
260                &mut logger2index,
261            )
262        }
263
264        if let AutotuneLogLevel::Disabled = config.autotune.logger.level {
265        } else {
266            register_logger(
267                &config.autotune.logger,
268                config.autotune.logger.append,
269                config.autotune.logger.log,
270                &mut autotune_index,
271                &mut loggers,
272                &mut logger2index,
273            )
274        }
275
276        if let StreamingLogLevel::Disabled = config.streaming.logger.level {
277        } else {
278            register_logger(
279                &config.streaming.logger,
280                config.streaming.logger.append,
281                config.streaming.logger.log,
282                &mut streaming_index,
283                &mut loggers,
284                &mut logger2index,
285            )
286        }
287
288        if let MemoryLogLevel::Disabled = config.memory.logger.level {
289        } else {
290            register_logger(
291                &config.memory.logger,
292                config.memory.logger.append,
293                config.memory.logger.log,
294                &mut memory_index,
295                &mut loggers,
296                &mut logger2index,
297            )
298        }
299
300        Self {
301            loggers,
302            compilation_index,
303            profiling_index,
304            autotune_index,
305            streaming_index,
306            memory_index,
307            config,
308        }
309    }
310
311    /// Logs a message for streaming, directing it to all configured streaming loggers.
312    pub fn log_streaming<S: Display>(&mut self, msg: &S) {
313        let length = self.streaming_index.len();
314        if length > 1 {
315            let msg = msg.to_string();
316            for i in 0..length {
317                let index = self.streaming_index[i];
318                self.log(&msg, index)
319            }
320        } else if let Some(index) = self.streaming_index.first() {
321            self.log(&msg, *index)
322        }
323    }
324
325    /// Logs a message for memory, directing it to all configured streaming loggers.
326    pub fn log_memory<S: Display>(&mut self, msg: &S) {
327        let length = self.memory_index.len();
328        if length > 1 {
329            let msg = msg.to_string();
330            for i in 0..length {
331                let index = self.memory_index[i];
332                self.log(&msg, index)
333            }
334        } else if let Some(index) = self.memory_index.first() {
335            self.log(&msg, *index)
336        }
337    }
338
339    /// Logs a message for compilation, directing it to all configured compilation loggers.
340    pub fn log_compilation<S: Display>(&mut self, msg: &S) {
341        let length = self.compilation_index.len();
342        if length > 1 {
343            let msg = msg.to_string();
344            for i in 0..length {
345                let index = self.compilation_index[i];
346                self.log(&msg, index)
347            }
348        } else if let Some(index) = self.compilation_index.first() {
349            self.log(&msg, *index)
350        }
351    }
352
353    /// Logs a message for profiling, directing it to all configured profiling loggers.
354    pub fn log_profiling<S: Display>(&mut self, msg: &S) {
355        let length = self.profiling_index.len();
356        if length > 1 {
357            let msg = msg.to_string();
358            for i in 0..length {
359                let index = self.profiling_index[i];
360                self.log(&msg, index)
361            }
362        } else if let Some(index) = self.profiling_index.first() {
363            self.log(&msg, *index)
364        }
365    }
366
367    /// Logs a message for autotuning, directing it to all configured autotuning loggers.
368    pub fn log_autotune<S: Display>(&mut self, msg: &S) {
369        let length = self.autotune_index.len();
370        if length > 1 {
371            let msg = msg.to_string();
372            for i in 0..length {
373                let index = self.autotune_index[i];
374                self.log(&msg, index)
375            }
376        } else if let Some(index) = self.autotune_index.first() {
377            self.log(&msg, *index)
378        }
379    }
380
381    /// Returns the current streaming log level from the global configuration.
382    pub fn log_level_streaming(&self) -> StreamingLogLevel {
383        self.config.streaming.logger.level
384    }
385
386    /// Returns the current autotune log level from the global configuration.
387    pub fn log_level_autotune(&self) -> AutotuneLogLevel {
388        self.config.autotune.logger.level
389    }
390
391    /// Returns the current compilation log level from the global configuration.
392    pub fn log_level_compilation(&self) -> CompilationLogLevel {
393        self.config.compilation.logger.level
394    }
395
396    /// Returns the current profiling log level from the global configuration.
397    pub fn log_level_profiling(&self) -> ProfilingLogLevel {
398        self.config.profiling.logger.level
399    }
400
401    fn log<S: Display>(&mut self, msg: &S, index: usize) {
402        let logger = &mut self.loggers[index];
403        logger.log(msg);
404    }
405}
406
407/// Represents different types of loggers.
408#[derive(Debug)]
409enum LoggerKind {
410    /// Logs to a file.
411    #[cfg(std_io)]
412    File(FileLogger),
413
414    /// Logs to standard output.
415    #[cfg(feature = "std")]
416    Stdout,
417
418    /// Logs to standard error.
419    #[cfg(feature = "std")]
420    Stderr,
421
422    /// Logs using the `log` crate with a specified level.
423    Log(LogCrateLevel),
424}
425
426impl LoggerKind {
427    fn log<S: Display>(&mut self, msg: &S) {
428        match self {
429            #[cfg(std_io)]
430            LoggerKind::File(file_logger) => file_logger.log(msg),
431            #[cfg(feature = "std")]
432            LoggerKind::Stdout => println!("{msg}"),
433            #[cfg(feature = "std")]
434            LoggerKind::Stderr => eprintln!("{msg}"),
435            LoggerKind::Log(level) => match level {
436                LogCrateLevel::Info => log::info!("{msg}"),
437                LogCrateLevel::Trace => log::debug!("{msg}"),
438                LogCrateLevel::Debug => log::trace!("{msg}"),
439            },
440        }
441    }
442}
443
444/// Logger that writes messages to a file.
445#[derive(Debug)]
446#[cfg(std_io)]
447struct FileLogger {
448    writer: BufWriter<File>,
449}
450
451#[cfg(std_io)]
452impl FileLogger {
453    // Creates a new file logger.
454    fn new(path: &PathBuf, append: bool) -> Self {
455        let file = OpenOptions::new()
456            .write(true)
457            .append(append)
458            .create(true)
459            .open(path)
460            .unwrap();
461
462        Self {
463            writer: BufWriter::new(file),
464        }
465    }
466
467    // Logs a message to the file, flushing the buffer to ensure immediate write.
468    fn log<S: Display>(&mut self, msg: &S) {
469        writeln!(self.writer, "{msg}").expect("Should be able to log debug information.");
470        self.writer.flush().expect("Can complete write operation.");
471    }
472}