1#![forbid(unsafe_code)]
2#![allow(clippy::nursery)]
4#![allow(clippy::pedantic)]
5
6use backtrace::Backtrace;
39use lipgloss::{Color, Style};
40use std::collections::HashMap;
41use std::fmt;
42use std::io::{self, Write};
43use std::sync::{Arc, RwLock};
44use std::time::{SystemTime, UNIX_EPOCH};
45use thiserror::Error;
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49#[repr(i32)]
50pub enum Level {
51 Debug = -4,
53 Info = 0,
55 Warn = 4,
57 Error = 8,
59 Fatal = 12,
61}
62
63impl Level {
64 #[must_use]
66 pub fn as_str(&self) -> &'static str {
67 match self {
68 Self::Debug => "debug",
69 Self::Info => "info",
70 Self::Warn => "warn",
71 Self::Error => "error",
72 Self::Fatal => "fatal",
73 }
74 }
75
76 #[must_use]
78 pub fn as_upper_str(&self) -> &'static str {
79 match self {
80 Self::Debug => "DEBU",
81 Self::Info => "INFO",
82 Self::Warn => "WARN",
83 Self::Error => "ERRO",
84 Self::Fatal => "FATA",
85 }
86 }
87}
88
89impl PartialOrd for Level {
90 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
91 Some(self.cmp(other))
92 }
93}
94
95impl Ord for Level {
96 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
97 (*self as i32).cmp(&(*other as i32))
98 }
99}
100
101impl fmt::Display for Level {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 write!(f, "{}", self.as_str())
104 }
105}
106
107impl std::str::FromStr for Level {
108 type Err = ParseLevelError;
109
110 fn from_str(s: &str) -> Result<Self, Self::Err> {
111 match s.to_lowercase().as_str() {
112 "debug" => Ok(Self::Debug),
113 "info" => Ok(Self::Info),
114 "warn" => Ok(Self::Warn),
115 "error" => Ok(Self::Error),
116 "fatal" => Ok(Self::Fatal),
117 _ => Err(ParseLevelError(s.to_string())),
118 }
119 }
120}
121
122#[derive(Error, Debug, Clone)]
147#[error("invalid level: {0:?}")]
148pub struct ParseLevelError(String);
149
150pub type ParseResult<T> = std::result::Result<T, ParseLevelError>;
152
153#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
155pub enum Formatter {
156 #[default]
158 Text,
159 Json,
161 Logfmt,
163}
164
165pub mod keys {
167 pub const TIMESTAMP: &str = "time";
169 pub const MESSAGE: &str = "msg";
171 pub const LEVEL: &str = "level";
173 pub const CALLER: &str = "caller";
175 pub const PREFIX: &str = "prefix";
177}
178
179pub const DEFAULT_TIME_FORMAT: &str = "%Y/%m/%d %H:%M:%S";
181
182#[derive(Debug, Clone)]
184pub struct Styles {
185 pub timestamp: Style,
187 pub caller: Style,
189 pub prefix: Style,
191 pub message: Style,
193 pub key: Style,
195 pub value: Style,
197 pub separator: Style,
199 pub levels: HashMap<Level, Style>,
201 pub keys: HashMap<String, Style>,
203 pub values: HashMap<String, Style>,
205}
206
207impl Default for Styles {
208 fn default() -> Self {
209 Self::new()
210 }
211}
212
213impl Styles {
214 #[must_use]
216 pub fn new() -> Self {
217 let mut levels = HashMap::new();
218 levels.insert(
219 Level::Debug,
220 Style::new().bold().foreground_color(Color::from("63")),
221 );
222 levels.insert(
223 Level::Info,
224 Style::new().bold().foreground_color(Color::from("86")),
225 );
226 levels.insert(
227 Level::Warn,
228 Style::new().bold().foreground_color(Color::from("192")),
229 );
230 levels.insert(
231 Level::Error,
232 Style::new().bold().foreground_color(Color::from("204")),
233 );
234 levels.insert(
235 Level::Fatal,
236 Style::new().bold().foreground_color(Color::from("134")),
237 );
238
239 Self {
240 timestamp: Style::new(),
241 caller: Style::new().faint(),
242 prefix: Style::new().bold().faint(),
243 message: Style::new(),
244 key: Style::new().faint(),
245 value: Style::new(),
246 separator: Style::new().faint(),
247 levels,
248 keys: HashMap::new(),
249 values: HashMap::new(),
250 }
251 }
252}
253
254pub type TimeFunction = fn(std::time::SystemTime) -> std::time::SystemTime;
256
257#[must_use]
259pub fn now_utc(t: SystemTime) -> SystemTime {
260 t }
262
263pub type CallerFormatter = fn(&str, u32, &str) -> String;
265
266pub type ErrorHandler = Arc<dyn Fn(io::Error) + Send + Sync>;
283
284#[must_use]
286pub fn short_caller_formatter(file: &str, line: u32, _fn_name: &str) -> String {
287 let trimmed = trim_caller_path(file, 2);
288 format!("{trimmed}:{line}")
289}
290
291#[must_use]
293pub fn long_caller_formatter(file: &str, line: u32, _fn_name: &str) -> String {
294 format!("{file}:{line}")
295}
296
297fn trim_caller_path(path: &str, n: usize) -> &str {
299 if n == 0 {
300 return path;
301 }
302
303 let mut last_idx = path.len();
304 for _ in 0..n {
305 if let Some(idx) = path[..last_idx].rfind('/') {
306 last_idx = idx;
307 } else {
308 return path;
309 }
310 }
311
312 &path[last_idx + 1..]
313}
314
315#[derive(Debug, Clone)]
317pub struct CallerInfo {
318 pub file: String,
320 pub line: u32,
322 pub function: String,
324}
325
326impl CallerInfo {
327 #[must_use]
340 pub fn capture(skip: usize) -> Option<Self> {
341 let bt = Backtrace::new();
342 let frames: Vec<_> = bt.frames().iter().collect();
343
344 let skip_total = skip + 4;
347
348 for frame in frames.iter().skip(skip_total) {
349 for symbol in frame.symbols() {
350 let fn_name = symbol
352 .name()
353 .map(|n| n.to_string())
354 .unwrap_or_else(|| "<unknown>".to_string());
355
356 if fn_name.contains("charmed_log::") || fn_name.contains("backtrace::") {
358 continue;
359 }
360
361 let file = symbol
362 .filename()
363 .and_then(|p| p.to_str())
364 .unwrap_or("<unknown>")
365 .to_string();
366
367 let line = symbol.lineno().unwrap_or(0);
368
369 return Some(Self {
370 file,
371 line,
372 function: fn_name,
373 });
374 }
375 }
376
377 None
378 }
379}
380
381#[derive(Clone)]
383pub struct Options {
384 pub time_function: TimeFunction,
386 pub time_format: String,
388 pub level: Level,
390 pub prefix: String,
392 pub report_timestamp: bool,
394 pub report_caller: bool,
402 pub caller_formatter: CallerFormatter,
404 pub caller_offset: usize,
406 pub fields: Vec<(String, String)>,
408 pub formatter: Formatter,
410}
411
412impl Default for Options {
413 fn default() -> Self {
414 Self {
415 time_function: now_utc,
416 time_format: DEFAULT_TIME_FORMAT.to_string(),
417 level: Level::Info,
418 prefix: String::new(),
419 report_timestamp: false,
420 report_caller: false,
421 caller_formatter: short_caller_formatter,
422 caller_offset: 0,
423 fields: Vec::new(),
424 formatter: Formatter::Text,
425 }
426 }
427}
428
429struct LoggerInner {
431 writer: Box<dyn Write + Send + Sync>,
432 level: Level,
433 prefix: String,
434 time_function: TimeFunction,
435 time_format: String,
436 caller_offset: usize,
437 caller_formatter: CallerFormatter,
438 formatter: Formatter,
439 report_timestamp: bool,
440 report_caller: bool,
441 fields: Vec<(String, String)>,
442 styles: Styles,
443 error_handler: Option<ErrorHandler>,
445 has_warned_io_failure: bool,
447 warned_caller_overhead: bool,
449 suppress_caller_warning: bool,
451}
452
453pub struct Logger {
455 inner: Arc<RwLock<LoggerInner>>,
456}
457
458impl Default for Logger {
459 fn default() -> Self {
460 Self::new()
461 }
462}
463
464impl Clone for Logger {
465 fn clone(&self) -> Self {
466 Self {
467 inner: Arc::clone(&self.inner),
468 }
469 }
470}
471
472impl fmt::Debug for Logger {
473 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
474 let inner = self.inner.read().unwrap_or_else(|e| e.into_inner());
475 f.debug_struct("Logger")
476 .field("level", &inner.level)
477 .field("prefix", &inner.prefix)
478 .field("formatter", &inner.formatter)
479 .field("report_timestamp", &inner.report_timestamp)
480 .field("report_caller", &inner.report_caller)
481 .finish()
482 }
483}
484
485impl Logger {
486 #[must_use]
488 pub fn new() -> Self {
489 Self::with_options(Options::default())
490 }
491
492 #[must_use]
494 pub fn with_options(opts: Options) -> Self {
495 Self {
496 inner: Arc::new(RwLock::new(LoggerInner {
497 writer: Box::new(io::stderr()),
498 level: opts.level,
499 prefix: opts.prefix,
500 time_function: opts.time_function,
501 time_format: opts.time_format,
502 caller_offset: opts.caller_offset,
503 caller_formatter: opts.caller_formatter,
504 formatter: opts.formatter,
505 report_timestamp: opts.report_timestamp,
506 report_caller: opts.report_caller,
507 fields: opts.fields,
508 styles: Styles::new(),
509 error_handler: None,
510 has_warned_io_failure: false,
511 warned_caller_overhead: false,
512 suppress_caller_warning: false,
513 })),
514 }
515 }
516
517 pub fn set_level(&self, level: Level) {
519 let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
520 inner.level = level;
521 }
522
523 #[must_use]
525 pub fn level(&self) -> Level {
526 let inner = self.inner.read().unwrap_or_else(|e| e.into_inner());
527 inner.level
528 }
529
530 pub fn set_prefix(&self, prefix: impl Into<String>) {
532 let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
533 inner.prefix = prefix.into();
534 }
535
536 #[must_use]
538 pub fn prefix(&self) -> String {
539 let inner = self.inner.read().unwrap_or_else(|e| e.into_inner());
540 inner.prefix.clone()
541 }
542
543 pub fn set_report_timestamp(&self, report: bool) {
545 let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
546 inner.report_timestamp = report;
547 }
548
549 pub fn set_report_caller(&self, report: bool) {
569 let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
570 inner.report_caller = report;
571 }
572
573 pub fn suppress_caller_warning(&self) {
594 let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
595 inner.suppress_caller_warning = true;
596 }
597
598 pub fn set_time_format(&self, format: impl Into<String>) {
600 let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
601 inner.time_format = format.into();
602 }
603
604 pub fn set_formatter(&self, formatter: Formatter) {
606 let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
607 inner.formatter = formatter;
608 }
609
610 pub fn set_styles(&self, styles: Styles) {
612 let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
613 inner.styles = styles;
614 }
615
616 #[must_use]
621 pub fn with_fields(&self, fields: &[(&str, &str)]) -> Self {
622 let inner = self.inner.read().unwrap_or_else(|e| e.into_inner());
623 let mut new_fields = inner.fields.clone();
624 new_fields.extend(fields.iter().map(|(k, v)| (k.to_string(), v.to_string())));
625
626 Self {
627 inner: Arc::new(RwLock::new(LoggerInner {
628 writer: Box::new(io::stderr()),
629 level: inner.level,
630 prefix: inner.prefix.clone(),
631 time_function: inner.time_function,
632 time_format: inner.time_format.clone(),
633 caller_offset: inner.caller_offset,
634 caller_formatter: inner.caller_formatter,
635 formatter: inner.formatter,
636 report_timestamp: inner.report_timestamp,
637 report_caller: inner.report_caller,
638 fields: new_fields,
639 styles: inner.styles.clone(),
640 error_handler: inner.error_handler.clone(),
641 has_warned_io_failure: false, warned_caller_overhead: false, suppress_caller_warning: inner.suppress_caller_warning, })),
645 }
646 }
647
648 #[must_use]
663 pub fn with(&self, fields: &[(&str, &str)]) -> Self {
664 self.with_fields(fields)
665 }
666
667 #[must_use]
669 pub fn with_prefix(&self, prefix: impl Into<String>) -> Self {
670 let new_logger = self.with_fields(&[]);
671 new_logger.set_prefix(prefix);
672 new_logger
673 }
674
675 #[must_use]
703 pub fn with_error_handler<F>(self, handler: F) -> Self
704 where
705 F: Fn(io::Error) + Send + Sync + 'static,
706 {
707 let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
708 inner.error_handler = Some(Arc::new(handler));
709 drop(inner);
710 self
711 }
712
713 pub fn log(&self, level: Level, msg: &str, keyvals: &[(&str, &str)]) {
719 let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
723
724 if inner.report_caller && !inner.warned_caller_overhead && !inner.suppress_caller_warning {
726 inner.warned_caller_overhead = true;
727 let _ = io::stderr().write_all(
729 b"[charmed_log] PERF WARNING: caller reporting enabled - expect 100-1000x slowdown\n",
730 );
731 }
732
733 if level < inner.level {
735 return;
736 }
737
738 let mut output = String::new();
740 match inner.formatter {
741 Formatter::Text => Self::format_text_inner(&inner, level, msg, keyvals, &mut output),
742 Formatter::Json => Self::format_json_inner(&inner, level, msg, keyvals, &mut output),
743 Formatter::Logfmt => {
744 Self::format_logfmt_inner(&inner, level, msg, keyvals, &mut output);
745 }
746 }
747
748 if let Err(e) = inner.writer.write_all(output.as_bytes()) {
750 if let Some(ref handler) = inner.error_handler {
752 let handler = Arc::clone(handler);
754 drop(inner);
755 handler(e);
756 } else if !inner.has_warned_io_failure {
757 inner.has_warned_io_failure = true;
759 drop(inner);
760 let _ =
762 io::stderr().write_all(format!("charmed_log: write failed: {e}\n").as_bytes());
763 }
764 }
765 }
766
767 fn format_text_inner(
773 inner: &LoggerInner,
774 level: Level,
775 msg: &str,
776 keyvals: &[(&str, &str)],
777 output: &mut String,
778 ) {
779 let styles = &inner.styles;
780 let mut first = true;
781
782 if inner.report_timestamp {
784 let ts = (inner.time_function)(SystemTime::now());
785 if let Ok(duration) = ts.duration_since(UNIX_EPOCH) {
786 let secs = duration.as_secs();
787 let ts_str = format_timestamp(secs, &inner.time_format);
788 let styled = styles.timestamp.render(&ts_str);
789 if !first {
790 output.push(' ');
791 }
792 output.push_str(&styled);
793 first = false;
794 }
795 }
796
797 if let Some(level_style) = styles.levels.get(&level) {
799 let lvl = level_style.render(level.as_upper_str());
800 if !first {
801 output.push(' ');
802 }
803 output.push_str(&lvl);
804 first = false;
805 }
806
807 if inner.report_caller {
809 let caller_str = if let Some(info) = CallerInfo::capture(inner.caller_offset) {
810 (inner.caller_formatter)(&info.file, info.line, &info.function)
811 } else {
812 (inner.caller_formatter)("unknown", 0, "unknown")
813 };
814 let styled = styles.caller.render(&format!("<{caller_str}>"));
815 if !first {
816 output.push(' ');
817 }
818 output.push_str(&styled);
819 first = false;
820 }
821
822 if !inner.prefix.is_empty() {
824 let styled = styles.prefix.render(&format!("{}:", inner.prefix));
825 if !first {
826 output.push(' ');
827 }
828 output.push_str(&styled);
829 first = false;
830 }
831
832 if !msg.is_empty() {
834 let styled = styles.message.render(msg);
835 if !first {
836 output.push(' ');
837 }
838 output.push_str(&styled);
839 first = false;
840 }
841
842 for (key, value) in &inner.fields {
844 Self::format_text_keyval_inner(styles, key, value, &mut first, output);
845 }
846
847 for (key, value) in keyvals {
849 Self::format_text_keyval_inner(styles, key, value, &mut first, output);
850 }
851
852 output.push('\n');
853 }
854
855 fn format_text_keyval_inner(
857 styles: &Styles,
858 key: &str,
859 value: &str,
860 first: &mut bool,
861 output: &mut String,
862 ) {
863 let sep = styles.separator.render("=");
864 let key_styled = if let Some(style) = styles.keys.get(key) {
865 style.render(key)
866 } else {
867 styles.key.render(key)
868 };
869 let value_styled = if let Some(style) = styles.values.get(key) {
870 style.render(value)
871 } else {
872 styles.value.render(value)
873 };
874
875 if !*first {
876 output.push(' ');
877 }
878 output.push_str(&key_styled);
879 output.push_str(&sep);
880 output.push_str(&value_styled);
881 *first = false;
882 }
883
884 fn format_json_inner(
886 inner: &LoggerInner,
887 level: Level,
888 msg: &str,
889 keyvals: &[(&str, &str)],
890 output: &mut String,
891 ) {
892 output.push('{');
893 let mut first = true;
894
895 if inner.report_timestamp {
897 let ts = (inner.time_function)(SystemTime::now());
898 if let Ok(duration) = ts.duration_since(UNIX_EPOCH) {
899 let secs = duration.as_secs();
900 let ts_str = format_timestamp(secs, &inner.time_format);
901 write_json_field(output, keys::TIMESTAMP, &ts_str, &mut first);
902 }
903 }
904
905 write_json_field(output, keys::LEVEL, level.as_str(), &mut first);
907
908 if !inner.prefix.is_empty() {
910 write_json_field(output, keys::PREFIX, &inner.prefix, &mut first);
911 }
912
913 if !msg.is_empty() {
915 write_json_field(output, keys::MESSAGE, msg, &mut first);
916 }
917
918 for (key, value) in &inner.fields {
920 write_json_field(output, key, value, &mut first);
921 }
922
923 for (key, value) in keyvals {
925 write_json_field(output, key, value, &mut first);
926 }
927
928 output.push_str("}\n");
929 }
930
931 fn format_logfmt_inner(
933 inner: &LoggerInner,
934 level: Level,
935 msg: &str,
936 keyvals: &[(&str, &str)],
937 output: &mut String,
938 ) {
939 let mut first = true;
940
941 if inner.report_timestamp {
943 let ts = (inner.time_function)(SystemTime::now());
944 if let Ok(duration) = ts.duration_since(UNIX_EPOCH) {
945 let secs = duration.as_secs();
946 let ts_str = format_timestamp(secs, &inner.time_format);
947 write_logfmt_field(output, keys::TIMESTAMP, &ts_str, &mut first);
948 }
949 }
950
951 write_logfmt_field(output, keys::LEVEL, level.as_str(), &mut first);
953
954 if !inner.prefix.is_empty() {
956 write_logfmt_field(output, keys::PREFIX, &inner.prefix, &mut first);
957 }
958
959 if !msg.is_empty() {
961 write_logfmt_field(output, keys::MESSAGE, msg, &mut first);
962 }
963
964 for (key, value) in &inner.fields {
966 write_logfmt_field(output, key, value, &mut first);
967 }
968
969 for (key, value) in keyvals {
971 write_logfmt_field(output, key, value, &mut first);
972 }
973
974 output.push('\n');
975 }
976
977 #[expect(dead_code, reason = "Kept for API compatibility")]
982 fn format_text(
983 &self,
984 inner: &LoggerInner,
985 level: Level,
986 msg: &str,
987 keyvals: &[(&str, &str)],
988 output: &mut String,
989 ) {
990 Self::format_text_inner(inner, level, msg, keyvals, output);
991 }
992
993 #[expect(dead_code, reason = "Kept for API compatibility")]
994 fn format_text_keyval(
995 &self,
996 styles: &Styles,
997 key: &str,
998 value: &str,
999 first: &mut bool,
1000 output: &mut String,
1001 ) {
1002 Self::format_text_keyval_inner(styles, key, value, first, output);
1003 }
1004
1005 #[expect(dead_code, reason = "Kept for API compatibility")]
1006 fn format_json(
1007 &self,
1008 inner: &LoggerInner,
1009 level: Level,
1010 msg: &str,
1011 keyvals: &[(&str, &str)],
1012 output: &mut String,
1013 ) {
1014 Self::format_json_inner(inner, level, msg, keyvals, output);
1015 }
1016
1017 #[expect(dead_code, reason = "Kept for API compatibility")]
1018 fn format_logfmt(
1019 &self,
1020 inner: &LoggerInner,
1021 level: Level,
1022 msg: &str,
1023 keyvals: &[(&str, &str)],
1024 output: &mut String,
1025 ) {
1026 Self::format_logfmt_inner(inner, level, msg, keyvals, output);
1027 }
1028
1029 pub fn debug(&self, msg: &str, keyvals: &[(&str, &str)]) {
1031 self.log(Level::Debug, msg, keyvals);
1032 }
1033
1034 pub fn info(&self, msg: &str, keyvals: &[(&str, &str)]) {
1036 self.log(Level::Info, msg, keyvals);
1037 }
1038
1039 pub fn warn(&self, msg: &str, keyvals: &[(&str, &str)]) {
1041 self.log(Level::Warn, msg, keyvals);
1042 }
1043
1044 pub fn error(&self, msg: &str, keyvals: &[(&str, &str)]) {
1046 self.log(Level::Error, msg, keyvals);
1047 }
1048
1049 pub fn fatal(&self, msg: &str, keyvals: &[(&str, &str)]) {
1051 self.log(Level::Fatal, msg, keyvals);
1052 }
1053
1054 pub fn logf(&self, level: Level, format: &str, args: &[&dyn fmt::Display]) {
1056 let msg = format_args_simple(format, args);
1057 self.log(level, &msg, &[]);
1058 }
1059
1060 pub fn debugf(&self, format: &str, args: &[&dyn fmt::Display]) {
1062 self.logf(Level::Debug, format, args);
1063 }
1064
1065 pub fn infof(&self, format: &str, args: &[&dyn fmt::Display]) {
1067 self.logf(Level::Info, format, args);
1068 }
1069
1070 pub fn warnf(&self, format: &str, args: &[&dyn fmt::Display]) {
1072 self.logf(Level::Warn, format, args);
1073 }
1074
1075 pub fn errorf(&self, format: &str, args: &[&dyn fmt::Display]) {
1077 self.logf(Level::Error, format, args);
1078 }
1079
1080 pub fn fatalf(&self, format: &str, args: &[&dyn fmt::Display]) {
1082 self.logf(Level::Fatal, format, args);
1083 }
1084}
1085
1086fn format_args_simple(format: &str, args: &[&dyn fmt::Display]) -> String {
1091 use fmt::Write;
1092
1093 let mut result = String::with_capacity(format.len());
1094 let mut arg_idx = 0;
1095 let mut rest = format;
1096
1097 while let Some(pos) = rest.find("{}") {
1098 result.push_str(&rest[..pos]);
1099 if arg_idx < args.len() {
1100 let _ = write!(result, "{}", args[arg_idx]);
1101 arg_idx += 1;
1102 } else {
1103 result.push_str("{}");
1104 }
1105 rest = &rest[pos + 2..];
1106 }
1107 result.push_str(rest);
1108 result
1109}
1110
1111fn format_timestamp(secs: u64, format: &str) -> String {
1113 use chrono::{DateTime, Utc};
1114
1115 if let Some(datetime) = DateTime::from_timestamp(secs as i64, 0) {
1116 datetime.with_timezone(&Utc).format(format).to_string()
1117 } else {
1118 "INVALID TIMESTAMP".to_string()
1119 }
1120}
1121
1122fn write_json_field(output: &mut String, key: &str, value: &str, first: &mut bool) {
1124 if !*first {
1125 output.push(',');
1126 }
1127 output.push('"');
1128 output.push_str(&escape_json(key));
1129 output.push_str("\":\"");
1130 output.push_str(&escape_json(value));
1131 output.push('"');
1132 *first = false;
1133}
1134
1135fn escape_json(s: &str) -> String {
1137 let mut result = String::with_capacity(s.len());
1138 for c in s.chars() {
1139 match c {
1140 '"' => result.push_str("\\\""),
1141 '\\' => result.push_str("\\\\"),
1142 '\n' => result.push_str("\\n"),
1143 '\r' => result.push_str("\\r"),
1144 '\t' => result.push_str("\\t"),
1145 c if c.is_control() => {
1146 let cp = c as u32;
1147 if cp <= 0xFFFF {
1148 result.push_str(&format!("\\u{cp:04x}"));
1149 } else {
1150 let s = cp - 0x10000;
1152 let hi = 0xD800 + (s >> 10);
1153 let lo = 0xDC00 + (s & 0x3FF);
1154 result.push_str(&format!("\\u{hi:04x}\\u{lo:04x}"));
1155 }
1156 }
1157 c => result.push(c),
1158 }
1159 }
1160 result
1161}
1162
1163fn write_logfmt_field(output: &mut String, key: &str, value: &str, first: &mut bool) {
1165 if !*first {
1166 output.push(' ');
1167 }
1168 output.push_str(key);
1169 output.push('=');
1170 if needs_quoting(value) {
1171 output.push('"');
1172 output.push_str(&escape_logfmt(value));
1173 output.push('"');
1174 } else {
1175 output.push_str(value);
1176 }
1177 *first = false;
1178}
1179
1180fn needs_quoting(s: &str) -> bool {
1182 s.is_empty()
1183 || s.chars()
1184 .any(|c| c.is_whitespace() || c == '"' || c == '=' || c.is_control())
1185}
1186
1187fn escape_logfmt(s: &str) -> String {
1189 let mut result = String::with_capacity(s.len());
1190 for c in s.chars() {
1191 match c {
1192 '"' => result.push_str("\\\""),
1193 '\\' => result.push_str("\\\\"),
1194 '\n' => result.push_str("\\n"),
1195 '\r' => result.push_str("\\r"),
1196 '\t' => result.push_str("\\t"),
1197 c => result.push(c),
1198 }
1199 }
1200 result
1201}
1202
1203pub mod prelude {
1205 pub use crate::{
1206 CallerInfo, DEFAULT_TIME_FORMAT, ErrorHandler, Formatter, Level, Logger, Options,
1207 ParseLevelError, ParseResult, Styles, keys, long_caller_formatter, now_utc,
1208 short_caller_formatter,
1209 };
1210}
1211
1212#[cfg(test)]
1213mod tests {
1214 use super::*;
1215
1216 #[test]
1217 fn test_level_ordering() {
1218 assert!(Level::Debug < Level::Info);
1219 assert!(Level::Info < Level::Warn);
1220 assert!(Level::Warn < Level::Error);
1221 assert!(Level::Error < Level::Fatal);
1222 }
1223
1224 #[test]
1225 fn test_level_display() {
1226 assert_eq!(Level::Debug.to_string(), "debug");
1227 assert_eq!(Level::Info.to_string(), "info");
1228 assert_eq!(Level::Warn.to_string(), "warn");
1229 assert_eq!(Level::Error.to_string(), "error");
1230 assert_eq!(Level::Fatal.to_string(), "fatal");
1231 }
1232
1233 #[test]
1234 fn test_level_parse() {
1235 assert_eq!("debug".parse::<Level>().unwrap(), Level::Debug);
1236 assert_eq!("INFO".parse::<Level>().unwrap(), Level::Info);
1237 assert_eq!("WARN".parse::<Level>().unwrap(), Level::Warn);
1238 assert!("warning".parse::<Level>().is_err());
1240 assert!("invalid".parse::<Level>().is_err());
1241 }
1242
1243 #[test]
1244 fn test_logger_new() {
1245 let logger = Logger::new();
1246 assert_eq!(logger.level(), Level::Info);
1247 assert!(logger.prefix().is_empty());
1248 }
1249
1250 #[test]
1251 fn test_logger_set_level() {
1252 let logger = Logger::new();
1253 logger.set_level(Level::Debug);
1254 assert_eq!(logger.level(), Level::Debug);
1255 }
1256
1257 #[test]
1258 fn test_logger_set_prefix() {
1259 let logger = Logger::new();
1260 logger.set_prefix("myapp");
1261 assert_eq!(logger.prefix(), "myapp");
1262 }
1263
1264 #[test]
1265 fn test_logger_with_prefix() {
1266 let logger = Logger::new();
1267 let prefixed = logger.with_prefix("myapp");
1268 assert_eq!(prefixed.prefix(), "myapp");
1269 assert!(logger.prefix().is_empty()); }
1271
1272 #[test]
1273 fn test_logger_with_fields() {
1274 let logger = Logger::new();
1275 let with_fields = logger.with_fields(&[("app", "test"), ("version", "1.0")]);
1276 drop(with_fields);
1278 }
1279
1280 #[test]
1281 fn test_logger_with_method() {
1282 let logger = Logger::new();
1284 let ctx_logger = logger.with(&[("request_id", "abc123"), ("user", "alice")]);
1285 ctx_logger.info("test message", &[]);
1288 logger.info("another message", &[]);
1289 }
1290
1291 #[test]
1292 fn test_caller_info_capture() {
1293 let info = CallerInfo::capture(0);
1295 if let Some(caller) = info {
1298 assert!(!caller.function.is_empty());
1300 }
1301 }
1302
1303 #[test]
1304 fn test_styles_default() {
1305 let styles = Styles::new();
1306 assert!(styles.levels.contains_key(&Level::Debug));
1307 assert!(styles.levels.contains_key(&Level::Info));
1308 assert!(styles.levels.contains_key(&Level::Warn));
1309 assert!(styles.levels.contains_key(&Level::Error));
1310 assert!(styles.levels.contains_key(&Level::Fatal));
1311 }
1312
1313 #[test]
1314 fn test_trim_caller_path() {
1315 assert_eq!(trim_caller_path("src/lib.rs", 1), "lib.rs");
1316 assert_eq!(trim_caller_path("foo/bar/baz.rs", 2), "bar/baz.rs");
1317 assert_eq!(trim_caller_path("baz.rs", 2), "baz.rs");
1318 assert_eq!(trim_caller_path("foo/bar/baz.rs", 0), "foo/bar/baz.rs");
1319 }
1320
1321 #[test]
1322 fn test_short_caller_formatter() {
1323 let result = short_caller_formatter("/home/user/project/src/main.rs", 42, "main");
1324 assert!(result.contains(":42"));
1325 }
1326
1327 #[test]
1328 fn test_long_caller_formatter() {
1329 let result = long_caller_formatter("/home/user/project/src/main.rs", 42, "main");
1330 assert_eq!(result, "/home/user/project/src/main.rs:42");
1331 }
1332
1333 #[test]
1334 fn test_escape_json() {
1335 assert_eq!(escape_json("hello"), "hello");
1336 assert_eq!(escape_json("hello \"world\""), "hello \\\"world\\\"");
1337 assert_eq!(escape_json("line1\nline2"), "line1\\nline2");
1338 }
1339
1340 #[test]
1341 fn test_needs_quoting() {
1342 assert!(needs_quoting(""));
1343 assert!(needs_quoting("hello world"));
1344 assert!(needs_quoting("key=value"));
1345 assert!(needs_quoting("has\"quote"));
1346 assert!(!needs_quoting("simple"));
1347 }
1348
1349 #[test]
1350 fn test_escape_logfmt() {
1351 assert_eq!(escape_logfmt("hello"), "hello");
1352 assert_eq!(escape_logfmt("hello \"world\""), "hello \\\"world\\\"");
1353 assert_eq!(escape_logfmt("line1\nline2"), "line1\\nline2");
1354 }
1355
1356 #[test]
1357 fn test_formatter_default() {
1358 assert_eq!(Formatter::default(), Formatter::Text);
1359 }
1360
1361 #[test]
1362 fn test_options_default() {
1363 let opts = Options::default();
1364 assert_eq!(opts.level, Level::Info);
1365 assert_eq!(opts.formatter, Formatter::Text);
1366 assert!(!opts.report_timestamp);
1367 assert!(!opts.report_caller);
1368 }
1369
1370 #[test]
1371 fn test_logger_with_options() {
1372 let opts = Options {
1373 level: Level::Debug,
1374 prefix: "test".to_string(),
1375 report_timestamp: true,
1376 ..Default::default()
1377 };
1378 let logger = Logger::with_options(opts);
1379 assert_eq!(logger.level(), Level::Debug);
1380 assert_eq!(logger.prefix(), "test");
1381 }
1382
1383 struct FailingWriter;
1385
1386 impl Write for FailingWriter {
1387 fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
1388 Err(io::Error::other("simulated failure"))
1389 }
1390
1391 fn flush(&mut self) -> io::Result<()> {
1392 Err(io::Error::other("simulated failure"))
1393 }
1394 }
1395
1396 #[test]
1397 fn test_error_handler_called_on_io_failure() {
1398 use std::sync::atomic::{AtomicUsize, Ordering};
1399
1400 let error_count = Arc::new(AtomicUsize::new(0));
1401 let counter = error_count.clone();
1402
1403 let logger = Logger::new().with_error_handler(move |_err| {
1405 counter.fetch_add(1, Ordering::Relaxed);
1406 });
1407
1408 {
1410 let mut inner = logger.inner.write().unwrap();
1411 inner.writer = Box::new(FailingWriter);
1412 }
1413
1414 logger.info("test message", &[]);
1416
1417 assert_eq!(error_count.load(Ordering::Relaxed), 1);
1419 }
1420
1421 #[test]
1422 fn test_error_handler_receives_correct_error() {
1423 use std::sync::Mutex;
1424
1425 let captured_error = Arc::new(Mutex::new(None::<String>));
1426 let error_capture = captured_error.clone();
1427
1428 let logger = Logger::new().with_error_handler(move |err| {
1429 *error_capture.lock().unwrap() = Some(err.to_string());
1430 });
1431
1432 {
1434 let mut inner = logger.inner.write().unwrap();
1435 inner.writer = Box::new(FailingWriter);
1436 }
1437
1438 logger.info("test", &[]);
1439
1440 let error_msg = captured_error.lock().unwrap();
1441 assert!(error_msg.is_some());
1442 assert!(error_msg.as_ref().unwrap().contains("simulated failure"));
1443 }
1444
1445 #[test]
1446 fn test_default_behavior_warns_once() {
1447 let logger = Logger::new();
1450
1451 {
1453 let mut inner = logger.inner.write().unwrap();
1454 inner.writer = Box::new(FailingWriter);
1455 }
1456
1457 logger.info("first message", &[]);
1459 logger.info("second message", &[]);
1460 logger.info("third message", &[]);
1461
1462 let inner = logger.inner.read().unwrap();
1464 assert!(inner.has_warned_io_failure);
1465 }
1466
1467 #[test]
1468 fn test_error_handler_inherited_by_with_fields() {
1469 use std::sync::atomic::{AtomicUsize, Ordering};
1470
1471 let error_count = Arc::new(AtomicUsize::new(0));
1472 let counter = error_count.clone();
1473
1474 let logger = Logger::new().with_error_handler(move |_err| {
1475 counter.fetch_add(1, Ordering::Relaxed);
1476 });
1477
1478 let child_logger = logger.with_fields(&[("component", "test")]);
1480
1481 {
1483 let mut inner = child_logger.inner.write().unwrap();
1484 inner.writer = Box::new(FailingWriter);
1485 }
1486
1487 child_logger.info("test message", &[]);
1489
1490 assert_eq!(error_count.load(Ordering::Relaxed), 1);
1491 }
1492
1493 #[test]
1494 fn test_with_error_handler_returns_same_logger() {
1495 use std::sync::atomic::{AtomicBool, Ordering};
1496
1497 let called = Arc::new(AtomicBool::new(false));
1498 let flag = called.clone();
1499
1500 let logger = Logger::new().with_error_handler(move |_| {
1501 flag.store(true, Ordering::Relaxed);
1502 });
1503
1504 assert_eq!(logger.level(), Level::Info);
1506
1507 let inner = logger.inner.read().unwrap();
1509 assert!(inner.error_handler.is_some());
1510 }
1511
1512 #[test]
1513 fn test_caller_warning_flag_set_on_first_log() {
1514 let logger = Logger::new();
1515 logger.set_report_caller(true);
1516
1517 {
1519 let inner = logger.inner.read().unwrap();
1520 assert!(!inner.warned_caller_overhead);
1521 }
1522
1523 logger.info("test message", &[]);
1525
1526 {
1528 let inner = logger.inner.read().unwrap();
1529 assert!(inner.warned_caller_overhead);
1530 }
1531 }
1532
1533 #[test]
1534 fn test_caller_warning_suppressed() {
1535 let logger = Logger::new();
1536 logger.set_report_caller(true);
1537 logger.suppress_caller_warning();
1538
1539 {
1541 let inner = logger.inner.read().unwrap();
1542 assert!(inner.suppress_caller_warning);
1543 }
1544
1545 logger.info("test message", &[]);
1547
1548 {
1550 let inner = logger.inner.read().unwrap();
1551 assert!(!inner.warned_caller_overhead);
1552 }
1553 }
1554
1555 #[test]
1556 fn test_caller_warning_not_triggered_when_caller_disabled() {
1557 let logger = Logger::new();
1558 logger.info("test message", &[]);
1562
1563 {
1565 let inner = logger.inner.read().unwrap();
1566 assert!(!inner.warned_caller_overhead);
1567 }
1568 }
1569
1570 #[test]
1571 fn test_caller_warning_inherits_suppression_via_with_fields() {
1572 let logger = Logger::new();
1573 logger.set_report_caller(true);
1574 logger.suppress_caller_warning();
1575
1576 let child = logger.with_fields(&[("key", "value")]);
1578
1579 {
1581 let inner = child.inner.read().unwrap();
1582 assert!(inner.suppress_caller_warning);
1583 }
1584 }
1585
1586 #[test]
1587 fn test_caller_warning_resets_for_child_logger() {
1588 let logger = Logger::new();
1589 logger.set_report_caller(true);
1590
1591 logger.info("parent message", &[]);
1593
1594 let child = logger.with_fields(&[("key", "value")]);
1596
1597 {
1599 let inner = child.inner.read().unwrap();
1600 assert!(!inner.warned_caller_overhead);
1601 }
1602 }
1603}