rustyclaw_core/
logging.rs1use tracing_subscriber::{
22 EnvFilter,
23 fmt::{self, format::FmtSpan},
24 prelude::*,
25};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
29pub enum LogFormat {
30 #[default]
32 Pretty,
33 Compact,
35 Json,
37}
38
39impl LogFormat {
40 pub fn from_str(s: &str) -> Self {
42 match s.to_lowercase().as_str() {
43 "json" => Self::Json,
44 "compact" => Self::Compact,
45 "pretty" | _ => Self::Pretty,
46 }
47 }
48}
49
50#[derive(Debug, Clone)]
52pub struct LogConfig {
53 pub filter: String,
55 pub format: LogFormat,
57 pub with_spans: bool,
59 pub with_file: bool,
61 pub with_thread_ids: bool,
63 pub with_target: bool,
65}
66
67impl Default for LogConfig {
68 fn default() -> Self {
69 Self {
70 filter: "rustyclaw=info,warn".to_string(),
71 format: LogFormat::Pretty,
72 with_spans: false,
73 with_file: false,
74 with_thread_ids: false,
75 with_target: true,
76 }
77 }
78}
79
80impl LogConfig {
81 pub fn from_env() -> Self {
83 let filter = std::env::var("RUSTYCLAW_LOG")
84 .or_else(|_| std::env::var("RUST_LOG"))
85 .unwrap_or_else(|_| "rustyclaw=info,warn".to_string());
86
87 let format = std::env::var("RUSTYCLAW_LOG_FORMAT")
88 .map(|s| LogFormat::from_str(&s))
89 .unwrap_or_default();
90
91 Self {
92 filter,
93 format,
94 ..Default::default()
95 }
96 }
97
98 pub fn debug() -> Self {
100 Self {
101 filter: "rustyclaw=debug,info".to_string(),
102 with_file: true,
103 ..Default::default()
104 }
105 }
106
107 pub fn production() -> Self {
109 Self {
110 filter: "rustyclaw=info,warn".to_string(),
111 format: LogFormat::Json,
112 with_spans: true,
113 with_target: true,
114 ..Default::default()
115 }
116 }
117}
118
119pub fn init(config: LogConfig) {
136 let env_filter = EnvFilter::try_new(&config.filter)
137 .unwrap_or_else(|_| EnvFilter::new("rustyclaw=info,warn"));
138
139 let span_events = if config.with_spans {
140 FmtSpan::NEW | FmtSpan::CLOSE
141 } else {
142 FmtSpan::NONE
143 };
144
145 match config.format {
146 LogFormat::Json => {
147 let subscriber = tracing_subscriber::registry().with(env_filter).with(
148 fmt::layer()
149 .json()
150 .with_span_events(span_events)
151 .with_file(config.with_file)
152 .with_line_number(config.with_file)
153 .with_thread_ids(config.with_thread_ids)
154 .with_target(config.with_target),
155 );
156 let _ = tracing::subscriber::set_global_default(subscriber);
157 }
158 LogFormat::Compact => {
159 let subscriber = tracing_subscriber::registry().with(env_filter).with(
160 fmt::layer()
161 .compact()
162 .with_span_events(span_events)
163 .with_file(config.with_file)
164 .with_line_number(config.with_file)
165 .with_thread_ids(config.with_thread_ids)
166 .with_target(config.with_target),
167 );
168 let _ = tracing::subscriber::set_global_default(subscriber);
169 }
170 LogFormat::Pretty => {
171 let subscriber = tracing_subscriber::registry().with(env_filter).with(
172 fmt::layer()
173 .pretty()
174 .with_span_events(span_events)
175 .with_file(config.with_file)
176 .with_line_number(config.with_file)
177 .with_thread_ids(config.with_thread_ids)
178 .with_target(config.with_target),
179 );
180 let _ = tracing::subscriber::set_global_default(subscriber);
181 }
182 }
183}
184
185pub fn init_from_env() {
189 init(LogConfig::from_env());
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn test_log_format_parsing() {
198 assert_eq!(LogFormat::from_str("json"), LogFormat::Json);
199 assert_eq!(LogFormat::from_str("JSON"), LogFormat::Json);
200 assert_eq!(LogFormat::from_str("compact"), LogFormat::Compact);
201 assert_eq!(LogFormat::from_str("pretty"), LogFormat::Pretty);
202 assert_eq!(LogFormat::from_str("unknown"), LogFormat::Pretty);
203 }
204
205 #[test]
206 fn test_config_from_env() {
207 unsafe {
210 std::env::remove_var("RUSTYCLAW_LOG");
211 std::env::remove_var("RUST_LOG");
212 std::env::remove_var("RUSTYCLAW_LOG_FORMAT");
213 }
214
215 let config = LogConfig::from_env();
216 assert_eq!(config.filter, "rustyclaw=info,warn");
217 assert_eq!(config.format, LogFormat::Pretty);
218 }
219
220 #[test]
221 fn test_debug_config() {
222 let config = LogConfig::debug();
223 assert!(config.filter.contains("debug"));
224 assert!(config.with_file);
225 }
226
227 #[test]
228 fn test_production_config() {
229 let config = LogConfig::production();
230 assert_eq!(config.format, LogFormat::Json);
231 assert!(config.with_spans);
232 }
233}