Skip to main content

li_logger/
lib.rs

1use std::sync::{Arc, Mutex, Weak};
2use colored::{self, Color, Colorize};
3
4lazy_static::lazy_static! {
5    static ref BUS: Mutex<Option<Arc<EventBus<LogEvent>>>> = Mutex::new(None);
6}
7
8#[cfg(feature = "middleware")]
9pub mod middleware;
10
11#[derive(Clone)]
12pub struct EventBus<E> {
13    sender: crossbeam::channel::Sender<E>,
14    receiver: crossbeam::channel::Receiver<E>,
15}
16
17impl<E> EventBus<E> {
18    pub fn new(cap: usize) -> Self {
19        let (sender, receiver) = crossbeam::channel::bounded(cap);
20        Self { sender, receiver }
21    }
22
23    pub fn push(&self, event: E) {
24        let _ = self.sender.send(event);
25    }
26}
27
28/// The function creates a logging thread. 
29/// 
30/// The [`get_logger`] function will be usable after [`init`] is called.
31/// 
32/// ### Close
33/// To close the logger thread, just call [`li_logger::close()`].
34/// 
35/// All [`Logger`] will not be usable after [`close`] is called.
36/// 
37/// ### Example
38/// ```rust
39/// use li_logger::default_formatter;
40/// 
41/// #[tokio::main]
42/// async fn main() {
43/// 
44///     let handle = li_logger::init(100, default_formatter);
45///     let mut logger = li_logger::get_logger();
46///     
47///     logger.success("Li Logger Started!");
48///     
49///     li_logger::close();
50///     handle.await;
51/// }
52/// ``
53pub fn init(cap: usize, formatter: fn(content: &str, level: &str, color: Color, strong: bool) -> String) -> std::thread::JoinHandle<()> {
54    let bus = Arc::new(EventBus::<LogEvent>::new(cap));
55    let receiver = bus.receiver.clone();
56    BUS.lock().unwrap().replace(bus);
57
58    std::thread::spawn(move || {
59        loop {
60            match receiver.recv() {
61                Ok(event) => {
62                    let event = formatter(
63                        &event.content,
64                        &event.meta.string,
65                        event.meta.color,
66                        event.strong
67                    );
68                    println!("{}", event);
69                }
70                Err(_) => {
71                    break;
72                }
73            }
74        }
75    })
76}
77
78
79#[cfg(feature = "async")]
80/// The function creates a async logging thread. The only difference is that this uses [`tokio::spawn`] instead of [`std::thread::spawn`].
81/// 
82/// The [`get_logger`] function will be usable after [`init`] is called.
83/// 
84/// ### Close
85/// To close the logger thread, just call [`li_logger::close()`].
86/// 
87/// All [`Logger`] will not be usable after [`close`] is called.
88/// 
89/// ### Example
90/// ```rust
91/// use li_logger::default_formatter;
92/// 
93/// #[tokio::main]
94/// async fn main() {
95/// 
96///     let handle = li_logger::async_init(100, default_formatter);
97///     let mut logger = li_logger::get_logger();
98///     
99///     logger.success("Li Logger Started!");
100///     
101///     li_logger::close();
102///     handle.await;
103/// }
104/// ```
105pub fn async_init(cap: usize, formatter: fn(content: &str, level: &str, color: Color, strong: bool) -> String) -> tokio::task::JoinHandle<()> {
106
107    
108    let bus = Arc::new(EventBus::<LogEvent>::new(cap));
109    let receiver = bus.receiver.clone();
110    BUS.lock().unwrap().replace(bus);
111
112    tokio::spawn(async move {
113        loop {
114            match tokio::task::spawn_blocking({
115                let receiver = receiver.clone();
116                move || receiver.recv()
117            }).await {
118                Ok(Ok(event)) => {
119                    let event = formatter(
120                        &event.content,
121                        &event.meta.string,
122                        event.meta.color,
123                        event.strong
124                    );
125                    println!("{}", event);
126                }
127                Ok(Err(_)) => {
128                    break;
129                }
130                Err(_) => {
131                    break;
132                }
133            }
134        }
135    })
136}
137
138pub fn close() {
139    *BUS.lock().unwrap() = None;
140}
141
142
143/// Default formatter for [`init()`]
144/// 
145/// Example: `21:50:29 [I] : Hello, World!`
146/// 
147/// You may customize the formatter by implementing a `fn(content: &str, level: &str, color: Color, strong: bool) -> String` and pass it to [`init()`]
148pub fn default_formatter(content: &str, level: &str, color: Color, strong: bool) -> String {
149
150    let mut result = String::new();
151    let timestamp = chrono::Local::now().format("%H:%M:%S").to_string()
152        .color(Color::BrightBlack);
153    let lines: Vec<&str> = content.lines().collect();
154
155    for (i, line) in lines.iter().enumerate() {
156        if i == 0 {
157            result.push_str(&format!(
158                "{} [{}] : {}",
159                timestamp,
160                level.color(color).bold(),
161                if strong { line.color(color).bold().to_string() }
162                else { line.to_string() }
163            ));
164        } else if i < lines.len() - 1 {
165            result.push_str(&format!(
166                "\n             {} {}",
167                ":".color(Color::BrightBlack),
168                if strong { line.color(color).bold().to_string() }
169                else { line.to_string() }
170            ));
171        } else {
172            result.push_str(&format!(
173                "\n             : {}",
174                if strong { line.color(color).bold().to_string() }
175                else { line.to_string() }
176            ));
177        }
178    }
179    result
180}
181
182pub struct LogMeta {
183    string: String,
184    color: Color
185}
186
187pub struct LogEvent {
188    meta: LogMeta,
189    content: String,
190    strong: bool
191}
192
193#[derive(Clone)]
194pub struct Logger {
195    bus: Weak<EventBus<LogEvent>>,
196    temporary_strong: bool
197}
198
199impl Logger {
200    pub fn log(&mut self, meta: LogMeta, content: impl std::fmt::Display) {
201        let content = format!("{}", content);
202        if let Some(bus) = self.bus.upgrade() {
203            let event = LogEvent {
204                meta,
205                content,
206                strong: self.temporary_strong
207            };
208            bus.push(event);
209        }
210        self.temporary_strong = false;
211    }
212
213    pub fn info(&mut self, content: impl std::fmt::Display) {
214        self.log(
215            LogMeta {
216                string: "I".to_string(),
217                color: Color::Blue
218            },
219            content
220        );
221    }
222
223    pub fn warn(&mut self, content: impl std::fmt::Display) {
224        self.log(
225            LogMeta {
226                string: "W".to_string(),
227                color: Color::Yellow
228            },
229            content
230        );
231    }
232
233    pub fn error(&mut self, content: impl std::fmt::Display) {
234        self.log(
235            LogMeta {
236                string: "E".to_string(),
237                color: Color::Red
238            },
239            content
240        );
241    }
242
243    pub fn debug(&mut self, content: impl std::fmt::Display) {
244        self.log(
245            LogMeta {
246                string: "D".to_string(),
247                color: Color::Magenta
248            },
249            content
250        );
251    }
252
253    pub fn success(&mut self, content: impl std::fmt::Display) {
254        self.log(
255            LogMeta {
256                string: "S".to_string(),
257                color: Color::Green
258            },
259            content
260        );
261    }
262
263    pub fn strong(&mut self) -> &mut Self {
264        self.temporary_strong = true;
265        self
266    }
267}
268
269/// This will panic if [`Logger`] was not initialized.
270pub fn get_logger() -> Logger {
271    Logger {
272        bus: Arc::downgrade(
273            BUS.lock().unwrap()
274            .as_ref()
275            .expect("Logger not initialized")
276        ),
277        temporary_strong: false
278    }
279}