1 use std::{
2 borrow::Cow,
3 collections::HashMap,
4 fmt, fs,
5 io::{self, BufWriter, Write},
6 sync::{mpsc, Arc, Mutex},
7 };
8
9 #[cfg(feature = "date-based")]
10 use std::{
11 ffi::OsString,
12 fs::OpenOptions,
13 path::{Path, PathBuf},
14 };
15
16 use log::{self, Log};
17
18 use crate::{Filter, Formatter};
19
20 #[cfg(all(not(windows), feature = "syslog-4"))]
21 use crate::{Syslog4Rfc3164Logger, Syslog4Rfc5424Logger};
22 #[cfg(all(not(windows), feature = "reopen-03"))]
23 use reopen;
24
25 pub enum LevelConfiguration {
26 JustDefault,
27 Minimal(Vec<(Cow<'static, str>, log::LevelFilter)>),
28 Many(HashMap<Cow<'static, str>, log::LevelFilter>),
29 }
30
31 pub struct Dispatch {
32 pub output: Vec<Output>,
33 pub default_level: log::LevelFilter,
34 pub levels: LevelConfiguration,
35 pub format: Option<Box<Formatter>>,
36 pub filters: Vec<Box<Filter>>,
37 }
38
39 /// Callback struct for use within a formatter closure
40 ///
41 /// Callbacks are used for formatting in order to allow usage of
42 /// [`std::fmt`]-based formatting without the allocation of the formatted
43 /// result which would be required to return it.
44 ///
45 /// Example usage:
46 ///
47 /// ```
48 /// fern::Dispatch::new().format(|callback: fern::FormatCallback, message, record| {
49 /// callback.finish(format_args!("[{}] {}", record.level(), message))
50 /// })
51 /// # ;
52 /// ```
53 ///
54 /// [`std::fmt`]: https://doc.rust-lang.org/std/fmt/index.html
55 #[must_use = "format callback must be used for log to process correctly"]
56 pub struct FormatCallback<'a>(InnerFormatCallback<'a>);
57
58 struct InnerFormatCallback<'a>(&'a mut bool, &'a Dispatch, &'a log::Record<'a>);
59
60 pub enum Output {
61 Stdout(Stdout),
62 Stderr(Stderr),
63 File(File),
64 Sender(Sender),
65 #[cfg(all(not(windows), feature = "syslog-3"))]
66 Syslog3(Syslog3),
67 #[cfg(all(not(windows), feature = "syslog-4"))]
68 Syslog4Rfc3164(Syslog4Rfc3164),
69 #[cfg(all(not(windows), feature = "syslog-4"))]
70 Syslog4Rfc5424(Syslog4Rfc5424),
71 Dispatch(Dispatch),
72 SharedDispatch(Arc<Dispatch>),
73 OtherBoxed(Box<dyn Log>),
74 OtherStatic(&'static dyn Log),
75 Panic(Panic),
76 Writer(Writer),
77 #[cfg(feature = "date-based")]
78 DateBased(DateBased),
79 #[cfg(all(not(windows), feature = "reopen-03"))]
80 Reopen(Reopen),
81 }
82
83 pub struct Stdout {
84 pub stream: io::Stdout,
85 pub line_sep: Cow<'static, str>,
86 }
87
88 pub struct Stderr {
89 pub stream: io::Stderr,
90 pub line_sep: Cow<'static, str>,
91 }
92
93 pub struct File {
94 pub stream: Mutex<BufWriter<fs::File>>,
95 pub line_sep: Cow<'static, str>,
96 }
97
98 pub struct Sender {
99 pub stream: Mutex<mpsc::Sender<String>>,
100 pub line_sep: Cow<'static, str>,
101 }
102
103 pub struct Writer {
104 pub stream: Mutex<Box<dyn Write + Send>>,
105 pub line_sep: Cow<'static, str>,
106 }
107
108 #[cfg(all(not(windows), feature = "reopen-03"))]
109 pub struct Reopen {
110 pub stream: Mutex<reopen::Reopen<fs::File>>,
111 pub line_sep: Cow<'static, str>,
112 }
113
114 #[cfg(all(not(windows), feature = "syslog-3"))]
115 pub struct Syslog3 {
116 pub inner: syslog3::Logger,
117 }
118
119 #[cfg(all(not(windows), feature = "syslog-4"))]
120 pub struct Syslog4Rfc3164 {
121 pub inner: Mutex<Syslog4Rfc3164Logger>,
122 }
123
124 #[cfg(all(not(windows), feature = "syslog-4"))]
125 pub struct Syslog4Rfc5424 {
126 pub inner: Mutex<Syslog4Rfc5424Logger>,
127 pub transform: Box<
128 dyn Fn(&log::Record) -> (i32, HashMap<String, HashMap<String, String>>, String)
129 + Sync
130 + Send,
131 >,
132 }
133
134 pub struct Panic;
135
136 pub struct Null;
137
138 /// File logger with a dynamic time-based name.
139 #[derive(Debug)]
140 #[cfg(feature = "date-based")]
141 pub struct DateBased {
142 pub config: DateBasedConfig,
143 pub state: Mutex<DateBasedState>,
144 }
145
146 #[derive(Debug)]
147 #[cfg(feature = "date-based")]
148 pub enum ConfiguredTimezone {
149 Local,
150 Utc,
151 }
152
153 #[derive(Debug)]
154 #[cfg(feature = "date-based")]
155 pub struct DateBasedConfig {
156 pub line_sep: Cow<'static, str>,
157 /// This is a Path not an str so it can hold invalid UTF8 paths correctly.
158 pub file_prefix: PathBuf,
159 pub file_suffix: Cow<'static, str>,
160 pub timezone: ConfiguredTimezone,
161 }
162
163 #[derive(Debug)]
164 #[cfg(feature = "date-based")]
165 pub struct DateBasedState {
166 pub current_suffix: String,
167 pub file_stream: Option<BufWriter<fs::File>>,
168 }
169
170 #[cfg(feature = "date-based")]
171 impl DateBasedState {
new(current_suffix: String, file_stream: Option<fs::File>) -> Self172 pub fn new(current_suffix: String, file_stream: Option<fs::File>) -> Self {
173 DateBasedState {
174 current_suffix,
175 file_stream: file_stream.map(BufWriter::new),
176 }
177 }
178
replace_file(&mut self, new_suffix: String, new_file: Option<fs::File>)179 pub fn replace_file(&mut self, new_suffix: String, new_file: Option<fs::File>) {
180 if let Some(mut old) = self.file_stream.take() {
181 let _ = old.flush();
182 }
183 self.current_suffix = new_suffix;
184 self.file_stream = new_file.map(BufWriter::new)
185 }
186 }
187
188 #[cfg(feature = "date-based")]
189 impl DateBasedConfig {
new( line_sep: Cow<'static, str>, file_prefix: PathBuf, file_suffix: Cow<'static, str>, timezone: ConfiguredTimezone, ) -> Self190 pub fn new(
191 line_sep: Cow<'static, str>,
192 file_prefix: PathBuf,
193 file_suffix: Cow<'static, str>,
194 timezone: ConfiguredTimezone,
195 ) -> Self {
196 DateBasedConfig {
197 line_sep,
198 file_prefix,
199 file_suffix,
200 timezone,
201 }
202 }
203
compute_current_suffix(&self) -> String204 pub fn compute_current_suffix(&self) -> String {
205 match self.timezone {
206 ConfiguredTimezone::Utc => chrono::Utc::now().format(&self.file_suffix).to_string(),
207 ConfiguredTimezone::Local => chrono::Local::now().format(&self.file_suffix).to_string(),
208 }
209 }
210
compute_file_path(&self, suffix: &str) -> PathBuf211 pub fn compute_file_path(&self, suffix: &str) -> PathBuf {
212 let mut path = OsString::from(&*self.file_prefix);
213 // use the OsString::push method, not PathBuf::push which would add a path
214 // separator
215 path.push(suffix);
216 path.into()
217 }
218
open_log_file(path: &Path) -> io::Result<fs::File>219 pub fn open_log_file(path: &Path) -> io::Result<fs::File> {
220 OpenOptions::new()
221 .write(true)
222 .create(true)
223 .append(true)
224 .open(path)
225 }
226
open_current_log_file(&self, suffix: &str) -> io::Result<fs::File>227 pub fn open_current_log_file(&self, suffix: &str) -> io::Result<fs::File> {
228 Self::open_log_file(&self.compute_file_path(&suffix))
229 }
230 }
231
232 impl From<Vec<(Cow<'static, str>, log::LevelFilter)>> for LevelConfiguration {
from(mut levels: Vec<(Cow<'static, str>, log::LevelFilter)>) -> Self233 fn from(mut levels: Vec<(Cow<'static, str>, log::LevelFilter)>) -> Self {
234 // Benchmarked separately: https://gist.github.com/daboross/976978d8200caf86e02acb6805961195
235 // Use Vec if there are fewer than 15 items, HashMap if there are more than 15.
236 match levels.len() {
237 0 => LevelConfiguration::JustDefault,
238 x if x > 15 => LevelConfiguration::Many(levels.into_iter().collect()),
239 _ => {
240 levels.shrink_to_fit();
241 LevelConfiguration::Minimal(levels)
242 }
243 }
244 }
245 }
246
247 impl LevelConfiguration {
248 // inline since we use it literally once.
249 #[inline]
find_module(&self, module: &str) -> Option<log::LevelFilter>250 fn find_module(&self, module: &str) -> Option<log::LevelFilter> {
251 match *self {
252 LevelConfiguration::JustDefault => None,
253 _ => {
254 if let Some(level) = self.find_exact(module) {
255 return Some(level);
256 }
257
258 // The manual for loop here lets us just iterate over the module string once
259 // while still finding each sub-module. For the module string
260 // "hyper::http::h1", this loop will test first "hyper::http"
261 // then "hyper".
262 let mut last_char_colon = false;
263
264 for (index, ch) in module.char_indices().rev() {
265 if last_char_colon {
266 last_char_colon = false;
267 if ch == ':' {
268 let sub_module = &module[0..index];
269
270 if let Some(level) = self.find_exact(sub_module) {
271 return Some(level);
272 }
273 }
274 } else if ch == ':' {
275 last_char_colon = true;
276 }
277 }
278
279 None
280 }
281 }
282 }
283
find_exact(&self, module: &str) -> Option<log::LevelFilter>284 fn find_exact(&self, module: &str) -> Option<log::LevelFilter> {
285 match *self {
286 LevelConfiguration::JustDefault => None,
287 LevelConfiguration::Minimal(ref levels) => levels
288 .iter()
289 .find(|&&(ref test_module, _)| test_module == module)
290 .map(|&(_, level)| level),
291 LevelConfiguration::Many(ref levels) => levels.get(module).cloned(),
292 }
293 }
294 }
295
296 impl Log for Output {
enabled(&self, metadata: &log::Metadata) -> bool297 fn enabled(&self, metadata: &log::Metadata) -> bool {
298 match *self {
299 Output::Stdout(ref s) => s.enabled(metadata),
300 Output::Stderr(ref s) => s.enabled(metadata),
301 Output::File(ref s) => s.enabled(metadata),
302 Output::Sender(ref s) => s.enabled(metadata),
303 Output::Dispatch(ref s) => s.enabled(metadata),
304 Output::SharedDispatch(ref s) => s.enabled(metadata),
305 Output::OtherBoxed(ref s) => s.enabled(metadata),
306 Output::OtherStatic(ref s) => s.enabled(metadata),
307 #[cfg(all(not(windows), feature = "syslog-3"))]
308 Output::Syslog3(ref s) => s.enabled(metadata),
309 #[cfg(all(not(windows), feature = "syslog-4"))]
310 Output::Syslog4Rfc3164(ref s) => s.enabled(metadata),
311 #[cfg(all(not(windows), feature = "syslog-4"))]
312 Output::Syslog4Rfc5424(ref s) => s.enabled(metadata),
313 Output::Panic(ref s) => s.enabled(metadata),
314 Output::Writer(ref s) => s.enabled(metadata),
315 #[cfg(feature = "date-based")]
316 Output::DateBased(ref s) => s.enabled(metadata),
317 #[cfg(all(not(windows), feature = "reopen-03"))]
318 Output::Reopen(ref s) => s.enabled(metadata),
319 }
320 }
321
log(&self, record: &log::Record)322 fn log(&self, record: &log::Record) {
323 match *self {
324 Output::Stdout(ref s) => s.log(record),
325 Output::Stderr(ref s) => s.log(record),
326 Output::File(ref s) => s.log(record),
327 Output::Sender(ref s) => s.log(record),
328 Output::Dispatch(ref s) => s.log(record),
329 Output::SharedDispatch(ref s) => s.log(record),
330 Output::OtherBoxed(ref s) => s.log(record),
331 Output::OtherStatic(ref s) => s.log(record),
332 #[cfg(all(not(windows), feature = "syslog-3"))]
333 Output::Syslog3(ref s) => s.log(record),
334 #[cfg(all(not(windows), feature = "syslog-4"))]
335 Output::Syslog4Rfc3164(ref s) => s.log(record),
336 #[cfg(all(not(windows), feature = "syslog-4"))]
337 Output::Syslog4Rfc5424(ref s) => s.log(record),
338 Output::Panic(ref s) => s.log(record),
339 Output::Writer(ref s) => s.log(record),
340 #[cfg(feature = "date-based")]
341 Output::DateBased(ref s) => s.log(record),
342 #[cfg(all(not(windows), feature = "reopen-03"))]
343 Output::Reopen(ref s) => s.log(record),
344 }
345 }
346
flush(&self)347 fn flush(&self) {
348 match *self {
349 Output::Stdout(ref s) => s.flush(),
350 Output::Stderr(ref s) => s.flush(),
351 Output::File(ref s) => s.flush(),
352 Output::Sender(ref s) => s.flush(),
353 Output::Dispatch(ref s) => s.flush(),
354 Output::SharedDispatch(ref s) => s.flush(),
355 Output::OtherBoxed(ref s) => s.flush(),
356 Output::OtherStatic(ref s) => s.flush(),
357 #[cfg(all(not(windows), feature = "syslog-3"))]
358 Output::Syslog3(ref s) => s.flush(),
359 #[cfg(all(not(windows), feature = "syslog-4"))]
360 Output::Syslog4Rfc3164(ref s) => s.flush(),
361 #[cfg(all(not(windows), feature = "syslog-4"))]
362 Output::Syslog4Rfc5424(ref s) => s.flush(),
363 Output::Panic(ref s) => s.flush(),
364 Output::Writer(ref s) => s.flush(),
365 #[cfg(feature = "date-based")]
366 Output::DateBased(ref s) => s.flush(),
367 #[cfg(all(not(windows), feature = "reopen-03"))]
368 Output::Reopen(ref s) => s.flush(),
369 }
370 }
371 }
372
373 impl Log for Null {
enabled(&self, _: &log::Metadata) -> bool374 fn enabled(&self, _: &log::Metadata) -> bool {
375 false
376 }
377
log(&self, _: &log::Record)378 fn log(&self, _: &log::Record) {}
379
flush(&self)380 fn flush(&self) {}
381 }
382
383 impl Log for Dispatch {
enabled(&self, metadata: &log::Metadata) -> bool384 fn enabled(&self, metadata: &log::Metadata) -> bool {
385 self.deep_enabled(metadata)
386 }
387
log(&self, record: &log::Record)388 fn log(&self, record: &log::Record) {
389 if self.shallow_enabled(record.metadata()) {
390 match self.format {
391 Some(ref format) => {
392 // flag to ensure the log message is completed even if the formatter doesn't
393 // complete the callback.
394 let mut callback_called_flag = false;
395
396 (format)(
397 FormatCallback(InnerFormatCallback(
398 &mut callback_called_flag,
399 self,
400 record,
401 )),
402 record.args(),
403 record,
404 );
405
406 if !callback_called_flag {
407 self.finish_logging(record);
408 }
409 }
410 None => {
411 self.finish_logging(record);
412 }
413 }
414 }
415 }
416
flush(&self)417 fn flush(&self) {
418 for log in &self.output {
419 log.flush();
420 }
421 }
422 }
423
424 impl Dispatch {
finish_logging(&self, record: &log::Record)425 fn finish_logging(&self, record: &log::Record) {
426 for log in &self.output {
427 log.log(record);
428 }
429 }
430
431 /// Check whether this log's filters prevent the given log from happening.
shallow_enabled(&self, metadata: &log::Metadata) -> bool432 fn shallow_enabled(&self, metadata: &log::Metadata) -> bool {
433 metadata.level()
434 <= self
435 .levels
436 .find_module(metadata.target())
437 .unwrap_or(self.default_level)
438 && self.filters.iter().all(|f| f(metadata))
439 }
440
441 /// Check whether a log with the given metadata would eventually end up
442 /// outputting something.
443 ///
444 /// This is recursive, and checks children.
deep_enabled(&self, metadata: &log::Metadata) -> bool445 fn deep_enabled(&self, metadata: &log::Metadata) -> bool {
446 self.shallow_enabled(metadata) && self.output.iter().any(|l| l.enabled(metadata))
447 }
448 }
449
450 impl<'a> FormatCallback<'a> {
451 /// Complete the formatting call that this FormatCallback was created for.
452 ///
453 /// This will call the rest of the logging chain using the given formatted
454 /// message as the new payload message.
455 ///
456 /// Example usage:
457 ///
458 /// ```
459 /// # fern::Dispatch::new()
460 /// # .format(|callback: fern::FormatCallback, message, record| {
461 /// callback.finish(format_args!("[{}] {}", record.level(), message))
462 /// # })
463 /// # .into_log();
464 /// ```
465 ///
466 /// See [`format_args!`].
467 ///
468 /// [`format_args!`]: https://doc.rust-lang.org/std/macro.format_args.html
finish(self, formatted_message: fmt::Arguments)469 pub fn finish(self, formatted_message: fmt::Arguments) {
470 let FormatCallback(InnerFormatCallback(callback_called_flag, dispatch, record)) = self;
471
472 // let the dispatch know that we did in fact get called.
473 *callback_called_flag = true;
474
475 // NOTE: This needs to be updated whenever new things are added to
476 // `log::Record`.
477 let new_record = log::RecordBuilder::new()
478 .args(formatted_message)
479 .metadata(record.metadata().clone())
480 .level(record.level())
481 .target(record.target())
482 .module_path(record.module_path())
483 .file(record.file())
484 .line(record.line())
485 .build();
486
487 dispatch.finish_logging(&new_record);
488 }
489 }
490
491 // No need to write this twice (used for Stdout and Stderr structs)
492 macro_rules! std_log_impl {
493 ($ident:ident) => {
494 impl Log for $ident {
495 fn enabled(&self, _: &log::Metadata) -> bool {
496 true
497 }
498
499 fn log(&self, record: &log::Record) {
500 fallback_on_error(record, |record| {
501 if cfg!(feature = "meta-logging-in-format") {
502 // Formatting first prevents deadlocks when the process of formatting
503 // itself is logged. note: this is only ever needed if some
504 // Debug, Display, or other formatting trait itself is
505 // logging things too.
506 let msg = format!("{}{}", record.args(), self.line_sep);
507
508 write!(self.stream.lock(), "{}", msg)?;
509 } else {
510 write!(self.stream.lock(), "{}{}", record.args(), self.line_sep)?;
511 }
512
513 Ok(())
514 });
515 }
516
517 fn flush(&self) {
518 let _ = self.stream.lock().flush();
519 }
520 }
521 };
522 }
523
524 std_log_impl!(Stdout);
525 std_log_impl!(Stderr);
526
527 macro_rules! writer_log_impl {
528 ($ident:ident) => {
529 impl Log for $ident {
530 fn enabled(&self, _: &log::Metadata) -> bool {
531 true
532 }
533
534 fn log(&self, record: &log::Record) {
535 fallback_on_error(record, |record| {
536 if cfg!(feature = "meta-logging-in-format") {
537 // Formatting first prevents deadlocks on file-logging,
538 // when the process of formatting itself is logged.
539 // note: this is only ever needed if some Debug, Display, or other
540 // formatting trait itself is logging.
541 let msg = format!("{}{}", record.args(), self.line_sep);
542
543 let mut writer = self.stream.lock().unwrap_or_else(|e| e.into_inner());
544
545 write!(writer, "{}", msg)?;
546
547 writer.flush()?;
548 } else {
549 let mut writer = self.stream.lock().unwrap_or_else(|e| e.into_inner());
550
551 write!(writer, "{}{}", record.args(), self.line_sep)?;
552
553 writer.flush()?;
554 }
555 Ok(())
556 });
557 }
558
559 fn flush(&self) {
560 let _ = self
561 .stream
562 .lock()
563 .unwrap_or_else(|e| e.into_inner())
564 .flush();
565 }
566 }
567 };
568 }
569
570 writer_log_impl!(File);
571 writer_log_impl!(Writer);
572
573 #[cfg(all(not(windows), feature = "reopen-03"))]
574 writer_log_impl!(Reopen);
575
576 impl Log for Sender {
enabled(&self, _: &log::Metadata) -> bool577 fn enabled(&self, _: &log::Metadata) -> bool {
578 true
579 }
580
log(&self, record: &log::Record)581 fn log(&self, record: &log::Record) {
582 fallback_on_error(record, |record| {
583 let msg = format!("{}{}", record.args(), self.line_sep);
584 self.stream
585 .lock()
586 .unwrap_or_else(|e| e.into_inner())
587 .send(msg)?;
588 Ok(())
589 });
590 }
591
flush(&self)592 fn flush(&self) {}
593 }
594
595 #[cfg(any(feature = "syslog-3", feature = "syslog-4"))]
596 macro_rules! send_syslog {
597 ($logger:expr, $level:expr, $message:expr) => {
598 use log::Level;
599 match $level {
600 Level::Error => $logger.err($message)?,
601 Level::Warn => $logger.warning($message)?,
602 Level::Info => $logger.info($message)?,
603 Level::Debug | Level::Trace => $logger.debug($message)?,
604 }
605 };
606 }
607
608 #[cfg(all(not(windows), feature = "syslog-3"))]
609 impl Log for Syslog3 {
enabled(&self, _: &log::Metadata) -> bool610 fn enabled(&self, _: &log::Metadata) -> bool {
611 true
612 }
613
log(&self, record: &log::Record)614 fn log(&self, record: &log::Record) {
615 fallback_on_error(record, |record| {
616 let message = record.args();
617 send_syslog!(self.inner, record.level(), message);
618
619 Ok(())
620 });
621 }
flush(&self)622 fn flush(&self) {}
623 }
624
625 #[cfg(all(not(windows), feature = "syslog-4"))]
626 impl Log for Syslog4Rfc3164 {
enabled(&self, _: &log::Metadata) -> bool627 fn enabled(&self, _: &log::Metadata) -> bool {
628 true
629 }
630
log(&self, record: &log::Record)631 fn log(&self, record: &log::Record) {
632 fallback_on_error(record, |record| {
633 let message = record.args().to_string();
634 let mut log = self.inner.lock().unwrap_or_else(|e| e.into_inner());
635 send_syslog!(log, record.level(), message);
636
637 Ok(())
638 });
639 }
flush(&self)640 fn flush(&self) {}
641 }
642
643 #[cfg(all(not(windows), feature = "syslog-4"))]
644 impl Log for Syslog4Rfc5424 {
enabled(&self, _: &log::Metadata) -> bool645 fn enabled(&self, _: &log::Metadata) -> bool {
646 true
647 }
648
log(&self, record: &log::Record)649 fn log(&self, record: &log::Record) {
650 fallback_on_error(record, |record| {
651 let transformed = (self.transform)(record);
652 let mut log = self.inner.lock().unwrap_or_else(|e| e.into_inner());
653 send_syslog!(log, record.level(), transformed);
654
655 Ok(())
656 });
657 }
flush(&self)658 fn flush(&self) {}
659 }
660
661 impl Log for Panic {
enabled(&self, _: &log::Metadata) -> bool662 fn enabled(&self, _: &log::Metadata) -> bool {
663 true
664 }
665
log(&self, record: &log::Record)666 fn log(&self, record: &log::Record) {
667 panic!("{}", record.args());
668 }
669
flush(&self)670 fn flush(&self) {}
671 }
672
673 #[cfg(feature = "date-based")]
674 impl Log for DateBased {
enabled(&self, _: &log::Metadata) -> bool675 fn enabled(&self, _: &log::Metadata) -> bool {
676 true
677 }
678
log(&self, record: &log::Record)679 fn log(&self, record: &log::Record) {
680 fallback_on_error(record, |record| {
681 // Formatting first prevents deadlocks on file-logging,
682 // when the process of formatting itself is logged.
683 // note: this is only ever needed if some Debug, Display, or other
684 // formatting trait itself is logging.
685 #[cfg(feature = "meta-logging-in-format")]
686 let msg = format!("{}{}", record.args(), self.config.line_sep);
687
688 let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
689
690 // check if log needs to be rotated
691 let new_suffix = self.config.compute_current_suffix();
692 if state.file_stream.is_none() || state.current_suffix != new_suffix {
693 let file_open_result = self.config.open_current_log_file(&new_suffix);
694 match file_open_result {
695 Ok(file) => {
696 state.replace_file(new_suffix, Some(file));
697 }
698 Err(e) => {
699 state.replace_file(new_suffix, None);
700 return Err(e.into());
701 }
702 }
703 }
704
705 // either just initialized writer above, or already errored out.
706 let writer = state.file_stream.as_mut().unwrap();
707
708 #[cfg(feature = "meta-logging-in-format")]
709 write!(writer, "{}", msg)?;
710 #[cfg(not(feature = "meta-logging-in-format"))]
711 write!(writer, "{}{}", record.args(), self.config.line_sep)?;
712
713 writer.flush()?;
714
715 Ok(())
716 });
717 }
718
flush(&self)719 fn flush(&self) {
720 let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
721
722 if let Some(stream) = &mut state.file_stream {
723 let _ = stream.flush();
724 }
725 }
726 }
727
728 #[inline(always)]
fallback_on_error<F>(record: &log::Record, log_func: F) where F: FnOnce(&log::Record) -> Result<(), LogError>,729 fn fallback_on_error<F>(record: &log::Record, log_func: F)
730 where
731 F: FnOnce(&log::Record) -> Result<(), LogError>,
732 {
733 if let Err(error) = log_func(record) {
734 backup_logging(record, &error)
735 }
736 }
737
backup_logging(record: &log::Record, error: &LogError)738 fn backup_logging(record: &log::Record, error: &LogError) {
739 let second = write!(
740 io::stderr(),
741 "Error performing logging.\
742 \n\tattempted to log: {}\
743 \n\trecord: {:?}\
744 \n\tlogging error: {}",
745 record.args(),
746 record,
747 error
748 );
749
750 if let Err(second_error) = second {
751 panic!(
752 "Error performing stderr logging after error occurred during regular logging.\
753 \n\tattempted to log: {}\
754 \n\trecord: {:?}\
755 \n\tfirst logging error: {}\
756 \n\tstderr error: {}",
757 record.args(),
758 record,
759 error,
760 second_error,
761 );
762 }
763 }
764
765 #[derive(Debug)]
766 enum LogError {
767 Io(io::Error),
768 Send(mpsc::SendError<String>),
769 #[cfg(all(not(windows), feature = "syslog-4"))]
770 Syslog4(syslog4::Error),
771 }
772
773 impl fmt::Display for LogError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result774 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
775 match *self {
776 LogError::Io(ref e) => write!(f, "{}", e),
777 LogError::Send(ref e) => write!(f, "{}", e),
778 #[cfg(all(not(windows), feature = "syslog-4"))]
779 LogError::Syslog4(ref e) => write!(f, "{}", e),
780 }
781 }
782 }
783
784 impl From<io::Error> for LogError {
from(error: io::Error) -> Self785 fn from(error: io::Error) -> Self {
786 LogError::Io(error)
787 }
788 }
789
790 impl From<mpsc::SendError<String>> for LogError {
from(error: mpsc::SendError<String>) -> Self791 fn from(error: mpsc::SendError<String>) -> Self {
792 LogError::Send(error)
793 }
794 }
795
796 #[cfg(all(not(windows), feature = "syslog-4"))]
797 impl From<syslog4::Error> for LogError {
from(error: syslog4::Error) -> Self798 fn from(error: syslog4::Error) -> Self {
799 LogError::Syslog4(error)
800 }
801 }
802
803 #[cfg(test)]
804 mod test {
805 use super::LevelConfiguration;
806 use log::LevelFilter::*;
807
808 #[test]
test_level_config_find_exact_minimal()809 fn test_level_config_find_exact_minimal() {
810 let config = LevelConfiguration::Minimal(
811 vec![("mod1", Info), ("mod2", Debug), ("mod3", Off)]
812 .into_iter()
813 .map(|(k, v)| (k.into(), v))
814 .collect(),
815 );
816
817 assert_eq!(config.find_exact("mod1"), Some(Info));
818 assert_eq!(config.find_exact("mod2"), Some(Debug));
819 assert_eq!(config.find_exact("mod3"), Some(Off));
820 }
821
822 #[test]
test_level_config_find_exact_many()823 fn test_level_config_find_exact_many() {
824 let config = LevelConfiguration::Many(
825 vec![("mod1", Info), ("mod2", Debug), ("mod3", Off)]
826 .into_iter()
827 .map(|(k, v)| (k.into(), v))
828 .collect(),
829 );
830
831 assert_eq!(config.find_exact("mod1"), Some(Info));
832 assert_eq!(config.find_exact("mod2"), Some(Debug));
833 assert_eq!(config.find_exact("mod3"), Some(Off));
834 }
835
836 #[test]
test_level_config_simple_hierarchy()837 fn test_level_config_simple_hierarchy() {
838 let config = LevelConfiguration::Minimal(
839 vec![("mod1", Info), ("mod2::sub_mod", Debug), ("mod3", Off)]
840 .into_iter()
841 .map(|(k, v)| (k.into(), v))
842 .collect(),
843 );
844
845 assert_eq!(config.find_module("mod1::sub_mod"), Some(Info));
846 assert_eq!(config.find_module("mod2::sub_mod::sub_mod_2"), Some(Debug));
847 assert_eq!(config.find_module("mod3::sub_mod::sub_mod_2"), Some(Off));
848 }
849
850 #[test]
test_level_config_hierarchy_correct()851 fn test_level_config_hierarchy_correct() {
852 let config = LevelConfiguration::Minimal(
853 vec![
854 ("root", Trace),
855 ("root::sub1", Debug),
856 ("root::sub2", Info),
857 // should work with all insertion orders
858 ("root::sub2::sub2.3::sub2.4", Error),
859 ("root::sub2::sub2.3", Warn),
860 ("root::sub3", Off),
861 ]
862 .into_iter()
863 .map(|(k, v)| (k.into(), v))
864 .collect(),
865 );
866
867 assert_eq!(config.find_module("root"), Some(Trace));
868 assert_eq!(config.find_module("root::other_module"), Some(Trace));
869
870 // We want to ensure that it does pick up most specific level before trying
871 // anything more general.
872 assert_eq!(config.find_module("root::sub1"), Some(Debug));
873 assert_eq!(config.find_module("root::sub1::other_module"), Some(Debug));
874
875 assert_eq!(config.find_module("root::sub2"), Some(Info));
876 assert_eq!(config.find_module("root::sub2::other"), Some(Info));
877
878 assert_eq!(config.find_module("root::sub2::sub2.3"), Some(Warn));
879 assert_eq!(
880 config.find_module("root::sub2::sub2.3::sub2.4"),
881 Some(Error)
882 );
883
884 assert_eq!(config.find_module("root::sub3"), Some(Off));
885 assert_eq!(
886 config.find_module("root::sub3::any::children::of::sub3"),
887 Some(Off)
888 );
889 }
890
891 #[test]
test_level_config_similar_names_are_not_same()892 fn test_level_config_similar_names_are_not_same() {
893 let config = LevelConfiguration::Minimal(
894 vec![("root", Trace), ("rootay", Info)]
895 .into_iter()
896 .map(|(k, v)| (k.into(), v))
897 .collect(),
898 );
899
900 assert_eq!(config.find_module("root"), Some(Trace));
901 assert_eq!(config.find_module("root::sub"), Some(Trace));
902 assert_eq!(config.find_module("rooty"), None);
903 assert_eq!(config.find_module("rooty::sub"), None);
904 assert_eq!(config.find_module("rootay"), Some(Info));
905 assert_eq!(config.find_module("rootay::sub"), Some(Info));
906 }
907
908 #[test]
test_level_config_single_colon_is_not_double_colon()909 fn test_level_config_single_colon_is_not_double_colon() {
910 let config = LevelConfiguration::Minimal(
911 vec![
912 ("root", Trace),
913 ("root::su", Debug),
914 ("root::su:b2", Info),
915 ("root::sub2", Warn),
916 ]
917 .into_iter()
918 .map(|(k, v)| (k.into(), v))
919 .collect(),
920 );
921
922 assert_eq!(config.find_module("root"), Some(Trace));
923
924 assert_eq!(config.find_module("root::su"), Some(Debug));
925 assert_eq!(config.find_module("root::su::b2"), Some(Debug));
926
927 assert_eq!(config.find_module("root::su:b2"), Some(Info));
928 assert_eq!(config.find_module("root::su:b2::b3"), Some(Info));
929
930 assert_eq!(config.find_module("root::sub2"), Some(Warn));
931 assert_eq!(config.find_module("root::sub2::b3"), Some(Warn));
932 }
933
934 #[test]
test_level_config_all_chars()935 fn test_level_config_all_chars() {
936 let config = LevelConfiguration::Minimal(
937 vec![("♲", Trace), ("☸", Debug), ("♲::☸", Info), ("♲::\t", Debug)]
938 .into_iter()
939 .map(|(k, v)| (k.into(), v))
940 .collect(),
941 );
942
943 assert_eq!(config.find_module("♲"), Some(Trace));
944 assert_eq!(config.find_module("♲::other"), Some(Trace));
945
946 assert_eq!(config.find_module("☸"), Some(Debug));
947 assert_eq!(config.find_module("☸::any"), Some(Debug));
948
949 assert_eq!(config.find_module("♲::☸"), Some(Info));
950 assert_eq!(config.find_module("♲☸"), None);
951
952 assert_eq!(config.find_module("♲::\t"), Some(Debug));
953 assert_eq!(config.find_module("♲::\t::\n\n::\t"), Some(Debug));
954 assert_eq!(config.find_module("♲::\t\t"), Some(Trace));
955 }
956 }
957