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