Skip to main content

steel_utils/logger/
mod.rs

1use std::{
2    fmt::{self, Debug, Display, Formatter, Write},
3    sync::{Arc, OnceLock},
4};
5use tracing::field::{Field, Visit};
6
7/// A reference to the Steel Logger\
8/// Use the log macros instead: `log::info`!, `logger::chat`!, etc...
9pub static STEEL_LOGGER: OnceLock<Arc<dyn SteelLogger>> = OnceLock::new();
10
11/// Levels of logging in Steel
12pub enum Level {
13    /// Standard levels from tracing
14    Tracing(tracing::Level),
15    /// Console input level
16    Console,
17    /// Chat message level
18    Chat(String),
19    /// Command level: Should contain the executor name
20    Command(String),
21}
22impl Display for Level {
23    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
24        write!(
25            f,
26            "{}",
27            match self {
28                Level::Tracing(level) => match *level {
29                    tracing::Level::ERROR => "\x1b[0;1;31m[Error]\x1b[0m".to_string(),
30                    tracing::Level::WARN => "\x1b[0;1;33m[Warn]\x1b[0m".to_string(),
31                    tracing::Level::INFO => "\x1b[0;1;34m[Info]\x1b[0m".to_string(),
32                    tracing::Level::DEBUG => "\x1b[0;1;32m[Debug]\x1b[0m".to_string(),
33                    tracing::Level::TRACE => "\x1b[0;1;90m[Trace]\x1b[0m".to_string(),
34                },
35                Level::Console => "\x1b[0;1;35m[Console]\x1b[0m".to_string(),
36                Level::Chat(name) => format!("\x1b[0;36m[Chat: {name}]\x1b[0m"),
37                Level::Command(name) => format!("\x1b[0;35m[Command: {name}]\x1b[0m"),
38            }
39        )
40    }
41}
42impl Debug for Level {
43    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
44        write!(
45            f,
46            "{}",
47            match self {
48                Level::Tracing(level) => match *level {
49                    tracing::Level::ERROR => "[Error]".to_string(),
50                    tracing::Level::WARN => "[Warn]".to_string(),
51                    tracing::Level::INFO => "[Info]".to_string(),
52                    tracing::Level::DEBUG => "[Debug]".to_string(),
53                    tracing::Level::TRACE => "[Trace]".to_string(),
54                },
55                Level::Console => "[Console]".to_string(),
56                Level::Chat(name) => format!("[Chat: {name}]"),
57                Level::Command(name) => format!("[Command: {name}]"),
58            }
59        )
60    }
61}
62
63/// A log macro for console input.
64#[macro_export]
65macro_rules! console {
66    ($($arg:tt)+) =>
67        ($crate::logger::STEEL_LOGGER.get().expect("Steel logger isn't initialized!").log(
68            $crate::logger::Level::Console,
69            $crate::logger::LogData::message(format!($($arg)+)),
70        ));
71}
72/// A log macro for chat messages, provide first the player name, and then the format.
73#[macro_export]
74macro_rules! chat {
75    ($player:expr,$($arg:tt)+) =>
76        ($crate::logger::STEEL_LOGGER.get().expect("Steel logger isn't initialized!").log(
77            $crate::logger::Level::Chat($player),
78            $crate::logger::LogData::message(format!($($arg)+)),
79        ));
80}
81/// A log macro for commands, provide first the player name, and then the format.
82#[macro_export]
83macro_rules! command {
84    ($player:expr,$($arg:tt)+) =>
85        ($crate::logger::STEEL_LOGGER.get().expect("Steel logger isn't initialized!").log(
86            $crate::logger::Level::Command($player),
87            $crate::logger::LogData::message(format!($($arg)+)),
88        ));
89}
90
91/// A message visitor for the Steel Logger
92#[derive(Default)]
93pub struct LogData {
94    /// The log message
95    pub message: String,
96    /// The module path to where logged
97    pub module_path: String,
98    /// All extra data
99    pub extra: String,
100}
101
102impl LogData {
103    /// Creates a new `LogData`
104    #[must_use]
105    pub const fn new() -> Self {
106        Self {
107            message: String::new(),
108            module_path: String::new(),
109            extra: String::new(),
110        }
111    }
112    /// Creates a `LogData` containing a message
113    #[must_use]
114    pub fn message(msg: String) -> Self {
115        Self {
116            message: msg,
117            module_path: module_path!().to_string(),
118            extra: String::new(),
119        }
120    }
121}
122
123impl Visit for LogData {
124    fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
125        match field.name() {
126            "message" => {
127                write!(self.message, "{value:?}").ok();
128            }
129            "log.module_path" => {
130                write!(self.module_path, "{value:?}").ok();
131            }
132            "log.target" => (),
133            name => {
134                write!(self.extra, " ({name}: {value:?})").ok();
135            }
136        }
137    }
138
139    fn record_str(&mut self, field: &Field, value: &str) {
140        match field.name() {
141            "message" => {
142                write!(self.message, "{value}").ok();
143            }
144            "log.module_path" => {
145                write!(self.module_path, "{value}").ok();
146            }
147            "log.target" => (),
148            name => {
149                write!(self.extra, " ({name}: {value})").ok();
150            }
151        }
152    }
153}
154
155/// A trait for Logging Steel data
156pub trait SteelLogger: Send + Sync {
157    /// Does the logging logic
158    fn log(&self, level: Level, data: LogData);
159}