openzeppelin_relayer/logging/
mod.rsuse chrono::Utc;
use log::info;
use simplelog::{Config, LevelFilter, SimpleLogger, WriteLogger};
use std::{
env,
fs::{create_dir_all, metadata, File, OpenOptions},
path::Path,
};
pub fn compute_rolled_file_path(base_file_path: &str, date_str: &str, index: u32) -> String {
if base_file_path.ends_with(".log") {
let trimmed = base_file_path.strip_suffix(".log").unwrap();
format!("{}-{}.{}.log", trimmed, date_str, index)
} else {
format!("{}-{}.{}.log", base_file_path, date_str, index)
}
}
pub fn time_based_rolling(base_file_path: &str, date_str: &str, index: u32) -> String {
compute_rolled_file_path(base_file_path, date_str, index)
}
pub fn space_based_rolling(
file_path: &str,
base_file_path: &str,
date_str: &str,
max_size: u64,
) -> String {
let mut final_path = file_path.to_string();
let mut index = 1;
while let Ok(metadata) = metadata(&final_path) {
if metadata.len() > max_size {
final_path = compute_rolled_file_path(base_file_path, date_str, index);
index += 1;
} else {
break;
}
}
final_path
}
pub fn setup_logging() {
let log_mode = env::var("LOG_MODE").unwrap_or_else(|_| "stdout".to_string());
let log_level = env::var("LOG_LEVEL").unwrap_or_else(|_| "info".to_string());
let level_filter = match log_level.to_lowercase().as_str() {
"trace" => LevelFilter::Trace,
"debug" => LevelFilter::Debug,
"info" => LevelFilter::Info,
"warn" => LevelFilter::Warn,
"error" => LevelFilter::Error,
_ => LevelFilter::Info,
};
if log_mode.to_lowercase() == "file" {
info!("Logging to file: {}", log_level);
let log_dir = if env::var("IN_DOCKER")
.map(|val| val == "true")
.unwrap_or(false)
{
"logs/".to_string()
} else {
env::var("LOG_DATA_DIR").unwrap_or_else(|_| "./logs".to_string())
};
let log_dir = format!("{}/", log_dir.trim_end_matches('/'));
let now = Utc::now();
let date_str = now.format("%Y-%m-%d").to_string();
let base_file_path = format!("{}relayer.log", log_dir);
if Path::new(&base_file_path).exists() {
info!(
"Base Log file already exists: {}. Proceeding to compute rolled log file path.",
base_file_path
);
}
let time_based_path = time_based_rolling(&base_file_path, &date_str, 1);
if let Some(parent) = Path::new(&time_based_path).parent() {
create_dir_all(parent).expect("Failed to create log directory");
}
let max_size: u64 = env::var("LOG_MAX_SIZE")
.map(|s| {
s.parse::<u64>()
.expect("LOG_MAX_SIZE must be a valid u64 if set")
})
.unwrap_or(1_073_741_824);
let final_path =
space_based_rolling(&time_based_path, &base_file_path, &date_str, max_size);
let log_file = if Path::new(&final_path).exists() {
OpenOptions::new()
.append(true)
.open(&final_path)
.unwrap_or_else(|e| panic!("Unable to open log file {}: {}", final_path, e))
} else {
File::create(&final_path)
.unwrap_or_else(|e| panic!("Unable to create log file {}: {}", final_path, e))
};
WriteLogger::init(level_filter, Config::default(), log_file)
.expect("Failed to initialize file logger");
} else {
SimpleLogger::init(level_filter, Config::default())
.expect("Failed to initialize simple logger");
}
info!("Logging is successfully configured (mode: {})", log_mode);
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::Write;
use std::sync::Once;
use tempfile::tempdir;
static INIT_LOGGER: Once = Once::new();
#[test]
fn test_compute_rolled_file_path() {
let result = compute_rolled_file_path("app.log", "2023-01-01", 1);
assert_eq!(result, "app-2023-01-01.1.log");
let result = compute_rolled_file_path("app", "2023-01-01", 2);
assert_eq!(result, "app-2023-01-01.2.log");
let result = compute_rolled_file_path("logs/app.log", "2023-01-01", 3);
assert_eq!(result, "logs/app-2023-01-01.3.log");
}
#[test]
fn test_time_based_rolling() {
let result = time_based_rolling("app.log", "2023-01-01", 1);
assert_eq!(result, "app-2023-01-01.1.log");
}
#[test]
fn test_space_based_rolling() {
let temp_dir = tempdir().expect("Failed to create temp directory");
let base_path = temp_dir
.path()
.join("test.log")
.to_str()
.unwrap()
.to_string();
let result = space_based_rolling(&base_path, &base_path, "2023-01-01", 100);
assert_eq!(result, base_path);
{
let mut file = File::create(&base_path).expect("Failed to create test file");
file.write_all(&[0; 200])
.expect("Failed to write to test file");
}
let expected_path = compute_rolled_file_path(&base_path, "2023-01-01", 1);
let result = space_based_rolling(&base_path, &base_path, "2023-01-01", 100);
assert_eq!(result, expected_path);
{
let mut file = File::create(&expected_path).expect("Failed to create test file");
file.write_all(&[0; 200])
.expect("Failed to write to test file");
}
let expected_path2 = compute_rolled_file_path(&base_path, "2023-01-01", 2);
let result = space_based_rolling(&base_path, &base_path, "2023-01-01", 100);
assert_eq!(result, expected_path2);
}
#[test]
fn test_logging_configuration() {
{
env::set_var("LOG_MODE", "stdout");
env::set_var("LOG_LEVEL", "debug");
INIT_LOGGER.call_once(|| {
setup_logging();
});
env::remove_var("LOG_MODE");
env::remove_var("LOG_LEVEL");
}
{
let temp_dir = tempdir().expect("Failed to create temp directory");
let log_path = temp_dir
.path()
.join("test_logs")
.to_str()
.unwrap()
.to_string();
env::set_var("LOG_MODE", "file");
env::set_var("LOG_LEVEL", "info");
env::set_var("LOG_DATA_DIR", &log_path);
env::set_var("LOG_MAX_SIZE", "1024"); if let Some(parent) = Path::new(&format!("{}/relayer.log", log_path)).parent() {
create_dir_all(parent).expect("Failed to create log directory");
}
assert!(Path::new(&log_path).exists());
env::remove_var("LOG_MODE");
env::remove_var("LOG_LEVEL");
env::remove_var("LOG_DATA_DIR");
env::remove_var("LOG_MAX_SIZE");
}
}
}