1use std::collections::HashMap;
10use std::{fmt, time::SystemTime};
11
12use crate::core::{
13 error::LarkAPIError,
14 error_codes::LarkErrorCode,
15 error_helper::ErrorHandlingCategory,
16 error_metrics::{ErrorEvent, ErrorSeverity},
17};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
21pub enum LogLevel {
22 Debug = 1,
24 Info = 2,
26 Warn = 3,
28 Error = 4,
30 Critical = 5,
32}
33
34impl LogLevel {
35 pub fn from_error_severity(severity: ErrorSeverity) -> Self {
37 match severity {
38 ErrorSeverity::Info => Self::Info,
39 ErrorSeverity::Warning => Self::Warn,
40 ErrorSeverity::Error => Self::Error,
41 ErrorSeverity::Critical => Self::Critical,
42 }
43 }
44
45 pub fn color_code(&self) -> &'static str {
47 match self {
48 Self::Debug => "\x1b[36m", Self::Info => "\x1b[32m", Self::Warn => "\x1b[33m", Self::Error => "\x1b[31m", Self::Critical => "\x1b[35m", }
54 }
55
56 pub fn reset_color() -> &'static str {
58 "\x1b[0m"
59 }
60
61 pub fn label(&self) -> &'static str {
63 match self {
64 Self::Debug => "DEBUG",
65 Self::Info => "INFO",
66 Self::Warn => "WARN",
67 Self::Error => "ERROR",
68 Self::Critical => "CRITICAL",
69 }
70 }
71}
72
73impl fmt::Display for LogLevel {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 write!(f, "{}", self.label())
76 }
77}
78
79#[derive(Debug, Clone)]
81pub struct LogEntry {
82 pub level: LogLevel,
84 pub timestamp: SystemTime,
86 pub message: String,
88 pub error: Option<LarkAPIError>,
90 pub category: Option<ErrorHandlingCategory>,
92 pub error_code: Option<LarkErrorCode>,
94 pub context: HashMap<String, String>,
96 pub caller: Option<String>,
98}
99
100impl LogEntry {
101 pub fn new(level: LogLevel, message: String) -> Self {
103 Self {
104 level,
105 timestamp: SystemTime::now(),
106 message,
107 error: None,
108 category: None,
109 error_code: None,
110 context: HashMap::new(),
111 caller: None,
112 }
113 }
114
115 pub fn from_error_event(event: &ErrorEvent) -> Self {
117 let level = LogLevel::from_error_severity(event.severity_level());
118 let message = format!("API调用错误: {}", event.error);
119
120 Self {
121 level,
122 timestamp: event.timestamp,
123 message,
124 error: Some(event.error.clone()),
125 category: Some(event.category),
126 error_code: event.error_code,
127 context: event.context.clone(),
128 caller: None,
129 }
130 }
131
132 pub fn with_context(mut self, key: &str, value: &str) -> Self {
134 self.context.insert(key.to_string(), value.to_string());
135 self
136 }
137
138 pub fn with_caller(mut self, caller: String) -> Self {
140 self.caller = Some(caller);
141 self
142 }
143
144 pub fn with_error(mut self, error: LarkAPIError) -> Self {
146 self.error = Some(error);
147 self
148 }
149}
150
151pub trait LogFormatter {
153 fn format(&self, entry: &LogEntry) -> String;
155}
156
157#[derive(Debug, Clone)]
159pub struct SimpleFormatter {
160 pub include_timestamp: bool,
162 pub use_colors: bool,
164}
165
166impl Default for SimpleFormatter {
167 fn default() -> Self {
168 Self {
169 include_timestamp: true,
170 use_colors: true,
171 }
172 }
173}
174
175impl LogFormatter for SimpleFormatter {
176 fn format(&self, entry: &LogEntry) -> String {
177 let mut output = String::new();
178
179 if self.use_colors {
181 output.push_str(entry.level.color_code());
182 }
183
184 if self.include_timestamp {
186 if let Ok(duration) = entry.timestamp.duration_since(SystemTime::UNIX_EPOCH) {
187 let seconds = duration.as_secs();
188 let millis = duration.subsec_millis();
189 output.push_str(&format!("[{seconds}.{millis:03}] "));
190 }
191 }
192
193 output.push_str(&format!("[{}] ", entry.level.label()));
195
196 output.push_str(&entry.message);
198
199 if let Some(ref error) = entry.error {
201 output.push_str(&format!(" | 错误: {error}"));
202 }
203
204 if let Some(code) = entry.error_code {
206 output.push_str(&format!(" | 错误码: {code}"));
207 }
208
209 if !entry.context.is_empty() {
211 output.push_str(" | 上下文: {");
212 for (i, (key, value)) in entry.context.iter().enumerate() {
213 if i > 0 {
214 output.push_str(", ");
215 }
216 output.push_str(&format!("{key}={value}"));
217 }
218 output.push('}');
219 }
220
221 if let Some(ref caller) = entry.caller {
223 output.push_str(&format!(" | 调用者: {caller}"));
224 }
225
226 if self.use_colors {
228 output.push_str(LogLevel::reset_color());
229 }
230
231 output
232 }
233}
234
235#[derive(Debug, Clone)]
237pub struct JsonFormatter;
238
239impl LogFormatter for JsonFormatter {
240 fn format(&self, entry: &LogEntry) -> String {
241 use serde_json::{Map, Value};
242
243 let mut json_entry = Map::new();
244
245 json_entry.insert(
247 "level".to_string(),
248 Value::String(entry.level.label().to_string()),
249 );
250 json_entry.insert(
251 "timestamp".to_string(),
252 Value::Number(serde_json::Number::from(
253 entry
254 .timestamp
255 .duration_since(SystemTime::UNIX_EPOCH)
256 .unwrap_or_default()
257 .as_millis() as u64,
258 )),
259 );
260 json_entry.insert("message".to_string(), Value::String(entry.message.clone()));
261
262 if let Some(ref error) = entry.error {
264 json_entry.insert("error".to_string(), Value::String(error.to_string()));
265 }
266
267 if let Some(category) = entry.category {
269 json_entry.insert(
270 "category".to_string(),
271 Value::String(format!("{category:?}")),
272 );
273 }
274
275 if let Some(code) = entry.error_code {
277 json_entry.insert(
278 "error_code".to_string(),
279 Value::Number(serde_json::Number::from(code as i32)),
280 );
281 }
282
283 if !entry.context.is_empty() {
285 let context_value = entry
286 .context
287 .iter()
288 .map(|(k, v)| (k.clone(), Value::String(v.clone())))
289 .collect::<Map<String, Value>>();
290 json_entry.insert("context".to_string(), Value::Object(context_value));
291 }
292
293 if let Some(ref caller) = entry.caller {
295 json_entry.insert("caller".to_string(), Value::String(caller.clone()));
296 }
297
298 serde_json::to_string(&Value::Object(json_entry)).unwrap_or_default()
299 }
300}
301
302#[derive(Debug, Clone)]
304pub struct StructuredFormatter {
305 pub separator: String,
307 pub kv_separator: String,
309}
310
311impl Default for StructuredFormatter {
312 fn default() -> Self {
313 Self {
314 separator: " | ".to_string(),
315 kv_separator: "=".to_string(),
316 }
317 }
318}
319
320impl LogFormatter for StructuredFormatter {
321 fn format(&self, entry: &LogEntry) -> String {
322 let mut fields = Vec::new();
323
324 if let Ok(duration) = entry.timestamp.duration_since(SystemTime::UNIX_EPOCH) {
326 fields.push(format!("time{}{}", self.kv_separator, duration.as_millis()));
327 }
328
329 fields.push(format!("level{}{}", self.kv_separator, entry.level.label()));
331
332 fields.push(format!("msg{}{}", self.kv_separator, entry.message));
334
335 if let Some(ref error) = entry.error {
337 fields.push(format!("error{}{}", self.kv_separator, error));
338 }
339
340 if let Some(category) = entry.category {
342 fields.push(format!("category{}{:?}", self.kv_separator, category));
343 }
344
345 if let Some(code) = entry.error_code {
347 fields.push(format!("error_code{}{}", self.kv_separator, code as i32));
348 }
349
350 for (key, value) in &entry.context {
352 fields.push(format!("{}{}{}", key, self.kv_separator, value));
353 }
354
355 if let Some(ref caller) = entry.caller {
357 fields.push(format!("caller{}{}", self.kv_separator, caller));
358 }
359
360 fields.join(&self.separator)
361 }
362}
363
364#[derive(Debug, Clone)]
366pub struct LoggerConfig {
367 pub min_level: LogLevel,
369 pub formatter: FormatterType,
371 pub output: OutputTarget,
373 pub include_context: bool,
375 pub include_caller: bool,
377}
378
379#[derive(Debug, Clone)]
381pub enum FormatterType {
382 Simple(SimpleFormatter),
383 Json(JsonFormatter),
384 Structured(StructuredFormatter),
385}
386
387#[derive(Debug, Clone)]
389pub enum OutputTarget {
390 Stdout,
392 Stderr,
394 File(String),
396 Multiple(Vec<OutputTarget>),
398}
399
400impl Default for LoggerConfig {
401 fn default() -> Self {
402 Self {
403 min_level: LogLevel::Info,
404 formatter: FormatterType::Simple(SimpleFormatter::default()),
405 output: OutputTarget::Stdout,
406 include_context: true,
407 include_caller: false,
408 }
409 }
410}
411
412pub struct ErrorLogger {
414 config: LoggerConfig,
415}
416
417impl Default for ErrorLogger {
418 fn default() -> Self {
419 Self::new(LoggerConfig::default())
420 }
421}
422
423impl ErrorLogger {
424 pub fn new(config: LoggerConfig) -> Self {
426 Self { config }
427 }
428
429 pub fn log(&self, entry: LogEntry) {
431 if entry.level < self.config.min_level {
433 return;
434 }
435
436 let formatted = match &self.config.formatter {
438 FormatterType::Simple(formatter) => formatter.format(&entry),
439 FormatterType::Json(formatter) => formatter.format(&entry),
440 FormatterType::Structured(formatter) => formatter.format(&entry),
441 };
442
443 self.output_log(&formatted);
445 }
446
447 pub fn error(&self, message: &str) {
449 let entry = LogEntry::new(LogLevel::Error, message.to_string());
450 self.log(entry);
451 }
452
453 pub fn error_with_context(&self, message: &str, context: HashMap<String, String>) {
455 let mut entry = LogEntry::new(LogLevel::Error, message.to_string());
456 entry.context = context;
457 self.log(entry);
458 }
459
460 pub fn log_api_error(&self, error: &LarkAPIError) {
462 let event = ErrorEvent::from_error(error.clone());
463 let entry = LogEntry::from_error_event(&event);
464 self.log(entry);
465 }
466
467 pub fn log_error_event(&self, event: &ErrorEvent) {
469 let entry = LogEntry::from_error_event(event);
470 self.log(entry);
471 }
472
473 pub fn warn(&self, message: &str) {
475 let entry = LogEntry::new(LogLevel::Warn, message.to_string());
476 self.log(entry);
477 }
478
479 pub fn info(&self, message: &str) {
481 let entry = LogEntry::new(LogLevel::Info, message.to_string());
482 self.log(entry);
483 }
484
485 pub fn debug(&self, message: &str) {
487 let entry = LogEntry::new(LogLevel::Debug, message.to_string());
488 self.log(entry);
489 }
490
491 fn output_log(&self, formatted: &str) {
493 match &self.config.output {
494 OutputTarget::Stdout => {
495 println!("{formatted}");
496 }
497 OutputTarget::Stderr => {
498 eprintln!("{formatted}");
499 }
500 OutputTarget::File(path) => {
501 self.write_to_file(path, formatted);
502 }
503 OutputTarget::Multiple(targets) => {
504 for target in targets {
505 let temp_config = LoggerConfig {
506 output: target.clone(),
507 ..self.config.clone()
508 };
509 let temp_logger = ErrorLogger::new(temp_config);
510 temp_logger.output_log(formatted);
511 }
512 }
513 }
514 }
515
516 fn write_to_file(&self, path: &str, content: &str) {
518 use std::{fs::OpenOptions, io::Write};
519
520 if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
521 let _ = writeln!(file, "{content}");
522 }
523 }
524}
525
526pub struct LoggerBuilder {
528 config: LoggerConfig,
529}
530
531impl LoggerBuilder {
532 pub fn new() -> Self {
534 Self {
535 config: LoggerConfig::default(),
536 }
537 }
538
539 pub fn min_level(mut self, level: LogLevel) -> Self {
541 self.config.min_level = level;
542 self
543 }
544
545 pub fn simple_format(mut self) -> Self {
547 self.config.formatter = FormatterType::Simple(SimpleFormatter::default());
548 self
549 }
550
551 pub fn json_format(mut self) -> Self {
553 self.config.formatter = FormatterType::Json(JsonFormatter);
554 self
555 }
556
557 pub fn structured_format(mut self) -> Self {
559 self.config.formatter = FormatterType::Structured(StructuredFormatter::default());
560 self
561 }
562
563 pub fn output_to_file(mut self, path: &str) -> Self {
565 self.config.output = OutputTarget::File(path.to_string());
566 self
567 }
568
569 pub fn output_to_stderr(mut self) -> Self {
571 self.config.output = OutputTarget::Stderr;
572 self
573 }
574
575 pub fn include_context(mut self, include: bool) -> Self {
577 self.config.include_context = include;
578 self
579 }
580
581 pub fn build(self) -> ErrorLogger {
583 ErrorLogger::new(self.config)
584 }
585}
586
587impl Default for LoggerBuilder {
588 fn default() -> Self {
589 Self::new()
590 }
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596
597 #[test]
598 fn test_log_level_ordering() {
599 assert!(LogLevel::Debug < LogLevel::Info);
600 assert!(LogLevel::Error > LogLevel::Warn);
601 assert!(LogLevel::Critical > LogLevel::Error);
602 }
603
604 #[test]
605 fn test_log_entry_creation() {
606 let entry = LogEntry::new(LogLevel::Error, "Test message".to_string());
607 assert_eq!(entry.level, LogLevel::Error);
608 assert_eq!(entry.message, "Test message");
609 assert!(entry.context.is_empty());
610 }
611
612 #[test]
613 fn test_simple_formatter() {
614 let formatter = SimpleFormatter::default();
615 let entry = LogEntry::new(LogLevel::Info, "Test message".to_string());
616 let formatted = formatter.format(&entry);
617
618 assert!(formatted.contains("[INFO]"));
619 assert!(formatted.contains("Test message"));
620 }
621
622 #[test]
623 fn test_json_formatter() {
624 let formatter = JsonFormatter;
625 let entry = LogEntry::new(LogLevel::Error, "Error message".to_string());
626 let formatted = formatter.format(&entry);
627
628 assert!(formatted.contains("\"level\":\"ERROR\""));
629 assert!(formatted.contains("\"message\":\"Error message\""));
630 }
631
632 #[test]
633 fn test_structured_formatter() {
634 let formatter = StructuredFormatter::default();
635 let entry = LogEntry::new(LogLevel::Warn, "Warning message".to_string());
636 let formatted = formatter.format(&entry);
637
638 assert!(formatted.contains("level=WARN"));
639 assert!(formatted.contains("msg=Warning message"));
640 }
641
642 #[test]
643 fn test_logger_builder() {
644 let logger = LoggerBuilder::new()
645 .min_level(LogLevel::Debug)
646 .json_format()
647 .output_to_stderr()
648 .build();
649
650 assert_eq!(logger.config.min_level, LogLevel::Debug);
651 matches!(logger.config.formatter, FormatterType::Json(_));
652 matches!(logger.config.output, OutputTarget::Stderr);
653 }
654}