custom_tracing_logger/
lib.rs

1//! Custom tracing logger that outputs structured JSON logs
2//! 
3//! This crate provides a simple interface to initialize a JSON-formatted logger
4//! using the tracing ecosystem. All logs are output as structured JSON with
5//! metadata including timestamp, level, target, and message.
6
7use tracing_subscriber::{
8    fmt,
9    layer::SubscriberExt,
10    util::SubscriberInitExt,
11    EnvFilter,
12};
13use tracing_appender::rolling::{RollingFileAppender, Rotation};
14
15/// Initialize the JSON logger
16/// 
17/// Behavior controlled by environment variables:
18/// - `RUST_LOG`: Log level filtering (e.g., "info", "debug", "off")
19/// - `LOG_FILE_DIR`: Directory for log files (e.g., "./logs")
20/// - `LOG_FILE_PREFIX`: Prefix for log files (e.g., "myapp")
21/// - `LOG_FILE_ONLY`: Set to "true" to disable console output
22/// 
23/// # Examples
24/// ```no_run
25/// // Console only
26/// custom_tracing_logger::init();
27/// 
28/// // Console + file (with LOG_FILE_DIR=./logs LOG_FILE_PREFIX=myapp)
29/// custom_tracing_logger::init();
30/// 
31/// // File only (with LOG_FILE_ONLY=true)
32/// custom_tracing_logger::init();
33/// ```
34pub fn init() {
35    // Handle RUST_LOG with whitespace trimming for Windows compatibility
36    let env_filter = match std::env::var("RUST_LOG") {
37        Ok(val) => EnvFilter::new(val.trim()),
38        Err(_) => EnvFilter::new("info"),
39    };
40
41    // Check for file logging configuration
42    let log_file_dir = std::env::var("LOG_FILE_DIR").ok();
43    let log_file_prefix = std::env::var("LOG_FILE_PREFIX").unwrap_or_else(|_| "app".to_string());
44    let file_only = std::env::var("LOG_FILE_ONLY").unwrap_or_default() == "true";
45
46    let registry = tracing_subscriber::registry().with(env_filter);
47
48    match (log_file_dir, file_only) {
49        // File logging + console
50        (Some(log_dir), false) => {
51            let console_layer = fmt::layer()
52                .json()
53                .with_current_span(false)
54                .with_span_list(false);
55            
56            let file_appender = RollingFileAppender::new(Rotation::DAILY, &log_dir, &log_file_prefix);
57            let file_layer = fmt::layer()
58                .json()
59                .with_current_span(false)
60                .with_span_list(false)
61                .with_writer(file_appender);
62            
63            let _ = registry.with(console_layer).with(file_layer).try_init();
64        },
65        // File logging only (no console)
66        (Some(log_dir), true) => {
67            let file_appender = RollingFileAppender::new(Rotation::DAILY, &log_dir, &log_file_prefix);
68            let file_layer = fmt::layer()
69                .json()
70                .with_current_span(false)
71                .with_span_list(false)
72                .with_writer(file_appender);
73            
74            let _ = registry.with(file_layer).try_init();
75        },
76        // Console only
77        (None, _) => {
78            let console_layer = fmt::layer()
79                .json()
80                .with_current_span(false)
81                .with_span_list(false);
82            
83            let _ = registry.with(console_layer).try_init();
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_init_does_not_panic() {
94        let _ = std::panic::catch_unwind(|| {
95            init();
96        });
97    }
98
99    #[test]
100    fn test_env_var_parsing() {
101        // Test that environment variables are read correctly
102        std::env::set_var("LOG_FILE_PREFIX", "test");
103        let prefix = std::env::var("LOG_FILE_PREFIX").unwrap_or_else(|_| "app".to_string());
104        assert_eq!(prefix, "test");
105        std::env::remove_var("LOG_FILE_PREFIX");
106    }
107
108
109}