1use crate::engine::ENGINE;
21use crate::log_format::LogFormat;
22use crate::log_level::LogLevel;
23use crate::logger::{RlgLogger, to_log_level_filter};
24use std::fmt;
25use std::sync::OnceLock;
26
27fn detect_default_format() -> LogFormat {
33 if std::env::var("RLG_ENV")
34 .map(|v| v == "production")
35 .unwrap_or(false)
36 {
37 return LogFormat::JSON;
38 }
39 if atty_stdout() {
40 LogFormat::Logfmt
41 } else {
42 LogFormat::JSON
43 }
44}
45
46fn atty_stdout() -> bool {
48 use std::io::IsTerminal;
49 std::io::stdout().is_terminal()
50}
51
52fn parse_rust_log() -> Option<LogLevel> {
57 let val = std::env::var("RUST_LOG").ok()?;
58 let mut most_permissive: Option<LogLevel> = None;
59 for directive in val.split(',') {
60 let level_str = directive
61 .split('=')
62 .next_back()
63 .unwrap_or(directive)
64 .trim();
65 if let Ok(level) = level_str.parse::<LogLevel>() {
66 match most_permissive {
67 None => most_permissive = Some(level),
68 Some(current)
69 if level.to_numeric() < current.to_numeric() =>
70 {
71 most_permissive = Some(level);
72 }
73 _ => {}
74 }
75 }
76 }
77 most_permissive
78}
79
80static INIT_GUARD: OnceLock<()> = OnceLock::new();
82
83static LOGGER: OnceLock<RlgLogger> = OnceLock::new();
85
86#[derive(Debug, Clone, Copy)]
88pub enum InitError {
89 LoggerAlreadySet,
91 SubscriberAlreadySet,
93 AlreadyInitialized,
95}
96
97impl fmt::Display for InitError {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 match self {
100 Self::LoggerAlreadySet => {
101 f.write_str("a log crate logger was already set")
102 }
103 Self::SubscriberAlreadySet => {
104 f.write_str("a tracing subscriber was already set")
105 }
106 Self::AlreadyInitialized => {
107 f.write_str("rlg was already initialized")
108 }
109 }
110 }
111}
112
113impl std::error::Error for InitError {}
114
115#[derive(Debug, Clone, Copy)]
117pub struct RlgBuilder {
118 level: LogLevel,
119 format: LogFormat,
120 install_log: bool,
121 install_tracing: bool,
122}
123
124impl Default for RlgBuilder {
125 fn default() -> Self {
126 Self {
127 level: LogLevel::INFO,
128 format: detect_default_format(),
129 install_log: true,
130 install_tracing: true,
131 }
132 }
133}
134
135impl RlgBuilder {
136 #[must_use]
138 pub const fn level(mut self, level: LogLevel) -> Self {
139 self.level = level;
140 self
141 }
142
143 #[must_use]
145 pub const fn format(mut self, format: LogFormat) -> Self {
146 self.format = format;
147 self
148 }
149
150 #[must_use]
152 pub const fn without_log(mut self) -> Self {
153 self.install_log = false;
154 self
155 }
156
157 #[must_use]
159 pub const fn without_tracing(mut self) -> Self {
160 self.install_tracing = false;
161 self
162 }
163
164 pub(crate) fn install_log_facade(
170 format: LogFormat,
171 level: LogLevel,
172 ) -> Result<(), InitError> {
173 let logger = LOGGER.get_or_init(|| RlgLogger::new(format));
174 log::set_logger(logger)
175 .map_err(|_| InitError::LoggerAlreadySet)?;
176 log::set_max_level(to_log_level_filter(level));
177 Ok(())
178 }
179
180 pub(crate) fn install_tracing_subscriber() -> Result<(), InitError>
186 {
187 let subscriber = crate::tracing::RlgSubscriber::new();
188 let dispatch =
189 tracing_core::dispatcher::Dispatch::new(subscriber);
190 tracing_core::dispatcher::set_global_default(dispatch)
191 .map_err(|_| InitError::SubscriberAlreadySet)?;
192 Ok(())
193 }
194
195 pub fn init(mut self) -> Result<FlushGuard, InitError> {
205 if INIT_GUARD.set(()).is_err() {
206 return Err(InitError::AlreadyInitialized);
207 }
208
209 if let Some(env_level) = parse_rust_log() {
211 self.level = env_level;
212 }
213
214 ENGINE.set_filter(self.level.to_numeric());
216
217 if self.install_log {
219 Self::install_log_facade(self.format, self.level)?;
220 }
221
222 if self.install_tracing {
224 Self::install_tracing_subscriber()?;
225 }
226
227 Ok(FlushGuard { _private: () })
228 }
229}
230
231#[must_use]
233pub fn builder() -> RlgBuilder {
234 RlgBuilder::default()
235}
236
237#[derive(Debug)]
248pub struct FlushGuard {
249 _private: (),
250}
251
252impl Drop for FlushGuard {
253 fn drop(&mut self) {
254 ENGINE.shutdown();
255 }
256}
257
258pub fn init() -> Result<FlushGuard, InitError> {
266 builder().init()
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn test_init_error_display_logger_already_set() {
275 let err = InitError::LoggerAlreadySet;
276 assert_eq!(
277 err.to_string(),
278 "a log crate logger was already set"
279 );
280 }
281
282 #[test]
283 fn test_init_error_display_subscriber_already_set() {
284 let err = InitError::SubscriberAlreadySet;
285 assert_eq!(
286 err.to_string(),
287 "a tracing subscriber was already set"
288 );
289 }
290
291 #[test]
292 fn test_init_error_display_already_initialized() {
293 let err = InitError::AlreadyInitialized;
294 assert_eq!(err.to_string(), "rlg was already initialized");
295 }
296
297 #[test]
298 fn test_init_error_debug() {
299 let err = InitError::LoggerAlreadySet;
300 assert_eq!(format!("{err:?}"), "LoggerAlreadySet");
301 }
302
303 #[test]
304 fn test_init_error_clone_copy() {
305 let err = InitError::AlreadyInitialized;
306 let cloned = err;
307 assert_eq!(format!("{err:?}"), format!("{cloned:?}"));
308 }
309
310 #[test]
311 fn test_init_error_is_error() {
312 let err = InitError::LoggerAlreadySet;
313 let _: &dyn std::error::Error = &err;
315 }
316
317 #[test]
318 fn test_builder_defaults() {
319 let b = RlgBuilder::default();
320 assert_eq!(b.level, LogLevel::INFO);
321 assert!(b.install_log);
322 assert!(b.install_tracing);
323 assert!(
325 b.format == LogFormat::JSON
326 || b.format == LogFormat::Logfmt
327 );
328 }
329
330 #[test]
331 fn test_builder_level() {
332 let b = builder().level(LogLevel::DEBUG);
333 assert_eq!(b.level, LogLevel::DEBUG);
334 }
335
336 #[test]
337 fn test_builder_format() {
338 let b = builder().format(LogFormat::JSON);
339 assert_eq!(b.format, LogFormat::JSON);
340 }
341
342 #[test]
343 fn test_builder_without_log() {
344 let b = builder().without_log();
345 assert!(!b.install_log);
346 assert!(b.install_tracing);
347 }
348
349 #[test]
350 fn test_builder_without_tracing() {
351 let b = builder().without_tracing();
352 assert!(b.install_log);
353 assert!(!b.install_tracing);
354 }
355
356 #[test]
357 fn test_builder_chaining() {
358 let b = builder()
359 .level(LogLevel::TRACE)
360 .format(LogFormat::ECS)
361 .without_log()
362 .without_tracing();
363 assert_eq!(b.level, LogLevel::TRACE);
364 assert_eq!(b.format, LogFormat::ECS);
365 assert!(!b.install_log);
366 assert!(!b.install_tracing);
367 }
368
369 #[test]
370 fn test_builder_clone_copy() {
371 let b = builder().level(LogLevel::WARN);
372 let b2 = b;
373 assert_eq!(b.level, b2.level);
375 assert_eq!(b.format, b2.format);
376 }
377
378 #[test]
379 fn test_builder_without_facades_configuration() {
380 let b = builder().without_log().without_tracing();
381 assert!(!b.install_log);
382 assert!(!b.install_tracing);
383 }
384
385 #[test]
386 fn test_builder_fn() {
387 let b = builder();
388 assert_eq!(b.level, LogLevel::INFO);
389 assert!(
391 b.format == LogFormat::JSON
392 || b.format == LogFormat::Logfmt
393 );
394 assert!(b.install_log);
395 assert!(b.install_tracing);
396 }
397
398 #[test]
399 fn test_init_error_source() {
400 let err = InitError::LoggerAlreadySet;
401 assert!(std::error::Error::source(&err).is_none());
403 }
404
405 #[test]
406 fn test_builder_default_impl() {
407 let b1 = RlgBuilder::default();
408 let b2 = builder();
409 assert_eq!(b1.level, b2.level);
410 assert_eq!(b1.format, b2.format);
411 assert_eq!(b1.install_log, b2.install_log);
412 assert_eq!(b1.install_tracing, b2.install_tracing);
413 }
414
415 #[test]
416 fn test_init_error_all_display_variants() {
417 let msgs: Vec<String> = vec![
419 InitError::LoggerAlreadySet,
420 InitError::SubscriberAlreadySet,
421 InitError::AlreadyInitialized,
422 ]
423 .into_iter()
424 .map(|e| e.to_string())
425 .collect();
426 assert_eq!(msgs.len(), 3);
427 assert!(msgs[0].contains("log"));
428 assert!(msgs[1].contains("tracing"));
429 assert!(msgs[2].contains("already initialized"));
430 }
431
432 #[test]
433 #[cfg_attr(miri, ignore)]
434 fn test_init_guard_static() {
435 let _ = INIT_GUARD.set(());
438 assert!(INIT_GUARD.set(()).is_err());
440 }
441
442 #[test]
443 #[cfg_attr(miri, ignore)]
444 fn test_logger_static() {
445 let logger =
447 LOGGER.get_or_init(|| RlgLogger::new(LogFormat::JSON));
448 assert!(format!("{logger:?}").contains("RlgLogger"));
449 }
450
451 #[test]
452 #[cfg_attr(miri, ignore)]
453 fn test_install_log_facade() {
454 let r1 = RlgBuilder::install_log_facade(
456 LogFormat::JSON,
457 LogLevel::INFO,
458 );
459 assert!(
460 r1.is_ok()
461 || matches!(r1, Err(InitError::LoggerAlreadySet))
462 );
463 let r2 = RlgBuilder::install_log_facade(
465 LogFormat::MCP,
466 LogLevel::DEBUG,
467 );
468 assert!(matches!(r2, Err(InitError::LoggerAlreadySet)));
469 }
470
471 #[test]
472 #[cfg_attr(miri, ignore)]
473 fn test_install_tracing_subscriber() {
474 let r1 = RlgBuilder::install_tracing_subscriber();
476 assert!(
477 r1.is_ok()
478 || matches!(r1, Err(InitError::SubscriberAlreadySet))
479 );
480 let r2 = RlgBuilder::install_tracing_subscriber();
482 assert!(matches!(r2, Err(InitError::SubscriberAlreadySet)));
483 }
484}