network_protocol/utils/
logging.rs

1use std::sync::Once;
2use tracing::Level;
3use tracing_appender::rolling;
4use tracing_subscriber::{
5    fmt::{self, format::FmtSpan},
6    prelude::*,
7    registry, EnvFilter,
8};
9
10static INIT: Once = Once::new();
11
12/// LogConfig provides options for configuring the logging system
13#[derive(Clone, Debug)]
14pub struct LogConfig {
15    /// The name of the application
16    pub app_name: String,
17    /// The log level (trace, debug, info, warn, error)
18    pub log_level: Level,
19    /// Whether to enable JSON log format (useful for log aggregation)
20    pub json_format: bool,
21    /// Directory where log files should be stored, None for console only
22    pub log_dir: Option<String>,
23    /// Whether to log to stdout in addition to files
24    pub log_to_stdout: bool,
25}
26
27impl Default for LogConfig {
28    fn default() -> Self {
29        Self {
30            app_name: "network-protocol".to_string(),
31            log_level: Level::INFO,
32            json_format: false,
33            log_dir: None,
34            log_to_stdout: true,
35        }
36    }
37}
38
39/// Initialize the tracing system with the given configuration
40///
41/// # Example
42/// ```
43/// use network_protocol::utils::logging::{LogConfig, init_logging};
44/// use tracing::Level;
45///
46/// let config = LogConfig {
47///     app_name: "my-service".to_string(),
48///     log_level: Level::DEBUG,
49///     ..Default::default()
50/// };
51///
52/// init_logging(&config);
53/// ```
54pub fn init_logging(config: &LogConfig) {
55    INIT.call_once(|| {
56        let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
57            EnvFilter::new(format!(
58                "{},{app_name}={level}",
59                std::env::var("RUST_LOG").unwrap_or_default(),
60                app_name = config.app_name,
61                level = config.log_level
62            ))
63        });
64
65        let registry = registry().with(filter);
66
67        match (&config.log_dir, config.log_to_stdout) {
68            // Log to both file and stdout
69            (Some(log_dir), true) => {
70                let file_appender = rolling::daily(log_dir, format!("{}.log", config.app_name));
71                let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
72
73                if config.json_format {
74                    let file_layer = fmt::layer()
75                        .json()
76                        .with_writer(non_blocking)
77                        .with_span_events(FmtSpan::CLOSE);
78
79                    let stdout_layer = fmt::layer().with_writer(std::io::stdout).with_ansi(true);
80
81                    registry.with(file_layer).with(stdout_layer).init();
82                } else {
83                    let file_layer = fmt::layer()
84                        .with_writer(non_blocking)
85                        .with_span_events(FmtSpan::CLOSE);
86
87                    let stdout_layer = fmt::layer().with_writer(std::io::stdout).with_ansi(true);
88
89                    registry.with(file_layer).with(stdout_layer).init();
90                }
91            }
92
93            // Log only to file
94            (Some(log_dir), false) => {
95                let file_appender = rolling::daily(log_dir, format!("{}.log", config.app_name));
96                let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
97
98                if config.json_format {
99                    let file_layer = fmt::layer()
100                        .json()
101                        .with_writer(non_blocking)
102                        .with_span_events(FmtSpan::CLOSE);
103
104                    registry.with(file_layer).init();
105                } else {
106                    let file_layer = fmt::layer()
107                        .with_writer(non_blocking)
108                        .with_span_events(FmtSpan::CLOSE);
109
110                    registry.with(file_layer).init();
111                }
112            }
113
114            // Log only to stdout
115            (None, true) => {
116                if config.json_format {
117                    let stdout_layer = fmt::layer()
118                        .json()
119                        .with_writer(std::io::stdout)
120                        .with_span_events(FmtSpan::CLOSE);
121
122                    registry.with(stdout_layer).init();
123                } else {
124                    let stdout_layer = fmt::layer()
125                        .with_writer(std::io::stdout)
126                        .with_ansi(true)
127                        .with_span_events(FmtSpan::CLOSE);
128
129                    registry.with(stdout_layer).init();
130                }
131            }
132
133            // No logging output configured, default to stdout
134            (None, false) => {
135                let stdout_layer = fmt::layer().with_writer(std::io::stdout).with_ansi(true);
136
137                registry.with(stdout_layer).init();
138
139                tracing::warn!("No log output configured, defaulting to stdout");
140            }
141        }
142
143        tracing::info!("Logging initialized at {} level", config.log_level);
144    });
145}
146
147/// Setup default logging configuration for quick startup
148pub fn setup_default_logging() {
149    init_logging(&LogConfig::default());
150}