1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! Logging via the [`log`] crate.
//!
//! # Usage
//! With the `"log"` feature enabled, [`ArcDpsLogger`] is set as logger before your `init` function is called.
//! By default all messages are logged to ArcDPS' log window and only warnings & errors are logged to the log file.
//! You can specify `"window"`, `"file"` or `"both"`/`"all"` as log target to control where the messages should be logged.
//!
//! ```no_run
//! use log::{error, info};
//!
//! error!("an error will log to window & file");
//! error!(target: "window", "window target will only log to window");
//! error!(target: "file", "file target will only log to file");
//! info!("below error/warn level will only log to window");
//! info!(target: "both", "target both/all will log to window & file");
//! ```
//!
//! *Requires the `"log"` feature.*

use crate::exports::{log_to_file, log_to_window};
use log::{Level, Log, Metadata, Record};

/// A logger logging to ArcDPS' log window and/or file.
///
/// By default all messages are logged to ArcDPS' log window and only warnings & errors are logged to the log file.
/// You can specify `"window"`, `"file"` or `"both"`/`"all"` as log target to control where the messages should be logged.
#[derive(Debug, Clone)]
pub struct ArcDpsLogger {
    name: &'static str,
}

impl ArcDpsLogger {
    /// Creates a new ArcDPS logger.
    #[inline]
    pub const fn new(name: &'static str) -> Self {
        Self { name }
    }

    /// Checks whether window logging is enabled for the given [`Metadata`].
    fn window_enabled(metadata: &Metadata) -> bool {
        metadata.target() != "file"
    }

    /// Checks whether file logging is enabled for the given [`Metadata`].
    fn file_enabled(metadata: &Metadata) -> bool {
        match metadata.target() {
            "file" | "both" | "all" => true,
            "window" => false,
            _ => matches!(metadata.level(), Level::Warn | Level::Error),
        }
    }
}

impl Log for ArcDpsLogger {
    fn enabled(&self, metadata: &Metadata) -> bool {
        Self::window_enabled(metadata) || Self::file_enabled(metadata)
    }

    fn log(&self, record: &Record) {
        let metadata = record.metadata();
        if Self::window_enabled(metadata) {
            WindowLogger { name: self.name }.log(record);
        }
        if Self::file_enabled(metadata) {
            FileLogger { name: self.name }.log(record);
        }
    }

    fn flush(&self) {}
}

/// A logger logging to ArcDPS' log window.
#[derive(Debug, Clone)]
pub struct WindowLogger {
    name: &'static str,
}

impl WindowLogger {
    /// Creates a new window logger.
    #[inline]
    pub const fn new(name: &'static str) -> Self {
        Self { name }
    }
}

impl Log for WindowLogger {
    fn enabled(&self, _metadata: &Metadata) -> bool {
        true
    }

    fn log(&self, record: &Record) {
        // TODO: coloring?
        let _ = log_to_window(format_message(self.name, record));
    }

    fn flush(&self) {}
}

/// A logger logging to ArcDPS' log file.
#[derive(Debug, Clone)]
pub struct FileLogger {
    name: &'static str,
}

impl FileLogger {
    /// Creates a new file logger.
    #[inline]
    pub const fn new(name: &'static str) -> Self {
        Self { name }
    }
}

impl Log for FileLogger {
    fn enabled(&self, _metadata: &Metadata) -> bool {
        true
    }

    fn log(&self, record: &Record) {
        let _ = log_to_file(format_message(self.name, record));
    }

    fn flush(&self) {}
}

/// Formats a log message.
fn format_message(name: &'static str, record: &Record) -> String {
    format!(
        "{} {}: {}",
        name,
        record.level().to_string().to_lowercase(),
        record.args()
    )
}

#[cfg(test)]
mod tests {
    use super::ArcDpsLogger;
    use log::{Level, Metadata};

    #[test]
    fn enabled() {
        fn meta(target: &str, level: Level) -> Metadata {
            Metadata::builder().target(target).level(level).build()
        }

        const MOD: &str = module_path!();

        let info = meta(MOD, Level::Info);
        assert!(ArcDpsLogger::window_enabled(&info));
        assert!(!ArcDpsLogger::file_enabled(&info));

        let warn = meta(MOD, Level::Warn);
        assert!(ArcDpsLogger::window_enabled(&warn));
        assert!(ArcDpsLogger::file_enabled(&warn));

        let error = meta(MOD, Level::Error);
        assert!(ArcDpsLogger::window_enabled(&error));
        assert!(ArcDpsLogger::file_enabled(&error));

        let info_file = meta("file", Level::Info);
        assert!(!ArcDpsLogger::window_enabled(&info_file));
        assert!(ArcDpsLogger::file_enabled(&info_file));

        let info_both = meta("both", Level::Info);
        assert!(ArcDpsLogger::window_enabled(&info_both));
        assert!(ArcDpsLogger::file_enabled(&info_both));
    }
}