use anyhow::Result;
use chrono::{NaiveDate, NaiveDateTime};
use log::info;
use num_traits::Num;
use serde::Serialize;
use std::fmt::Display;
use crate::config::{get_collect_id, get_collect_id_length, get_id_length, validate_id};
use crate::formatter::{
format_date, format_datetime, format_log, format_number, format_string, generate_timestamp,
};
pub fn escape_newlines(data: &str) -> String {
data.replace('\n', "\\n").replace('\r', "\\r")
}
pub fn serialize_to_json<T>(data: &T) -> Result<String>
where
T: Serialize,
{
let result = serde_json::to_string(data)?;
Ok(result)
}
pub fn log_as_string(id: &str, data: &str) -> Result<()> {
if let Err(e) = validate_id(id) {
log_error(&format!("ID validation failed: {}", e));
return Err(e);
}
let collect_id = get_collect_id()?;
let collect_id_length = get_collect_id_length()?;
let id_length = get_id_length(id)?;
let timestamp = generate_timestamp();
let escaped_data = escape_newlines(data);
let log_data = format_string(&escaped_data);
let formatted_log = format_log(
&collect_id,
collect_id_length,
id,
id_length,
×tamp,
&log_data,
);
info!("{}", formatted_log);
Ok(())
}
pub fn log_as_number<T>(id: &str, data: T) -> Result<()>
where
T: Num + Display + Copy + 'static,
{
if let Err(e) = validate_id(id) {
log_error(&format!("ID validation failed: {}", e));
return Err(e);
}
let collect_id = get_collect_id()?;
let collect_id_length = get_collect_id_length()?;
let id_length = get_id_length(id)?;
let timestamp = generate_timestamp();
let log_data = format_number(data);
let formatted_log = format_log(
&collect_id,
collect_id_length,
id,
id_length,
×tamp,
&log_data,
);
info!("{}", formatted_log);
Ok(())
}
pub fn log_as_date(id: &str, data: NaiveDate) -> Result<()> {
if let Err(e) = validate_id(id) {
log_error(&format!("ID validation failed: {}", e));
return Err(e);
}
let collect_id = get_collect_id()?;
let collect_id_length = get_collect_id_length()?;
let id_length = get_id_length(id)?;
let timestamp = generate_timestamp();
let log_data = format_date(data);
let formatted_log = format_log(
&collect_id,
collect_id_length,
id,
id_length,
×tamp,
&log_data,
);
info!("{}", formatted_log);
Ok(())
}
pub fn log_as_datetime(id: &str, data: NaiveDateTime) -> Result<()> {
if let Err(e) = validate_id(id) {
log_error(&format!("ID validation failed: {}", e));
return Err(e);
}
let collect_id = get_collect_id()?;
let collect_id_length = get_collect_id_length()?;
let id_length = get_id_length(id)?;
let timestamp = generate_timestamp();
let log_data = format_datetime(data);
let formatted_log = format_log(
&collect_id,
collect_id_length,
id,
id_length,
×tamp,
&log_data,
);
info!("{}", formatted_log);
Ok(())
}
pub fn log_as_json<T>(id: &str, data: &T) -> Result<()>
where
T: Serialize,
{
let json_string = serialize_to_json(data)?;
let result = log_as_string(id, &json_string);
result
}
fn log_error(message: &str) {
log::error!("[ERROR] {}", message);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{init_log_config, IdConfig, LogConfig, TargetConfig};
use serde::{Deserialize, Serialize};
use serial_test::serial;
use std::fs;
use tempfile::NamedTempFile;
fn setup_test_config() -> NamedTempFile {
let config_content = r#"{
"collect_id": "RESUME-AGENT",
"collect_id_length": 12,
"targets": [
{
"id": {
"name": "user_login",
"length": 10
}
},
{
"id": {
"name": "cpu_usage",
"length": 9
}
},
{
"id": {
"name": "report_date",
"length": 11
}
},
{
"id": {
"name": "event_time",
"length": 10
}
},
{
"id": {
"name": "user_event",
"length": 10
}
}
]
}"#;
let temp_file = NamedTempFile::new().unwrap();
fs::write(temp_file.path(), config_content).unwrap();
temp_file
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct TestData {
name: String,
value: i32,
}
#[test]
fn test_serialize_to_json_success() {
let data = TestData {
name: "test".to_string(),
value: 42,
};
let result = serialize_to_json(&data);
assert!(result.is_ok());
let json = result.unwrap();
assert!(json.contains("test"));
assert!(json.contains("42"));
}
#[test]
fn test_serialize_to_json_complex() {
let data = vec![
TestData {
name: "a".to_string(),
value: 1,
},
TestData {
name: "b".to_string(),
value: 2,
},
];
let result = serialize_to_json(&data);
assert!(result.is_ok());
let json = result.unwrap();
assert!(json.contains("a"));
assert!(json.contains("b"));
}
#[test]
fn test_serialize_to_json_empty_struct() {
#[derive(Serialize)]
struct EmptyStruct {}
let data = EmptyStruct {};
let result = serialize_to_json(&data);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "{}");
}
#[test]
fn test_escape_newlines_simple() {
let input = "hello\nworld";
let output = escape_newlines(input);
assert_eq!(output, "hello\\nworld");
}
#[test]
fn test_escape_newlines_multiple() {
let input = "line1\nline2\nline3";
let output = escape_newlines(input);
assert_eq!(output, "line1\\nline2\\nline3");
}
#[test]
fn test_escape_newlines_carriage_return() {
let input = "hello\rworld";
let output = escape_newlines(input);
assert_eq!(output, "hello\\rworld");
}
#[test]
fn test_escape_newlines_mixed() {
let input = "hello\r\nworld";
let output = escape_newlines(input);
assert_eq!(output, "hello\\r\\nworld");
}
#[test]
fn test_escape_newlines_empty() {
let input = "";
let output = escape_newlines(input);
assert_eq!(output, "");
}
#[test]
fn test_escape_newlines_no_newlines() {
let input = "hello world";
let output = escape_newlines(input);
assert_eq!(output, "hello world");
}
#[test]
fn test_escape_newlines_only_newlines() {
let input = "\n\n\n";
let output = escape_newlines(input);
assert_eq!(output, "\\n\\n\\n");
}
#[test]
#[serial]
fn test_log_as_string_success() {
let temp_file = setup_test_config();
init_log_config(temp_file.path()).unwrap();
let result = log_as_string("user_login", r#"{"user_id":123,"status":"success"}"#);
assert!(result.is_ok());
}
#[test]
#[serial]
fn test_log_as_string_with_newlines() {
let temp_file = setup_test_config();
init_log_config(temp_file.path()).unwrap();
let data = "line1\nline2\nline3";
let result = log_as_string("user_login", data);
assert!(result.is_ok());
}
#[test]
#[serial]
fn test_log_as_string_id_not_exist() {
let temp_file = setup_test_config();
init_log_config(temp_file.path()).unwrap();
let result = log_as_string("not_exist", "test data");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("在配置中不存在"));
}
#[test]
#[serial]
fn test_log_as_string_id_length_mismatch() {
let temp_file = setup_test_config();
init_log_config(temp_file.path()).unwrap();
let result = log_as_string("user_login", "test data");
let result = log_as_string("user", "test data");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("在配置中不存在"));
}
#[test]
#[serial]
fn test_log_as_number_success() {
let temp_file = setup_test_config();
init_log_config(temp_file.path()).unwrap();
let result = log_as_number("cpu_usage", 75.5f64);
assert!(result.is_ok());
let result = log_as_number("cpu_usage", 100i32);
assert!(result.is_ok());
}
#[test]
#[serial]
fn test_log_as_date_success() {
let temp_file = setup_test_config();
init_log_config(temp_file.path()).unwrap();
let date = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
let result = log_as_date("report_date", date);
assert!(result.is_ok());
}
#[test]
#[serial]
fn test_log_as_datetime_success() {
let temp_file = setup_test_config();
init_log_config(temp_file.path()).unwrap();
let datetime = NaiveDate::from_ymd_opt(2024, 1, 1)
.unwrap()
.and_hms_opt(10, 30, 0)
.unwrap();
let result = log_as_datetime("event_time", datetime);
assert!(result.is_ok());
}
#[test]
#[serial]
fn test_log_as_json_success() {
let temp_file = setup_test_config();
init_log_config(temp_file.path()).unwrap();
let data = TestData {
name: "test_user".to_string(),
value: 100,
};
let result = log_as_json("user_event", &data);
assert!(result.is_ok());
}
#[test]
#[serial]
fn test_log_as_json_with_nested_struct() {
let temp_file = setup_test_config();
init_log_config(temp_file.path()).unwrap();
#[derive(Serialize)]
struct NestedData {
inner: TestData,
count: u32,
}
let data = NestedData {
inner: TestData {
name: "inner".to_string(),
value: 50,
},
count: 10,
};
let result = log_as_json("user_event", &data);
assert!(result.is_ok());
}
#[test]
#[serial]
fn test_log_as_json_id_not_exist() {
let temp_file = setup_test_config();
init_log_config(temp_file.path()).unwrap();
let data = TestData {
name: "test".to_string(),
value: 1,
};
let result = log_as_json("not_exist", &data);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("在配置中不存在"));
}
#[test]
#[serial]
fn test_log_as_json_with_newlines_in_string() {
let temp_file = setup_test_config();
init_log_config(temp_file.path()).unwrap();
let data = TestData {
name: "line1\nline2".to_string(),
value: 1,
};
let result = log_as_json("user_event", &data);
assert!(result.is_ok());
}
}