network_protocol/utils/
logging.rs1use std::sync::{Once, OnceLock};
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
12static FILE_WRITER_GUARD: OnceLock<tracing_appender::non_blocking::WorkerGuard> = OnceLock::new();
14
15#[derive(Clone, Debug)]
17pub struct LogConfig {
18 pub app_name: String,
20 pub log_level: Level,
22 pub json_format: bool,
24 pub log_dir: Option<String>,
26 pub log_to_stdout: bool,
28}
29
30impl Default for LogConfig {
31 fn default() -> Self {
32 Self {
33 app_name: "network-protocol".to_string(),
34 log_level: Level::INFO,
35 json_format: false,
36 log_dir: None,
37 log_to_stdout: true,
38 }
39 }
40}
41
42pub fn init_logging(config: &LogConfig) {
58 INIT.call_once(|| {
59 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
60 EnvFilter::new(format!(
61 "{},{app_name}={level}",
62 std::env::var("RUST_LOG").unwrap_or_default(),
63 app_name = config.app_name,
64 level = config.log_level
65 ))
66 });
67
68 let registry = registry().with(filter);
69
70 match (&config.log_dir, config.log_to_stdout) {
71 (Some(log_dir), true) => {
73 let file_appender = rolling::daily(log_dir, format!("{}.log", config.app_name));
74 let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
75 let _ = FILE_WRITER_GUARD.set(guard);
78
79 if config.json_format {
80 let file_layer = fmt::layer()
81 .json()
82 .with_writer(non_blocking)
83 .with_span_events(FmtSpan::CLOSE);
84
85 let stdout_layer = fmt::layer().with_writer(std::io::stdout).with_ansi(true);
86
87 registry.with(file_layer).with(stdout_layer).init();
88 } else {
89 let file_layer = fmt::layer()
90 .with_writer(non_blocking)
91 .with_span_events(FmtSpan::CLOSE);
92
93 let stdout_layer = fmt::layer().with_writer(std::io::stdout).with_ansi(true);
94
95 registry.with(file_layer).with(stdout_layer).init();
96 }
97 }
98
99 (Some(log_dir), false) => {
101 let file_appender = rolling::daily(log_dir, format!("{}.log", config.app_name));
102 let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
103 let _ = FILE_WRITER_GUARD.set(guard);
106
107 if config.json_format {
108 let file_layer = fmt::layer()
109 .json()
110 .with_writer(non_blocking)
111 .with_span_events(FmtSpan::CLOSE);
112
113 registry.with(file_layer).init();
114 } else {
115 let file_layer = fmt::layer()
116 .with_writer(non_blocking)
117 .with_span_events(FmtSpan::CLOSE);
118
119 registry.with(file_layer).init();
120 }
121 }
122
123 (None, true) => {
125 if config.json_format {
126 let stdout_layer = fmt::layer()
127 .json()
128 .with_writer(std::io::stdout)
129 .with_span_events(FmtSpan::CLOSE);
130
131 registry.with(stdout_layer).init();
132 } else {
133 let stdout_layer = fmt::layer()
134 .with_writer(std::io::stdout)
135 .with_ansi(true)
136 .with_span_events(FmtSpan::CLOSE);
137
138 registry.with(stdout_layer).init();
139 }
140 }
141
142 (None, false) => {
144 let stdout_layer = fmt::layer().with_writer(std::io::stdout).with_ansi(true);
145
146 registry.with(stdout_layer).init();
147
148 tracing::warn!("No log output configured, defaulting to stdout");
149 }
150 }
151
152 tracing::info!("Logging initialized at {} level", config.log_level);
153 });
154}
155
156pub fn setup_default_logging() {
158 init_logging(&LogConfig::default());
159}