tui_logger/widget/
standard_formatter.rs1use crate::widget::logformatter::LogFormatter;
2use crate::ExtLogRecord;
3use crate::Style;
4use crate::TuiLoggerLevelOutput;
5use ratatui::text::{Line, Span};
6use std::borrow::Cow;
7use unicode_segmentation::UnicodeSegmentation;
8
9pub struct LogStandardFormatter {
10 pub style: Style,
12 pub style_error: Option<Style>,
14 pub style_warn: Option<Style>,
15 pub style_debug: Option<Style>,
16 pub style_trace: Option<Style>,
17 pub style_info: Option<Style>,
18 pub format_separator: char,
19 pub format_timestamp: Option<String>,
20 pub format_output_level: Option<TuiLoggerLevelOutput>,
21 pub format_output_target: bool,
22 pub format_output_file: bool,
23 pub format_output_line: bool,
24}
25
26impl LogStandardFormatter {
27 fn append_wrapped_line(
28 &self,
29 style: Style,
30 indent: usize,
31 lines: &mut Vec<Line>,
32 line: &str,
33 width: usize,
34 with_indent: bool,
35 ) {
36 let mut p = 0;
37 let mut wrap_len = width;
38 if with_indent {
39 wrap_len -= indent;
40 }
41 let space = " ".repeat(indent);
42 let line_chars = line.graphemes(true).collect::<Vec<_>>();
43 while p < line_chars.len() {
44 let linelen = std::cmp::min(wrap_len, line_chars.len() - p);
45 let subline = &line_chars[p..p + linelen];
46
47 let mut spans: Vec<Span> = Vec::new();
48 if wrap_len < width {
49 spans.push(Span {
51 style,
52 content: Cow::Owned(space.to_string()),
53 });
54 }
55 spans.push(Span {
56 style,
57 content: Cow::Owned(subline.iter().map(|x| x.to_string()).collect()),
58 });
59 let line = Line::from(spans);
60 lines.push(line);
61
62 p += linelen;
63 wrap_len = width - indent;
65 }
66 }
67}
68
69impl LogFormatter for LogStandardFormatter {
70 fn min_width(&self) -> u16 {
71 9 + 4
72 }
73 fn format(&self, width: usize, evt: &ExtLogRecord) -> Vec<Line> {
74 let mut lines = Vec::new();
75 let mut output = String::new();
76 let (col_style, lev_long, lev_abbr, with_loc) = match evt.level {
77 log::Level::Error => (self.style_error, "ERROR", "E", true),
78 log::Level::Warn => (self.style_warn, "WARN ", "W", true),
79 log::Level::Info => (self.style_info, "INFO ", "I", true),
80 log::Level::Debug => (self.style_debug, "DEBUG", "D", true),
81 log::Level::Trace => (self.style_trace, "TRACE", "T", true),
82 };
83 let col_style = col_style.unwrap_or(self.style);
84 if let Some(fmt) = self.format_timestamp.as_ref() {
85 output.push_str(&format!("{}", evt.timestamp.format(fmt)));
86 output.push(self.format_separator);
87 }
88 match &self.format_output_level {
89 None => {}
90 Some(TuiLoggerLevelOutput::Abbreviated) => {
91 output.push_str(lev_abbr);
92 output.push(self.format_separator);
93 }
94 Some(TuiLoggerLevelOutput::Long) => {
95 output.push_str(lev_long);
96 output.push(self.format_separator);
97 }
98 }
99 if self.format_output_target {
100 output.push_str(&evt.target);
101 output.push(self.format_separator);
102 }
103 if with_loc {
104 if self.format_output_file {
105 output.push_str(&evt.file);
106 output.push(self.format_separator);
107 }
108 if self.format_output_line {
109 output.push_str(&format!("{}", evt.line));
110 output.push(self.format_separator);
111 }
112 }
113 let mut sublines: Vec<&str> = evt.msg.lines().rev().collect();
114
115 output.push_str(sublines.pop().unwrap());
116 self.append_wrapped_line(col_style, 9, &mut lines, &output, width, false);
117
118 for subline in sublines.iter().rev() {
119 self.append_wrapped_line(col_style, 9, &mut lines, subline, width, true);
120 }
121 lines
122 }
123}