cursive_logger_view/
formatter.rs

1use crate::{
2    log_buffer::{self, GET_LOCK_ERR_MSG},
3    CursiveLogWriter, LogItems,
4};
5use compact_str::{format_compact, CompactString, ToCompactString};
6use cursive_core::{
7    theme::{BaseColor, Color},
8    utils::markup::StyledString,
9};
10use flexi_logger::{writers::LogWriter, DeferredNow, Record};
11use getset::WithSetters;
12use log::Level;
13use std::{io, thread};
14use tap::Pipe;
15
16const fn log_level_as_dark_color(level: &Level) -> Color {
17    use BaseColor::{Cyan, Green, Magenta, Red, Yellow};
18    use Level::*;
19    let base_color = match level {
20        Trace => Magenta,
21        Debug => Cyan,
22        Info => Green,
23        Warn => Yellow,
24        Error => Red,
25    };
26    Color::Dark(base_color)
27}
28
29#[derive(Debug, WithSetters)]
30struct StyledTextConfig<'a> {
31    line: &'a mut StyledString,
32    #[getset(set_with)]
33    content: CompactString,
34    #[getset(set_with)]
35    color_enabled: bool,
36    color: Color,
37}
38
39impl StyledTextConfig<'_> {
40    fn append_line(mut self) -> Self {
41        let content = (&mut self.content) //
42            .pipe(core::mem::take);
43
44        match self.color_enabled {
45            true => self
46                .line
47                .append_styled(content, self.color),
48            _ => self.line.append_plain(content),
49        }
50        self
51    }
52
53    fn append_mod_line(self, rec: &Record) -> Self {
54        let (path, line_num) = (
55            rec.module_path().unwrap_or(""), //
56            rec.line().unwrap_or(0),
57        );
58
59        format_compact!("<{path}:") //
60            .pipe(|s| self.line.append_plain(s));
61
62        format_compact!("{line_num}") //
63            .pipe(|s| {
64                self.line
65                    .append_styled(s, Color::Dark(BaseColor::Blue))
66            });
67
68        self.line.append_plain("> ");
69
70        self
71    }
72}
73
74impl LogWriter for CursiveLogWriter<'_> {
75    fn write(&self, now: &mut DeferredNow, record: &Record) -> io::Result<()> {
76        let styled_config = StyledTextConfig {
77            line: &mut StyledString::new(),
78            content: "".into(),
79            color_enabled: false,
80            color: record
81                .level()
82                .pipe_ref(log_level_as_dark_color),
83        };
84
85        let line = self
86            .format
87            .iter()
88            .fold(styled_config, |cfg, item| {
89                use crate::LogItems::{
90                    DateTime, File, FileLine, Level, Message, ModLine, Thread,
91                };
92
93                match item {
94                    DateTime => now
95                        .format(&self.time_format)
96                        .pipe(|fmt| format_compact!("{fmt} "))
97                        .pipe(|s| cfg.with_content(s))
98                        .with_color_enabled(false)
99                        .append_line(),
100                    //
101                    Thread => thread::current()
102                        .name()
103                        .unwrap_or(" ")
104                        .pipe(|name| format_compact!("[{name}] "))
105                        .pipe(|s| cfg.with_content(s))
106                        .with_color_enabled(false)
107                        .append_line(),
108                    //
109                    Level => record
110                        .level()
111                        .pipe(|lv| format_compact!("[{lv}] "))
112                        .pipe(|s| cfg.with_content(s))
113                        .with_color_enabled(true)
114                        .append_line(),
115                    //
116                    File => record
117                        .file()
118                        .unwrap_or("")
119                        .pipe(|file| format_compact!("<{file}> "))
120                        .pipe(|s| cfg.with_content(s))
121                        .with_color_enabled(false)
122                        .append_line(),
123                    //
124                    FileLine => format_compact!(
125                        "<{}:{}> ",
126                        record.file().unwrap_or(""),
127                        record.line().unwrap_or(0),
128                    )
129                    .pipe(|s| cfg.with_content(s))
130                    .with_color_enabled(false)
131                    .append_line(),
132                    //
133                    ModLine => cfg.append_mod_line(record),
134                    //
135                    Message => record
136                        .args()
137                        .pipe(|x| format_compact!("{x}"))
138                        .pipe(|s| cfg.with_content(s))
139                        .with_color_enabled(true)
140                        .append_line(),
141                    //
142                    LogItems::Custom(txt) => cfg
143                        .with_content(txt.to_compact_string())
144                        .with_color_enabled(true)
145                        .append_line(), // line.append_plain(txt.as_str()),
146                }
147            })
148            .line
149            .pipe(core::mem::take);
150
151        log_buffer::static_logs()
152            .lock()
153            .expect(GET_LOCK_ERR_MSG)
154            .push_back(line);
155
156        let io_broken_pipe = |msg| io::Error::new(io::ErrorKind::BrokenPipe, msg);
157
158        self.sink
159            .send(Box::new(|_| {}))
160            .map_err(|_| io_broken_pipe("cursive callback sink is closed!"))
161    }
162
163    fn flush(&self) -> io::Result<()> {
164        // we are not buffering
165        Ok(())
166    }
167
168    fn max_log_level(&self) -> log::LevelFilter {
169        log::LevelFilter::max()
170    }
171}