1pub use chrono;
2pub use tracing;
3pub use tracing_subscriber;
4
5#[macro_export]
6macro_rules! init_logging {
7 ($file_path:expr, $service:expr, $component:expr, $log_level:expr) => {
8 $crate::init_logging!($file_path, $service, $component, $log_level, "", true)
9 };
10 ($file_path:expr, $service:expr, $component:expr, $log_level:expr, $console:expr) => {
11 $crate::init_logging!($file_path, $service, $component, $log_level, "", $console)
12 };
13 ($file_path:expr, $service:expr, $component:expr, $log_level:expr, $filters:expr, $console:expr) => {{
14 use std::sync::Once;
15 static INIT: Once = Once::new();
16
17 INIT.call_once(|| {
18 use std::fs::OpenOptions;
19 use $crate::tracing_subscriber::prelude::*;
20 use $crate::tracing_subscriber::{EnvFilter, fmt};
21
22 let file = OpenOptions::new()
23 .create(true)
24 .write(true)
25 .append(true)
26 .open($file_path)
27 .expect("Failed to open log file");
28
29 let file_layer = fmt::layer()
30 .json()
31 .with_writer(file)
32 .with_current_span(false)
33 .with_span_list(false)
34 .with_target(false)
35 .with_thread_ids(false)
36 .with_thread_names(false);
37
38 let mut filter = EnvFilter::from_default_env().add_directive(
39 $log_level
40 .parse()
41 .unwrap_or_else(|e| panic!("Invalid log level '{}': {}", $log_level, e)),
42 );
43
44 if !$filters.is_empty() {
45 for filter_directive in $filters.split(',') {
46 if !filter_directive.trim().is_empty() {
47 filter = filter.add_directive(
48 filter_directive.trim().parse().unwrap_or_else(|e| {
49 panic!("Invalid filter directive '{}': {}", filter_directive, e)
50 }),
51 );
52 }
53 }
54 }
55
56 if $console {
57 let console_layer = fmt::layer()
58 .json()
59 .with_writer(std::io::stdout)
60 .with_current_span(false)
61 .with_span_list(false)
62 .with_target(false)
63 .with_thread_ids(false)
64 .with_thread_names(false);
65
66 $crate::tracing_subscriber::registry()
67 .with(file_layer)
68 .with(console_layer)
69 .with(filter)
70 .try_init()
71 .ok();
72 } else {
73 $crate::tracing_subscriber::registry()
74 .with(file_layer)
75 .with(filter)
76 .try_init()
77 .ok();
78 }
79 });
80 }};
81}
82
83#[macro_export]
84macro_rules! app_log {
85 ($level:ident, $($arg:tt)*) => {
86 $crate::tracing::$level!(
87 service = env!("CARGO_PKG_NAME"),
88 component = "main",
89 timestamp = $crate::chrono::Utc::now().to_rfc3339(),
90 $($arg)*
91 )
92 };
93 ($level:ident, $service:expr, $component:expr, $($arg:tt)*) => {
94 $crate::tracing::$level!(
95 service = $service,
96 component = $component,
97 timestamp = $crate::chrono::Utc::now().to_rfc3339(),
98 $($arg)*
99 )
100 };
101}
102
103#[macro_export]
104macro_rules! app_span {
105 ($name:expr, $($field:tt)*) => {
106 $crate::tracing::info_span!(
107 $name,
108 service = env!("CARGO_PKG_NAME"),
109 component = "main",
110 timestamp = $crate::chrono::Utc::now().to_rfc3339(),
111 $($field)*
112 )
113 };
114 ($name:expr, $service:expr, $component:expr, $($field:tt)*) => {
115 $crate::tracing::info_span!(
116 $name,
117 service = $service,
118 component = $component,
119 timestamp = $crate::chrono::Utc::now().to_rfc3339(),
120 $($field)*
121 )
122 };
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn test_init_logging() {
131 init_logging!("/tmp/test.log", "test", "component", "info");
132 app_log!(info, "Test log message");
133 }
134
135 #[test]
136 fn test_init_logging_no_console() {
137 init_logging!(
138 "/tmp/test_no_console.log",
139 "test",
140 "component",
141 "info",
142 false
143 );
144 app_log!(warn, "Test warning");
145 }
146
147 #[test]
148 fn test_logging_with_filters() {
149 init_logging!(
150 "/tmp/test_filters.log",
151 "test",
152 "component",
153 "debug",
154 "rocket::server=off",
155 true
156 );
157 app_log!(debug, "Testing with filters");
158 }
159}
160