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
//! Basic built-in logging system

use std::{
    fmt::{self, Arguments, Display},
    sync::{
        atomic::{AtomicBool, AtomicU8, Ordering},
        RwLock,
    },
};

/// afire's global log level.
static LEVEL: AtomicU8 = AtomicU8::new(1);
/// Whether or not to colorize the log output.
static COLOR: AtomicBool = AtomicBool::new(true);
/// The global log formatter.
/// Will use [`DefaultFormatter`] if none is set.
static FORMATTER: RwLock<Option<Box<dyn Formatter + Send + Sync + 'static>>> = RwLock::new(None);
/// Whether or not a formatter has been set.
/// Used because loading a bool is faster than a RwLock.
/// This is always loaded before the RwLock to improve performance when using the default formatter.
static FORMATTER_PRESENT: AtomicBool = AtomicBool::new(false);

/// Log levels.
/// Used to control the verbosity of afire's internal logging.
/// The default log level is [`Level::Off`].
#[repr(u8)]
#[derive(Debug, Copy, Clone)]
pub enum Level {
    /// Disables all logging.
    Off = 0,
    /// Only shows errors.
    Error = 1,
    /// Shows [`Level::Error`] and some helpful information during startup.
    Trace = 2,
    /// Shows [`Level::Error`], [`Level::Trace`] and raw socket stuff.
    /// You probably don't need this, its really only intended for afire development.
    Debug = 3,
}

impl Level {
    /// Returns the log level as a string
    pub fn as_str(&self) -> &'static str {
        match self {
            Level::Off => "OFF",
            Level::Error => "ERROR",
            Level::Trace => "TRACE",
            Level::Debug => "DEBUG",
        }
    }

    /// Gets the ansi color code for the log level.
    /// By default, this is used to colorize the log output if color is enabled.
    pub fn get_color(&self) -> &'static str {
        match self {
            Level::Trace | Level::Off => "\x1b[0m",
            Level::Error => "\x1b[31m",
            Level::Debug => "\x1b[36m",
        }
    }
}

impl Display for Level {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

/// Sets the global afire log level.
/// Setting to [`Level::Off`] will disable all logging.
pub fn set_log_level(level: Level) {
    LEVEL.store(level as u8, Ordering::Relaxed);
}

/// Globally enables or disables colorized log output.
/// Enabled by default.
pub fn set_log_color(color: bool) {
    COLOR.store(color, Ordering::Relaxed);
}

/// Sets the global log formatter.
/// This can be used to redirect afire's log output to a file, or to another logging system.
/// By default, afire will use a simple formatter that prints to stdout.
pub fn set_log_formatter(formatter: impl Formatter + Send + Sync + 'static) {
    FORMATTER_PRESENT.store(true, Ordering::Relaxed);
    *FORMATTER.write().unwrap() = Some(Box::new(formatter));
}

/// Logs a message at the specified log level.
/// Hidden from the docs, as it is only intended for internal use through the [`trace!`] macro.
#[doc(hidden)]
pub fn _trace(level: Level, fmt: Arguments) {
    let log_level = LEVEL.load(Ordering::Relaxed);
    if level as u8 > log_level {
        return;
    }

    let msg = fmt.to_string();
    if FORMATTER_PRESENT.load(Ordering::Relaxed) {
        let formatter = FORMATTER.read().unwrap();
        if let Some(formatter) = &*formatter {
            formatter.format(level, COLOR.load(Ordering::Relaxed), msg);
            return;
        }
    }

    DefaultFormatter.format(level, COLOR.load(Ordering::Relaxed), msg);
}

// this is a totally normal and necessary function
pub(crate) fn emoji(emoji: &str) -> String {
    #[cfg(feature = "emoji-logging")]
    return emoji.to_owned() + " ";
    #[cfg(not(feature = "emoji-logging"))]
    String::new()
}

/// Simple logging system.
/// See [`mod@crate::trace`] for more information.
///
/// Enabled with the `tracing` feature
#[macro_export]
macro_rules! trace {
    (Level::$level: ident, $($arg: tt) *) => {
        #[cfg(feature = "tracing")]
        $crate::trace::_trace($crate::trace::Level::$level, format_args!($($arg)+));
    };
    ($($arg : tt) +) => {
        #[cfg(feature = "tracing")]
        $crate::trace::_trace($crate::trace::Level::Trace, format_args!($($arg)+));
    };
}

/// A trait for custom log formatters.
pub trait Formatter {
    /// Processes a log message.
    /// This will usually print the message to stdout, write it to a file, or pass it to another logging system.
    ///
    /// Note: Only log messages with a level equal to or higher than the global log level will be passed to the formatter.
    fn format(&self, level: Level, color: bool, msg: String);
}

/// The default log formatter.
/// afire will use this if no custom formatter is set.
///
/// Prints logs to stdout in the following format:
/// ```text
/// [LEVEL] MESSAGE
/// ```
pub struct DefaultFormatter;

impl Formatter for DefaultFormatter {
    fn format(&self, level: Level, _color: bool, msg: String) {
        let color = COLOR.load(Ordering::Relaxed);

        println!(
            "[{}] {}{}{}",
            level.as_str(),
            if color { level.get_color() } else { "" },
            msg,
            if color { "\x1b[0m" } else { "" }
        );
    }
}