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 use rstest::rstest;
597 use std::collections::HashMap;
598
599 #[test]
600 fn test_log_level_ordering() {
601 assert!(LogLevel::Debug < LogLevel::Info);
602 assert!(LogLevel::Error > LogLevel::Warn);
603 assert!(LogLevel::Critical > LogLevel::Error);
604 }
605
606 #[test]
607 fn test_log_entry_creation() {
608 let entry = LogEntry::new(LogLevel::Error, "Test message".to_string());
609 assert_eq!(entry.level, LogLevel::Error);
610 assert_eq!(entry.message, "Test message");
611 assert!(entry.context.is_empty());
612 }
613
614 #[test]
615 fn test_simple_formatter() {
616 let formatter = SimpleFormatter::default();
617 let entry = LogEntry::new(LogLevel::Info, "Test message".to_string());
618 let formatted = formatter.format(&entry);
619
620 assert!(formatted.contains("[INFO]"));
621 assert!(formatted.contains("Test message"));
622 }
623
624 #[test]
625 fn test_json_formatter() {
626 let formatter = JsonFormatter;
627 let entry = LogEntry::new(LogLevel::Error, "Error message".to_string());
628 let formatted = formatter.format(&entry);
629
630 assert!(formatted.contains("\"level\":\"ERROR\""));
631 assert!(formatted.contains("\"message\":\"Error message\""));
632 }
633
634 #[test]
635 fn test_structured_formatter() {
636 let formatter = StructuredFormatter::default();
637 let entry = LogEntry::new(LogLevel::Warn, "Warning message".to_string());
638 let formatted = formatter.format(&entry);
639
640 assert!(formatted.contains("level=WARN"));
641 assert!(formatted.contains("msg=Warning message"));
642 }
643
644 #[test]
645 fn test_logger_builder() {
646 let logger = LoggerBuilder::new()
647 .min_level(LogLevel::Debug)
648 .json_format()
649 .output_to_stderr()
650 .build();
651
652 assert_eq!(logger.config.min_level, LogLevel::Debug);
653 matches!(logger.config.formatter, FormatterType::Json(_));
654 matches!(logger.config.output, OutputTarget::Stderr);
655 }
656
657 #[rstest]
660 #[case(ErrorSeverity::Info, LogLevel::Info)]
661 #[case(ErrorSeverity::Warning, LogLevel::Warn)]
662 #[case(ErrorSeverity::Error, LogLevel::Error)]
663 #[case(ErrorSeverity::Critical, LogLevel::Critical)]
664 fn test_log_level_from_error_severity(
665 #[case] severity: ErrorSeverity,
666 #[case] expected_level: LogLevel,
667 ) {
668 assert_eq!(LogLevel::from_error_severity(severity), expected_level);
669 }
670
671 #[test]
672 fn test_log_level_color_codes() {
673 assert_eq!(LogLevel::Debug.color_code(), "\x1b[36m");
674 assert_eq!(LogLevel::Info.color_code(), "\x1b[32m");
675 assert_eq!(LogLevel::Warn.color_code(), "\x1b[33m");
676 assert_eq!(LogLevel::Error.color_code(), "\x1b[31m");
677 assert_eq!(LogLevel::Critical.color_code(), "\x1b[35m");
678 assert_eq!(LogLevel::reset_color(), "\x1b[0m");
679 }
680
681 #[test]
682 fn test_log_level_labels() {
683 assert_eq!(LogLevel::Debug.label(), "DEBUG");
684 assert_eq!(LogLevel::Info.label(), "INFO");
685 assert_eq!(LogLevel::Warn.label(), "WARN");
686 assert_eq!(LogLevel::Error.label(), "ERROR");
687 assert_eq!(LogLevel::Critical.label(), "CRITICAL");
688 }
689
690 #[test]
691 fn test_log_level_display() {
692 assert_eq!(format!("{}", LogLevel::Debug), "DEBUG");
693 assert_eq!(format!("{}", LogLevel::Info), "INFO");
694 }
695
696 #[test]
697 fn test_log_entry_with_context() {
698 let entry = LogEntry::new(LogLevel::Error, "Test message".to_string())
699 .with_context("key1", "value1")
700 .with_context("key2", "value2");
701
702 assert_eq!(entry.context.len(), 2);
703 assert_eq!(entry.context.get("key1"), Some(&"value1".to_string()));
704 assert_eq!(entry.context.get("key2"), Some(&"value2".to_string()));
705 }
706
707 #[test]
708 fn test_log_entry_with_caller() {
709 let entry = LogEntry::new(LogLevel::Info, "Test message".to_string())
710 .with_caller("test_function".to_string());
711
712 assert_eq!(entry.caller, Some("test_function".to_string()));
713 }
714
715 #[test]
716 fn test_log_entry_with_error() {
717 let error = LarkAPIError::MissingAccessToken;
718 let entry =
719 LogEntry::new(LogLevel::Error, "Test message".to_string()).with_error(error.clone());
720
721 assert!(entry.error.is_some());
722 match entry.error.unwrap() {
723 LarkAPIError::MissingAccessToken => (),
724 _ => panic!("Wrong error type"),
725 }
726 }
727
728 #[test]
729 fn test_log_entry_from_error_event() {
730 let error = LarkAPIError::api_error(403, "Forbidden", None);
731 let event = ErrorEvent::from_error(error.clone());
732 let entry = LogEntry::from_error_event(&event);
733
734 assert_eq!(entry.level, LogLevel::Error);
735 assert!(entry.message.contains("API调用错误"));
736 assert!(entry.error.is_some());
737 assert!(entry.category.is_some());
738 }
739
740 #[test]
741 fn test_simple_formatter_with_options() {
742 let formatter = SimpleFormatter {
744 include_timestamp: false,
745 ..Default::default()
746 };
747 let entry = LogEntry::new(LogLevel::Info, "Test".to_string());
748 let formatted = formatter.format(&entry);
749 assert!(formatted.contains("[INFO]"));
751 assert!(!formatted.contains(".")); let formatter = SimpleFormatter {
755 use_colors: false,
756 ..Default::default()
757 };
758 let formatted = formatter.format(&entry);
759 assert!(!formatted.contains("\x1b["));
760 }
761
762 #[test]
763 fn test_simple_formatter_with_complete_entry() {
764 let formatter = SimpleFormatter::default();
765 let error = LarkAPIError::api_error(400, "Bad Request", None);
766 let mut entry = LogEntry::new(LogLevel::Error, "API call failed".to_string())
767 .with_error(error)
768 .with_context("endpoint", "/api/test")
769 .with_caller("test_function".to_string());
770 entry.error_code = Some(LarkErrorCode::BadRequest);
771
772 let formatted = formatter.format(&entry);
773
774 assert!(formatted.contains("[ERROR]"));
775 assert!(formatted.contains("API call failed"));
776 assert!(formatted.contains("错误:"));
777 assert!(formatted.contains("错误码:"));
778 assert!(formatted.contains("上下文:"));
779 assert!(formatted.contains("调用者:"));
780 }
781
782 #[test]
783 fn test_json_formatter_complete() {
784 let formatter = JsonFormatter;
785 let error = LarkAPIError::IllegalParamError("invalid param".to_string());
786 let mut entry = LogEntry::new(LogLevel::Error, "Validation failed".to_string())
787 .with_error(error)
788 .with_context("field", "username")
789 .with_caller("validate_user".to_string());
790 entry.category = Some(ErrorHandlingCategory::ClientError);
791 entry.error_code = Some(LarkErrorCode::BadRequest);
792
793 let formatted = formatter.format(&entry);
794
795 assert!(formatted.contains("\"level\":\"ERROR\""));
796 assert!(formatted.contains("\"message\":\"Validation failed\""));
797 assert!(formatted.contains("\"error\":"));
798 assert!(formatted.contains("\"category\":\"ClientError\""));
799 assert!(formatted.contains("\"error_code\":400"));
800 assert!(formatted.contains("\"context\":{"));
801 assert!(formatted.contains("\"caller\":\"validate_user\""));
802 }
803
804 #[test]
805 fn test_structured_formatter_custom() {
806 let formatter = StructuredFormatter {
807 separator: " || ".to_string(),
808 kv_separator: ":".to_string(),
809 };
810
811 let entry = LogEntry::new(LogLevel::Warn, "Warning message".to_string())
812 .with_context("module", "auth");
813
814 let formatted = formatter.format(&entry);
815
816 assert!(formatted.contains("level:WARN"));
817 assert!(formatted.contains("msg:Warning message"));
818 assert!(formatted.contains("module:auth"));
819 assert!(formatted.contains(" || "));
820 }
821
822 #[test]
823 fn test_logger_config_default() {
824 let config = LoggerConfig::default();
825
826 assert_eq!(config.min_level, LogLevel::Info);
827 assert!(matches!(config.formatter, FormatterType::Simple(_)));
828 assert!(matches!(config.output, OutputTarget::Stdout));
829 assert!(config.include_context);
830 assert!(!config.include_caller);
831 }
832
833 #[test]
834 fn test_error_logger_default() {
835 let logger = ErrorLogger::default();
836 assert_eq!(logger.config.min_level, LogLevel::Info);
837 }
838
839 #[test]
840 fn test_error_logger_new() {
841 let config = LoggerConfig {
842 min_level: LogLevel::Debug,
843 formatter: FormatterType::Json(JsonFormatter),
844 output: OutputTarget::Stderr,
845 include_context: false,
846 include_caller: true,
847 };
848 let logger = ErrorLogger::new(config);
849 assert_eq!(logger.config.min_level, LogLevel::Debug);
850 }
851
852 #[test]
853 fn test_error_logger_log_level_filtering() {
854 let config = LoggerConfig {
855 min_level: LogLevel::Warn,
856 ..LoggerConfig::default()
857 };
858 let logger = ErrorLogger::new(config);
859
860 assert_eq!(logger.config.min_level, LogLevel::Warn);
863
864 assert!(LogLevel::Debug < logger.config.min_level);
866 assert!(LogLevel::Info < logger.config.min_level);
867
868 assert!(LogLevel::Warn >= logger.config.min_level);
870 assert!(LogLevel::Error >= logger.config.min_level);
871 assert!(LogLevel::Critical >= logger.config.min_level);
872 }
873
874 #[test]
875 fn test_error_logger_convenience_methods() {
876 let logger = ErrorLogger::default();
877
878 logger.error("Test error");
880 logger.warn("Test warning");
881 logger.info("Test info");
882 logger.debug("Test debug");
883 }
884
885 #[test]
886 fn test_error_logger_error_with_context() {
887 let logger = ErrorLogger::default();
888 let mut context = HashMap::new();
889 context.insert("user_id".to_string(), "123".to_string());
890 context.insert("action".to_string(), "login".to_string());
891
892 logger.error_with_context("Login failed", context);
894 }
895
896 #[test]
897 fn test_error_logger_log_api_error() {
898 let logger = ErrorLogger::default();
899 let error = LarkAPIError::api_error(429, "Too Many Requests", None);
900
901 logger.log_api_error(&error);
903 }
904
905 #[test]
906 fn test_error_logger_log_error_event() {
907 let logger = ErrorLogger::default();
908 let error = LarkAPIError::RequestError("Network timeout".to_string());
909 let event = ErrorEvent::from_error(error);
910
911 logger.log_error_event(&event);
913 }
914
915 #[test]
916 fn test_logger_builder_complete() {
917 let logger = LoggerBuilder::new()
918 .min_level(LogLevel::Critical)
919 .simple_format()
920 .output_to_file("/tmp/test.log")
921 .include_context(false)
922 .build();
923
924 assert_eq!(logger.config.min_level, LogLevel::Critical);
925 assert!(matches!(logger.config.formatter, FormatterType::Simple(_)));
926 assert!(matches!(logger.config.output, OutputTarget::File(_)));
927 assert!(!logger.config.include_context);
928 }
929
930 #[test]
931 fn test_logger_builder_structured_format() {
932 let logger = LoggerBuilder::new().structured_format().build();
933
934 assert!(matches!(
935 logger.config.formatter,
936 FormatterType::Structured(_)
937 ));
938 }
939
940 #[test]
941 fn test_logger_builder_default() {
942 let builder = LoggerBuilder::default();
943 let logger = builder.build();
944 assert_eq!(logger.config.min_level, LogLevel::Info);
945 }
946
947 #[test]
948 fn test_output_target_multiple() {
949 let targets = vec![
950 OutputTarget::Stdout,
951 OutputTarget::Stderr,
952 OutputTarget::File("/tmp/test.log".to_string()),
953 ];
954 let config = LoggerConfig {
955 output: OutputTarget::Multiple(targets),
956 ..LoggerConfig::default()
957 };
958 let logger = ErrorLogger::new(config);
959
960 logger.info("Test message for multiple targets");
962 }
963
964 #[test]
965 fn test_formatter_type_variants() {
966 let simple = FormatterType::Simple(SimpleFormatter::default());
967 let json = FormatterType::Json(JsonFormatter);
968 let structured = FormatterType::Structured(StructuredFormatter::default());
969
970 match simple {
972 FormatterType::Simple(_) => (),
973 _ => panic!("Wrong formatter type"),
974 }
975
976 match json {
977 FormatterType::Json(_) => (),
978 _ => panic!("Wrong formatter type"),
979 }
980
981 match structured {
982 FormatterType::Structured(_) => (),
983 _ => panic!("Wrong formatter type"),
984 }
985 }
986
987 #[test]
988 fn test_log_entry_timestamp() {
989 let entry = LogEntry::new(LogLevel::Info, "Test".to_string());
990 let now = SystemTime::now();
991
992 let diff = now.duration_since(entry.timestamp).unwrap_or_default();
994 assert!(diff.as_secs() < 1);
995 }
996}