team04_server/log/
mod.rs

1//! This module is used for logging.
2//!
3//! It logs directly to `stderr`, prefixing log entries
4//! with their log level:
5//!
6//! ```text
7//! >F: Failed to load config files:
8//!  F: IO Error: No such file or directory (os error 2)
9//! ```
10//!
11//! For multiline logs, each line gets the log level prefix,
12//! but only the first line starts with a `>`.
13//!
14//! # Log Levels
15//!
16//! If a certain log level is selected, all logs of
17//! this level or above will be shown in the output.
18//!
19//! - Fatal
20//! - Error
21//! - Warning
22//! - Info
23//! - Debug
24//!
25//! # Usage
26//!
27//! ```
28//! log::<loglevel>!(<format string>[, <format arguments>]*; <prefix>);
29//! ```
30//!
31//! Where
32//!
33//! - `loglevel` is `fatal`, `error`, `warning`, `info`, or `debug`
34//! - `format string` and `format arguments` work like they do in `println!`, i.e. if `println!(...)` is valid, then so is `log::_!(...; pfx)`
35//!
36//! # Prefix
37//!
38//! The prefix is used to give context to a log entry.
39//! It can contain a lobby and/or player id which will
40//! then be shown when logging.
41//!
42//! # Examples:
43//!
44//! ```
45//! log::info!("doing thing now"; &log::pfx());
46//! ```
47//!
48//! ```
49//! log::info!("generated not-random value {}", 42; &log::pfx());
50//! ```
51//!
52//! ```
53//! let mut pfx = log::pfx();
54//! pfx.lobby(todo!("obtain a lobby id"));
55//! log::info!("my log {}", "message"; &pfx);
56//! ```
57//!
58//! See also: [Prefix]
59
60pub mod uuid_human_hash;
61
62/// returns a log prefix containing no information.
63///
64/// Usage:
65///
66/// ```
67/// let mut pfx = log::pfx();
68/// pfx.lobby(todo!("obtain a lobby id"));
69/// log::info!("my log {}", "message"; &pfx);
70/// ```
71pub fn pfx() -> Prefix {
72    Prefix::new()
73}
74
75#[macro_export]
76macro_rules! fatal {
77    ($($f:expr),+ ; $p:expr) => {
78        #[allow(unused_imports)]
79        use std::fmt::Write as _;
80        let _ = writeln!(
81            crate::log::_impl::log_fmt(crate::log::_impl::LogLevel::Fatal, $p),
82            $($f),+
83        );
84    };
85}
86pub use _impl::LogLevel;
87pub use _impl::Prefix;
88pub use _impl::logger;
89#[allow(unused)]
90pub use fatal;
91#[macro_export]
92macro_rules! error {
93    ($($f:expr),+ ; $p:expr) => {
94        #[allow(unused_imports)]
95        use std::fmt::Write as _;
96        let _ = writeln!(
97            crate::log::_impl::log_fmt(crate::log::_impl::LogLevel::Error, $p),
98            $($f),+
99        );
100    };
101}
102#[allow(unused)]
103pub use error;
104#[macro_export]
105macro_rules! warning {
106    ($($f:expr),+ ; $p:expr) => {
107        #[allow(unused_imports)]
108        use std::fmt::Write as _;
109        let _ = writeln!(
110            crate::log::_impl::log_fmt(crate::log::_impl::LogLevel::Warn, $p),
111            $($f),+
112        );
113    };
114}
115#[allow(unused)]
116pub use warning;
117#[macro_export]
118macro_rules! info {
119    ($($f:expr),+ ; $p:expr) => {
120        #[allow(unused_imports)]
121        use std::fmt::Write as _;
122        let _ = writeln!(
123            crate::log::_impl::log_fmt(crate::log::_impl::LogLevel::Info, $p),
124            $($f),+
125        );
126    };
127}
128#[allow(unused)]
129pub use info;
130#[macro_export]
131macro_rules! debug {
132    ($($f:expr),+ ; $p:expr) => {
133        #[allow(unused_imports)]
134        use std::fmt::Write as _;
135        let _ = writeln!(
136            crate::log::_impl::log_fmt(crate::log::_impl::LogLevel::Debug, $p),
137            $($f),+
138        );
139    };
140}
141#[allow(unused)]
142pub use debug;
143#[macro_export]
144macro_rules! debug_force {
145    ($($f:expr),+ ; $p:expr) => {
146        #[allow(unused_imports)]
147        use std::fmt::Write as _;
148        let _ = writeln!(
149            crate::log::_impl::LogFmtForce(crate::log::_impl::log_fmt(crate::log::_impl::LogLevel::Debug, $p)),
150            $($f),+
151        );
152    };
153}
154#[allow(unused)]
155pub use debug_force;
156
157pub mod _impl {
158
159    use std::{
160        io::{StderrLock, Write},
161        sync::LazyLock,
162    };
163
164    use crate::{lobby::state::clients::PlayerId, server::state::LobbyId};
165
166    static LOGGER: LazyLock<Log> = LazyLock::new(Log::new);
167
168    /// Returns the public logger used for logging,
169    /// which contains the log level and
170    /// whether logging should use colors or not.
171    pub fn logger<'a>() -> &'a Log {
172        LazyLock::force(&LOGGER)
173    }
174
175    pub fn log_fmt<'a>(level: LogLevel, prefix: &'a Prefix) -> LogFmt<'a> {
176        LogFmt(std::io::stderr().lock(), Newline::First, prefix, level)
177    }
178
179    pub struct Log {
180        pub level: LogLevel,
181        pub colors: bool,
182    }
183
184    impl Log {
185        /// Returns `true` if logs of the given level would actually be printed.
186        pub fn level(&self, level: LogLevel) -> bool {
187            self.level >= level
188        }
189        fn new() -> Self {
190            Self {
191                #[cfg(test)]
192                level: LogLevel::Debug,
193                #[cfg(not(test))]
194                level: match std::env::var("LOG_LEVEL")
195                    .map(|v| v.to_lowercase())
196                    .as_ref()
197                    .map(|v| v.as_str())
198                    .unwrap_or("warn")
199                {
200                    "fatal" => LogLevel::Fatal,
201                    "err" | "error" => LogLevel::Error,
202                    "warn" | "warning" => LogLevel::Warn,
203                    "info" => LogLevel::Info,
204                    "debug" | "dbg" => LogLevel::Debug,
205                    _ => LogLevel::Debug,
206                },
207                colors: match std::env::var("LOG_COLORS")
208                    .map(|v| v.to_lowercase())
209                    .as_ref()
210                    .map(|v| v.as_str())
211                    .unwrap_or("yes")
212                {
213                    "yes" => true,
214                    _ => false,
215                },
216            }
217        }
218    }
219
220    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
221    pub enum LogLevel {
222        Fatal,
223        Error,
224        Warn,
225        Info,
226        Debug,
227    }
228
229    #[derive(Clone, Copy)]
230    pub struct Prefix {
231        lobby: Option<LobbyId>,
232        player: Option<PlayerId>,
233    }
234
235    impl Prefix {
236        pub(super) fn new() -> Self {
237            Prefix {
238                lobby: None,
239                player: None,
240            }
241        }
242        pub fn lobby(&mut self, id: LobbyId) -> &mut Self {
243            self.lobby = Some(id);
244            self
245        }
246        pub fn no_lobby(&mut self) -> &mut Self {
247            self.lobby = None;
248            self
249        }
250        pub fn player(&mut self, id: PlayerId) -> &mut Self {
251            self.player = Some(id);
252            self
253        }
254        pub fn no_player(&mut self) -> &mut Self {
255            self.player = None;
256            self
257        }
258    }
259
260    impl LogLevel {
261        const fn defs(&self) -> [&'static str; 4] {
262            match self {
263                Self::Fatal => [">F", " F", "\x1b[1;31m>F\x1b[0m", "\x1b[1;31m F\x1b[0m"],
264                Self::Error => [">E", " E", "\x1b[0;31m>E\x1b[0m", "\x1b[0;31m E\x1b[0m"],
265                Self::Warn => [">W", " W", "\x1b[0;33m>W\x1b[0m", "\x1b[0;33m W\x1b[0m"],
266                Self::Info => [">I", " I", "\x1b[0;34m>I\x1b[0m", "\x1b[0;34m I\x1b[0m"],
267                Self::Debug => [">D", " D", "\x1b[0;90m>D", "\x1b[0;90m D"],
268            }
269        }
270        const fn pfx_simple_first(&self) -> &'static str {
271            self.defs()[0]
272        }
273        const fn pfx_simple_other(&self) -> &'static str {
274            self.defs()[1]
275        }
276        const fn pfx_colored_first(&self) -> &'static str {
277            self.defs()[2]
278        }
279        const fn pfx_colored_other(&self) -> &'static str {
280            self.defs()[3]
281        }
282    }
283    pub struct LogFmt<'a>(StderrLock<'a>, Newline, &'a Prefix, LogLevel);
284    pub struct LogFmtForce<'a>(pub LogFmt<'a>);
285
286    impl std::fmt::Write for LogFmt<'_> {
287        fn write_str(&mut self, s: &str) -> std::fmt::Result {
288            let Self(stderr, newline, prefix, level) = self;
289            let log = logger();
290            if !log.level(*level) {
291                return Ok(());
292            }
293            log_impl(s, *level, log, prefix, stderr, newline)
294        }
295    }
296    impl std::fmt::Write for LogFmtForce<'_> {
297        fn write_str(&mut self, s: &str) -> std::fmt::Result {
298            let Self(LogFmt(stderr, newline, prefix, level)) = self;
299            let log = logger();
300            // NOTE: Lack of log level check is on purpose
301            log_impl(s, *level, log, prefix, stderr, newline)
302        }
303    }
304
305    enum Newline {
306        First,
307        Yes,
308        No,
309    }
310
311    fn log_impl<'a>(
312        s: &str,
313        level: LogLevel,
314        log: &Log,
315        prefix: &Prefix,
316        stderr: &mut StderrLock<'a>,
317        newline: &mut Newline,
318    ) -> std::fmt::Result {
319        if s.is_empty() {
320            return Ok(());
321        }
322        macro_rules! write_prefix {
323            () => {
324                if let Some(lobby) = &prefix.lobby {
325                    let _ = write!(stderr, " L{lobby}");
326                }
327                if let Some(player) = &prefix.player {
328                    let _ = write!(stderr, " P{player}");
329                }
330                let _ = write!(stderr, ": ");
331            };
332        }
333        if let Newline::First = newline {
334            let _ = stderr.write(
335                (if log.colors {
336                    level.pfx_colored_first()
337                } else {
338                    level.pfx_simple_first()
339                })
340                .as_bytes(),
341            );
342            write_prefix!();
343            *newline = Newline::No;
344        } else if let Newline::Yes = *newline {
345            let _ = stderr.write(
346                (if log.colors {
347                    level.pfx_colored_other()
348                } else {
349                    level.pfx_simple_other()
350                })
351                .as_bytes(),
352            );
353            write_prefix!();
354            *newline = Newline::No;
355        }
356        if let Some(i) = s.find('\n') {
357            let _ = stderr.write(s[0..=i].as_bytes());
358            *newline = Newline::Yes;
359            log_impl(&s[i + 1..], level, log, prefix, stderr, newline)?;
360        } else {
361            let _ = stderr.write(s.as_bytes());
362        }
363        Ok(())
364    }
365}