291 lines
10 KiB
Rust
291 lines
10 KiB
Rust
|
|
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<TraceContent> {
|
||
|
|
Vec::new()
|
||
|
|
}
|
||
|
|
fn default_tracecontent() -> TraceContent {
|
||
|
|
TraceContent::default()
|
||
|
|
}
|
||
|
|
fn default_tracecontent_req() -> HashMap<String,String> {
|
||
|
|
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<String,String>,
|
||
|
|
}
|
||
|
|
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<TraceContent>,
|
||
|
|
}
|
||
|
|
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<String> {
|
||
|
|
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<BufReader<File>> {
|
||
|
|
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<TraceLogContent> {
|
||
|
|
let mut log: Vec<TraceLogContent> = 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<TraceContent> = 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(())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|