use std::path::Path;
use std::time::Instant;
#[derive(Debug, Clone)]
pub struct RequestContext {
pub method: String,
pub path: String,
}
impl RequestContext {
pub fn new(method: String, path: String) -> Self {
Self { method, path }
}
}
pub struct ValidationMetrics {
start_time: Instant,
method: String,
path: String,
}
impl ValidationMetrics {
pub fn new(method: &str, path: &str) -> Self {
Self {
start_time: Instant::now(),
method: method.to_string(),
path: path.to_string(),
}
}
pub fn from_context(context: &RequestContext) -> Self {
Self::new(&context.method, &context.path)
}
pub fn record_success(self) {
let duration_ms = self.start_time.elapsed().as_millis();
let timestamp = chrono::Utc::now().timestamp_millis();
log::info!(
"openapi_validation method=\"{}\" path=\"{}\" success=true duration_ms={} timestamp={}",
self.method,
self.path,
duration_ms,
timestamp
);
}
pub fn record_failure(self, error: String) {
let duration_ms = self.start_time.elapsed().as_millis();
let timestamp = chrono::Utc::now().timestamp_millis();
log::warn!(
"openapi_validation method=\"{}\" path=\"{}\" success=false duration_ms={} error=\"{}\" timestamp={}",
self.method,
self.path,
duration_ms,
error,
timestamp
);
}
}
#[derive(Debug, Clone)]
pub struct LogConfig {
pub level: String,
pub log_file: Option<String>,
pub console_output: bool,
pub show_timestamp: bool,
pub show_target: bool,
pub show_thread: bool,
}
impl Default for LogConfig {
fn default() -> Self {
Self {
level: "info".to_string(),
log_file: None,
console_output: true,
show_timestamp: true,
show_target: false,
show_thread: false,
}
}
}
impl LogConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_level(mut self, level: &str) -> Self {
self.level = level.to_string();
self
}
pub fn with_log_file<P: AsRef<Path>>(mut self, file: P) -> Self {
self.log_file = Some(file.as_ref().to_string_lossy().to_string());
self
}
pub fn with_console_output(mut self, enabled: bool) -> Self {
self.console_output = enabled;
self
}
pub fn with_timestamp(mut self, enabled: bool) -> Self {
self.show_timestamp = enabled;
self
}
pub fn with_target(mut self, enabled: bool) -> Self {
self.show_target = enabled;
self
}
pub fn with_thread(mut self, enabled: bool) -> Self {
self.show_thread = enabled;
self
}
}
pub fn init_logger() {
init_logger_with_config(LogConfig::default());
}
pub fn init_logger_with_config(config: LogConfig) {
let log_level = match config.level.as_str() {
"trace" => log::LevelFilter::Trace,
"debug" => log::LevelFilter::Debug,
"info" => log::LevelFilter::Info,
"warn" => log::LevelFilter::Warn,
"error" => log::LevelFilter::Error,
_ => log::LevelFilter::Info,
};
let mut dispatch = fern::Dispatch::new()
.format(move |out, message, record| {
let mut format_str = String::new();
if config.show_timestamp {
format_str.push_str(&format!(
"{} ",
chrono::Utc::now().format("%Y-%m-%d %H:%M:%S%.3f")
));
}
format_str.push_str(&format!("[{}]", record.level()));
if config.show_thread {
format_str.push_str(&format!(
" [{}]",
std::thread::current().name().unwrap_or("main")
));
}
if config.show_target {
format_str.push_str(&format!(" {}", record.target()));
}
format_str.push_str(&format!(" - {message}"));
out.finish(format_args!("{format_str}"))
})
.level(log_level);
if config.console_output {
dispatch = dispatch.chain(std::io::stdout());
}
if let Some(log_file) = &config.log_file {
if let Some(parent) = Path::new(log_file).parent() {
if let Err(e) = std::fs::create_dir_all(parent) {
eprintln!("Failed to create log directory {parent:?}: {e}");
return;
}
}
match fern::log_file(log_file) {
Ok(file) => {
dispatch = dispatch.chain(file);
}
Err(e) => {
eprintln!("Failed to create log file {log_file}: {e}");
return;
}
}
}
if let Err(e) = dispatch.apply() {
eprintln!("Failed to initialize logger: {e}");
} else {
log::info!("Logger initialized with config: {config:?}");
}
}