graflog/
lib.rs

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