1use std::ffi::OsStr;
2use std::fs::{self, File};
3use std::io::{prelude::*, BufWriter};
4use std::path::PathBuf;
5use std::sync::Mutex;
6use std::{fmt, io};
7
8use log::{Metadata, Record};
9use smallvec::SmallVec;
10use termion::color;
11
12pub struct Output {
15 endpoint: Mutex<Box<dyn Write + Send + 'static>>,
17
18 flush_on_newline: bool,
21
22 filter: log::LevelFilter,
24
25 ansi: bool,
27}
28impl fmt::Debug for Output {
29 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
30 f.debug_struct("Output")
31 .field("endpoint", &"opaque")
32 .field("flush_on_newline", &self.flush_on_newline)
33 .field("filter", &self.filter)
34 .field("ansi", &self.ansi)
35 .finish()
36 }
37}
38
39pub struct OutputBuilder {
40 endpoint: Box<dyn Write + Send + 'static>,
41 flush_on_newline: Option<bool>,
42 filter: Option<log::LevelFilter>,
43 ansi: Option<bool>,
44}
45impl OutputBuilder {
46 pub fn in_redox_logging_scheme<A, B, C>(
47 category: A,
48 subcategory: B,
49 logfile: C,
50 ) -> Result<Self, io::Error>
51 where
52 A: AsRef<OsStr>,
53 B: AsRef<OsStr>,
54 C: AsRef<OsStr>,
55 {
56 if !cfg!(target_os = "redox") {
57 return Ok(Self::with_endpoint(Vec::new()));
58 }
59
60 let mut path = PathBuf::from("/scheme/logging/");
61 path.push(category.as_ref());
62 path.push(subcategory.as_ref());
63 path.push(logfile.as_ref());
64 path.set_extension("log");
65
66 if let Some(parent) = path.parent() {
67 if !parent.exists() {
68 fs::create_dir_all(parent)?;
69 }
70 }
71
72 Ok(Self::with_endpoint(BufWriter::new(File::create(path)?)))
73 }
74
75 pub fn stdout() -> Self {
76 Self::with_endpoint(io::stdout())
77 }
78 pub fn stderr() -> Self {
79 Self::with_endpoint(io::stderr())
80 }
81
82 pub fn with_endpoint<T>(endpoint: T) -> Self
83 where
84 T: Write + Send + 'static,
85 {
86 Self::with_dyn_endpoint(Box::new(endpoint))
87 }
88 pub fn with_dyn_endpoint(endpoint: Box<dyn Write + Send + 'static>) -> Self {
89 Self {
90 endpoint,
91 flush_on_newline: None,
92 filter: None,
93 ansi: None,
94 }
95 }
96 pub fn flush_on_newline(mut self, flush: bool) -> Self {
97 self.flush_on_newline = Some(flush);
98 self
99 }
100 pub fn with_filter(mut self, filter: log::LevelFilter) -> Self {
101 self.filter = Some(filter);
102 self
103 }
104 pub fn with_ansi_escape_codes(mut self) -> Self {
105 self.ansi = Some(true);
106 self
107 }
108 pub fn build(self) -> Output {
109 Output {
110 endpoint: Mutex::new(self.endpoint),
111 filter: self.filter.unwrap_or(log::LevelFilter::Info),
112 flush_on_newline: self.flush_on_newline.unwrap_or(true),
113 ansi: self.ansi.unwrap_or(false),
114 }
115 }
116}
117
118const AVG_OUTPUTS: usize = 2;
119
120#[derive(Debug, Default)]
121pub struct RedoxLogger {
122 outputs: SmallVec<[Output; AVG_OUTPUTS]>,
123 min_filter: Option<log::LevelFilter>,
124 max_filter: Option<log::LevelFilter>,
125 max_level_in_use: Option<log::LevelFilter>,
126 min_level_in_use: Option<log::LevelFilter>,
127 process_name: Option<String>,
128}
129
130impl RedoxLogger {
131 pub fn new() -> Self {
132 Self::default()
133 }
134 fn adjust_output_level(
135 max_filter: Option<log::LevelFilter>,
136 min_filter: Option<log::LevelFilter>,
137 max_in_use: &mut Option<log::LevelFilter>,
138 min_in_use: &mut Option<log::LevelFilter>,
139 output: &mut Output,
140 ) {
141 if let Some(max) = max_filter {
142 output.filter = std::cmp::max(output.filter, max);
143 }
144 if let Some(min) = min_filter {
145 output.filter = std::cmp::min(output.filter, min);
146 }
147 match max_in_use {
148 &mut Some(ref mut max) => *max = std::cmp::max(output.filter, *max),
149 max @ &mut None => *max = Some(output.filter),
150 }
151 match min_in_use {
152 &mut Some(ref mut min) => *min = std::cmp::min(output.filter, *min),
153 min @ &mut None => *min = Some(output.filter),
154 }
155 }
156 pub fn with_output(mut self, mut output: Output) -> Self {
157 Self::adjust_output_level(
158 self.max_filter,
159 self.min_filter,
160 &mut self.max_level_in_use,
161 &mut self.min_level_in_use,
162 &mut output,
163 );
164 self.outputs.push(output);
165 self
166 }
167 pub fn with_min_level_override(mut self, min: log::LevelFilter) -> Self {
168 self.min_filter = Some(min);
169 for output in &mut self.outputs {
170 Self::adjust_output_level(
171 self.max_filter,
172 self.min_filter,
173 &mut self.max_level_in_use,
174 &mut self.min_level_in_use,
175 output,
176 );
177 }
178 self
179 }
180 pub fn with_max_level_override(mut self, max: log::LevelFilter) -> Self {
181 self.max_filter = Some(max);
182 for output in &mut self.outputs {
183 Self::adjust_output_level(
184 self.max_filter,
185 self.min_filter,
186 &mut self.max_level_in_use,
187 &mut self.min_level_in_use,
188 output,
189 );
190 }
191 self
192 }
193 pub fn with_process_name(mut self, name: String) -> Self {
194 self.process_name = Some(name);
195 self
196 }
197 pub fn enable(self) -> Result<&'static Self, log::SetLoggerError> {
198 let leak = Box::leak(Box::new(self));
199 log::set_logger(leak)?;
200 if let Some(max) = leak.max_level_in_use {
201 log::set_max_level(max);
202 } else {
203 log::set_max_level(log::LevelFilter::Off);
204 }
205 Ok(leak)
206 }
207 fn write_record<W: Write>(
208 ansi: bool,
209 record: &Record,
210 process_name: Option<&str>,
211 writer: &mut W,
212 ) -> io::Result<()> {
213 use log::Level;
214 use termion::style;
215
216 let now_local = chrono::Local::now();
219 let time = now_local.format("%Y-%m-%dT%H-%M-%S%.3f");
220 let mut zone = format!("{}", now_local.format("%:z"));
221 if zone.as_str() == "+00:00" {
222 zone = "Z".to_string();
223 }
224
225 let target = record.module_path().unwrap_or(record.target());
226 let level = record.level();
227 let message = record.args();
228
229 let reset = color::Fg(color::Reset);
230
231 let show_lines = true;
232 let line_number = if show_lines { record.line() } else { None };
233
234 let process_name = process_name.unwrap_or("");
235 let line = &LineFmt(line_number, false);
236
237 if ansi {
238 let time_color = color::Fg(color::LightWhite);
239 let zone_color = color::Fg(color::White);
240
241 let trace_col = color::Fg(color::LightBlack);
242 let debug_col = color::Fg(color::White);
243 let info_col = color::Fg(color::LightBlue);
244 let warn_col = color::Fg(color::LightYellow);
245 let err_col = color::Fg(color::LightRed);
246
247 let level_color: &dyn fmt::Display = match level {
248 Level::Trace => &trace_col,
249 Level::Debug => &debug_col,
250 Level::Info => &info_col,
251 Level::Warn => &warn_col,
252 Level::Error => &err_col,
253 };
254
255 let dim_white = color::Fg(color::White);
256 let bright_white = color::Fg(color::LightWhite);
257 let regular_style = "";
258 let bold_style = style::Bold;
259
260 let [message_color, message_style]: [&dyn fmt::Display; 2] = match level {
261 Level::Trace | Level::Debug => [&dim_white, ®ular_style],
262 Level::Info | Level::Warn | Level::Error => [&bright_white, &bold_style],
263 };
264 let target_color = color::Fg(color::White);
265
266 let i = style::Italic;
267 let b = style::Bold;
268 let r = reset;
269 let rs = style::Reset;
270
271 writeln!(
272 writer,
273 "{time}{zone} [{target}{line} {level}] {msg}",
274 time = format_args!("{i}{time_color}{time}{rs}{r}"),
275 zone = format_args!("{i}{zone_color}{zone}{rs}{r}"),
276 level = format_args!("{b}{level_color}{level}{rs}{r}"),
277 target = format_args!("{target_color}{process_name}@{target}{r}"),
278 msg = format_args!("{message_style}{message_color}{message}{rs}{r}"),
279 )
280 } else {
281 writeln!(
282 writer,
283 "{time}{zone} [{process_name}@{target}{line} {level}] {message}",
284 )
285 }
286 }
287}
288
289impl log::Log for RedoxLogger {
290 fn enabled(&self, metadata: &Metadata) -> bool {
291 self.max_level_in_use
292 .map(|min| metadata.level() >= min)
293 .unwrap_or(false)
294 && self
295 .min_level_in_use
296 .map(|max| metadata.level() <= max)
297 .unwrap_or(false)
298 }
299 fn log(&self, record: &Record) {
300 for output in &self.outputs {
301 if record.metadata().level() <= output.filter {
302 let mut endpoint_guard = match output.endpoint.lock() {
303 Ok(e) => e,
304 _ => continue,
306 };
307
308 let _ = Self::write_record(
309 output.ansi,
310 record,
311 self.process_name.as_deref(),
312 &mut *endpoint_guard,
313 );
314
315 if output.flush_on_newline {
316 let _ = endpoint_guard.flush();
317 }
318 }
319 }
320 }
321 fn flush(&self) {
322 for output in &self.outputs {
323 match output.endpoint.lock() {
324 Ok(ref mut e) => {
325 let _ = e.flush();
326 }
327 _ => continue,
328 }
329 }
330 }
331}
332
333struct LineFmt(Option<u32>, bool);
334impl fmt::Display for LineFmt {
335 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
336 if let Some(line) = self.0 {
337 if self.1 {
338 let color = color::Fg(color::LightBlack);
340 let reset = color::Fg(color::Reset);
341 write!(f, "{color}:{line}{reset}")
342 } else {
343 write!(f, ":{line}")
345 }
346 } else {
347 write!(f, "")
348 }
349 }
350}