my_env_logger_style/
lib.rs

1#![allow(clippy::tabs_in_doc_comments)]
2#![warn(rust_2018_idioms, unreachable_pub)]
3#![forbid(elided_lifetimes_in_paths)]
4#![cfg_attr(all(doc, nightly), feature(doc_auto_cfg))]
5
6//! A pretty, opinionated style for [env_logger](https://crates.io/crates/env_logger) inspirated by [pretty-env-logger](https://crates.io/crates/pretty_env_logger).
7//!
8//! It is not a goal of this crate to create a feature rich wrapper around [env_logger](https://crates.io/crates/env_logger).
9//! Instead it does provide a formater, which can be applied to the [`env_logger::Builder`].
10//! Additional an optional [function](just_log) to create and register a zero config logger is provided.
11//!
12//! Timestamp, emojis and modules can be disable separately.
13//!
14//! # Preview
15//!
16//! ![image](https://user-images.githubusercontent.com/44570204/236641121-5071e42a-9f9b-4bff-a6fb-03ff294f5d9e.png)
17//!
18//! with timestamps:
19//!
20//! ![image](https://user-images.githubusercontent.com/44570204/236641172-fb304d1f-7e50-4283-969e-949a76b0ba00.png)
21//! # Usage
22//! #### Quickstart
23//! ```
24//! # use log::info;
25//! my_env_logger_style::just_log();
26//! info!("Hello, world!");
27//! ```
28//! This creates the default env_logger from environment variables and register it as logger.
29//! #### Advance
30//! You can also create an [`env_logger::Builder`] and apply the style definded at this crate,
31//! by using the [`format()`] function.
32//! ```
33//! use log::info;
34//! use my_env_logger_style::format;
35//!
36//! env_logger::Builder::new()
37//! 	.parse_default_env()
38//! 	.format(format)
39//! 	.init();
40//! info!("Hello, world!");
41//! ```
42//! # Feature-flags
43//! #### time (default)
44//! Enable RFC3339 timestamps
45//! #### custom-arg-formatter
46//! Allow using a custom formater to format the args (the actual message) of the log record.
47//! As example this can be used to avoid logging private userdata.
48use anstyle::Style;
49use env_logger::fmt::Formatter;
50use log::{Level, Record};
51#[cfg(feature = "custom-arg-formatter")]
52use once_cell::sync::OnceCell;
53use std::{
54	io,
55	io::Write,
56	sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering}
57};
58
59static MAX_MODULE_LEN: AtomicUsize = AtomicUsize::new(0);
60static SHOW_MODULE: AtomicBool = AtomicBool::new(true);
61static SHOW_EMOJIS: AtomicBool = AtomicBool::new(true);
62#[cfg(feature = "time")]
63static SHOW_TIME: AtomicU8 = AtomicU8::new(TimestampPrecision::Seconds as u8);
64#[cfg(feature = "custom-arg-formatter")]
65static ARG_FORMATTER: OnceCell<Box<dyn ArgFormatter + Send + Sync>> = OnceCell::new();
66
67pub use env_logger;
68
69#[repr(u8)]
70/// RFC3339 timestamps
71pub enum TimestampPrecision {
72	Disable,
73	Seconds,
74	Millis,
75	Micros,
76	Nanos
77}
78
79/// Create a preconfigured builder,
80/// with same configuration like [`just_log()`].
81///
82/// For an unconfigurated bulider use [`env_logger::Builder::new()`]
83pub fn builder() -> env_logger::Builder {
84	let mut builder = env_logger::Builder::new();
85	builder.filter_level(log::LevelFilter::Info) //set defaul log level
86		.parse_default_env()
87		.format(format);
88	builder
89}
90
91///create and regstier a logger from the default environment variables
92pub fn just_log() {
93	builder().init();
94}
95
96///enable or disabel showing the module path
97pub fn show_module(show: bool) {
98	SHOW_MODULE.store(show, Ordering::Relaxed);
99}
100
101///enable or disabel showing emojis before the log level
102pub fn show_emoji(show: bool) {
103	SHOW_EMOJIS.store(show, Ordering::Relaxed);
104}
105
106/// return the current module len and set the module length to the maximum of the current value and the given `len`.
107///
108/// Usefull if you already know the length of module and would like to have an consistant indentation from the beginnig.
109pub fn get_set_max_module_len(len: usize) -> usize {
110	let module_len = MAX_MODULE_LEN.load(Ordering::Relaxed);
111	if module_len < len {
112		MAX_MODULE_LEN.store(len, Ordering::Relaxed);
113	}
114	module_len
115}
116
117#[cfg(feature = "time")]
118/// set the timestamp precision or disable timestamps complete
119pub fn set_timestamp_precision(timestamp_precission: TimestampPrecision) {
120	SHOW_TIME.store(timestamp_precission as u8, Ordering::Relaxed);
121}
122
123pub trait ArgFormatter {
124	fn arg_format(&self, buf: &mut Formatter, record: &Record<'_>) -> io::Result<()>;
125}
126
127impl<F: Fn(&mut Formatter, &Record<'_>) -> io::Result<()>> ArgFormatter for F {
128	fn arg_format(&self, buf: &mut Formatter, record: &Record<'_>) -> io::Result<()> {
129		self(buf, record)
130	}
131}
132
133/// Use a custom formater to format the args (the actual message) of the record .
134/// This function can only be called once and return an Error if called a second time.
135///
136/// This example remove the private user ipv4 loggeg by `hickory` from the log, if the loglevel is below Debug.
137/// ```
138/// use env_logger::fmt::Formatter;
139/// use log::{Level, Record};
140/// use once_cell::sync::Lazy;
141/// use regex::Regex;
142/// use std::{io, io::Write};
143///
144/// static REGEX: Lazy<Regex> = Lazy::new(|| {
145/// 	// https://ihateregex.io/expr/ip/
146/// 	Regex::new(r"(\b25[0-5]|\b2[0-4][0-9]|\b[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}")
147/// 		.unwrap()
148/// });
149///
150/// fn arg_format(buf: &mut Formatter, record: &Record<'_>) -> io::Result<()> {
151/// 	if let Some(mod_path) = record.module_path() {
152/// 		if log::max_level() < Level::Debug && mod_path.starts_with("hickory") {
153/// 			let message = format!("{}", record.args());
154/// 			let message = REGEX.replace_all(&message, "RESTRAINED");
155/// 			return writeln!(buf, "{}", message);
156/// 		}
157/// 	};
158/// 	writeln!(buf, "{}", record.args())
159/// }
160///
161/// my_env_logger_style::set_arg_formatter(Box::new(arg_format)).unwrap();
162/// ```
163#[cfg(feature = "custom-arg-formatter")]
164pub fn set_arg_formatter(
165	forrmatter: Box<dyn ArgFormatter + Send + Sync>
166) -> Result<(), ()> {
167	ARG_FORMATTER.set(forrmatter).map_err(|_| ())
168}
169
170///log formater witch can be used at the [`format()`](env_logger::Builder::format()) function of the [`env_logger::Builder`].
171pub fn format(buf: &mut Formatter, record: &Record<'_>) -> io::Result<()> {
172	let bold = Style::new().bold();
173	let dimmed = Style::new().dimmed();
174
175	#[cfg(feature = "time")]
176	{
177		let show_time = SHOW_TIME.load(Ordering::Relaxed);
178		// safety: SHOW_TIME is inilized with TimestampPrecision::Seconds
179		// and can only be written by using set_timestamp_precision()
180		match unsafe { std::mem::transmute(show_time) } {
181			TimestampPrecision::Disable => Ok(()),
182			TimestampPrecision::Seconds => {
183				write!(buf, "{dimmed}{}{dimmed:#} ", buf.timestamp_seconds())
184			},
185			TimestampPrecision::Millis => {
186				write!(buf, "{dimmed}{}{dimmed:#} ", buf.timestamp_millis())
187			},
188			TimestampPrecision::Micros => {
189				write!(buf, "{dimmed}{}{dimmed:#} ", buf.timestamp_micros())
190			},
191			TimestampPrecision::Nanos => {
192				write!(buf, "{dimmed}{}{dimmed:#} ", buf.timestamp_nanos())
193			}
194		}?;
195	}
196
197	let level_style = buf.default_level_style(record.level());
198	let level_symbol = if SHOW_EMOJIS.load(Ordering::Relaxed) {
199		match record.level() {
200			//💥 and 🔬 are 2 chars big at the terminal. How does it look with other fonts/terminals?
201			Level::Trace => "🔬",
202			Level::Debug => " ⚙️",
203			Level::Info => " ℹ",
204			Level::Warn => " ⚠",
205			Level::Error => "💥"
206		}
207	} else {
208		""
209	};
210	write!(
211		buf,
212		"{level_symbol} {level_style}{:5}{level_style:#} ",
213		record.level()
214	)?;
215
216	if SHOW_MODULE.load(Ordering::Relaxed) {
217		let module = record.module_path().unwrap_or_default();
218		let module_len = get_set_max_module_len(module.len());
219		write!(
220			buf,
221			"{dimmed}{module:module_len$}{dimmed:#} {bold}>{bold:#} "
222		)?;
223	}
224
225	#[cfg(feature = "custom-arg-formatter")]
226	if let Some(formatter) = ARG_FORMATTER.get() {
227		return formatter.arg_format(buf, record);
228	}
229	writeln!(buf, "{}", record.args())
230}