cursive_logger_view/
formatter.rs

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