1use tracing::Level;
10use tracing_subscriber::{
11 EnvFilter, Layer,
12 fmt::{self, format::FmtSpan},
13 layer::SubscriberExt,
14 util::SubscriberInitExt,
15};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum LogLevel {
20 Trace,
22 Debug,
24 Info,
26 Warn,
28 Error,
30}
31
32impl From<LogLevel> for Level {
33 fn from(level: LogLevel) -> Self {
34 match level {
35 LogLevel::Trace => Level::TRACE,
36 LogLevel::Debug => Level::DEBUG,
37 LogLevel::Info => Level::INFO,
38 LogLevel::Warn => Level::WARN,
39 LogLevel::Error => Level::ERROR,
40 }
41 }
42}
43
44impl std::fmt::Display for LogLevel {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 match self {
47 LogLevel::Trace => write!(f, "trace"),
48 LogLevel::Debug => write!(f, "debug"),
49 LogLevel::Info => write!(f, "info"),
50 LogLevel::Warn => write!(f, "warn"),
51 LogLevel::Error => write!(f, "error"),
52 }
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum LogFormat {
59 Pretty,
61 Compact,
63 Json,
65}
66
67#[derive(Debug, Clone)]
69pub struct LogConfig {
70 pub level: LogLevel,
72 pub format: LogFormat,
74 pub show_time: bool,
76 pub show_thread_ids: bool,
78 pub show_target: bool,
80 pub show_span_events: bool,
82}
83
84impl Default for LogConfig {
85 fn default() -> Self {
86 Self {
87 level: LogLevel::Info,
88 format: LogFormat::Pretty,
89 show_time: true,
90 show_thread_ids: false,
91 show_target: true,
92 show_span_events: false,
93 }
94 }
95}
96
97impl LogConfig {
98 pub fn development() -> Self {
100 Self {
101 level: LogLevel::Debug,
102 format: LogFormat::Pretty,
103 show_time: true,
104 show_thread_ids: false,
105 show_target: true,
106 show_span_events: true,
107 }
108 }
109
110 pub fn production() -> Self {
112 Self {
113 level: LogLevel::Info,
114 format: LogFormat::Json,
115 show_time: true,
116 show_thread_ids: true,
117 show_target: true,
118 show_span_events: false,
119 }
120 }
121
122 pub fn test() -> Self {
124 Self {
125 level: LogLevel::Warn,
126 format: LogFormat::Compact,
127 show_time: false,
128 show_thread_ids: false,
129 show_target: false,
130 show_span_events: false,
131 }
132 }
133}
134
135pub fn init_logging(config: LogConfig) {
153 let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
154 EnvFilter::new(format!(
155 "ccxt_core={},ccxt_exchanges={}",
156 config.level, config.level
157 ))
158 });
159
160 match config.format {
161 LogFormat::Pretty => {
162 let fmt_layer = fmt::layer()
163 .pretty()
164 .with_timer(fmt::time::time())
165 .with_thread_ids(config.show_thread_ids)
166 .with_target(config.show_target)
167 .with_span_events(if config.show_span_events {
168 FmtSpan::ENTER | FmtSpan::CLOSE
169 } else {
170 FmtSpan::NONE
171 })
172 .with_filter(env_filter);
173
174 tracing_subscriber::registry().with(fmt_layer).init();
175 }
176 LogFormat::Compact => {
177 let fmt_layer = fmt::layer()
178 .compact()
179 .with_timer(fmt::time::time())
180 .with_thread_ids(config.show_thread_ids)
181 .with_target(config.show_target)
182 .with_span_events(if config.show_span_events {
183 FmtSpan::ENTER | FmtSpan::CLOSE
184 } else {
185 FmtSpan::NONE
186 })
187 .with_filter(env_filter);
188
189 tracing_subscriber::registry().with(fmt_layer).init();
190 }
191 LogFormat::Json => {
192 let fmt_layer = fmt::layer()
193 .json()
194 .with_timer(fmt::time::time())
195 .with_thread_ids(config.show_thread_ids)
196 .with_target(config.show_target)
197 .with_span_events(if config.show_span_events {
198 FmtSpan::ENTER | FmtSpan::CLOSE
199 } else {
200 FmtSpan::NONE
201 })
202 .with_filter(env_filter);
203
204 tracing_subscriber::registry().with(fmt_layer).init();
205 }
206 }
207}
208
209pub fn try_init_logging(config: LogConfig) {
213 let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
214 EnvFilter::new(format!(
215 "ccxt_core={},ccxt_exchanges={}",
216 config.level, config.level
217 ))
218 });
219
220 let result = match config.format {
221 LogFormat::Pretty => {
222 let fmt_layer = fmt::layer()
223 .pretty()
224 .with_timer(fmt::time::time())
225 .with_thread_ids(config.show_thread_ids)
226 .with_target(config.show_target)
227 .with_span_events(if config.show_span_events {
228 FmtSpan::ENTER | FmtSpan::CLOSE
229 } else {
230 FmtSpan::NONE
231 })
232 .with_filter(env_filter);
233
234 tracing_subscriber::registry().with(fmt_layer).try_init()
235 }
236 LogFormat::Compact => {
237 let fmt_layer = fmt::layer()
238 .compact()
239 .with_timer(fmt::time::time())
240 .with_thread_ids(config.show_thread_ids)
241 .with_target(config.show_target)
242 .with_span_events(if config.show_span_events {
243 FmtSpan::ENTER | FmtSpan::CLOSE
244 } else {
245 FmtSpan::NONE
246 })
247 .with_filter(env_filter);
248
249 tracing_subscriber::registry().with(fmt_layer).try_init()
250 }
251 LogFormat::Json => {
252 let fmt_layer = fmt::layer()
253 .json()
254 .with_timer(fmt::time::time())
255 .with_thread_ids(config.show_thread_ids)
256 .with_target(config.show_target)
257 .with_span_events(if config.show_span_events {
258 FmtSpan::ENTER | FmtSpan::CLOSE
259 } else {
260 FmtSpan::NONE
261 })
262 .with_filter(env_filter);
263
264 tracing_subscriber::registry().with(fmt_layer).try_init()
265 }
266 };
267
268 let _ = result;
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn test_log_level_conversion() {
277 assert_eq!(Level::from(LogLevel::Trace), Level::TRACE);
278 assert_eq!(Level::from(LogLevel::Debug), Level::DEBUG);
279 assert_eq!(Level::from(LogLevel::Info), Level::INFO);
280 assert_eq!(Level::from(LogLevel::Warn), Level::WARN);
281 assert_eq!(Level::from(LogLevel::Error), Level::ERROR);
282 }
283
284 #[test]
285 fn test_log_level_display() {
286 assert_eq!(LogLevel::Trace.to_string(), "trace");
287 assert_eq!(LogLevel::Debug.to_string(), "debug");
288 assert_eq!(LogLevel::Info.to_string(), "info");
289 assert_eq!(LogLevel::Warn.to_string(), "warn");
290 assert_eq!(LogLevel::Error.to_string(), "error");
291 }
292
293 #[test]
294 fn test_log_config_default() {
295 let config = LogConfig::default();
296 assert_eq!(config.level, LogLevel::Info);
297 assert_eq!(config.format, LogFormat::Pretty);
298 assert!(config.show_time);
299 assert!(!config.show_thread_ids);
300 assert!(config.show_target);
301 assert!(!config.show_span_events);
302 }
303
304 #[test]
305 fn test_log_config_development() {
306 let config = LogConfig::development();
307 assert_eq!(config.level, LogLevel::Debug);
308 assert_eq!(config.format, LogFormat::Pretty);
309 assert!(config.show_span_events);
310 }
311
312 #[test]
313 fn test_log_config_production() {
314 let config = LogConfig::production();
315 assert_eq!(config.level, LogLevel::Info);
316 assert_eq!(config.format, LogFormat::Json);
317 assert!(config.show_thread_ids);
318 }
319
320 #[test]
321 fn test_log_config_test() {
322 let config = LogConfig::test();
323 assert_eq!(config.level, LogLevel::Warn);
324 assert_eq!(config.format, LogFormat::Compact);
325 assert!(!config.show_time);
326 }
327
328 #[test]
329 fn test_try_init_logging() {
330 try_init_logging(LogConfig::test());
331 try_init_logging(LogConfig::test());
332 }
333}