use serde::{Deserialize,Serialize}; use std::{ io::Write, // sync::Arc, fmt::Debug, fs::{self,File}, path::{Path, PathBuf}, io::{Error, ErrorKind, BufRead, BufReader}, collections::HashMap, }; use log::error; use crate::{ defs::{ FILE_SCHEME, SID_TRACE_FILE, Config, }, tools::str_date_from_timestamp, }; fn default_empty() -> String { "/".to_string() } fn default_tracedata_items() -> Vec { Vec::new() } fn default_tracecontent() -> TraceContent { TraceContent::default() } fn default_tracecontent_req() -> HashMap { HashMap::new() } #[derive(Default,Deserialize,Serialize,Debug,Clone)] pub struct RequestData { #[serde(default = "default_empty")] pub agent: String, } #[derive(Default,Deserialize,Serialize,Debug,Clone)] pub struct TraceContent { #[serde(default = "default_empty")] pub when: String, #[serde(default = "default_empty")] pub sid: String, #[serde(default = "default_empty")] pub origin: String, #[serde(default = "default_empty")] pub trigger: String, #[serde(default = "default_empty")] pub id: String, #[serde(default = "default_empty")] pub info: String, #[serde(default = "default_empty")] pub context: String, #[serde(default = "default_empty")] pub role: String, #[serde(default = "default_tracecontent_req")] pub req: HashMap, } impl TraceContent { pub fn to_json(&self) -> String { serde_json::to_string(self).unwrap_or_else(|e|{ println!("Error to convert TraceContent to json: {}",e); String::from("") }) } } #[derive(Default,Deserialize,Serialize,Debug,Clone)] pub struct TraceLogContent { #[serde(default = "default_empty")] pub line_id: String, #[serde(default = "default_tracecontent")] pub content: TraceContent, } #[derive(Default,Deserialize,Serialize,Debug,Clone)] pub struct TraceData { #[serde(default = "default_empty")] pub user_id: String, #[serde(default = "default_empty")] pub timestamp: String, #[serde(default = "default_tracedata_items")] pub contents: Vec, } impl TraceData { fn id_path(&self, file: &str, config: &Config) -> String { if self.user_id.is_empty() { format!("{}/{}", config.trace_store_uri.replace(FILE_SCHEME, ""), file) } else { format!("{}/{}/{}", config.trace_store_uri.replace(FILE_SCHEME, ""), self.user_id,file) } } fn write_data(&self, file_path: &str, data: &str, overwrite: bool, verbose: u8 ) -> std::io::Result<()> { let check_path = |path: &Path| -> std::io::Result<()> { if ! Path::new(&path).exists() { if let Err(e) = std::fs::create_dir(&path) { return Err(Error::new( ErrorKind::InvalidInput, format!("Error create path {}: {}",&path.display(), e) )); } } Ok(()) }; if file_path.is_empty() || data.is_empty() { return Err(Error::new( ErrorKind::InvalidInput, format!("Error save {}",&file_path) )); } if ! Path::new(&file_path).exists() { let path = PathBuf::from(&file_path); if let Some(dir_path) = path.parent() { if ! Path::new(&dir_path).exists() { if let Some(parent_dir_path) = dir_path.parent() { if ! Path::new(&parent_dir_path).exists() { let res = check_path(&parent_dir_path); if res.is_err() { return res; } } } let res = check_path(&dir_path); if res.is_err() { return res; } } } } if overwrite || ! Path::new(&file_path).exists() { fs::write(&file_path, data)?; if verbose > 2 { println!("Overwrite: {}",&file_path); } } else { let sid_settings_file = fs::OpenOptions::new() .write(true) .append(true) // This is needed to append to file .open(&file_path); if let Ok(mut file) = sid_settings_file { file.write_all(data.as_bytes())?; } if verbose > 2 { println!("write: {}",&file_path); } } Ok(()) } pub fn contents_to_json(&self) -> Vec { self.contents.clone().into_iter().map(|item| item.to_json()).collect() } pub fn save(&self, config: &Config, overwrite: bool) -> std::io::Result<()> { let file_trace_path = self.id_path(&format!("{}",SID_TRACE_FILE), config); let contents = self.contents_to_json(); let mut result = Ok(()); let cnt_lines = contents.len(); let lines = if cnt_lines > 0 { cnt_lines - 1 } else { cnt_lines }; let mut write_overwrite = overwrite; for (idx, line) in contents.iter().enumerate() { let prfx = if idx == 0 && Path::new(&file_trace_path).exists() { ",\n" } else { "" }; let sfx = if idx == lines { "" } else { ",\n" }; result = self.write_data( &file_trace_path, format!("{}{}{}",&prfx,&line,&sfx).as_str(), write_overwrite, config.verbose ); let _ = result.as_ref().unwrap_or_else(|e|{ println!("Error save trace contets: {} line {}",&e, &idx); &() }); if write_overwrite { write_overwrite = false} if result.is_err() { break; } } result } pub fn get_reader(self, config: &Config) -> std::io::Result> { let file_path = self.id_path(&format!("{}",SID_TRACE_FILE), config); if ! Path::new(&file_path).exists() { error!("Error file path not exist: {}",&file_path); return Err(Error::new( ErrorKind::InvalidInput, format!("Error file path not exist: {}",file_path) )); } let file = File::open(&file_path)?; let reader = BufReader::new(file); Ok(reader) } pub fn load(&self, config: &Config, human: bool, reverse: bool) -> Vec { let mut log: Vec = Vec::new(); match self.clone().get_reader(config) { Ok(reader) => { let mut line_pos = 0; for line in reader.lines() { line_pos +=1; let line = match line { Ok(res) => if res.ends_with(",") { res[0..res.len() - 1].to_owned() } else { res }, Err(_) => continue, }; if ! line.is_empty() { let mut log_line: TraceContent = serde_json::from_str(&line).unwrap_or_else(|e| { println!("Error parse load line {}: {}", &line_pos,e); TraceContent::default() }); if ! log_line.when.is_empty() { let line_id = log_line.when.to_owned(); if human { log_line.when = str_date_from_timestamp(&log_line.when); } log.push(TraceLogContent { line_id, content: log_line }) } } } }, Err(e) => { error!("Error on load log: {}",e); } } if reverse { log.reverse(); } log } pub fn clean(&self, config: &Config, line_id: &str) -> std::io::Result<()> { let file_path = self.id_path(&format!("{}",SID_TRACE_FILE), config); if ! Path::new(&file_path).exists() { error!("Error file path not exist: {}",&file_path); return Err(Error::new( ErrorKind::InvalidInput, format!("Error file path not exist: {}",file_path) )); } if line_id == "ALL" { std::fs::remove_file(&file_path) } else { let mut log: Vec = Vec::new(); match self.clone().get_reader(config) { Ok(reader) => { let mut line_pos = 0; for line in reader.lines() { line_pos +=1; let line = match line { Ok(res) => if res.ends_with(",") { res[0..res.len() - 1].to_owned() } else { res }, Err(_) => continue, }; if ! line.is_empty() { let log_line: TraceContent = serde_json::from_str(&line).unwrap_or_else(|e| { println!("Error parse load line {}: {}", &line_pos,e); TraceContent::default() }); if ! log_line.when.is_empty() && log_line.when != line_id { log.push(log_line) } } } }, Err(e) => { error!("Error on load log: {}",e); } } if log.len() == 0 { return std::fs::remove_file(&file_path) } let trace_data = TraceData { user_id: self.user_id.to_owned(), timestamp: String::from(""), contents: log }; trace_data.save(config, true).unwrap_or_else(|e|{ println!("Error save filter {} to path {}: {}",line_id, &file_path,&e); error!("Error save filter {} to path {}: {}",&line_id, &file_path,e); }); Ok(()) } } }