arcdps/
log.rs

1//! Logging via the [`log`] crate.
2//!
3//! # Usage
4//! With the `"log"` feature enabled, [`ArcDpsLogger`] is set as logger before your `init` function is called.
5//! By default all messages are logged to ArcDPS' log window and only warnings & errors are logged to the log file.
6//! You can specify `"window"`, `"file"` or `"both"`/`"all"` as log target to control where the messages should be logged.
7//!
8//! ```no_run
9//! use log::{error, info};
10//!
11//! error!("an error will log to window & file");
12//! error!(target: "window", "window target will only log to window");
13//! error!(target: "file", "file target will only log to file");
14//! info!("below error/warn level will only log to window");
15//! info!(target: "both", "target both/all will log to window & file");
16//! ```
17//!
18//! *Requires the `"log"` feature.*
19
20use crate::exports::{log_to_file, log_to_window};
21use log::{Level, Log, Metadata, Record};
22
23/// A logger logging to ArcDPS' log window and/or file.
24///
25/// By default all messages are logged to ArcDPS' log window and only warnings & errors are logged to the log file.
26/// You can specify `"window"`, `"file"` or `"both"`/`"all"` as log target to control where the messages should be logged.
27#[derive(Debug, Clone)]
28pub struct ArcDpsLogger {
29    name: &'static str,
30}
31
32impl ArcDpsLogger {
33    /// Creates a new ArcDPS logger.
34    #[inline]
35    pub const fn new(name: &'static str) -> Self {
36        Self { name }
37    }
38
39    /// Checks whether window logging is enabled for the given [`Metadata`].
40    fn window_enabled(metadata: &Metadata) -> bool {
41        metadata.target() != "file"
42    }
43
44    /// Checks whether file logging is enabled for the given [`Metadata`].
45    fn file_enabled(metadata: &Metadata) -> bool {
46        match metadata.target() {
47            "file" | "both" | "all" => true,
48            "window" => false,
49            _ => matches!(metadata.level(), Level::Warn | Level::Error),
50        }
51    }
52}
53
54impl Log for ArcDpsLogger {
55    fn enabled(&self, metadata: &Metadata) -> bool {
56        Self::window_enabled(metadata) || Self::file_enabled(metadata)
57    }
58
59    fn log(&self, record: &Record) {
60        let metadata = record.metadata();
61        if Self::window_enabled(metadata) {
62            WindowLogger { name: self.name }.log(record);
63        }
64        if Self::file_enabled(metadata) {
65            FileLogger { name: self.name }.log(record);
66        }
67    }
68
69    fn flush(&self) {}
70}
71
72/// A logger logging to ArcDPS' log window.
73#[derive(Debug, Clone)]
74pub struct WindowLogger {
75    name: &'static str,
76}
77
78impl WindowLogger {
79    /// Creates a new window logger.
80    #[inline]
81    pub const fn new(name: &'static str) -> Self {
82        Self { name }
83    }
84}
85
86impl Log for WindowLogger {
87    fn enabled(&self, _metadata: &Metadata) -> bool {
88        true
89    }
90
91    fn log(&self, record: &Record) {
92        // TODO: coloring?
93        let _ = log_to_window(format_message(self.name, record));
94    }
95
96    fn flush(&self) {}
97}
98
99/// A logger logging to ArcDPS' log file.
100#[derive(Debug, Clone)]
101pub struct FileLogger {
102    name: &'static str,
103}
104
105impl FileLogger {
106    /// Creates a new file logger.
107    #[inline]
108    pub const fn new(name: &'static str) -> Self {
109        Self { name }
110    }
111}
112
113impl Log for FileLogger {
114    fn enabled(&self, _metadata: &Metadata) -> bool {
115        true
116    }
117
118    fn log(&self, record: &Record) {
119        let _ = log_to_file(format_message(self.name, record));
120    }
121
122    fn flush(&self) {}
123}
124
125/// Formats a log message.
126fn format_message(name: &'static str, record: &Record) -> String {
127    format!(
128        "{} {}: {}",
129        name,
130        record.level().to_string().to_lowercase(),
131        record.args()
132    )
133}
134
135#[cfg(test)]
136mod tests {
137    use super::ArcDpsLogger;
138    use log::{Level, Metadata};
139
140    #[test]
141    fn enabled() {
142        fn meta(target: &str, level: Level) -> Metadata {
143            Metadata::builder().target(target).level(level).build()
144        }
145
146        const MOD: &str = module_path!();
147
148        let info = meta(MOD, Level::Info);
149        assert!(ArcDpsLogger::window_enabled(&info));
150        assert!(!ArcDpsLogger::file_enabled(&info));
151
152        let warn = meta(MOD, Level::Warn);
153        assert!(ArcDpsLogger::window_enabled(&warn));
154        assert!(ArcDpsLogger::file_enabled(&warn));
155
156        let error = meta(MOD, Level::Error);
157        assert!(ArcDpsLogger::window_enabled(&error));
158        assert!(ArcDpsLogger::file_enabled(&error));
159
160        let info_file = meta("file", Level::Info);
161        assert!(!ArcDpsLogger::window_enabled(&info_file));
162        assert!(ArcDpsLogger::file_enabled(&info_file));
163
164        let info_both = meta("both", Level::Info);
165        assert!(ArcDpsLogger::window_enabled(&info_both));
166        assert!(ArcDpsLogger::file_enabled(&info_both));
167    }
168}