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}
42
43/// A log macro for console input.
44#[macro_export]
45macro_rules! console {
46    ($($arg:tt)+) =>
47        ($crate::logger::STEEL_LOGGER.get().expect("Steel logger isn't initialized!").log(
48            $crate::logger::Level::Console,
49            $crate::logger::LogData::message(format!($($arg)+)),
50        ));
51}
52/// A log macro for chat messages, provide first the player name, and then the format.
53#[macro_export]
54macro_rules! chat {
55    ($player:expr,$($arg:tt)+) =>
56        ($crate::logger::STEEL_LOGGER.get().expect("Steel logger isn't initialized!").log(
57            $crate::logger::Level::Chat($player),
58            $crate::logger::LogData::message(format!($($arg)+)),
59        ));
60}
61/// A log macro for commands, provide first the player name, and then the format.
62#[macro_export]
63macro_rules! command {
64    ($player:expr,$($arg:tt)+) =>
65        ($crate::logger::STEEL_LOGGER.get().expect("Steel logger isn't initialized!").log(
66            $crate::logger::Level::Command($player),
67            $crate::logger::LogData::message(format!($($arg)+)),
68        ));
69}
70
71/// A message visitor for the Steel Logger
72#[derive(Default)]
73pub struct LogData {
74    /// The log message
75    pub message: String,
76    /// The module path to where logged
77    pub module_path: String,
78    /// All extra data
79    pub extra: String,
80}
81
82impl LogData {
83    /// Creates a new `LogData`
84    #[must_use]
85    pub const fn new() -> Self {
86        Self {
87            message: String::new(),
88            module_path: String::new(),
89            extra: String::new(),
90        }
91    }
92    /// Creates a `LogData` containing a message
93    #[must_use]
94    pub fn message(msg: String) -> Self {
95        Self {
96            message: msg,
97            module_path: module_path!().to_string(),
98            extra: String::new(),
99        }
100    }
101}
102
103impl Visit for LogData {
104    fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
105        match field.name() {
106            "message" => {
107                write!(self.message, "{value:?}").ok();
108            }
109            "log.module_path" => {
110                write!(self.module_path, "{value:?}").ok();
111            }
112            "log.target" => (),
113            name => {
114                write!(self.extra, " ({name}: {value:?})").ok();
115            }
116        }
117    }
118
119    fn record_str(&mut self, field: &Field, value: &str) {
120        match field.name() {
121            "message" => {
122                write!(self.message, "{value}").ok();
123            }
124            "log.module_path" => {
125                write!(self.module_path, "{value}").ok();
126            }
127            "log.target" => (),
128            name => {
129                write!(self.extra, " ({name}: {value})").ok();
130            }
131        }
132    }
133}
134
135/// A trait for Logging Steel data
136pub trait SteelLogger: Send + Sync {
137    /// Does the logging logic
138    fn log(&self, level: Level, data: LogData);
139}