lexa_logger/stdout/
mod.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ Copyright: (c) 2023, Mike 'PhiSyX' S. (https://github.com/PhiSyX)         ┃
3// ┃ SPDX-License-Identifier: MPL-2.0                                          ┃
4// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
5// ┃                                                                           ┃
6// ┃  This Source Code Form is subject to the terms of the Mozilla Public      ┃
7// ┃  License, v. 2.0. If a copy of the MPL was not distributed with this      ┃
8// ┃  file, You can obtain one at https://mozilla.org/MPL/2.0/.                ┃
9// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
10
11mod builder;
12mod extension;
13
14use std::collections::HashMap;
15use std::sync::{Arc, Mutex};
16
17use console::style;
18
19pub use self::builder::LoggerStdoutBuilder;
20pub use self::extension::LoggerStdoutBuilderExtension;
21use crate::builder::{LoggerFilterCallback, LoggerFormatFn};
22use crate::echo::Echo;
23use crate::layout;
24
25// --------- //
26// Structure //
27// --------- //
28
29// Contrat
30pub struct LoggerStdout
31{
32	pub(crate) colorized: bool,
33	pub(crate) timestamp: bool,
34	#[cfg(not(feature = "tracing"))]
35	pub(crate) level: log::LevelFilter,
36	#[cfg(feature = "tracing")]
37	pub(crate) level: tracing::level_filters::LevelFilter,
38	pub(crate) format_fn: LoggerFormatFn,
39	pub(crate) filter: LoggerFilter,
40	pub(crate) cache: Arc<Mutex<HashMap<String, bool>>>,
41}
42
43#[derive(Default)]
44pub struct LoggerFilter
45{
46	callbacks: Vec<Box<LoggerFilterCallback>>,
47	dependencies: Vec<String>,
48}
49
50// -------------- //
51// Implémentation //
52// -------------- //
53
54impl LoggerStdout
55{
56	pub fn builder() -> builder::LoggerStdoutBuilder
57	{
58		builder::LoggerStdoutBuilder::default()
59	}
60}
61
62impl LoggerStdout
63{
64	#[cfg(not(feature = "tracing"))]
65	pub fn level(&self) -> log::LevelFilter
66	{
67		self.level
68	}
69
70	#[cfg(feature = "tracing")]
71	pub fn level(&self) -> tracing::level_filters::LevelFilter
72	{
73		self.level
74	}
75
76	fn default_format(message: &std::fmt::Arguments, record: &log::Record, echo: &mut Echo) -> String
77	{
78		let local_date_format = echo
79			.time
80			.map(|local_datetime| local_datetime.format("%Y-%m-%d@%H:%M:%S"));
81
82		if let Some(time) = local_date_format {
83			echo.table.add_line([
84				layout::Cell::new(&echo.level).with_alignment(layout::Alignment::Right),
85				layout::Cell::new(&echo.delimiter),
86				layout::Cell::new(time),
87				layout::Cell::new(&echo.delimiter),
88				layout::Cell::new(format!(
89					"{} {} {}",
90					if echo.colorized {
91						style(record.target()).black().bright()
92					} else {
93						style(record.target())
94					},
95					if echo.colorized { style("->").red() } else { style("->") },
96					message
97				)),
98			]);
99		} else {
100			echo.table.add_line([
101				layout::Cell::new(&echo.level).with_alignment(layout::Alignment::Right),
102				layout::Cell::new(&echo.delimiter),
103				layout::Cell::new(style(record.target()).black().bright()),
104				layout::Cell::new(style("->").red()),
105				layout::Cell::new(message),
106			]);
107		}
108
109		echo.table.render()
110	}
111}
112
113impl LoggerFilter
114{
115	/// Pousse une callback dans le tableau des callbacks.
116	pub(crate) fn push_callback<F>(&mut self, predicate: F)
117	where
118		F: 'static,
119		F: Send + Sync,
120		F: Fn(&log::Metadata) -> bool,
121	{
122		self.callbacks.push(Box::new(predicate));
123	}
124
125	/// Ajoute une dépendance dans le tableau des dépendances.
126	pub(crate) fn add_dependency(&mut self, dependency: impl ToString)
127	{
128		self.dependencies.push(dependency.to_string());
129	}
130}
131
132// -------------- //
133// Implémentation // -> Interface
134// -------------- //
135
136impl log::Log for LoggerStdout
137{
138	/// On ne veut pas afficher les logs si le niveau est à
139	/// [log::LevelFilter::Off].
140	///
141	/// Des conditions utilisateurs peuvent être utilisées pour filtrer les
142	/// logs.
143	fn enabled(&self, metadata: &log::Metadata) -> bool
144	{
145		let mut guard = self.cache.lock().expect("cache guard");
146
147		metadata.level() != log::LevelFilter::Off
148			&& (self.filter.callbacks.is_empty()
149				|| self.filter.callbacks.iter().enumerate().any(|(idx, once_fn)| {
150					let cache_key = format!("{}_{}", self.filter.dependencies[idx], metadata.target());
151
152					if let Some(has) = guard.get(&cache_key) {
153						return *has;
154					}
155
156					let is_ok = once_fn(metadata);
157					guard.insert(cache_key, is_ok);
158					is_ok
159				}))
160	}
161
162	/// Affiche le log.
163	//
164	// FIXME(phisyx): améliorer les performances du logger. Le simple fait de
165	// les afficher nous fait perdre un temps considérable.
166	fn log(&self, record: &log::Record)
167	{
168		if !self.enabled(record.metadata()) {
169			return;
170		}
171
172		let message = record.args();
173		if message.to_string().trim().is_empty() {
174			return;
175		}
176
177		let level = if self.colorized {
178			match record.level() {
179				| log::Level::Error => style("ERROR").red(),
180				| log::Level::Warn => style(" WARN").yellow(),
181				| log::Level::Info => style(" INFO").blue(),
182				| log::Level::Debug => style("DEBUG").magenta(),
183				| log::Level::Trace => style("TRACE").white(),
184			}
185			.to_string()
186		} else {
187			record.level().to_string()
188		};
189
190		let mut table = layout::GridLayout::default().define_max_width(120).without_boarder();
191
192		let mut echo = Echo {
193			colorized: self.colorized,
194			delimiter: if self.colorized { style("|").red() } else { style("|") }.to_string(),
195			level,
196			record_level: record.level(),
197			table: &mut table,
198			time: if self.timestamp {
199				Some(chrono::Local::now())
200			} else {
201				None
202			},
203		};
204
205		let text = (self.format_fn)(message, record, &mut echo);
206
207		echo.log(text);
208	}
209
210	fn flush(&self) {}
211}