1use chrono::Utc;
2use clap::ValueEnum;
3use serde_json::json;
4use std::sync::OnceLock;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
7pub enum LogLevel {
8 Trace,
9 Verbose,
10 Debug,
11 Info,
12 Warning,
13 Error,
14 Critical,
15}
16
17impl LogLevel {
18 fn priority(self) -> u8 {
19 match self {
20 LogLevel::Trace => 0,
21 LogLevel::Verbose => 1,
22 LogLevel::Debug => 2,
23 LogLevel::Info => 3,
24 LogLevel::Warning => 4,
25 LogLevel::Error => 5,
26 LogLevel::Critical => 6,
27 }
28 }
29
30 fn as_str(self) -> &'static str {
31 match self {
32 LogLevel::Trace => "trace",
33 LogLevel::Verbose => "verbose",
34 LogLevel::Debug => "debug",
35 LogLevel::Info => "info",
36 LogLevel::Warning => "warning",
37 LogLevel::Error => "error",
38 LogLevel::Critical => "critical",
39 }
40 }
41}
42
43#[derive(Debug, Clone)]
44pub struct LoggerConfig {
45 pub level: LogLevel,
46 pub json_output: bool,
47 pub json_only: bool,
48}
49
50static LOGGER: OnceLock<LoggerConfig> = OnceLock::new();
51
52pub fn init(config: LoggerConfig) {
53 let _ = LOGGER.set(config);
54}
55
56pub fn log(
57 level: LogLevel,
58 event: &str,
59 message: impl AsRef<str>,
60 context: Option<serde_json::Value>,
61) {
62 let Some(config) = LOGGER.get() else {
63 return;
64 };
65 if level.priority() < config.level.priority() {
66 return;
67 }
68 if config.json_output {
69 let payload = json!({
70 "ts": Utc::now().to_rfc3339(),
71 "level": level.as_str(),
72 "event": event,
73 "message": message.as_ref(),
74 "context": context,
75 });
76 if let Ok(line) = serde_json::to_string(&payload) {
77 eprintln!("{}", line);
78 }
79 return;
80 }
81
82 if config.json_only {
83 return;
84 }
85
86 eprintln!("[{}] {}: {}", level.as_str(), event, message.as_ref());
87}