1use clap::ValueEnum;
2use std::sync::OnceLock;
3use tracing::level_filters::LevelFilter;
4use tracing_subscriber::EnvFilter;
5use tracing_subscriber::{Layer, prelude::*};
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, ValueEnum, Default)]
8#[clap(rename_all = "kebab-case")]
9pub enum LogFormat {
10 #[default]
11 Auto,
12 Pretty,
13 Simplified,
14 Json,
15}
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, ValueEnum, Default)]
18#[clap(rename_all = "kebab-case")]
19pub enum Level {
20 Error,
21 Warn,
22 #[default]
23 Info,
24 Debug,
25 Trace,
26 Off,
27}
28
29impl Level {
30 pub const fn level_filter(self) -> LevelFilter {
31 match self {
32 Self::Error => LevelFilter::ERROR,
33 Self::Warn => LevelFilter::WARN,
34 Self::Info => LevelFilter::INFO,
35 Self::Debug => LevelFilter::DEBUG,
36 Self::Trace => LevelFilter::TRACE,
37 Self::Off => LevelFilter::OFF,
38 }
39 }
40}
41
42pub fn init(log_level: Level, log_format: LogFormat, log_filter: Option<&str>) {
43 static INIT: OnceLock<()> = OnceLock::new();
44 let _ = INIT.get_or_init(|| {
45 let subscriber = tracing_subscriber::fmt::layer()
46 .with_writer(std::io::stderr)
47 .with_target(true);
48
49 let format = match (log_format, console::user_attended()) {
50 (LogFormat::Auto, true) | (LogFormat::Pretty, _) => {
51 subscriber.compact().without_time().boxed()
52 }
53 (LogFormat::Auto, false) | (LogFormat::Simplified, _) => {
54 subscriber.with_ansi(false).boxed()
55 }
56 (LogFormat::Json, _) => subscriber
57 .json()
58 .flatten_event(true)
59 .with_current_span(true)
60 .with_span_list(true)
61 .with_file(true)
62 .with_line_number(true)
63 .boxed(),
64 };
65
66 let filter = match log_filter {
67 Some(directive) => EnvFilter::builder()
68 .with_default_directive(log_level.level_filter().into())
69 .parse_lossy(directive),
70 None => EnvFilter::builder()
71 .with_default_directive(log_level.level_filter().into())
72 .from_env_lossy(),
73 };
74
75 tracing_subscriber::registry()
76 .with(format.with_filter(filter))
77 .init();
78 });
79}