openzeppelin_relayer/logging/
mod.rs1use chrono::Utc;
10use std::{
11 env,
12 fs::{create_dir_all, metadata, File, OpenOptions},
13 path::Path,
14};
15use tracing::info;
16use tracing_appender::non_blocking;
17use tracing_error::ErrorLayer;
18use tracing_subscriber::{fmt, prelude::*, EnvFilter};
19
20use crate::constants::{
21 DEFAULT_LOG_DIR, DEFAULT_LOG_FORMAT, DEFAULT_LOG_LEVEL, DEFAULT_LOG_MODE,
22 DEFAULT_MAX_LOG_FILE_SIZE, DOCKER_LOG_DIR, LOG_FILE_NAME,
23};
24
25pub fn compute_rolled_file_path(base_file_path: &str, date_str: &str, index: u32) -> String {
27 if base_file_path.ends_with(".log") {
28 let trimmed = base_file_path.strip_suffix(".log").unwrap();
29 format!("{trimmed}-{date_str}.{index}.log")
30 } else {
31 format!("{base_file_path}-{date_str}.{index}.log")
32 }
33}
34
35pub fn time_based_rolling(base_file_path: &str, date_str: &str, index: u32) -> String {
38 compute_rolled_file_path(base_file_path, date_str, index)
39}
40
41pub fn space_based_rolling(
49 file_path: &str,
50 base_file_path: &str,
51 date_str: &str,
52 max_size: u64,
53) -> String {
54 let mut final_path = file_path.to_string();
55 let mut index = 1;
56 while let Ok(metadata) = metadata(&final_path) {
57 if metadata.len() > max_size {
58 final_path = compute_rolled_file_path(base_file_path, date_str, index);
59 index += 1;
60 } else {
61 break;
62 }
63 }
64 final_path
65}
66
67pub fn setup_logging() {
69 if std::env::var_os("RUST_LOG").is_none() {
71 if let Ok(level) = env::var("LOG_LEVEL") {
72 std::env::set_var("RUST_LOG", level);
73 }
74 }
75
76 let env_filter =
78 EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(DEFAULT_LOG_LEVEL));
79 let format = env::var("LOG_FORMAT").unwrap_or_else(|_| DEFAULT_LOG_FORMAT.to_string());
80 let log_mode = env::var("LOG_MODE").unwrap_or_else(|_| DEFAULT_LOG_MODE.to_string());
81
82 if log_mode.eq_ignore_ascii_case("file") {
84 let log_dir = if env::var("IN_DOCKER").ok().as_deref() == Some("true") {
86 DOCKER_LOG_DIR.to_string()
87 } else {
88 env::var("LOG_DATA_DIR").unwrap_or_else(|_| DEFAULT_LOG_DIR.to_string())
89 };
90 let log_dir = format!("{}/", log_dir.trim_end_matches('/'));
91
92 let now = Utc::now();
93 let date_str = now.format("%Y-%m-%d").to_string();
94 let base_file_path = format!("{log_dir}{LOG_FILE_NAME}");
95
96 if let Some(parent) = Path::new(&base_file_path).parent() {
97 create_dir_all(parent).expect("Failed to create log directory");
98 }
99
100 let time_based_path = time_based_rolling(&base_file_path, &date_str, 1);
101 let max_size = match env::var("LOG_MAX_SIZE") {
102 Ok(value) => value.parse().unwrap_or_else(|_| {
103 panic!("LOG_MAX_SIZE must be a valid u64 if set");
104 }),
105 Err(_) => DEFAULT_MAX_LOG_FILE_SIZE,
106 };
107 let final_path =
108 space_based_rolling(&time_based_path, &base_file_path, &date_str, max_size);
109
110 let file = if Path::new(&final_path).exists() {
111 OpenOptions::new()
112 .append(true)
113 .open(&final_path)
114 .expect("Failed to open log file")
115 } else {
116 File::create(&final_path).expect("Failed to create log file")
117 };
118
119 let (non_blocking_writer, guard) = non_blocking(file);
120 Box::leak(Box::new(guard)); match format.as_str() {
123 "pretty" => {
124 tracing_subscriber::registry()
125 .with(env_filter)
126 .with(ErrorLayer::default())
127 .with(
128 fmt::layer()
129 .with_writer(non_blocking_writer)
130 .with_ansi(false)
131 .pretty()
132 .with_thread_ids(true)
133 .with_file(true)
134 .with_line_number(true),
135 )
136 .init();
137 }
138 "json" => {
139 tracing_subscriber::registry()
140 .with(env_filter)
141 .with(ErrorLayer::default())
142 .with(
143 fmt::layer()
144 .with_writer(non_blocking_writer)
145 .with_ansi(false)
146 .json()
147 .with_current_span(true)
148 .with_span_list(true)
149 .with_thread_ids(true)
150 .with_file(true)
151 .with_line_number(true),
152 )
153 .init();
154 }
155 _ => {
156 tracing_subscriber::registry()
158 .with(env_filter)
159 .with(ErrorLayer::default())
160 .with(
161 fmt::layer()
162 .with_writer(non_blocking_writer)
163 .with_ansi(false)
164 .compact()
165 .with_target(false),
166 )
167 .init();
168 }
169 }
170 } else {
171 match format.as_str() {
173 "pretty" => {
174 tracing_subscriber::registry()
175 .with(env_filter)
176 .with(ErrorLayer::default())
177 .with(
178 fmt::layer()
179 .pretty()
180 .with_thread_ids(true)
181 .with_file(true)
182 .with_line_number(true),
183 )
184 .init();
185 }
186 "json" => {
187 tracing_subscriber::registry()
188 .with(env_filter)
189 .with(ErrorLayer::default())
190 .with(
191 fmt::layer()
192 .json()
193 .with_current_span(true)
194 .with_span_list(true)
195 .with_thread_ids(true)
196 .with_file(true)
197 .with_line_number(true),
198 )
199 .init();
200 }
201 _ => {
202 tracing_subscriber::registry()
204 .with(env_filter)
205 .with(ErrorLayer::default())
206 .with(fmt::layer().compact().with_target(false))
207 .init();
208 }
209 }
210 }
211
212 info!(mode=%log_mode, format=%format, "logging configured");
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use std::fs::File;
219 use std::io::Write;
220 use std::sync::Once;
221 use tempfile::tempdir;
222
223 static INIT_LOGGER: Once = Once::new();
225
226 #[test]
227 fn test_compute_rolled_file_path() {
228 let result = compute_rolled_file_path("app.log", "2023-01-01", 1);
230 assert_eq!(result, "app-2023-01-01.1.log");
231
232 let result = compute_rolled_file_path("app", "2023-01-01", 2);
234 assert_eq!(result, "app-2023-01-01.2.log");
235
236 let result = compute_rolled_file_path("logs/app.log", "2023-01-01", 3);
238 assert_eq!(result, "logs/app-2023-01-01.3.log");
239 }
240
241 #[test]
242 fn test_time_based_rolling() {
243 let result = time_based_rolling("app.log", "2023-01-01", 1);
245 assert_eq!(result, "app-2023-01-01.1.log");
246 }
247
248 #[test]
249 fn test_space_based_rolling() {
250 let temp_dir = tempdir().expect("Failed to create temp directory");
252 let base_path = temp_dir
253 .path()
254 .join("test.log")
255 .to_str()
256 .unwrap()
257 .to_string();
258
259 let result = space_based_rolling(&base_path, &base_path, "2023-01-01", 100);
261 assert_eq!(result, base_path);
262
263 {
265 let mut file = File::create(&base_path).expect("Failed to create test file");
266 file.write_all(&[0; 200])
267 .expect("Failed to write to test file");
268 }
269
270 let expected_path = compute_rolled_file_path(&base_path, "2023-01-01", 1);
272 let result = space_based_rolling(&base_path, &base_path, "2023-01-01", 100);
273 assert_eq!(result, expected_path);
274
275 {
277 let mut file = File::create(&expected_path).expect("Failed to create test file");
278 file.write_all(&[0; 200])
279 .expect("Failed to write to test file");
280 }
281
282 let expected_path2 = compute_rolled_file_path(&base_path, "2023-01-01", 2);
284 let result = space_based_rolling(&base_path, &base_path, "2023-01-01", 100);
285 assert_eq!(result, expected_path2);
286 }
287
288 #[test]
289 fn test_logging_configuration() {
290 {
294 env::set_var("LOG_MODE", "stdout");
296 env::set_var("LOG_LEVEL", "debug");
297
298 INIT_LOGGER.call_once(|| {
300 setup_logging();
301 });
302
303 env::remove_var("LOG_MODE");
305 env::remove_var("LOG_LEVEL");
306 }
307
308 {
310 let temp_dir = tempdir().expect("Failed to create temp directory");
312 let log_path = temp_dir
313 .path()
314 .join("test_logs")
315 .to_str()
316 .unwrap()
317 .to_string();
318
319 env::set_var("LOG_MODE", "file");
321 env::set_var("LOG_LEVEL", "info");
322 env::set_var("LOG_DATA_DIR", &log_path);
323 env::set_var("LOG_MAX_SIZE", "1024"); if let Some(parent) = Path::new(&format!("{}/relayer.log", log_path)).parent() {
327 create_dir_all(parent).expect("Failed to create log directory");
328 }
329
330 assert!(Path::new(&log_path).exists());
332
333 env::remove_var("LOG_MODE");
335 env::remove_var("LOG_LEVEL");
336 env::remove_var("LOG_DATA_DIR");
337 env::remove_var("LOG_MAX_SIZE");
338 }
339 }
340}