use std::collections::HashMap;
use std::{fmt, time::SystemTime};
use crate::core::{
error::LarkAPIError,
error_codes::LarkErrorCode,
error_helper::ErrorHandlingCategory,
error_metrics::{ErrorEvent, ErrorSeverity},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
Debug = 1,
Info = 2,
Warn = 3,
Error = 4,
Critical = 5,
}
impl LogLevel {
pub fn from_error_severity(severity: ErrorSeverity) -> Self {
match severity {
ErrorSeverity::Info => Self::Info,
ErrorSeverity::Warning => Self::Warn,
ErrorSeverity::Error => Self::Error,
ErrorSeverity::Critical => Self::Critical,
}
}
pub fn color_code(&self) -> &'static str {
match self {
Self::Debug => "\x1b[36m", Self::Info => "\x1b[32m", Self::Warn => "\x1b[33m", Self::Error => "\x1b[31m", Self::Critical => "\x1b[35m", }
}
pub fn reset_color() -> &'static str {
"\x1b[0m"
}
pub fn label(&self) -> &'static str {
match self {
Self::Debug => "DEBUG",
Self::Info => "INFO",
Self::Warn => "WARN",
Self::Error => "ERROR",
Self::Critical => "CRITICAL",
}
}
}
impl fmt::Display for LogLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.label())
}
}
#[derive(Debug, Clone)]
pub struct LogEntry {
pub level: LogLevel,
pub timestamp: SystemTime,
pub message: String,
pub error: Option<LarkAPIError>,
pub category: Option<ErrorHandlingCategory>,
pub error_code: Option<LarkErrorCode>,
pub context: HashMap<String, String>,
pub caller: Option<String>,
}
impl LogEntry {
pub fn new(level: LogLevel, message: String) -> Self {
Self {
level,
timestamp: SystemTime::now(),
message,
error: None,
category: None,
error_code: None,
context: HashMap::new(),
caller: None,
}
}
pub fn from_error_event(event: &ErrorEvent) -> Self {
let level = LogLevel::from_error_severity(event.severity_level());
let message = format!("API调用错误: {}", event.error);
Self {
level,
timestamp: event.timestamp,
message,
error: Some(event.error.clone()),
category: Some(event.category),
error_code: event.error_code,
context: event.context.clone(),
caller: None,
}
}
pub fn with_context(mut self, key: &str, value: &str) -> Self {
self.context.insert(key.to_string(), value.to_string());
self
}
pub fn with_caller(mut self, caller: String) -> Self {
self.caller = Some(caller);
self
}
pub fn with_error(mut self, error: LarkAPIError) -> Self {
self.error = Some(error);
self
}
}
pub trait LogFormatter {
fn format(&self, entry: &LogEntry) -> String;
}
#[derive(Debug, Clone)]
pub struct SimpleFormatter {
pub include_timestamp: bool,
pub use_colors: bool,
}
impl Default for SimpleFormatter {
fn default() -> Self {
Self {
include_timestamp: true,
use_colors: true,
}
}
}
impl LogFormatter for SimpleFormatter {
fn format(&self, entry: &LogEntry) -> String {
let mut output = String::new();
if self.use_colors {
output.push_str(entry.level.color_code());
}
if self.include_timestamp {
if let Ok(duration) = entry.timestamp.duration_since(SystemTime::UNIX_EPOCH) {
let seconds = duration.as_secs();
let millis = duration.subsec_millis();
output.push_str(&format!("[{seconds}.{millis:03}] "));
}
}
output.push_str(&format!("[{}] ", entry.level.label()));
output.push_str(&entry.message);
if let Some(ref error) = entry.error {
output.push_str(&format!(" | 错误: {error}"));
}
if let Some(code) = entry.error_code {
output.push_str(&format!(" | 错误码: {code}"));
}
if !entry.context.is_empty() {
output.push_str(" | 上下文: {");
for (i, (key, value)) in entry.context.iter().enumerate() {
if i > 0 {
output.push_str(", ");
}
output.push_str(&format!("{key}={value}"));
}
output.push('}');
}
if let Some(ref caller) = entry.caller {
output.push_str(&format!(" | 调用者: {caller}"));
}
if self.use_colors {
output.push_str(LogLevel::reset_color());
}
output
}
}
#[derive(Debug, Clone)]
pub struct JsonFormatter;
impl LogFormatter for JsonFormatter {
fn format(&self, entry: &LogEntry) -> String {
use serde_json::{Map, Value};
let mut json_entry = Map::new();
json_entry.insert(
"level".to_string(),
Value::String(entry.level.label().to_string()),
);
json_entry.insert(
"timestamp".to_string(),
Value::Number(serde_json::Number::from(
entry
.timestamp
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64,
)),
);
json_entry.insert("message".to_string(), Value::String(entry.message.clone()));
if let Some(ref error) = entry.error {
json_entry.insert("error".to_string(), Value::String(error.to_string()));
}
if let Some(category) = entry.category {
json_entry.insert(
"category".to_string(),
Value::String(format!("{category:?}")),
);
}
if let Some(code) = entry.error_code {
json_entry.insert(
"error_code".to_string(),
Value::Number(serde_json::Number::from(code as i32)),
);
}
if !entry.context.is_empty() {
let context_value = entry
.context
.iter()
.map(|(k, v)| (k.clone(), Value::String(v.clone())))
.collect::<Map<String, Value>>();
json_entry.insert("context".to_string(), Value::Object(context_value));
}
if let Some(ref caller) = entry.caller {
json_entry.insert("caller".to_string(), Value::String(caller.clone()));
}
serde_json::to_string(&Value::Object(json_entry)).unwrap_or_default()
}
}
#[derive(Debug, Clone)]
pub struct StructuredFormatter {
pub separator: String,
pub kv_separator: String,
}
impl Default for StructuredFormatter {
fn default() -> Self {
Self {
separator: " | ".to_string(),
kv_separator: "=".to_string(),
}
}
}
impl LogFormatter for StructuredFormatter {
fn format(&self, entry: &LogEntry) -> String {
let mut fields = Vec::new();
if let Ok(duration) = entry.timestamp.duration_since(SystemTime::UNIX_EPOCH) {
fields.push(format!("time{}{}", self.kv_separator, duration.as_millis()));
}
fields.push(format!("level{}{}", self.kv_separator, entry.level.label()));
fields.push(format!("msg{}{}", self.kv_separator, entry.message));
if let Some(ref error) = entry.error {
fields.push(format!("error{}{}", self.kv_separator, error));
}
if let Some(category) = entry.category {
fields.push(format!("category{}{:?}", self.kv_separator, category));
}
if let Some(code) = entry.error_code {
fields.push(format!("error_code{}{}", self.kv_separator, code as i32));
}
for (key, value) in &entry.context {
fields.push(format!("{}{}{}", key, self.kv_separator, value));
}
if let Some(ref caller) = entry.caller {
fields.push(format!("caller{}{}", self.kv_separator, caller));
}
fields.join(&self.separator)
}
}
#[derive(Debug, Clone)]
pub struct LoggerConfig {
pub min_level: LogLevel,
pub formatter: FormatterType,
pub output: OutputTarget,
pub include_context: bool,
pub include_caller: bool,
}
#[derive(Debug, Clone)]
pub enum FormatterType {
Simple(SimpleFormatter),
Json(JsonFormatter),
Structured(StructuredFormatter),
}
#[derive(Debug, Clone)]
pub enum OutputTarget {
Stdout,
Stderr,
File(String),
Multiple(Vec<OutputTarget>),
}
impl Default for LoggerConfig {
fn default() -> Self {
Self {
min_level: LogLevel::Info,
formatter: FormatterType::Simple(SimpleFormatter::default()),
output: OutputTarget::Stdout,
include_context: true,
include_caller: false,
}
}
}
pub struct ErrorLogger {
config: LoggerConfig,
}
impl Default for ErrorLogger {
fn default() -> Self {
Self::new(LoggerConfig::default())
}
}
impl ErrorLogger {
pub fn new(config: LoggerConfig) -> Self {
Self { config }
}
pub fn log(&self, entry: LogEntry) {
if entry.level < self.config.min_level {
return;
}
let formatted = match &self.config.formatter {
FormatterType::Simple(formatter) => formatter.format(&entry),
FormatterType::Json(formatter) => formatter.format(&entry),
FormatterType::Structured(formatter) => formatter.format(&entry),
};
self.output_log(&formatted);
}
pub fn error(&self, message: &str) {
let entry = LogEntry::new(LogLevel::Error, message.to_string());
self.log(entry);
}
pub fn error_with_context(&self, message: &str, context: HashMap<String, String>) {
let mut entry = LogEntry::new(LogLevel::Error, message.to_string());
entry.context = context;
self.log(entry);
}
pub fn log_api_error(&self, error: &LarkAPIError) {
let event = ErrorEvent::from_error(error.clone());
let entry = LogEntry::from_error_event(&event);
self.log(entry);
}
pub fn log_error_event(&self, event: &ErrorEvent) {
let entry = LogEntry::from_error_event(event);
self.log(entry);
}
pub fn warn(&self, message: &str) {
let entry = LogEntry::new(LogLevel::Warn, message.to_string());
self.log(entry);
}
pub fn info(&self, message: &str) {
let entry = LogEntry::new(LogLevel::Info, message.to_string());
self.log(entry);
}
pub fn debug(&self, message: &str) {
let entry = LogEntry::new(LogLevel::Debug, message.to_string());
self.log(entry);
}
fn output_log(&self, formatted: &str) {
match &self.config.output {
OutputTarget::Stdout => {
println!("{formatted}");
}
OutputTarget::Stderr => {
eprintln!("{formatted}");
}
OutputTarget::File(path) => {
self.write_to_file(path, formatted);
}
OutputTarget::Multiple(targets) => {
for target in targets {
let temp_config = LoggerConfig {
output: target.clone(),
..self.config.clone()
};
let temp_logger = ErrorLogger::new(temp_config);
temp_logger.output_log(formatted);
}
}
}
}
fn write_to_file(&self, path: &str, content: &str) {
use std::{fs::OpenOptions, io::Write};
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
let _ = writeln!(file, "{content}");
}
}
}
pub struct LoggerBuilder {
config: LoggerConfig,
}
impl LoggerBuilder {
pub fn new() -> Self {
Self {
config: LoggerConfig::default(),
}
}
pub fn min_level(mut self, level: LogLevel) -> Self {
self.config.min_level = level;
self
}
pub fn simple_format(mut self) -> Self {
self.config.formatter = FormatterType::Simple(SimpleFormatter::default());
self
}
pub fn json_format(mut self) -> Self {
self.config.formatter = FormatterType::Json(JsonFormatter);
self
}
pub fn structured_format(mut self) -> Self {
self.config.formatter = FormatterType::Structured(StructuredFormatter::default());
self
}
pub fn output_to_file(mut self, path: &str) -> Self {
self.config.output = OutputTarget::File(path.to_string());
self
}
pub fn output_to_stderr(mut self) -> Self {
self.config.output = OutputTarget::Stderr;
self
}
pub fn include_context(mut self, include: bool) -> Self {
self.config.include_context = include;
self
}
pub fn build(self) -> ErrorLogger {
ErrorLogger::new(self.config)
}
}
impl Default for LoggerBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_log_level_ordering() {
assert!(LogLevel::Debug < LogLevel::Info);
assert!(LogLevel::Error > LogLevel::Warn);
assert!(LogLevel::Critical > LogLevel::Error);
}
#[test]
fn test_log_entry_creation() {
let entry = LogEntry::new(LogLevel::Error, "Test message".to_string());
assert_eq!(entry.level, LogLevel::Error);
assert_eq!(entry.message, "Test message");
assert!(entry.context.is_empty());
}
#[test]
fn test_simple_formatter() {
let formatter = SimpleFormatter::default();
let entry = LogEntry::new(LogLevel::Info, "Test message".to_string());
let formatted = formatter.format(&entry);
assert!(formatted.contains("[INFO]"));
assert!(formatted.contains("Test message"));
}
#[test]
fn test_json_formatter() {
let formatter = JsonFormatter;
let entry = LogEntry::new(LogLevel::Error, "Error message".to_string());
let formatted = formatter.format(&entry);
assert!(formatted.contains("\"level\":\"ERROR\""));
assert!(formatted.contains("\"message\":\"Error message\""));
}
#[test]
fn test_structured_formatter() {
let formatter = StructuredFormatter::default();
let entry = LogEntry::new(LogLevel::Warn, "Warning message".to_string());
let formatted = formatter.format(&entry);
assert!(formatted.contains("level=WARN"));
assert!(formatted.contains("msg=Warning message"));
}
#[test]
fn test_logger_builder() {
let logger = LoggerBuilder::new()
.min_level(LogLevel::Debug)
.json_format()
.output_to_stderr()
.build();
assert_eq!(logger.config.min_level, LogLevel::Debug);
matches!(logger.config.formatter, FormatterType::Json(_));
matches!(logger.config.output, OutputTarget::Stderr);
}
}