tui_logger/widget/
standard_formatter.rs

1use 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    /// Base style of the widget
11    pub style: Style,
12    /// Level based style
13    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                // need indent
50                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            // following lines need to be indented
64            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}