fern/builders.rs
1use std::{
2 borrow::Cow,
3 cmp, fmt, fs, io,
4 io::Write,
5 sync::{mpsc::Sender, Arc, Mutex},
6};
7
8#[cfg(feature = "date-based")]
9use std::path::{Path, PathBuf};
10
11#[cfg(all(not(windows), any(feature = "syslog-4", feature = "syslog-6")))]
12use std::collections::HashMap;
13
14#[cfg(all(not(windows), feature = "syslog-7"))]
15use std::collections::BTreeMap;
16
17use log::Log;
18
19use crate::{log_impl, Filter, FormatCallback, Formatter};
20
21#[cfg(feature = "date-based")]
22use crate::log_impl::DateBasedState;
23
24#[cfg(all(not(windows), feature = "syslog-4"))]
25use crate::{Syslog4Rfc3164Logger, Syslog4Rfc5424Logger, Syslog4TransformFn};
26
27#[cfg(all(not(windows), feature = "syslog-6"))]
28use crate::{Syslog6Rfc3164Logger, Syslog6Rfc5424Logger, Syslog6TransformFn};
29
30#[cfg(all(not(windows), feature = "syslog-7"))]
31use crate::{Syslog7Rfc3164Logger, Syslog7Rfc5424Logger, Syslog7TransformFn};
32
33/// The base dispatch logger.
34///
35/// This allows for formatting log records, limiting what records can be passed
36/// through, and then dispatching records to other dispatch loggers or output
37/// loggers.
38///
39/// Note that all methods are position-insensitive.
40/// `Dispatch::new().format(a).chain(b)` produces the exact same result
41/// as `Dispatch::new().chain(b).format(a)`. Given this, it is preferred to put
42/// 'format' and other modifiers before 'chain' for the sake of clarity.
43///
44/// Example usage demonstrating all features:
45///
46/// ```no_run
47/// # // no_run because this creates log files.
48/// use std::{fs, io};
49///
50/// # fn setup_logger() -> Result<(), fern::InitError> {
51/// fern::Dispatch::new()
52/// .format(|out, message, record| {
53/// out.finish(format_args!(
54/// "[{} {}] {}",
55/// record.level(),
56/// record.target(),
57/// message,
58/// ))
59/// })
60/// .chain(
61/// fern::Dispatch::new()
62/// // by default only accept warn messages
63/// .level(log::LevelFilter::Warn)
64/// // accept info messages from the current crate too
65/// .level_for("my_crate", log::LevelFilter::Info)
66/// // `io::Stdout`, `io::Stderr` and `io::File` can be directly passed in.
67/// .chain(io::stdout()),
68/// )
69/// .chain(
70/// fern::Dispatch::new()
71/// // output all messages
72/// .level(log::LevelFilter::Trace)
73/// // except for hyper, in that case only show info messages
74/// .level_for("hyper", log::LevelFilter::Info)
75/// // `log_file(x)` equates to
76/// // `OpenOptions::new().write(true).append(true).create(true).open(x)`
77/// .chain(fern::log_file("persistent-log.log")?)
78/// .chain(
79/// fs::OpenOptions::new()
80/// .write(true)
81/// .create(true)
82/// .truncate(true)
83/// .create(true)
84/// .open("/tmp/temp.log")?,
85/// ),
86/// )
87/// .chain(
88/// fern::Dispatch::new()
89/// .level(log::LevelFilter::Error)
90/// .filter(|_meta_data| {
91/// // as an example, randomly reject half of the messages
92/// # /*
93/// rand::random()
94/// # */
95/// # true
96/// })
97/// .chain(io::stderr()),
98/// )
99/// // and finally, set as the global logger!
100/// .apply()?;
101/// # Ok(())
102/// # }
103/// #
104/// # fn main() { setup_logger().expect("failed to set up logger") }
105/// ```
106#[must_use = "this is only a logger configuration and must be consumed with into_log() or apply()"]
107pub struct Dispatch {
108 format: Option<Box<Formatter>>,
109 children: Vec<OutputInner>,
110 default_level: log::LevelFilter,
111 levels: Vec<(Cow<'static, str>, log::LevelFilter)>,
112 filters: Vec<Box<Filter>>,
113}
114
115/// Logger which is usable as an output for multiple other loggers.
116///
117/// This struct contains a built logger stored in an [`Arc`], and can be
118/// safely cloned.
119///
120/// See [`Dispatch::into_shared`].
121///
122/// [`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html
123/// [`Dispatch::into_shared`]: struct.Dispatch.html#method.into_shared
124#[derive(Clone)]
125pub struct SharedDispatch {
126 inner: Arc<log_impl::Dispatch>,
127 min_level: log::LevelFilter,
128}
129
130impl Dispatch {
131 /// Creates a dispatch, which will initially do nothing.
132 #[inline]
133 pub fn new() -> Self {
134 Dispatch {
135 format: None,
136 children: Vec::new(),
137 default_level: log::LevelFilter::Trace,
138 levels: Vec::new(),
139 filters: Vec::new(),
140 }
141 }
142
143 /// Sets the formatter of this dispatch. The closure should accept a
144 /// callback, a message and a log record, and write the resulting
145 /// format to the writer.
146 ///
147 /// The log record is passed for completeness, but the `args()` method of
148 /// the record should be ignored, and the [`fmt::Arguments`] given
149 /// should be used instead. `record.args()` may be used to retrieve the
150 /// _original_ log message, but in order to allow for true log
151 /// chaining, formatters should use the given message instead whenever
152 /// including the message in the output.
153 ///
154 /// To avoid all allocation of intermediate results, the formatter is
155 /// "completed" by calling a callback, which then calls the rest of the
156 /// logging chain with the new formatted message. The callback object keeps
157 /// track of if it was called or not via a stack boolean as well, so if
158 /// you don't use `out.finish` the log message will continue down
159 /// the logger chain unformatted.
160 ///
161 /// [`fmt::Arguments`]: https://doc.rust-lang.org/std/fmt/struct.Arguments.html
162 ///
163 /// Example usage:
164 ///
165 /// ```
166 /// fern::Dispatch::new().format(|out, message, record| {
167 /// out.finish(format_args!(
168 /// "[{} {}] {}",
169 /// record.level(),
170 /// record.target(),
171 /// message
172 /// ))
173 /// })
174 /// # .into_log();
175 /// ```
176 #[inline]
177 pub fn format<F>(mut self, formatter: F) -> Self
178 where
179 F: Fn(FormatCallback, &fmt::Arguments, &log::Record) + Sync + Send + 'static,
180 {
181 self.format = Some(Box::new(formatter));
182 self
183 }
184
185 /// Adds a child to this dispatch.
186 ///
187 /// All log records which pass all filters will be formatted and then sent
188 /// to all child loggers in sequence.
189 ///
190 /// Note: If the child logger is also a Dispatch, and cannot accept any log
191 /// records, it will be dropped. This only happens if the child either
192 /// has no children itself, or has a minimum log level of
193 /// [`LevelFilter::Off`].
194 ///
195 /// [`LevelFilter::Off`]: https://docs.rs/log/0.4/log/enum.LevelFilter.html#variant.Off
196 ///
197 /// Example usage:
198 ///
199 /// ```
200 /// fern::Dispatch::new().chain(fern::Dispatch::new().chain(std::io::stdout()))
201 /// # .into_log();
202 /// ```
203 #[inline]
204 pub fn chain<T: Into<Output>>(mut self, logger: T) -> Self {
205 self.children.push(logger.into().0);
206 self
207 }
208
209 /// Sets the overarching level filter for this logger. All messages not
210 /// already filtered by something set by [`Dispatch::level_for`] will
211 /// be affected.
212 ///
213 /// All messages filtered will be discarded if less severe than the given
214 /// level.
215 ///
216 /// Default level is [`LevelFilter::Trace`].
217 ///
218 /// [`Dispatch::level_for`]: #method.level_for
219 /// [`LevelFilter::Trace`]: https://docs.rs/log/0.4/log/enum.LevelFilter.html#variant.Trace
220 ///
221 /// Example usage:
222 ///
223 /// ```
224 /// # fn main() {
225 /// fern::Dispatch::new().level(log::LevelFilter::Info)
226 /// # .into_log();
227 /// # }
228 /// ```
229 #[inline]
230 pub fn level(mut self, level: log::LevelFilter) -> Self {
231 self.default_level = level;
232 self
233 }
234
235 /// Sets a per-target log level filter. Default target for log messages is
236 /// `crate_name::module_name` or
237 /// `crate_name` for logs in the crate root. Targets can also be set with
238 /// `info!(target: "target-name", ...)`.
239 ///
240 /// For each log record fern will first try to match the most specific
241 /// level_for, and then progressively more general ones until either a
242 /// matching level is found, or the default level is used.
243 ///
244 /// For example, a log for the target `hyper::http::h1` will first test a
245 /// level_for for `hyper::http::h1`, then for `hyper::http`, then for
246 /// `hyper`, then use the default level.
247 ///
248 /// Examples:
249 ///
250 /// A program wants to include a lot of debugging output, but the library
251 /// "hyper" is known to work well, so debug output from it should be
252 /// excluded:
253 ///
254 /// ```
255 /// # fn main() {
256 /// fern::Dispatch::new()
257 /// .level(log::LevelFilter::Trace)
258 /// .level_for("hyper", log::LevelFilter::Info)
259 /// # .into_log();
260 /// # }
261 /// ```
262 ///
263 /// A program has a ton of debug output per-module, but there is so much
264 /// that debugging more than one module at a time is not very useful.
265 /// The command line accepts a list of modules to debug, while keeping the
266 /// rest of the program at info level:
267 ///
268 /// ```
269 /// fn setup_logging<T, I>(verbose_modules: T) -> Result<(), fern::InitError>
270 /// where
271 /// I: AsRef<str>,
272 /// T: IntoIterator<Item = I>,
273 /// {
274 /// let mut config = fern::Dispatch::new().level(log::LevelFilter::Info);
275 ///
276 /// for module_name in verbose_modules {
277 /// config = config.level_for(
278 /// format!("my_crate_name::{}", module_name.as_ref()),
279 /// log::LevelFilter::Debug,
280 /// );
281 /// }
282 ///
283 /// config.chain(std::io::stdout()).apply()?;
284 ///
285 /// Ok(())
286 /// }
287 /// #
288 /// # // we're ok with apply() failing.
289 /// # fn main() { let _ = setup_logging(&["hi"]); }
290 /// ```
291 #[inline]
292 pub fn level_for<T: Into<Cow<'static, str>>>(
293 mut self,
294 module: T,
295 level: log::LevelFilter,
296 ) -> Self {
297 let module = module.into();
298
299 if let Some((index, _)) = self
300 .levels
301 .iter()
302 .enumerate()
303 .find(|(_, (name, _))| *name == module)
304 {
305 self.levels.remove(index);
306 }
307
308 self.levels.push((module, level));
309 self
310 }
311
312 /// Adds a custom filter which can reject messages passing through this
313 /// logger.
314 ///
315 /// The logger will continue to process log records only if all filters
316 /// return `true`.
317 ///
318 /// [`Dispatch::level`] and [`Dispatch::level_for`] are preferred if
319 /// applicable.
320 ///
321 /// [`Dispatch::level`]: #method.level
322 /// [`Dispatch::level_for`]: #method.level_for
323 ///
324 /// Example usage:
325 ///
326 /// This sends error level messages to stderr and others to stdout.
327 ///
328 /// ```
329 /// # fn main() {
330 /// fern::Dispatch::new()
331 /// .level(log::LevelFilter::Info)
332 /// .chain(
333 /// fern::Dispatch::new()
334 /// .filter(|metadata| {
335 /// // Reject messages with the `Error` log level.
336 /// metadata.level() != log::LevelFilter::Error
337 /// })
338 /// .chain(std::io::stderr()),
339 /// )
340 /// .chain(
341 /// fern::Dispatch::new()
342 /// .level(log::LevelFilter::Error)
343 /// .chain(std::io::stdout()),
344 /// )
345 /// # .into_log();
346 /// # }
347 #[inline]
348 pub fn filter<F>(mut self, filter: F) -> Self
349 where
350 F: Fn(&log::Metadata) -> bool + Send + Sync + 'static,
351 {
352 self.filters.push(Box::new(filter));
353 self
354 }
355
356 /// Builds this dispatch and stores it in a clonable structure containing
357 /// an [`Arc`].
358 ///
359 /// Once "shared", the dispatch can be used as an output for multiple other
360 /// dispatch loggers.
361 ///
362 /// Example usage:
363 ///
364 /// This separates info and warn messages, sending info to stdout + a log
365 /// file, and warn to stderr + the same log file. Shared is used so the
366 /// program only opens "file.log" once.
367 ///
368 /// ```no_run
369 /// # fn setup_logger() -> Result<(), fern::InitError> {
370 ///
371 /// let file_out = fern::Dispatch::new()
372 /// .chain(fern::log_file("file.log")?)
373 /// .into_shared();
374 ///
375 /// let info_out = fern::Dispatch::new()
376 /// .level(log::LevelFilter::Debug)
377 /// .filter(|metadata|
378 /// // keep only info and debug (reject warn and error)
379 /// metadata.level() <= log::Level::Info)
380 /// .chain(std::io::stdout())
381 /// .chain(file_out.clone());
382 ///
383 /// let warn_out = fern::Dispatch::new()
384 /// .level(log::LevelFilter::Warn)
385 /// .chain(std::io::stderr())
386 /// .chain(file_out);
387 ///
388 /// fern::Dispatch::new()
389 /// .chain(info_out)
390 /// .chain(warn_out)
391 /// .apply();
392 ///
393 /// # Ok(())
394 /// # }
395 /// #
396 /// # fn main() { setup_logger().expect("failed to set up logger"); }
397 /// ```
398 ///
399 /// [`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html
400 pub fn into_shared(self) -> SharedDispatch {
401 let (min_level, dispatch) = self.into_dispatch();
402
403 SharedDispatch {
404 inner: Arc::new(dispatch),
405 min_level,
406 }
407 }
408
409 /// Builds this into the actual logger implementation.
410 ///
411 /// This could probably be refactored, but having everything in one place
412 /// is also nice.
413 fn into_dispatch(self) -> (log::LevelFilter, log_impl::Dispatch) {
414 let Dispatch {
415 format,
416 children,
417 default_level,
418 levels,
419 mut filters,
420 } = self;
421
422 let mut max_child_level = log::LevelFilter::Off;
423
424 let output = children
425 .into_iter()
426 .flat_map(|child| match child {
427 OutputInner::Stdout { stream, line_sep } => {
428 max_child_level = log::LevelFilter::Trace;
429 Some(log_impl::Output::Stdout(log_impl::Stdout {
430 stream,
431 line_sep,
432 }))
433 }
434 OutputInner::Stderr { stream, line_sep } => {
435 max_child_level = log::LevelFilter::Trace;
436 Some(log_impl::Output::Stderr(log_impl::Stderr {
437 stream,
438 line_sep,
439 }))
440 }
441 OutputInner::File { stream, line_sep } => {
442 max_child_level = log::LevelFilter::Trace;
443 Some(log_impl::Output::File(log_impl::File {
444 stream: Mutex::new(io::BufWriter::new(stream)),
445 line_sep,
446 }))
447 }
448 OutputInner::Writer { stream, line_sep } => {
449 max_child_level = log::LevelFilter::Trace;
450 Some(log_impl::Output::Writer(log_impl::Writer {
451 stream: Mutex::new(stream),
452 line_sep,
453 }))
454 }
455 #[cfg(all(not(windows), feature = "reopen-03"))]
456 OutputInner::Reopen { stream, line_sep } => {
457 max_child_level = log::LevelFilter::Trace;
458 Some(log_impl::Output::Reopen(log_impl::Reopen {
459 stream: Mutex::new(stream),
460 line_sep,
461 }))
462 }
463 #[cfg(all(not(windows), feature = "reopen-1"))]
464 OutputInner::Reopen1 { stream, line_sep } => {
465 max_child_level = log::LevelFilter::Trace;
466 Some(log_impl::Output::Reopen1(log_impl::Reopen1 {
467 stream: Mutex::new(stream),
468 line_sep,
469 }))
470 }
471 OutputInner::Sender { stream, line_sep } => {
472 max_child_level = log::LevelFilter::Trace;
473 Some(log_impl::Output::Sender(log_impl::Sender {
474 stream: Mutex::new(stream),
475 line_sep,
476 }))
477 }
478 #[cfg(all(not(windows), feature = "syslog-3"))]
479 OutputInner::Syslog3(log) => {
480 max_child_level = log::LevelFilter::Trace;
481 Some(log_impl::Output::Syslog3(log_impl::Syslog3 { inner: log }))
482 }
483 #[cfg(all(not(windows), feature = "syslog-4"))]
484 OutputInner::Syslog4Rfc3164(logger) => {
485 max_child_level = log::LevelFilter::Trace;
486 Some(log_impl::Output::Syslog4Rfc3164(log_impl::Syslog4Rfc3164 {
487 inner: Mutex::new(logger),
488 }))
489 }
490 #[cfg(all(not(windows), feature = "syslog-4"))]
491 OutputInner::Syslog4Rfc5424 { logger, transform } => {
492 max_child_level = log::LevelFilter::Trace;
493 Some(log_impl::Output::Syslog4Rfc5424(log_impl::Syslog4Rfc5424 {
494 inner: Mutex::new(logger),
495 transform,
496 }))
497 }
498 #[cfg(all(not(windows), feature = "syslog-6"))]
499 OutputInner::Syslog6Rfc3164(logger) => {
500 max_child_level = log::LevelFilter::Trace;
501 Some(log_impl::Output::Syslog6Rfc3164(log_impl::Syslog6Rfc3164 {
502 inner: Mutex::new(logger),
503 }))
504 }
505 #[cfg(all(not(windows), feature = "syslog-6"))]
506 OutputInner::Syslog6Rfc5424 { logger, transform } => {
507 max_child_level = log::LevelFilter::Trace;
508 Some(log_impl::Output::Syslog6Rfc5424(log_impl::Syslog6Rfc5424 {
509 inner: Mutex::new(logger),
510 transform,
511 }))
512 }
513 #[cfg(all(not(windows), feature = "syslog-7"))]
514 OutputInner::Syslog7Rfc3164(logger) => {
515 max_child_level = log::LevelFilter::Trace;
516 Some(log_impl::Output::Syslog7Rfc3164(log_impl::Syslog7Rfc3164 {
517 inner: Mutex::new(logger),
518 }))
519 }
520 #[cfg(all(not(windows), feature = "syslog-7"))]
521 OutputInner::Syslog7Rfc5424 { logger, transform } => {
522 max_child_level = log::LevelFilter::Trace;
523 Some(log_impl::Output::Syslog7Rfc5424(log_impl::Syslog7Rfc5424 {
524 inner: Mutex::new(logger),
525 transform,
526 }))
527 }
528 OutputInner::Panic => {
529 max_child_level = log::LevelFilter::Trace;
530 Some(log_impl::Output::Panic(log_impl::Panic))
531 }
532 OutputInner::Dispatch(child_dispatch) => {
533 let (child_level, child) = child_dispatch.into_dispatch();
534 if child_level > log::LevelFilter::Off {
535 max_child_level = cmp::max(max_child_level, child_level);
536 Some(log_impl::Output::Dispatch(child))
537 } else {
538 None
539 }
540 }
541 OutputInner::SharedDispatch(child_dispatch) => {
542 let SharedDispatch {
543 inner: child,
544 min_level: child_level,
545 } = child_dispatch;
546
547 if child_level > log::LevelFilter::Off {
548 max_child_level = cmp::max(max_child_level, child_level);
549 Some(log_impl::Output::SharedDispatch(child))
550 } else {
551 None
552 }
553 }
554 OutputInner::OtherBoxed(child_log) => {
555 max_child_level = log::LevelFilter::Trace;
556 Some(log_impl::Output::OtherBoxed(child_log))
557 }
558 OutputInner::OtherStatic(child_log) => {
559 max_child_level = log::LevelFilter::Trace;
560 Some(log_impl::Output::OtherStatic(child_log))
561 }
562 #[cfg(feature = "date-based")]
563 OutputInner::DateBased { config } => {
564 max_child_level = log::LevelFilter::Trace;
565
566 let config = log_impl::DateBasedConfig::new(
567 config.line_sep,
568 config.file_prefix,
569 config.file_suffix,
570 if config.utc_time {
571 log_impl::ConfiguredTimezone::Utc
572 } else {
573 log_impl::ConfiguredTimezone::Local
574 },
575 );
576
577 let computed_suffix = config.compute_current_suffix();
578
579 // ignore errors - we'll just retry later.
580 let initial_file = config.open_current_log_file(&computed_suffix).ok();
581
582 Some(log_impl::Output::DateBased(log_impl::DateBased {
583 config,
584 state: Mutex::new(DateBasedState::new(computed_suffix, initial_file)),
585 }))
586 }
587 })
588 .collect();
589
590 let min_level = levels
591 .iter()
592 .map(|t| t.1)
593 .max()
594 .map_or(default_level, |lvl| cmp::max(lvl, default_level));
595 let real_min = cmp::min(min_level, max_child_level);
596
597 filters.shrink_to_fit();
598
599 let dispatch = log_impl::Dispatch {
600 output,
601 default_level,
602 levels: levels.into(),
603 format,
604 filters,
605 };
606
607 (real_min, dispatch)
608 }
609
610 /// Builds this logger into a `Box<log::Log>` and calculates the minimum
611 /// log level needed to have any effect.
612 ///
613 /// While this method is exposed publicly, [`Dispatch::apply`] is typically
614 /// used instead.
615 ///
616 /// The returned LevelFilter is a calculation for all level filters of this
617 /// logger and child loggers, and is the minimum log level needed to
618 /// for a record to have any chance of passing through this logger.
619 ///
620 /// [`Dispatch::apply`]: #method.apply
621 ///
622 /// Example usage:
623 ///
624 /// ```
625 /// # fn main() {
626 /// let (min_level, log) = fern::Dispatch::new()
627 /// .level(log::LevelFilter::Info)
628 /// .chain(std::io::stdout())
629 /// .into_log();
630 ///
631 /// assert_eq!(min_level, log::LevelFilter::Info);
632 /// # }
633 /// ```
634 pub fn into_log(self) -> (log::LevelFilter, Box<dyn log::Log>) {
635 let (level, logger) = self.into_dispatch();
636 if level == log::LevelFilter::Off {
637 (level, Box::new(log_impl::Null))
638 } else {
639 (level, Box::new(logger))
640 }
641 }
642
643 /// Builds this logger and instantiates it as the global [`log`] logger.
644 ///
645 /// # Errors:
646 ///
647 /// This function will return an error if a global logger has already been
648 /// set to a previous logger.
649 ///
650 /// [`log`]: https://github.com/rust-lang-nursery/log
651 pub fn apply(self) -> Result<(), log::SetLoggerError> {
652 let (max_level, log) = self.into_log();
653
654 log::set_boxed_logger(log)?;
655 log::set_max_level(max_level);
656
657 Ok(())
658 }
659}
660
661/// This enum contains various outputs that you can send messages to.
662enum OutputInner {
663 /// Prints all messages to stdout with `line_sep` separator.
664 Stdout {
665 stream: io::Stdout,
666 line_sep: Cow<'static, str>,
667 },
668 /// Prints all messages to stderr with `line_sep` separator.
669 Stderr {
670 stream: io::Stderr,
671 line_sep: Cow<'static, str>,
672 },
673 /// Writes all messages to file with `line_sep` separator.
674 File {
675 stream: fs::File,
676 line_sep: Cow<'static, str>,
677 },
678 /// Writes all messages to the writer with `line_sep` separator.
679 Writer {
680 stream: Box<dyn Write + Send>,
681 line_sep: Cow<'static, str>,
682 },
683 /// Writes all messages to the reopen::Reopen file with `line_sep`
684 /// separator.
685 #[cfg(all(not(windows), feature = "reopen-03"))]
686 Reopen {
687 stream: reopen03::Reopen<fs::File>,
688 line_sep: Cow<'static, str>,
689 },
690 /// Writes all messages to the reopen::Reopen file with `line_sep`
691 /// separator.
692 #[cfg(all(not(windows), feature = "reopen-1"))]
693 Reopen1 {
694 stream: reopen1::Reopen<fs::File>,
695 line_sep: Cow<'static, str>,
696 },
697 /// Writes all messages to mpst::Sender with `line_sep` separator.
698 Sender {
699 stream: Sender<String>,
700 line_sep: Cow<'static, str>,
701 },
702 /// Passes all messages to other dispatch.
703 Dispatch(Dispatch),
704 /// Passes all messages to other dispatch that's shared.
705 SharedDispatch(SharedDispatch),
706 /// Passes all messages to other logger.
707 OtherBoxed(Box<dyn Log>),
708 /// Passes all messages to other logger.
709 OtherStatic(&'static dyn Log),
710 /// Passes all messages to the syslog.
711 #[cfg(all(not(windows), feature = "syslog-3"))]
712 Syslog3(syslog3::Logger),
713 /// Passes all messages to the syslog.
714 #[cfg(all(not(windows), feature = "syslog-4"))]
715 Syslog4Rfc3164(Syslog4Rfc3164Logger),
716 /// Sends all messages through the transform then passes to the syslog.
717 #[cfg(all(not(windows), feature = "syslog-4"))]
718 Syslog4Rfc5424 {
719 logger: Syslog4Rfc5424Logger,
720 transform: Box<Syslog4TransformFn>,
721 },
722 #[cfg(all(not(windows), feature = "syslog-6"))]
723 Syslog6Rfc3164(Syslog6Rfc3164Logger),
724 /// Sends all messages through the transform then passes to the syslog.
725 #[cfg(all(not(windows), feature = "syslog-6"))]
726 Syslog6Rfc5424 {
727 logger: Syslog6Rfc5424Logger,
728 transform: Box<Syslog6TransformFn>,
729 },
730 #[cfg(all(not(windows), feature = "syslog-7"))]
731 Syslog7Rfc3164(Syslog7Rfc3164Logger),
732 /// Sends all messages through the transform then passes to the syslog.
733 #[cfg(all(not(windows), feature = "syslog-7"))]
734 Syslog7Rfc5424 {
735 logger: Syslog7Rfc5424Logger,
736 transform: Box<Syslog7TransformFn>,
737 },
738 /// Panics with messages text for all messages.
739 Panic,
740 /// File logger with custom date and timestamp suffix in file name.
741 #[cfg(feature = "date-based")]
742 DateBased { config: DateBased },
743}
744
745/// Logger which will panic whenever anything is logged. The panic
746/// will be exactly the message of the log.
747///
748/// `Panic` is useful primarily as a secondary logger, filtered by warning or
749/// error.
750///
751/// # Examples
752///
753/// This configuration will output all messages to stdout and panic if an Error
754/// message is sent.
755///
756/// ```
757/// fern::Dispatch::new()
758/// // format, etc.
759/// .chain(std::io::stdout())
760/// .chain(
761/// fern::Dispatch::new()
762/// .level(log::LevelFilter::Error)
763/// .chain(fern::Panic),
764/// )
765/// # /*
766/// .apply()?;
767/// # */ .into_log();
768/// ```
769///
770/// This sets up a "panic on warn+" logger, and ignores errors so it can be
771/// called multiple times.
772///
773/// This might be useful in test setup, for example, to disallow warn-level
774/// messages.
775///
776/// ```no_run
777/// fn setup_panic_logging() {
778/// fern::Dispatch::new()
779/// .level(log::LevelFilter::Warn)
780/// .chain(fern::Panic)
781/// .apply()
782/// // ignore errors from setting up logging twice
783/// .ok();
784/// }
785/// ```
786pub struct Panic;
787
788/// Configuration for a logger output.
789pub struct Output(OutputInner);
790
791impl From<Dispatch> for Output {
792 /// Creates an output logger forwarding all messages to the dispatch.
793 fn from(log: Dispatch) -> Self {
794 Output(OutputInner::Dispatch(log))
795 }
796}
797
798impl From<SharedDispatch> for Output {
799 /// Creates an output logger forwarding all messages to the dispatch.
800 fn from(log: SharedDispatch) -> Self {
801 Output(OutputInner::SharedDispatch(log))
802 }
803}
804
805impl From<Box<dyn Log>> for Output {
806 /// Creates an output logger forwarding all messages to the custom logger.
807 fn from(log: Box<dyn Log>) -> Self {
808 Output(OutputInner::OtherBoxed(log))
809 }
810}
811
812impl From<&'static dyn Log> for Output {
813 /// Creates an output logger forwarding all messages to the custom logger.
814 fn from(log: &'static dyn Log) -> Self {
815 Output(OutputInner::OtherStatic(log))
816 }
817}
818
819impl From<fs::File> for Output {
820 /// Creates an output logger which writes all messages to the file with
821 /// `\n` as the separator.
822 ///
823 /// File writes are buffered and flushed once per log record.
824 fn from(file: fs::File) -> Self {
825 Output(OutputInner::File {
826 stream: file,
827 line_sep: "\n".into(),
828 })
829 }
830}
831
832impl From<Box<dyn Write + Send>> for Output {
833 /// Creates an output logger which writes all messages to the writer with
834 /// `\n` as the separator.
835 ///
836 /// This does no buffering and it is up to the writer to do buffering as
837 /// needed (eg. wrap it in `BufWriter`). However, flush is called after
838 /// each log record.
839 fn from(writer: Box<dyn Write + Send>) -> Self {
840 Output(OutputInner::Writer {
841 stream: writer,
842 line_sep: "\n".into(),
843 })
844 }
845}
846
847#[cfg(all(not(windows), feature = "reopen-03"))]
848impl From<reopen03::Reopen<fs::File>> for Output {
849 /// Creates an output logger which writes all messages to the file contained
850 /// in the Reopen struct, using `\n` as the separator.
851 fn from(reopen: reopen03::Reopen<fs::File>) -> Self {
852 Output(OutputInner::Reopen {
853 stream: reopen,
854 line_sep: "\n".into(),
855 })
856 }
857}
858
859#[cfg(all(not(windows), feature = "reopen-1"))]
860impl From<reopen1::Reopen<fs::File>> for Output {
861 /// Creates an output logger which writes all messages to the file contained
862 /// in the Reopen struct, using `\n` as the separator.
863 fn from(reopen: reopen1::Reopen<fs::File>) -> Self {
864 Output(OutputInner::Reopen1 {
865 stream: reopen,
866 line_sep: "\n".into(),
867 })
868 }
869}
870
871impl From<io::Stdout> for Output {
872 /// Creates an output logger which writes all messages to stdout with the
873 /// given handle and `\n` as the separator.
874 fn from(stream: io::Stdout) -> Self {
875 Output(OutputInner::Stdout {
876 stream,
877 line_sep: "\n".into(),
878 })
879 }
880}
881
882impl From<io::Stderr> for Output {
883 /// Creates an output logger which writes all messages to stderr with the
884 /// given handle and `\n` as the separator.
885 fn from(stream: io::Stderr) -> Self {
886 Output(OutputInner::Stderr {
887 stream,
888 line_sep: "\n".into(),
889 })
890 }
891}
892
893impl From<Sender<String>> for Output {
894 /// Creates an output logger which writes all messages to the given
895 /// mpsc::Sender with '\n' as the separator.
896 ///
897 /// All messages sent to the mpsc channel are suffixed with '\n'.
898 fn from(stream: Sender<String>) -> Self {
899 Output(OutputInner::Sender {
900 stream,
901 line_sep: "\n".into(),
902 })
903 }
904}
905
906#[cfg(all(not(windows), feature = "syslog-3"))]
907impl From<syslog3::Logger> for Output {
908 /// Creates an output logger which writes all messages to the given syslog
909 /// output.
910 ///
911 /// Log levels are translated trace => debug, debug => debug, info =>
912 /// informational, warn => warning, and error => error.
913 ///
914 /// This requires the `"syslog-3"` feature.
915 fn from(log: syslog3::Logger) -> Self {
916 Output(OutputInner::Syslog3(log))
917 }
918}
919
920#[cfg(all(not(windows), feature = "syslog-3"))]
921impl From<Box<syslog3::Logger>> for Output {
922 /// Creates an output logger which writes all messages to the given syslog
923 /// output.
924 ///
925 /// Log levels are translated trace => debug, debug => debug, info =>
926 /// informational, warn => warning, and error => error.
927 ///
928 /// Note that while this takes a `Box<Logger>` for convenience (syslog
929 /// methods return `Box`es), it will be immediately unboxed upon storage
930 /// in the configuration structure. This will create a configuration
931 /// identical to that created by passing a raw `syslog::Logger`.
932 ///
933 /// This requires the `"syslog-3"` feature.
934 fn from(log: Box<syslog3::Logger>) -> Self {
935 Output(OutputInner::Syslog3(*log))
936 }
937}
938
939#[cfg(all(not(windows), feature = "syslog-4"))]
940impl From<Syslog4Rfc3164Logger> for Output {
941 /// Creates an output logger which writes all messages to the given syslog.
942 ///
943 /// Log levels are translated trace => debug, debug => debug, info =>
944 /// informational, warn => warning, and error => error.
945 ///
946 /// Note that due to <https://github.com/Geal/rust-syslog/issues/41>,
947 /// logging to this backend requires one allocation per log call.
948 ///
949 /// This is for RFC 3164 loggers. To use an RFC 5424 logger, use the
950 /// [`Output::syslog_5424`] helper method.
951 ///
952 /// This requires the `"syslog-4"` feature.
953 fn from(log: Syslog4Rfc3164Logger) -> Self {
954 Output(OutputInner::Syslog4Rfc3164(log))
955 }
956}
957
958#[cfg(all(not(windows), feature = "syslog-6"))]
959impl From<Syslog6Rfc3164Logger> for Output {
960 /// Creates an output logger which writes all messages to the given syslog.
961 ///
962 /// Log levels are translated trace => debug, debug => debug, info =>
963 /// informational, warn => warning, and error => error.
964 ///
965 /// Note that due to <https://github.com/Geal/rust-syslog/issues/41>,
966 /// logging to this backend requires one allocation per log call.
967 ///
968 /// This is for RFC 3164 loggers. To use an RFC 5424 logger, use the
969 /// [`Output::syslog_5424`] helper method.
970 ///
971 /// This requires the `"syslog-6"` feature.
972 fn from(log: Syslog6Rfc3164Logger) -> Self {
973 Output(OutputInner::Syslog6Rfc3164(log))
974 }
975}
976
977#[cfg(all(not(windows), feature = "syslog-7"))]
978impl From<Syslog7Rfc3164Logger> for Output {
979 /// Creates an output logger which writes all messages to the given syslog.
980 ///
981 /// Log levels are translated trace => debug, debug => debug, info =>
982 /// informational, warn => warning, and error => error.
983 ///
984 /// Note that due to <https://github.com/Geal/rust-syslog/issues/41>,
985 /// logging to this backend requires one allocation per log call.
986 ///
987 /// This is for RFC 3164 loggers. To use an RFC 5424 logger, use the
988 /// [`Output::syslog_5424`] helper method.
989 ///
990 /// This requires the `"syslog-7"` feature.
991 fn from(log: Syslog7Rfc3164Logger) -> Self {
992 Output(OutputInner::Syslog7Rfc3164(log))
993 }
994}
995
996impl From<Panic> for Output {
997 /// Creates an output logger which will panic with message text for all
998 /// messages.
999 fn from(_: Panic) -> Self {
1000 Output(OutputInner::Panic)
1001 }
1002}
1003
1004impl Output {
1005 /// Returns a file logger using a custom separator.
1006 ///
1007 /// If the default separator of `\n` is acceptable, an [`fs::File`]
1008 /// instance can be passed into [`Dispatch::chain`] directly.
1009 ///
1010 /// ```no_run
1011 /// # fn setup_logger() -> Result<(), fern::InitError> {
1012 /// fern::Dispatch::new().chain(std::fs::File::create("log")?)
1013 /// # .into_log();
1014 /// # Ok(())
1015 /// # }
1016 /// #
1017 /// # fn main() { setup_logger().expect("failed to set up logger"); }
1018 /// ```
1019 ///
1020 /// ```no_run
1021 /// # fn setup_logger() -> Result<(), fern::InitError> {
1022 /// fern::Dispatch::new().chain(fern::log_file("log")?)
1023 /// # .into_log();
1024 /// # Ok(())
1025 /// # }
1026 /// #
1027 /// # fn main() { setup_logger().expect("failed to set up logger"); }
1028 /// ```
1029 ///
1030 /// Example usage (using [`fern::log_file`]):
1031 ///
1032 /// ```no_run
1033 /// # fn setup_logger() -> Result<(), fern::InitError> {
1034 /// fern::Dispatch::new().chain(fern::Output::file(fern::log_file("log")?, "\r\n"))
1035 /// # .into_log();
1036 /// # Ok(())
1037 /// # }
1038 /// #
1039 /// # fn main() { setup_logger().expect("failed to set up logger"); }
1040 /// ```
1041 ///
1042 /// [`fs::File`]: https://doc.rust-lang.org/std/fs/struct.File.html
1043 /// [`Dispatch::chain`]: struct.Dispatch.html#method.chain
1044 /// [`fern::log_file`]: fn.log_file.html
1045 pub fn file<T: Into<Cow<'static, str>>>(file: fs::File, line_sep: T) -> Self {
1046 Output(OutputInner::File {
1047 stream: file,
1048 line_sep: line_sep.into(),
1049 })
1050 }
1051
1052 /// Returns a logger using arbitrary write object and custom separator.
1053 ///
1054 /// If the default separator of `\n` is acceptable, an `Box<Write + Send>`
1055 /// instance can be passed into [`Dispatch::chain`] directly.
1056 ///
1057 /// ```no_run
1058 /// # fn setup_logger() -> Result<(), fern::InitError> {
1059 /// // Anything implementing 'Write' works.
1060 /// let mut writer = std::io::Cursor::new(Vec::<u8>::new());
1061 ///
1062 /// fern::Dispatch::new()
1063 /// // as long as we explicitly cast into a type-erased Box
1064 /// .chain(Box::new(writer) as Box<std::io::Write + Send>)
1065 /// # .into_log();
1066 /// # Ok(())
1067 /// # }
1068 /// #
1069 /// # fn main() { setup_logger().expect("failed to set up logger"); }
1070 /// ```
1071 ///
1072 /// Example usage:
1073 ///
1074 /// ```no_run
1075 /// # fn setup_logger() -> Result<(), fern::InitError> {
1076 /// let writer = Box::new(std::io::Cursor::new(Vec::<u8>::new()));
1077 ///
1078 /// fern::Dispatch::new().chain(fern::Output::writer(writer, "\r\n"))
1079 /// # .into_log();
1080 /// # Ok(())
1081 /// # }
1082 /// #
1083 /// # fn main() { setup_logger().expect("failed to set up logger"); }
1084 /// ```
1085 ///
1086 /// [`Dispatch::chain`]: struct.Dispatch.html#method.chain
1087 pub fn writer<T: Into<Cow<'static, str>>>(writer: Box<dyn Write + Send>, line_sep: T) -> Self {
1088 Output(OutputInner::Writer {
1089 stream: writer,
1090 line_sep: line_sep.into(),
1091 })
1092 }
1093
1094 /// Returns a reopenable logger, i.e., handling SIGHUP.
1095 ///
1096 /// If the default separator of `\n` is acceptable, a `Reopen`
1097 /// instance can be passed into [`Dispatch::chain`] directly.
1098 ///
1099 /// This function is not available on Windows, and it requires the `reopen-03`
1100 /// feature to be enabled.
1101 ///
1102 /// ```no_run
1103 /// use std::fs::OpenOptions;
1104 /// # fn setup_logger() -> Result<(), fern::InitError> {
1105 /// let reopenable = reopen03::Reopen::new(Box::new(|| {
1106 /// OpenOptions::new()
1107 /// .create(true)
1108 /// .write(true)
1109 /// .append(true)
1110 /// .open("/tmp/output.log")
1111 /// }))
1112 /// .unwrap();
1113 ///
1114 /// fern::Dispatch::new().chain(fern::Output::reopen(reopenable, "\n"))
1115 /// # .into_log();
1116 /// # Ok(())
1117 /// # }
1118 /// #
1119 /// # fn main() { setup_logger().expect("failed to set up logger"); }
1120 /// ```
1121 /// [`Dispatch::chain`]: struct.Dispatch.html#method.chain
1122 #[cfg(all(not(windows), feature = "reopen-03"))]
1123 pub fn reopen<T: Into<Cow<'static, str>>>(
1124 reopen: reopen03::Reopen<fs::File>,
1125 line_sep: T,
1126 ) -> Self {
1127 Output(OutputInner::Reopen {
1128 stream: reopen,
1129 line_sep: line_sep.into(),
1130 })
1131 }
1132
1133 /// Returns a reopenable logger, i.e., handling SIGHUP.
1134 ///
1135 /// If the default separator of `\n` is acceptable, a `Reopen`
1136 /// instance can be passed into [`Dispatch::chain`] directly.
1137 ///
1138 /// This function is not available on Windows, and it requires the `reopen-03`
1139 /// feature to be enabled.
1140 ///
1141 /// ```no_run
1142 /// use std::fs::OpenOptions;
1143 /// # fn setup_logger() -> Result<(), fern::InitError> {
1144 /// let reopenable = reopen1::Reopen::new(Box::new(|| {
1145 /// OpenOptions::new()
1146 /// .create(true)
1147 /// .write(true)
1148 /// .append(true)
1149 /// .open("/tmp/output.log")
1150 /// }))
1151 /// .unwrap();
1152 ///
1153 /// fern::Dispatch::new().chain(fern::Output::reopen1(reopenable, "\n"))
1154 /// # .into_log();
1155 /// # Ok(())
1156 /// # }
1157 /// #
1158 /// # fn main() { setup_logger().expect("failed to set up logger"); }
1159 /// ```
1160 /// [`Dispatch::chain`]: struct.Dispatch.html#method.chain
1161 #[cfg(all(not(windows), feature = "reopen-1"))]
1162 pub fn reopen1<T: Into<Cow<'static, str>>>(
1163 reopen: reopen1::Reopen<fs::File>,
1164 line_sep: T,
1165 ) -> Self {
1166 Output(OutputInner::Reopen1 {
1167 stream: reopen,
1168 line_sep: line_sep.into(),
1169 })
1170 }
1171
1172 /// Returns an stdout logger using a custom separator.
1173 ///
1174 /// If the default separator of `\n` is acceptable, an `io::Stdout`
1175 /// instance can be passed into `Dispatch::chain()` directly.
1176 ///
1177 /// ```
1178 /// fern::Dispatch::new().chain(std::io::stdout())
1179 /// # .into_log();
1180 /// ```
1181 ///
1182 /// Example usage:
1183 ///
1184 /// ```
1185 /// fern::Dispatch::new()
1186 /// // some unix tools use null bytes as message terminators so
1187 /// // newlines in messages can be treated differently.
1188 /// .chain(fern::Output::stdout("\0"))
1189 /// # .into_log();
1190 /// ```
1191 pub fn stdout<T: Into<Cow<'static, str>>>(line_sep: T) -> Self {
1192 Output(OutputInner::Stdout {
1193 stream: io::stdout(),
1194 line_sep: line_sep.into(),
1195 })
1196 }
1197
1198 /// Returns an stderr logger using a custom separator.
1199 ///
1200 /// If the default separator of `\n` is acceptable, an `io::Stderr`
1201 /// instance can be passed into `Dispatch::chain()` directly.
1202 ///
1203 /// ```
1204 /// fern::Dispatch::new().chain(std::io::stderr())
1205 /// # .into_log();
1206 /// ```
1207 ///
1208 /// Example usage:
1209 ///
1210 /// ```
1211 /// fern::Dispatch::new().chain(fern::Output::stderr("\n\n\n"))
1212 /// # .into_log();
1213 /// ```
1214 pub fn stderr<T: Into<Cow<'static, str>>>(line_sep: T) -> Self {
1215 Output(OutputInner::Stderr {
1216 stream: io::stderr(),
1217 line_sep: line_sep.into(),
1218 })
1219 }
1220
1221 /// Returns a mpsc::Sender logger using a custom separator.
1222 ///
1223 /// If the default separator of `\n` is acceptable, an
1224 /// `mpsc::Sender<String>` instance can be passed into `Dispatch::
1225 /// chain()` directly.
1226 ///
1227 /// Each log message will be suffixed with the separator, then sent as a
1228 /// single String to the given sender.
1229 ///
1230 /// ```
1231 /// use std::sync::mpsc::channel;
1232 ///
1233 /// let (tx, rx) = channel();
1234 /// fern::Dispatch::new().chain(tx)
1235 /// # .into_log();
1236 /// ```
1237 pub fn sender<T: Into<Cow<'static, str>>>(sender: Sender<String>, line_sep: T) -> Self {
1238 Output(OutputInner::Sender {
1239 stream: sender,
1240 line_sep: line_sep.into(),
1241 })
1242 }
1243
1244 /// Returns a logger which logs into an RFC5424 syslog.
1245 ///
1246 /// This method takes an additional transform method to turn the log data
1247 /// into RFC5424 data.
1248 ///
1249 /// I've honestly got no clue what the expected keys and values are for
1250 /// this kind of logging, so I'm just going to link [the rfc] instead.
1251 ///
1252 /// If you're an expert on syslog logging and would like to contribute
1253 /// an example to put here, it would be gladly accepted!
1254 ///
1255 /// This requires the `"syslog-4"` feature.
1256 ///
1257 /// [the rfc]: https://tools.ietf.org/html/rfc5424
1258 #[cfg(all(not(windows), feature = "syslog-4"))]
1259 pub fn syslog_5424<F>(logger: Syslog4Rfc5424Logger, transform: F) -> Self
1260 where
1261 F: Fn(&log::Record) -> (i32, HashMap<String, HashMap<String, String>>, String)
1262 + Sync
1263 + Send
1264 + 'static,
1265 {
1266 Output(OutputInner::Syslog4Rfc5424 {
1267 logger,
1268 transform: Box::new(transform),
1269 })
1270 }
1271
1272 /// Returns a logger which logs into an RFC5424 syslog (using syslog version 6)
1273 ///
1274 /// This method takes an additional transform method to turn the log data
1275 /// into RFC5424 data.
1276 ///
1277 /// I've honestly got no clue what the expected keys and values are for
1278 /// this kind of logging, so I'm just going to link [the rfc] instead.
1279 ///
1280 /// If you're an expert on syslog logging and would like to contribute
1281 /// an example to put here, it would be gladly accepted!
1282 ///
1283 /// This requires the `"syslog-6"` feature.
1284 ///
1285 /// [the rfc]: https://tools.ietf.org/html/rfc5424
1286 #[cfg(all(not(windows), feature = "syslog-6"))]
1287 pub fn syslog6_5424<F>(logger: Syslog6Rfc5424Logger, transform: F) -> Self
1288 where
1289 F: Fn(&log::Record) -> (u32, HashMap<String, HashMap<String, String>>, String)
1290 + Sync
1291 + Send
1292 + 'static,
1293 {
1294 Output(OutputInner::Syslog6Rfc5424 {
1295 logger,
1296 transform: Box::new(transform),
1297 })
1298 }
1299
1300 /// Returns a logger which logs into an RFC5424 syslog (using syslog version 6)
1301 ///
1302 /// This method takes an additional transform method to turn the log data
1303 /// into RFC5424 data.
1304 ///
1305 /// I've honestly got no clue what the expected keys and values are for
1306 /// this kind of logging, so I'm just going to link [the rfc] instead.
1307 ///
1308 /// If you're an expert on syslog logging and would like to contribute
1309 /// an example to put here, it would be gladly accepted!
1310 ///
1311 /// This requires the `"syslog-7"` feature.
1312 ///
1313 /// [the rfc]: https://tools.ietf.org/html/rfc5424
1314 #[cfg(all(not(windows), feature = "syslog-7"))]
1315 pub fn syslog7_5424<F>(logger: Syslog7Rfc5424Logger, transform: F) -> Self
1316 where
1317 F: Fn(&log::Record) -> (u32, BTreeMap<String, BTreeMap<String, String>>, String)
1318 + Sync
1319 + Send
1320 + 'static,
1321 {
1322 Output(OutputInner::Syslog7Rfc5424 {
1323 logger,
1324 transform: Box::new(transform),
1325 })
1326 }
1327
1328 /// Returns a logger which simply calls the given function with each
1329 /// message.
1330 ///
1331 /// The function will be called inline in the thread the log occurs on.
1332 ///
1333 /// Example usage:
1334 ///
1335 /// ```
1336 /// fern::Dispatch::new().chain(fern::Output::call(|record| {
1337 /// // this is mundane, but you can do anything here.
1338 /// println!("{}", record.args());
1339 /// }))
1340 /// # .into_log();
1341 /// ```
1342 pub fn call<F>(func: F) -> Self
1343 where
1344 F: Fn(&log::Record) + Sync + Send + 'static,
1345 {
1346 struct CallShim<F>(F);
1347 impl<F> log::Log for CallShim<F>
1348 where
1349 F: Fn(&log::Record) + Sync + Send + 'static,
1350 {
1351 fn enabled(&self, _: &log::Metadata) -> bool {
1352 true
1353 }
1354 fn log(&self, record: &log::Record) {
1355 (self.0)(record)
1356 }
1357 fn flush(&self) {}
1358 }
1359
1360 Self::from(Box::new(CallShim(func)) as Box<dyn log::Log>)
1361 }
1362}
1363
1364impl Default for Dispatch {
1365 /// Returns a logger configuration that does nothing with log records.
1366 ///
1367 /// Equivalent to [`Dispatch::new`].
1368 ///
1369 /// [`Dispatch::new`]: #method.new
1370 fn default() -> Self {
1371 Self::new()
1372 }
1373}
1374
1375impl fmt::Debug for Dispatch {
1376 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1377 struct LevelsDebug<'a>(&'a [(Cow<'static, str>, log::LevelFilter)]);
1378 impl<'a> fmt::Debug for LevelsDebug<'a> {
1379 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1380 f.debug_map()
1381 .entries(self.0.iter().map(|t| (t.0.as_ref(), t.1)))
1382 .finish()
1383 }
1384 }
1385 struct FiltersDebug<'a>(&'a [Box<Filter>]);
1386 impl<'a> fmt::Debug for FiltersDebug<'a> {
1387 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1388 f.debug_list()
1389 .entries(self.0.iter().map(|_| "<filter closure>"))
1390 .finish()
1391 }
1392 }
1393 f.debug_struct("Dispatch")
1394 .field(
1395 "format",
1396 &self.format.as_ref().map(|_| "<formatter closure>"),
1397 )
1398 .field("children", &self.children)
1399 .field("default_level", &self.default_level)
1400 .field("levels", &LevelsDebug(&self.levels))
1401 .field("filters", &FiltersDebug(&self.filters))
1402 .finish()
1403 }
1404}
1405
1406impl fmt::Debug for OutputInner {
1407 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1408 match *self {
1409 OutputInner::Stdout {
1410 ref stream,
1411 ref line_sep,
1412 } => f
1413 .debug_struct("Output::Stdout")
1414 .field("stream", stream)
1415 .field("line_sep", line_sep)
1416 .finish(),
1417 OutputInner::Stderr {
1418 ref stream,
1419 ref line_sep,
1420 } => f
1421 .debug_struct("Output::Stderr")
1422 .field("stream", stream)
1423 .field("line_sep", line_sep)
1424 .finish(),
1425 OutputInner::File {
1426 ref stream,
1427 ref line_sep,
1428 } => f
1429 .debug_struct("Output::File")
1430 .field("stream", stream)
1431 .field("line_sep", line_sep)
1432 .finish(),
1433 OutputInner::Writer { ref line_sep, .. } => f
1434 .debug_struct("Output::Writer")
1435 .field("stream", &"<unknown writer>")
1436 .field("line_sep", line_sep)
1437 .finish(),
1438 #[cfg(all(not(windows), feature = "reopen-03"))]
1439 OutputInner::Reopen { ref line_sep, .. } => f
1440 .debug_struct("Output::Reopen")
1441 .field("stream", &"<unknown reopen file>")
1442 .field("line_sep", line_sep)
1443 .finish(),
1444 #[cfg(all(not(windows), feature = "reopen-1"))]
1445 OutputInner::Reopen1 {
1446 ref line_sep,
1447 ref stream,
1448 } => f
1449 .debug_struct("Output::Reopen1")
1450 .field("stream", stream)
1451 .field("line_sep", line_sep)
1452 .finish(),
1453 OutputInner::Sender {
1454 ref stream,
1455 ref line_sep,
1456 } => f
1457 .debug_struct("Output::Sender")
1458 .field("stream", stream)
1459 .field("line_sep", line_sep)
1460 .finish(),
1461 #[cfg(all(not(windows), feature = "syslog-3"))]
1462 OutputInner::Syslog3(_) => f
1463 .debug_tuple("Output::Syslog3")
1464 .field(&"<unprintable syslog::Logger>")
1465 .finish(),
1466 #[cfg(all(not(windows), feature = "syslog-4"))]
1467 OutputInner::Syslog4Rfc3164 { .. } => f
1468 .debug_tuple("Output::Syslog4Rfc3164")
1469 .field(&"<unprintable syslog::Logger>")
1470 .finish(),
1471 #[cfg(all(not(windows), feature = "syslog-4"))]
1472 OutputInner::Syslog4Rfc5424 { .. } => f
1473 .debug_tuple("Output::Syslog4Rfc5424")
1474 .field(&"<unprintable syslog::Logger>")
1475 .finish(),
1476 #[cfg(all(not(windows), feature = "syslog-6"))]
1477 OutputInner::Syslog6Rfc3164 { .. } => f
1478 .debug_tuple("Output::Syslog6Rfc3164")
1479 .field(&"<unprintable syslog::Logger>")
1480 .finish(),
1481 #[cfg(all(not(windows), feature = "syslog-6"))]
1482 OutputInner::Syslog6Rfc5424 { .. } => f
1483 .debug_tuple("Output::Syslog6Rfc5424")
1484 .field(&"<unprintable syslog::Logger>")
1485 .finish(),
1486 #[cfg(all(not(windows), feature = "syslog-7"))]
1487 OutputInner::Syslog7Rfc3164 { .. } => f
1488 .debug_tuple("Output::Syslog7Rfc3164")
1489 .field(&"<unprintable syslog::Logger>")
1490 .finish(),
1491 #[cfg(all(not(windows), feature = "syslog-7"))]
1492 OutputInner::Syslog7Rfc5424 { .. } => f
1493 .debug_tuple("Output::Syslog7Rfc5424")
1494 .field(&"<unprintable syslog::Logger>")
1495 .finish(),
1496 OutputInner::Dispatch(ref dispatch) => {
1497 f.debug_tuple("Output::Dispatch").field(dispatch).finish()
1498 }
1499 OutputInner::SharedDispatch(_) => f
1500 .debug_tuple("Output::SharedDispatch")
1501 .field(&"<built Dispatch logger>")
1502 .finish(),
1503 OutputInner::OtherBoxed { .. } => f
1504 .debug_tuple("Output::OtherBoxed")
1505 .field(&"<boxed logger>")
1506 .finish(),
1507 OutputInner::OtherStatic { .. } => f
1508 .debug_tuple("Output::OtherStatic")
1509 .field(&"<boxed logger>")
1510 .finish(),
1511 OutputInner::Panic => f.debug_tuple("Output::Panic").finish(),
1512 #[cfg(feature = "date-based")]
1513 OutputInner::DateBased { ref config } => f
1514 .debug_struct("Output::DateBased")
1515 .field("config", config)
1516 .finish(),
1517 }
1518 }
1519}
1520
1521impl fmt::Debug for Output {
1522 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1523 self.0.fmt(f)
1524 }
1525}
1526
1527/// This is used to generate log file suffixed based on date, hour, and minute.
1528///
1529/// The log file will be rotated automatically when the date changes.
1530#[derive(Debug)]
1531#[cfg(feature = "date-based")]
1532pub struct DateBased {
1533 file_prefix: PathBuf,
1534 file_suffix: Cow<'static, str>,
1535 line_sep: Cow<'static, str>,
1536 utc_time: bool,
1537}
1538
1539#[cfg(feature = "date-based")]
1540impl DateBased {
1541 /// Create new date-based file logger with the given file prefix and
1542 /// strftime-based suffix pattern.
1543 ///
1544 /// On initialization, fern will create a file with the suffix formatted
1545 /// with the current time (either utc or local, see below). Each time a
1546 /// record is logged, the format is checked against the current time, and if
1547 /// the time has changed, the old file is closed and a new one opened.
1548 ///
1549 /// `file_suffix` will be interpreted as an `strftime` format. See
1550 /// [`chrono::format::strftime`] for more information.
1551 ///
1552 /// `file_prefix` may be a full file path, and will be prepended to the
1553 /// suffix to create the final file.
1554 ///
1555 /// Note that no separator will be placed in between `file_name` and
1556 /// `file_suffix_pattern`. So if you call `DateBased::new("hello",
1557 /// "%Y")`, the result will be a filepath `hello2019`.
1558 ///
1559 /// By default, this will use local time. For UTC time instead, use the
1560 /// [`.utc_time()`][DateBased::utc_time] method after creating.
1561 ///
1562 /// By default, this will use `\n` as a line separator. For a custom
1563 /// separator, use the [`.line_sep`][DateBased::line_sep] method
1564 /// after creating.
1565 ///
1566 /// # Examples
1567 ///
1568 /// Containing the date (year, month and day):
1569 ///
1570 /// ```
1571 /// // logs/2019-10-23-my-program.log
1572 /// let log = fern::DateBased::new("logs/", "%Y-%m-%d-my-program.log");
1573 ///
1574 /// // program.log.23102019
1575 /// let log = fern::DateBased::new("my-program.log.", "%d%m%Y");
1576 /// ```
1577 ///
1578 /// Containing the hour:
1579 ///
1580 /// ```
1581 /// // logs/2019-10-23 13 my-program.log
1582 /// let log = fern::DateBased::new("logs/", "%Y-%m-%d %H my-program.log");
1583 ///
1584 /// // program.log.2310201913
1585 /// let log = fern::DateBased::new("my-program.log.", "%d%m%Y%H");
1586 /// ```
1587 ///
1588 /// Containing the minute:
1589 ///
1590 /// ```
1591 /// // logs/2019-10-23 13 my-program.log
1592 /// let log = fern::DateBased::new("logs/", "%Y-%m-%d %H my-program.log");
1593 ///
1594 /// // program.log.2310201913
1595 /// let log = fern::DateBased::new("my-program.log.", "%d%m%Y%H");
1596 /// ```
1597 ///
1598 /// UNIX time, or seconds since 00:00 Jan 1st 1970:
1599 ///
1600 /// ```
1601 /// // logs/1571822854-my-program.log
1602 /// let log = fern::DateBased::new("logs/", "%s-my-program.log");
1603 ///
1604 /// // program.log.1571822854
1605 /// let log = fern::DateBased::new("my-program.log.", "%s");
1606 /// ```
1607 ///
1608 /// Hourly, using UTC time:
1609 ///
1610 /// ```
1611 /// // logs/2019-10-23 23 my-program.log
1612 /// let log = fern::DateBased::new("logs/", "%Y-%m-%d %H my-program.log").utc_time();
1613 ///
1614 /// // program.log.2310201923
1615 /// let log = fern::DateBased::new("my-program.log.", "%d%m%Y%H").utc_time();
1616 /// ```
1617 ///
1618 /// [`chrono::format::strftime`]: https://docs.rs/chrono/0.4.6/chrono/format/strftime/index.html
1619 pub fn new<T, U>(file_prefix: T, file_suffix: U) -> Self
1620 where
1621 T: AsRef<Path>,
1622 U: Into<Cow<'static, str>>,
1623 {
1624 DateBased {
1625 utc_time: false,
1626 file_prefix: file_prefix.as_ref().to_owned(),
1627 file_suffix: file_suffix.into(),
1628 line_sep: "\n".into(),
1629 }
1630 }
1631
1632 /// Changes the line separator this logger will use.
1633 ///
1634 /// The default line separator is `\n`.
1635 ///
1636 /// # Examples
1637 ///
1638 /// Using a windows line separator:
1639 ///
1640 /// ```
1641 /// let log = fern::DateBased::new("logs", "%s.log").line_sep("\r\n");
1642 /// ```
1643 pub fn line_sep<T>(mut self, line_sep: T) -> Self
1644 where
1645 T: Into<Cow<'static, str>>,
1646 {
1647 self.line_sep = line_sep.into();
1648 self
1649 }
1650
1651 /// Orients this log file suffix formatting to use UTC time.
1652 ///
1653 /// The default is local time.
1654 ///
1655 /// # Examples
1656 ///
1657 /// This will use UTC time to determine the date:
1658 ///
1659 /// ```
1660 /// // program.log.2310201923
1661 /// let log = fern::DateBased::new("my-program.log.", "%d%m%Y%H").utc_time();
1662 /// ```
1663 pub fn utc_time(mut self) -> Self {
1664 self.utc_time = true;
1665 self
1666 }
1667
1668 /// Orients this log file suffix formatting to use local time.
1669 ///
1670 /// This is the default option.
1671 ///
1672 /// # Examples
1673 ///
1674 /// This log file will use local time - the latter method call overrides the
1675 /// former.
1676 ///
1677 /// ```
1678 /// // program.log.2310201923
1679 /// let log = fern::DateBased::new("my-program.log.", "%d%m%Y%H")
1680 /// .utc_time()
1681 /// .local_time();
1682 /// ```
1683 pub fn local_time(mut self) -> Self {
1684 self.utc_time = false;
1685 self
1686 }
1687}
1688
1689#[cfg(feature = "date-based")]
1690impl From<DateBased> for Output {
1691 /// Create an output logger which defers to the given date-based logger. Use
1692 /// configuration methods on [DateBased] to set line separator and filename.
1693 fn from(config: DateBased) -> Self {
1694 Output(OutputInner::DateBased { config })
1695 }
1696}