blob: 55bb8d499e401e83e2500c399b8105e6faf76039 [file] [log] [blame]
use std::{
borrow::Cow,
cmp, fmt, fs, io,
io::Write,
sync::{mpsc::Sender, Arc, Mutex},
};
#[cfg(feature = "date-based")]
use std::path::{Path, PathBuf};
#[cfg(all(not(windows), any(feature = "syslog-4", feature = "syslog-6")))]
use std::collections::HashMap;
use log::Log;
use crate::{log_impl, Filter, FormatCallback, Formatter};
#[cfg(feature = "date-based")]
use crate::log_impl::DateBasedState;
#[cfg(all(not(windows), feature = "syslog-4"))]
use crate::{Syslog4Rfc3164Logger, Syslog4Rfc5424Logger, Syslog4TransformFn};
#[cfg(all(not(windows), feature = "syslog-6"))]
use crate::{Syslog6Rfc3164Logger, Syslog6Rfc5424Logger, Syslog6TransformFn};
/// The base dispatch logger.
///
/// This allows for formatting log records, limiting what records can be passed
/// through, and then dispatching records to other dispatch loggers or output
/// loggers.
///
/// Note that all methods are position-insensitive.
/// `Dispatch::new().format(a).chain(b)` produces the exact same result
/// as `Dispatch::new().chain(b).format(a)`. Given this, it is preferred to put
/// 'format' and other modifiers before 'chain' for the sake of clarity.
///
/// Example usage demonstrating all features:
///
/// ```no_run
/// # // no_run because this creates log files.
/// use std::{fs, io};
///
/// # fn setup_logger() -> Result<(), fern::InitError> {
/// fern::Dispatch::new()
/// .format(|out, message, record| {
/// out.finish(format_args!(
/// "[{} {}] {}",
/// record.level(),
/// record.target(),
/// message,
/// ))
/// })
/// .chain(
/// fern::Dispatch::new()
/// // by default only accept warn messages
/// .level(log::LevelFilter::Warn)
/// // accept info messages from the current crate too
/// .level_for("my_crate", log::LevelFilter::Info)
/// // `io::Stdout`, `io::Stderr` and `io::File` can be directly passed in.
/// .chain(io::stdout()),
/// )
/// .chain(
/// fern::Dispatch::new()
/// // output all messages
/// .level(log::LevelFilter::Trace)
/// // except for hyper, in that case only show info messages
/// .level_for("hyper", log::LevelFilter::Info)
/// // `log_file(x)` equates to
/// // `OpenOptions::new().write(true).append(true).create(true).open(x)`
/// .chain(fern::log_file("persistent-log.log")?)
/// .chain(
/// fs::OpenOptions::new()
/// .write(true)
/// .create(true)
/// .truncate(true)
/// .create(true)
/// .open("/tmp/temp.log")?,
/// ),
/// )
/// .chain(
/// fern::Dispatch::new()
/// .level(log::LevelFilter::Error)
/// .filter(|_meta_data| {
/// // as an example, randomly reject half of the messages
/// # /*
/// rand::random()
/// # */
/// # true
/// })
/// .chain(io::stderr()),
/// )
/// // and finally, set as the global logger!
/// .apply()?;
/// # Ok(())
/// # }
/// #
/// # fn main() { setup_logger().expect("failed to set up logger") }
/// ```
#[must_use = "this is only a logger configuration and must be consumed with into_log() or apply()"]
pub struct Dispatch {
format: Option<Box<Formatter>>,
children: Vec<OutputInner>,
default_level: log::LevelFilter,
levels: Vec<(Cow<'static, str>, log::LevelFilter)>,
filters: Vec<Box<Filter>>,
}
/// Logger which is usable as an output for multiple other loggers.
///
/// This struct contains a built logger stored in an [`Arc`], and can be
/// safely cloned.
///
/// See [`Dispatch::into_shared`].
///
/// [`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html
/// [`Dispatch::into_shared`]: struct.Dispatch.html#method.into_shared
#[derive(Clone)]
pub struct SharedDispatch {
inner: Arc<log_impl::Dispatch>,
min_level: log::LevelFilter,
}
impl Dispatch {
/// Creates a dispatch, which will initially do nothing.
#[inline]
pub fn new() -> Self {
Dispatch {
format: None,
children: Vec::new(),
default_level: log::LevelFilter::Trace,
levels: Vec::new(),
filters: Vec::new(),
}
}
/// Sets the formatter of this dispatch. The closure should accept a
/// callback, a message and a log record, and write the resulting
/// format to the writer.
///
/// The log record is passed for completeness, but the `args()` method of
/// the record should be ignored, and the [`fmt::Arguments`] given
/// should be used instead. `record.args()` may be used to retrieve the
/// _original_ log message, but in order to allow for true log
/// chaining, formatters should use the given message instead whenever
/// including the message in the output.
///
/// To avoid all allocation of intermediate results, the formatter is
/// "completed" by calling a callback, which then calls the rest of the
/// logging chain with the new formatted message. The callback object keeps
/// track of if it was called or not via a stack boolean as well, so if
/// you don't use `out.finish` the log message will continue down
/// the logger chain unformatted.
///
/// [`fmt::Arguments`]: https://doc.rust-lang.org/std/fmt/struct.Arguments.html
///
/// Example usage:
///
/// ```
/// fern::Dispatch::new().format(|out, message, record| {
/// out.finish(format_args!(
/// "[{} {}] {}",
/// record.level(),
/// record.target(),
/// message
/// ))
/// })
/// # .into_log();
/// ```
#[inline]
pub fn format<F>(mut self, formatter: F) -> Self
where
F: Fn(FormatCallback, &fmt::Arguments, &log::Record) + Sync + Send + 'static,
{
self.format = Some(Box::new(formatter));
self
}
/// Adds a child to this dispatch.
///
/// All log records which pass all filters will be formatted and then sent
/// to all child loggers in sequence.
///
/// Note: If the child logger is also a Dispatch, and cannot accept any log
/// records, it will be dropped. This only happens if the child either
/// has no children itself, or has a minimum log level of
/// [`LevelFilter::Off`].
///
/// [`LevelFilter::Off`]: https://docs.rs/log/0.4/log/enum.LevelFilter.html#variant.Off
///
/// Example usage:
///
/// ```
/// fern::Dispatch::new().chain(fern::Dispatch::new().chain(std::io::stdout()))
/// # .into_log();
/// ```
#[inline]
pub fn chain<T: Into<Output>>(mut self, logger: T) -> Self {
self.children.push(logger.into().0);
self
}
/// Sets the overarching level filter for this logger. All messages not
/// already filtered by something set by [`Dispatch::level_for`] will
/// be affected.
///
/// All messages filtered will be discarded if less severe than the given
/// level.
///
/// Default level is [`LevelFilter::Trace`].
///
/// [`Dispatch::level_for`]: #method.level_for
/// [`LevelFilter::Trace`]: https://docs.rs/log/0.4/log/enum.LevelFilter.html#variant.Trace
///
/// Example usage:
///
/// ```
/// # fn main() {
/// fern::Dispatch::new().level(log::LevelFilter::Info)
/// # .into_log();
/// # }
/// ```
#[inline]
pub fn level(mut self, level: log::LevelFilter) -> Self {
self.default_level = level;
self
}
/// Sets a per-target log level filter. Default target for log messages is
/// `crate_name::module_name` or
/// `crate_name` for logs in the crate root. Targets can also be set with
/// `info!(target: "target-name", ...)`.
///
/// For each log record fern will first try to match the most specific
/// level_for, and then progressively more general ones until either a
/// matching level is found, or the default level is used.
///
/// For example, a log for the target `hyper::http::h1` will first test a
/// level_for for `hyper::http::h1`, then for `hyper::http`, then for
/// `hyper`, then use the default level.
///
/// Examples:
///
/// A program wants to include a lot of debugging output, but the library
/// "hyper" is known to work well, so debug output from it should be
/// excluded:
///
/// ```
/// # fn main() {
/// fern::Dispatch::new()
/// .level(log::LevelFilter::Trace)
/// .level_for("hyper", log::LevelFilter::Info)
/// # .into_log();
/// # }
/// ```
///
/// A program has a ton of debug output per-module, but there is so much
/// that debugging more than one module at a time is not very useful.
/// The command line accepts a list of modules to debug, while keeping the
/// rest of the program at info level:
///
/// ```
/// fn setup_logging<T, I>(verbose_modules: T) -> Result<(), fern::InitError>
/// where
/// I: AsRef<str>,
/// T: IntoIterator<Item = I>,
/// {
/// let mut config = fern::Dispatch::new().level(log::LevelFilter::Info);
///
/// for module_name in verbose_modules {
/// config = config.level_for(
/// format!("my_crate_name::{}", module_name.as_ref()),
/// log::LevelFilter::Debug,
/// );
/// }
///
/// config.chain(std::io::stdout()).apply()?;
///
/// Ok(())
/// }
/// #
/// # // we're ok with apply() failing.
/// # fn main() { let _ = setup_logging(&["hi"]); }
/// ```
#[inline]
pub fn level_for<T: Into<Cow<'static, str>>>(
mut self,
module: T,
level: log::LevelFilter,
) -> Self {
let module = module.into();
if let Some((index, _)) = self
.levels
.iter()
.enumerate()
.find(|(_, (name, _))| *name == module)
{
self.levels.remove(index);
}
self.levels.push((module, level));
self
}
/// Adds a custom filter which can reject messages passing through this
/// logger.
///
/// The logger will continue to process log records only if all filters
/// return `true`.
///
/// [`Dispatch::level`] and [`Dispatch::level_for`] are preferred if
/// applicable.
///
/// [`Dispatch::level`]: #method.level
/// [`Dispatch::level_for`]: #method.level_for
///
/// Example usage:
///
/// This sends error level messages to stderr and others to stdout.
///
/// ```
/// # fn main() {
/// fern::Dispatch::new()
/// .level(log::LevelFilter::Info)
/// .chain(
/// fern::Dispatch::new()
/// .filter(|metadata| {
/// // Reject messages with the `Error` log level.
/// metadata.level() != log::LevelFilter::Error
/// })
/// .chain(std::io::stderr()),
/// )
/// .chain(
/// fern::Dispatch::new()
/// .level(log::LevelFilter::Error)
/// .chain(std::io::stdout()),
/// )
/// # .into_log();
/// # }
#[inline]
pub fn filter<F>(mut self, filter: F) -> Self
where
F: Fn(&log::Metadata) -> bool + Send + Sync + 'static,
{
self.filters.push(Box::new(filter));
self
}
/// Builds this dispatch and stores it in a clonable structure containing
/// an [`Arc`].
///
/// Once "shared", the dispatch can be used as an output for multiple other
/// dispatch loggers.
///
/// Example usage:
///
/// This separates info and warn messages, sending info to stdout + a log
/// file, and warn to stderr + the same log file. Shared is used so the
/// program only opens "file.log" once.
///
/// ```no_run
/// # fn setup_logger() -> Result<(), fern::InitError> {
///
/// let file_out = fern::Dispatch::new()
/// .chain(fern::log_file("file.log")?)
/// .into_shared();
///
/// let info_out = fern::Dispatch::new()
/// .level(log::LevelFilter::Debug)
/// .filter(|metadata|
/// // keep only info and debug (reject warn and error)
/// metadata.level() <= log::Level::Info)
/// .chain(std::io::stdout())
/// .chain(file_out.clone());
///
/// let warn_out = fern::Dispatch::new()
/// .level(log::LevelFilter::Warn)
/// .chain(std::io::stderr())
/// .chain(file_out);
///
/// fern::Dispatch::new()
/// .chain(info_out)
/// .chain(warn_out)
/// .apply();
///
/// # Ok(())
/// # }
/// #
/// # fn main() { setup_logger().expect("failed to set up logger"); }
/// ```
///
/// [`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html
pub fn into_shared(self) -> SharedDispatch {
let (min_level, dispatch) = self.into_dispatch();
SharedDispatch {
inner: Arc::new(dispatch),
min_level,
}
}
/// Builds this into the actual logger implementation.
///
/// This could probably be refactored, but having everything in one place
/// is also nice.
fn into_dispatch(self) -> (log::LevelFilter, log_impl::Dispatch) {
let Dispatch {
format,
children,
default_level,
levels,
mut filters,
} = self;
let mut max_child_level = log::LevelFilter::Off;
let output = children
.into_iter()
.flat_map(|child| match child {
OutputInner::Stdout { stream, line_sep } => {
max_child_level = log::LevelFilter::Trace;
Some(log_impl::Output::Stdout(log_impl::Stdout {
stream,
line_sep,
}))
}
OutputInner::Stderr { stream, line_sep } => {
max_child_level = log::LevelFilter::Trace;
Some(log_impl::Output::Stderr(log_impl::Stderr {
stream,
line_sep,
}))
}
OutputInner::File { stream, line_sep } => {
max_child_level = log::LevelFilter::Trace;
Some(log_impl::Output::File(log_impl::File {
stream: Mutex::new(io::BufWriter::new(stream)),
line_sep,
}))
}
OutputInner::Writer { stream, line_sep } => {
max_child_level = log::LevelFilter::Trace;
Some(log_impl::Output::Writer(log_impl::Writer {
stream: Mutex::new(stream),
line_sep,
}))
}
#[cfg(all(not(windows), feature = "reopen-03"))]
OutputInner::Reopen { stream, line_sep } => {
max_child_level = log::LevelFilter::Trace;
Some(log_impl::Output::Reopen(log_impl::Reopen {
stream: Mutex::new(stream),
line_sep,
}))
}
#[cfg(all(not(windows), feature = "reopen-1"))]
OutputInner::Reopen1 { stream, line_sep } => {
max_child_level = log::LevelFilter::Trace;
Some(log_impl::Output::Reopen1(log_impl::Reopen1 {
stream: Mutex::new(stream),
line_sep,
}))
}
OutputInner::Sender { stream, line_sep } => {
max_child_level = log::LevelFilter::Trace;
Some(log_impl::Output::Sender(log_impl::Sender {
stream: Mutex::new(stream),
line_sep,
}))
}
#[cfg(all(not(windows), feature = "syslog-3"))]
OutputInner::Syslog3(log) => {
max_child_level = log::LevelFilter::Trace;
Some(log_impl::Output::Syslog3(log_impl::Syslog3 { inner: log }))
}
#[cfg(all(not(windows), feature = "syslog-4"))]
OutputInner::Syslog4Rfc3164(logger) => {
max_child_level = log::LevelFilter::Trace;
Some(log_impl::Output::Syslog4Rfc3164(log_impl::Syslog4Rfc3164 {
inner: Mutex::new(logger),
}))
}
#[cfg(all(not(windows), feature = "syslog-4"))]
OutputInner::Syslog4Rfc5424 { logger, transform } => {
max_child_level = log::LevelFilter::Trace;
Some(log_impl::Output::Syslog4Rfc5424(log_impl::Syslog4Rfc5424 {
inner: Mutex::new(logger),
transform,
}))
}
#[cfg(all(not(windows), feature = "syslog-6"))]
OutputInner::Syslog6Rfc3164(logger) => {
max_child_level = log::LevelFilter::Trace;
Some(log_impl::Output::Syslog6Rfc3164(log_impl::Syslog6Rfc3164 {
inner: Mutex::new(logger),
}))
}
#[cfg(all(not(windows), feature = "syslog-6"))]
OutputInner::Syslog6Rfc5424 { logger, transform } => {
max_child_level = log::LevelFilter::Trace;
Some(log_impl::Output::Syslog6Rfc5424(log_impl::Syslog6Rfc5424 {
inner: Mutex::new(logger),
transform,
}))
}
OutputInner::Panic => {
max_child_level = log::LevelFilter::Trace;
Some(log_impl::Output::Panic(log_impl::Panic))
}
OutputInner::Dispatch(child_dispatch) => {
let (child_level, child) = child_dispatch.into_dispatch();
if child_level > log::LevelFilter::Off {
max_child_level = cmp::max(max_child_level, child_level);
Some(log_impl::Output::Dispatch(child))
} else {
None
}
}
OutputInner::SharedDispatch(child_dispatch) => {
let SharedDispatch {
inner: child,
min_level: child_level,
} = child_dispatch;
if child_level > log::LevelFilter::Off {
max_child_level = cmp::max(max_child_level, child_level);
Some(log_impl::Output::SharedDispatch(child))
} else {
None
}
}
OutputInner::OtherBoxed(child_log) => {
max_child_level = log::LevelFilter::Trace;
Some(log_impl::Output::OtherBoxed(child_log))
}
OutputInner::OtherStatic(child_log) => {
max_child_level = log::LevelFilter::Trace;
Some(log_impl::Output::OtherStatic(child_log))
}
#[cfg(feature = "date-based")]
OutputInner::DateBased { config } => {
max_child_level = log::LevelFilter::Trace;
let config = log_impl::DateBasedConfig::new(
config.line_sep,
config.file_prefix,
config.file_suffix,
if config.utc_time {
log_impl::ConfiguredTimezone::Utc
} else {
log_impl::ConfiguredTimezone::Local
},
);
let computed_suffix = config.compute_current_suffix();
// ignore errors - we'll just retry later.
let initial_file = config.open_current_log_file(&computed_suffix).ok();
Some(log_impl::Output::DateBased(log_impl::DateBased {
config,
state: Mutex::new(DateBasedState::new(computed_suffix, initial_file)),
}))
}
})
.collect();
let min_level = levels
.iter()
.map(|t| t.1)
.max()
.map_or(default_level, |lvl| cmp::max(lvl, default_level));
let real_min = cmp::min(min_level, max_child_level);
filters.shrink_to_fit();
let dispatch = log_impl::Dispatch {
output,
default_level,
levels: levels.into(),
format,
filters,
};
(real_min, dispatch)
}
/// Builds this logger into a `Box<log::Log>` and calculates the minimum
/// log level needed to have any effect.
///
/// While this method is exposed publicly, [`Dispatch::apply`] is typically
/// used instead.
///
/// The returned LevelFilter is a calculation for all level filters of this
/// logger and child loggers, and is the minimum log level needed to
/// for a record to have any chance of passing through this logger.
///
/// [`Dispatch::apply`]: #method.apply
///
/// Example usage:
///
/// ```
/// # fn main() {
/// let (min_level, log) = fern::Dispatch::new()
/// .level(log::LevelFilter::Info)
/// .chain(std::io::stdout())
/// .into_log();
///
/// assert_eq!(min_level, log::LevelFilter::Info);
/// # }
/// ```
pub fn into_log(self) -> (log::LevelFilter, Box<dyn log::Log>) {
let (level, logger) = self.into_dispatch();
if level == log::LevelFilter::Off {
(level, Box::new(log_impl::Null))
} else {
(level, Box::new(logger))
}
}
/// Builds this logger and instantiates it as the global [`log`] logger.
///
/// # Errors:
///
/// This function will return an error if a global logger has already been
/// set to a previous logger.
///
/// [`log`]: https://github.com/rust-lang-nursery/log
pub fn apply(self) -> Result<(), log::SetLoggerError> {
let (max_level, log) = self.into_log();
log::set_boxed_logger(log)?;
log::set_max_level(max_level);
Ok(())
}
}
/// This enum contains various outputs that you can send messages to.
enum OutputInner {
/// Prints all messages to stdout with `line_sep` separator.
Stdout {
stream: io::Stdout,
line_sep: Cow<'static, str>,
},
/// Prints all messages to stderr with `line_sep` separator.
Stderr {
stream: io::Stderr,
line_sep: Cow<'static, str>,
},
/// Writes all messages to file with `line_sep` separator.
File {
stream: fs::File,
line_sep: Cow<'static, str>,
},
/// Writes all messages to the writer with `line_sep` separator.
Writer {
stream: Box<dyn Write + Send>,
line_sep: Cow<'static, str>,
},
/// Writes all messages to the reopen::Reopen file with `line_sep`
/// separator.
#[cfg(all(not(windows), feature = "reopen-03"))]
Reopen {
stream: reopen03::Reopen<fs::File>,
line_sep: Cow<'static, str>,
},
/// Writes all messages to the reopen::Reopen file with `line_sep`
/// separator.
#[cfg(all(not(windows), feature = "reopen-1"))]
Reopen1 {
stream: reopen1::Reopen<fs::File>,
line_sep: Cow<'static, str>,
},
/// Writes all messages to mpst::Sender with `line_sep` separator.
Sender {
stream: Sender<String>,
line_sep: Cow<'static, str>,
},
/// Passes all messages to other dispatch.
Dispatch(Dispatch),
/// Passes all messages to other dispatch that's shared.
SharedDispatch(SharedDispatch),
/// Passes all messages to other logger.
OtherBoxed(Box<dyn Log>),
/// Passes all messages to other logger.
OtherStatic(&'static dyn Log),
/// Passes all messages to the syslog.
#[cfg(all(not(windows), feature = "syslog-3"))]
Syslog3(syslog3::Logger),
/// Passes all messages to the syslog.
#[cfg(all(not(windows), feature = "syslog-4"))]
Syslog4Rfc3164(Syslog4Rfc3164Logger),
/// Sends all messages through the transform then passes to the syslog.
#[cfg(all(not(windows), feature = "syslog-4"))]
Syslog4Rfc5424 {
logger: Syslog4Rfc5424Logger,
transform: Box<Syslog4TransformFn>,
},
#[cfg(all(not(windows), feature = "syslog-6"))]
Syslog6Rfc3164(Syslog6Rfc3164Logger),
/// Sends all messages through the transform then passes to the syslog.
#[cfg(all(not(windows), feature = "syslog-6"))]
Syslog6Rfc5424 {
logger: Syslog6Rfc5424Logger,
transform: Box<Syslog6TransformFn>,
},
/// Panics with messages text for all messages.
Panic,
/// File logger with custom date and timestamp suffix in file name.
#[cfg(feature = "date-based")]
DateBased { config: DateBased },
}
/// Logger which will panic whenever anything is logged. The panic
/// will be exactly the message of the log.
///
/// `Panic` is useful primarily as a secondary logger, filtered by warning or
/// error.
///
/// # Examples
///
/// This configuration will output all messages to stdout and panic if an Error
/// message is sent.
///
/// ```
/// fern::Dispatch::new()
/// // format, etc.
/// .chain(std::io::stdout())
/// .chain(
/// fern::Dispatch::new()
/// .level(log::LevelFilter::Error)
/// .chain(fern::Panic),
/// )
/// # /*
/// .apply()?;
/// # */ .into_log();
/// ```
///
/// This sets up a "panic on warn+" logger, and ignores errors so it can be
/// called multiple times.
///
/// This might be useful in test setup, for example, to disallow warn-level
/// messages.
///
/// ```no_run
/// fn setup_panic_logging() {
/// fern::Dispatch::new()
/// .level(log::LevelFilter::Warn)
/// .chain(fern::Panic)
/// .apply()
/// // ignore errors from setting up logging twice
/// .ok();
/// }
/// ```
pub struct Panic;
/// Configuration for a logger output.
pub struct Output(OutputInner);
impl From<Dispatch> for Output {
/// Creates an output logger forwarding all messages to the dispatch.
fn from(log: Dispatch) -> Self {
Output(OutputInner::Dispatch(log))
}
}
impl From<SharedDispatch> for Output {
/// Creates an output logger forwarding all messages to the dispatch.
fn from(log: SharedDispatch) -> Self {
Output(OutputInner::SharedDispatch(log))
}
}
impl From<Box<dyn Log>> for Output {
/// Creates an output logger forwarding all messages to the custom logger.
fn from(log: Box<dyn Log>) -> Self {
Output(OutputInner::OtherBoxed(log))
}
}
impl From<&'static dyn Log> for Output {
/// Creates an output logger forwarding all messages to the custom logger.
fn from(log: &'static dyn Log) -> Self {
Output(OutputInner::OtherStatic(log))
}
}
impl From<fs::File> for Output {
/// Creates an output logger which writes all messages to the file with
/// `\n` as the separator.
///
/// File writes are buffered and flushed once per log record.
fn from(file: fs::File) -> Self {
Output(OutputInner::File {
stream: file,
line_sep: "\n".into(),
})
}
}
impl From<Box<dyn Write + Send>> for Output {
/// Creates an output logger which writes all messages to the writer with
/// `\n` as the separator.
///
/// This does no buffering and it is up to the writer to do buffering as
/// needed (eg. wrap it in `BufWriter`). However, flush is called after
/// each log record.
fn from(writer: Box<dyn Write + Send>) -> Self {
Output(OutputInner::Writer {
stream: writer,
line_sep: "\n".into(),
})
}
}
#[cfg(all(not(windows), feature = "reopen-03"))]
impl From<reopen03::Reopen<fs::File>> for Output {
/// Creates an output logger which writes all messages to the file contained
/// in the Reopen struct, using `\n` as the separator.
fn from(reopen: reopen03::Reopen<fs::File>) -> Self {
Output(OutputInner::Reopen {
stream: reopen,
line_sep: "\n".into(),
})
}
}
#[cfg(all(not(windows), feature = "reopen-1"))]
impl From<reopen1::Reopen<fs::File>> for Output {
/// Creates an output logger which writes all messages to the file contained
/// in the Reopen struct, using `\n` as the separator.
fn from(reopen: reopen1::Reopen<fs::File>) -> Self {
Output(OutputInner::Reopen1 {
stream: reopen,
line_sep: "\n".into(),
})
}
}
impl From<io::Stdout> for Output {
/// Creates an output logger which writes all messages to stdout with the
/// given handle and `\n` as the separator.
fn from(stream: io::Stdout) -> Self {
Output(OutputInner::Stdout {
stream,
line_sep: "\n".into(),
})
}
}
impl From<io::Stderr> for Output {
/// Creates an output logger which writes all messages to stderr with the
/// given handle and `\n` as the separator.
fn from(stream: io::Stderr) -> Self {
Output(OutputInner::Stderr {
stream,
line_sep: "\n".into(),
})
}
}
impl From<Sender<String>> for Output {
/// Creates an output logger which writes all messages to the given
/// mpsc::Sender with '\n' as the separator.
///
/// All messages sent to the mpsc channel are suffixed with '\n'.
fn from(stream: Sender<String>) -> Self {
Output(OutputInner::Sender {
stream,
line_sep: "\n".into(),
})
}
}
#[cfg(all(not(windows), feature = "syslog-3"))]
impl From<syslog3::Logger> for Output {
/// Creates an output logger which writes all messages to the given syslog
/// output.
///
/// Log levels are translated trace => debug, debug => debug, info =>
/// informational, warn => warning, and error => error.
///
/// This requires the `"syslog-3"` feature.
fn from(log: syslog3::Logger) -> Self {
Output(OutputInner::Syslog3(log))
}
}
#[cfg(all(not(windows), feature = "syslog-3"))]
impl From<Box<syslog3::Logger>> for Output {
/// Creates an output logger which writes all messages to the given syslog
/// output.
///
/// Log levels are translated trace => debug, debug => debug, info =>
/// informational, warn => warning, and error => error.
///
/// Note that while this takes a `Box<Logger>` for convenience (syslog
/// methods return `Box`es), it will be immediately unboxed upon storage
/// in the configuration structure. This will create a configuration
/// identical to that created by passing a raw `syslog::Logger`.
///
/// This requires the `"syslog-3"` feature.
fn from(log: Box<syslog3::Logger>) -> Self {
Output(OutputInner::Syslog3(*log))
}
}
#[cfg(all(not(windows), feature = "syslog-4"))]
impl From<Syslog4Rfc3164Logger> for Output {
/// Creates an output logger which writes all messages to the given syslog.
///
/// Log levels are translated trace => debug, debug => debug, info =>
/// informational, warn => warning, and error => error.
///
/// Note that due to <https://github.com/Geal/rust-syslog/issues/41>,
/// logging to this backend requires one allocation per log call.
///
/// This is for RFC 3164 loggers. To use an RFC 5424 logger, use the
/// [`Output::syslog_5424`] helper method.
///
/// This requires the `"syslog-4"` feature.
fn from(log: Syslog4Rfc3164Logger) -> Self {
Output(OutputInner::Syslog4Rfc3164(log))
}
}
#[cfg(all(not(windows), feature = "syslog-6"))]
impl From<Syslog6Rfc3164Logger> for Output {
/// Creates an output logger which writes all messages to the given syslog.
///
/// Log levels are translated trace => debug, debug => debug, info =>
/// informational, warn => warning, and error => error.
///
/// Note that due to <https://github.com/Geal/rust-syslog/issues/41>,
/// logging to this backend requires one allocation per log call.
///
/// This is for RFC 3164 loggers. To use an RFC 5424 logger, use the
/// [`Output::syslog_5424`] helper method.
///
/// This requires the `"syslog-4"` feature.
fn from(log: Syslog6Rfc3164Logger) -> Self {
Output(OutputInner::Syslog6Rfc3164(log))
}
}
impl From<Panic> for Output {
/// Creates an output logger which will panic with message text for all
/// messages.
fn from(_: Panic) -> Self {
Output(OutputInner::Panic)
}
}
impl Output {
/// Returns a file logger using a custom separator.
///
/// If the default separator of `\n` is acceptable, an [`fs::File`]
/// instance can be passed into [`Dispatch::chain`] directly.
///
/// ```no_run
/// # fn setup_logger() -> Result<(), fern::InitError> {
/// fern::Dispatch::new().chain(std::fs::File::create("log")?)
/// # .into_log();
/// # Ok(())
/// # }
/// #
/// # fn main() { setup_logger().expect("failed to set up logger"); }
/// ```
///
/// ```no_run
/// # fn setup_logger() -> Result<(), fern::InitError> {
/// fern::Dispatch::new().chain(fern::log_file("log")?)
/// # .into_log();
/// # Ok(())
/// # }
/// #
/// # fn main() { setup_logger().expect("failed to set up logger"); }
/// ```
///
/// Example usage (using [`fern::log_file`]):
///
/// ```no_run
/// # fn setup_logger() -> Result<(), fern::InitError> {
/// fern::Dispatch::new().chain(fern::Output::file(fern::log_file("log")?, "\r\n"))
/// # .into_log();
/// # Ok(())
/// # }
/// #
/// # fn main() { setup_logger().expect("failed to set up logger"); }
/// ```
///
/// [`fs::File`]: https://doc.rust-lang.org/std/fs/struct.File.html
/// [`Dispatch::chain`]: struct.Dispatch.html#method.chain
/// [`fern::log_file`]: fn.log_file.html
pub fn file<T: Into<Cow<'static, str>>>(file: fs::File, line_sep: T) -> Self {
Output(OutputInner::File {
stream: file,
line_sep: line_sep.into(),
})
}
/// Returns a logger using arbitrary write object and custom separator.
///
/// If the default separator of `\n` is acceptable, an `Box<Write + Send>`
/// instance can be passed into [`Dispatch::chain`] directly.
///
/// ```no_run
/// # fn setup_logger() -> Result<(), fern::InitError> {
/// // Anything implementing 'Write' works.
/// let mut writer = std::io::Cursor::new(Vec::<u8>::new());
///
/// fern::Dispatch::new()
/// // as long as we explicitly cast into a type-erased Box
/// .chain(Box::new(writer) as Box<std::io::Write + Send>)
/// # .into_log();
/// # Ok(())
/// # }
/// #
/// # fn main() { setup_logger().expect("failed to set up logger"); }
/// ```
///
/// Example usage:
///
/// ```no_run
/// # fn setup_logger() -> Result<(), fern::InitError> {
/// let writer = Box::new(std::io::Cursor::new(Vec::<u8>::new()));
///
/// fern::Dispatch::new().chain(fern::Output::writer(writer, "\r\n"))
/// # .into_log();
/// # Ok(())
/// # }
/// #
/// # fn main() { setup_logger().expect("failed to set up logger"); }
/// ```
///
/// [`Dispatch::chain`]: struct.Dispatch.html#method.chain
pub fn writer<T: Into<Cow<'static, str>>>(writer: Box<dyn Write + Send>, line_sep: T) -> Self {
Output(OutputInner::Writer {
stream: writer,
line_sep: line_sep.into(),
})
}
/// Returns a reopenable logger, i.e., handling SIGHUP.
///
/// If the default separator of `\n` is acceptable, a `Reopen`
/// instance can be passed into [`Dispatch::chain`] directly.
///
/// This function is not available on Windows, and it requires the `reopen-03`
/// feature to be enabled.
///
/// ```no_run
/// use std::fs::OpenOptions;
/// # fn setup_logger() -> Result<(), fern::InitError> {
/// let reopenable = reopen03::Reopen::new(Box::new(|| {
/// OpenOptions::new()
/// .create(true)
/// .write(true)
/// .append(true)
/// .open("/tmp/output.log")
/// }))
/// .unwrap();
///
/// fern::Dispatch::new().chain(fern::Output::reopen(reopenable, "\n"))
/// # .into_log();
/// # Ok(())
/// # }
/// #
/// # fn main() { setup_logger().expect("failed to set up logger"); }
/// ```
/// [`Dispatch::chain`]: struct.Dispatch.html#method.chain
#[cfg(all(not(windows), feature = "reopen-03"))]
pub fn reopen<T: Into<Cow<'static, str>>>(
reopen: reopen03::Reopen<fs::File>,
line_sep: T,
) -> Self {
Output(OutputInner::Reopen {
stream: reopen,
line_sep: line_sep.into(),
})
}
/// Returns a reopenable logger, i.e., handling SIGHUP.
///
/// If the default separator of `\n` is acceptable, a `Reopen`
/// instance can be passed into [`Dispatch::chain`] directly.
///
/// This function is not available on Windows, and it requires the `reopen-03`
/// feature to be enabled.
///
/// ```no_run
/// use std::fs::OpenOptions;
/// # fn setup_logger() -> Result<(), fern::InitError> {
/// let reopenable = reopen1::Reopen::new(Box::new(|| {
/// OpenOptions::new()
/// .create(true)
/// .write(true)
/// .append(true)
/// .open("/tmp/output.log")
/// }))
/// .unwrap();
///
/// fern::Dispatch::new().chain(fern::Output::reopen1(reopenable, "\n"))
/// # .into_log();
/// # Ok(())
/// # }
/// #
/// # fn main() { setup_logger().expect("failed to set up logger"); }
/// ```
/// [`Dispatch::chain`]: struct.Dispatch.html#method.chain
#[cfg(all(not(windows), feature = "reopen-1"))]
pub fn reopen1<T: Into<Cow<'static, str>>>(
reopen: reopen1::Reopen<fs::File>,
line_sep: T,
) -> Self {
Output(OutputInner::Reopen1 {
stream: reopen,
line_sep: line_sep.into(),
})
}
/// Returns an stdout logger using a custom separator.
///
/// If the default separator of `\n` is acceptable, an `io::Stdout`
/// instance can be passed into `Dispatch::chain()` directly.
///
/// ```
/// fern::Dispatch::new().chain(std::io::stdout())
/// # .into_log();
/// ```
///
/// Example usage:
///
/// ```
/// fern::Dispatch::new()
/// // some unix tools use null bytes as message terminators so
/// // newlines in messages can be treated differently.
/// .chain(fern::Output::stdout("\0"))
/// # .into_log();
/// ```
pub fn stdout<T: Into<Cow<'static, str>>>(line_sep: T) -> Self {
Output(OutputInner::Stdout {
stream: io::stdout(),
line_sep: line_sep.into(),
})
}
/// Returns an stderr logger using a custom separator.
///
/// If the default separator of `\n` is acceptable, an `io::Stderr`
/// instance can be passed into `Dispatch::chain()` directly.
///
/// ```
/// fern::Dispatch::new().chain(std::io::stderr())
/// # .into_log();
/// ```
///
/// Example usage:
///
/// ```
/// fern::Dispatch::new().chain(fern::Output::stderr("\n\n\n"))
/// # .into_log();
/// ```
pub fn stderr<T: Into<Cow<'static, str>>>(line_sep: T) -> Self {
Output(OutputInner::Stderr {
stream: io::stderr(),
line_sep: line_sep.into(),
})
}
/// Returns a mpsc::Sender logger using a custom separator.
///
/// If the default separator of `\n` is acceptable, an
/// `mpsc::Sender<String>` instance can be passed into `Dispatch::
/// chain()` directly.
///
/// Each log message will be suffixed with the separator, then sent as a
/// single String to the given sender.
///
/// ```
/// use std::sync::mpsc::channel;
///
/// let (tx, rx) = channel();
/// fern::Dispatch::new().chain(tx)
/// # .into_log();
/// ```
pub fn sender<T: Into<Cow<'static, str>>>(sender: Sender<String>, line_sep: T) -> Self {
Output(OutputInner::Sender {
stream: sender,
line_sep: line_sep.into(),
})
}
/// Returns a logger which logs into an RFC5424 syslog.
///
/// This method takes an additional transform method to turn the log data
/// into RFC5424 data.
///
/// I've honestly got no clue what the expected keys and values are for
/// this kind of logging, so I'm just going to link [the rfc] instead.
///
/// If you're an expert on syslog logging and would like to contribute
/// an example to put here, it would be gladly accepted!
///
/// This requires the `"syslog-4"` feature.
///
/// [the rfc]: https://tools.ietf.org/html/rfc5424
#[cfg(all(not(windows), feature = "syslog-4"))]
pub fn syslog_5424<F>(logger: Syslog4Rfc5424Logger, transform: F) -> Self
where
F: Fn(&log::Record) -> (i32, HashMap<String, HashMap<String, String>>, String)
+ Sync
+ Send
+ 'static,
{
Output(OutputInner::Syslog4Rfc5424 {
logger,
transform: Box::new(transform),
})
}
/// Returns a logger which logs into an RFC5424 syslog (using syslog version 6)
///
/// This method takes an additional transform method to turn the log data
/// into RFC5424 data.
///
/// I've honestly got no clue what the expected keys and values are for
/// this kind of logging, so I'm just going to link [the rfc] instead.
///
/// If you're an expert on syslog logging and would like to contribute
/// an example to put here, it would be gladly accepted!
///
/// This requires the `"syslog-6"` feature.
///
/// [the rfc]: https://tools.ietf.org/html/rfc5424
#[cfg(all(not(windows), feature = "syslog-6"))]
pub fn syslog6_5424<F>(logger: Syslog6Rfc5424Logger, transform: F) -> Self
where
F: Fn(&log::Record) -> (u32, HashMap<String, HashMap<String, String>>, String)
+ Sync
+ Send
+ 'static,
{
Output(OutputInner::Syslog6Rfc5424 {
logger,
transform: Box::new(transform),
})
}
/// Returns a logger which simply calls the given function with each
/// message.
///
/// The function will be called inline in the thread the log occurs on.
///
/// Example usage:
///
/// ```
/// fern::Dispatch::new().chain(fern::Output::call(|record| {
/// // this is mundane, but you can do anything here.
/// println!("{}", record.args());
/// }))
/// # .into_log();
/// ```
pub fn call<F>(func: F) -> Self
where
F: Fn(&log::Record) + Sync + Send + 'static,
{
struct CallShim<F>(F);
impl<F> log::Log for CallShim<F>
where
F: Fn(&log::Record) + Sync + Send + 'static,
{
fn enabled(&self, _: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
(self.0)(record)
}
fn flush(&self) {}
}
Self::from(Box::new(CallShim(func)) as Box<dyn log::Log>)
}
}
impl Default for Dispatch {
/// Returns a logger configuration that does nothing with log records.
///
/// Equivalent to [`Dispatch::new`].
///
/// [`Dispatch::new`]: #method.new
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for Dispatch {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
struct LevelsDebug<'a>(&'a [(Cow<'static, str>, log::LevelFilter)]);
impl<'a> fmt::Debug for LevelsDebug<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_map()
.entries(self.0.iter().map(|t| (t.0.as_ref(), t.1)))
.finish()
}
}
struct FiltersDebug<'a>(&'a [Box<Filter>]);
impl<'a> fmt::Debug for FiltersDebug<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_list()
.entries(self.0.iter().map(|_| "<filter closure>"))
.finish()
}
}
f.debug_struct("Dispatch")
.field(
"format",
&self.format.as_ref().map(|_| "<formatter closure>"),
)
.field("children", &self.children)
.field("default_level", &self.default_level)
.field("levels", &LevelsDebug(&self.levels))
.field("filters", &FiltersDebug(&self.filters))
.finish()
}
}
impl fmt::Debug for OutputInner {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
OutputInner::Stdout {
ref stream,
ref line_sep,
} => f
.debug_struct("Output::Stdout")
.field("stream", stream)
.field("line_sep", line_sep)
.finish(),
OutputInner::Stderr {
ref stream,
ref line_sep,
} => f
.debug_struct("Output::Stderr")
.field("stream", stream)
.field("line_sep", line_sep)
.finish(),
OutputInner::File {
ref stream,
ref line_sep,
} => f
.debug_struct("Output::File")
.field("stream", stream)
.field("line_sep", line_sep)
.finish(),
OutputInner::Writer { ref line_sep, .. } => f
.debug_struct("Output::Writer")
.field("stream", &"<unknown writer>")
.field("line_sep", line_sep)
.finish(),
#[cfg(all(not(windows), feature = "reopen-03"))]
OutputInner::Reopen { ref line_sep, .. } => f
.debug_struct("Output::Reopen")
.field("stream", &"<unknown reopen file>")
.field("line_sep", line_sep)
.finish(),
#[cfg(all(not(windows), feature = "reopen-1"))]
OutputInner::Reopen1 {
ref line_sep,
ref stream,
} => f
.debug_struct("Output::Reopen1")
.field("stream", stream)
.field("line_sep", line_sep)
.finish(),
OutputInner::Sender {
ref stream,
ref line_sep,
} => f
.debug_struct("Output::Sender")
.field("stream", stream)
.field("line_sep", line_sep)
.finish(),
#[cfg(all(not(windows), feature = "syslog-3"))]
OutputInner::Syslog3(_) => f
.debug_tuple("Output::Syslog3")
.field(&"<unprintable syslog::Logger>")
.finish(),
#[cfg(all(not(windows), feature = "syslog-4"))]
OutputInner::Syslog4Rfc3164 { .. } => f
.debug_tuple("Output::Syslog4Rfc3164")
.field(&"<unprintable syslog::Logger>")
.finish(),
#[cfg(all(not(windows), feature = "syslog-4"))]
OutputInner::Syslog4Rfc5424 { .. } => f
.debug_tuple("Output::Syslog4Rfc5424")
.field(&"<unprintable syslog::Logger>")
.finish(),
#[cfg(all(not(windows), feature = "syslog-6"))]
OutputInner::Syslog6Rfc3164 { .. } => f
.debug_tuple("Output::Syslog6Rfc3164")
.field(&"<unprintable syslog::Logger>")
.finish(),
#[cfg(all(not(windows), feature = "syslog-6"))]
OutputInner::Syslog6Rfc5424 { .. } => f
.debug_tuple("Output::Syslog6Rfc5424")
.field(&"<unprintable syslog::Logger>")
.finish(),
OutputInner::Dispatch(ref dispatch) => {
f.debug_tuple("Output::Dispatch").field(dispatch).finish()
}
OutputInner::SharedDispatch(_) => f
.debug_tuple("Output::SharedDispatch")
.field(&"<built Dispatch logger>")
.finish(),
OutputInner::OtherBoxed { .. } => f
.debug_tuple("Output::OtherBoxed")
.field(&"<boxed logger>")
.finish(),
OutputInner::OtherStatic { .. } => f
.debug_tuple("Output::OtherStatic")
.field(&"<boxed logger>")
.finish(),
OutputInner::Panic => f.debug_tuple("Output::Panic").finish(),
#[cfg(feature = "date-based")]
OutputInner::DateBased { ref config } => f
.debug_struct("Output::DateBased")
.field("config", config)
.finish(),
}
}
}
impl fmt::Debug for Output {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
/// This is used to generate log file suffixed based on date, hour, and minute.
///
/// The log file will be rotated automatically when the date changes.
#[derive(Debug)]
#[cfg(feature = "date-based")]
pub struct DateBased {
file_prefix: PathBuf,
file_suffix: Cow<'static, str>,
line_sep: Cow<'static, str>,
utc_time: bool,
}
#[cfg(feature = "date-based")]
impl DateBased {
/// Create new date-based file logger with the given file prefix and
/// strftime-based suffix pattern.
///
/// On initialization, fern will create a file with the suffix formatted
/// with the current time (either utc or local, see below). Each time a
/// record is logged, the format is checked against the current time, and if
/// the time has changed, the old file is closed and a new one opened.
///
/// `file_suffix` will be interpreted as an `strftime` format. See
/// [`chrono::format::strftime`] for more information.
///
/// `file_prefix` may be a full file path, and will be prepended to the
/// suffix to create the final file.
///
/// Note that no separator will be placed in between `file_name` and
/// `file_suffix_pattern`. So if you call `DateBased::new("hello",
/// "%Y")`, the result will be a filepath `hello2019`.
///
/// By default, this will use local time. For UTC time instead, use the
/// [`.utc_time()`][DateBased::utc_time] method after creating.
///
/// By default, this will use `\n` as a line separator. For a custom
/// separator, use the [`.line_sep`][DateBased::line_sep] method
/// after creating.
///
/// # Examples
///
/// Containing the date (year, month and day):
///
/// ```
/// // logs/2019-10-23-my-program.log
/// let log = fern::DateBased::new("logs/", "%Y-%m-%d-my-program.log");
///
/// // program.log.23102019
/// let log = fern::DateBased::new("my-program.log.", "%d%m%Y");
/// ```
///
/// Containing the hour:
///
/// ```
/// // logs/2019-10-23 13 my-program.log
/// let log = fern::DateBased::new("logs/", "%Y-%m-%d %H my-program.log");
///
/// // program.log.2310201913
/// let log = fern::DateBased::new("my-program.log.", "%d%m%Y%H");
/// ```
///
/// Containing the minute:
///
/// ```
/// // logs/2019-10-23 13 my-program.log
/// let log = fern::DateBased::new("logs/", "%Y-%m-%d %H my-program.log");
///
/// // program.log.2310201913
/// let log = fern::DateBased::new("my-program.log.", "%d%m%Y%H");
/// ```
///
/// UNIX time, or seconds since 00:00 Jan 1st 1970:
///
/// ```
/// // logs/1571822854-my-program.log
/// let log = fern::DateBased::new("logs/", "%s-my-program.log");
///
/// // program.log.1571822854
/// let log = fern::DateBased::new("my-program.log.", "%s");
/// ```
///
/// Hourly, using UTC time:
///
/// ```
/// // logs/2019-10-23 23 my-program.log
/// let log = fern::DateBased::new("logs/", "%Y-%m-%d %H my-program.log").utc_time();
///
/// // program.log.2310201923
/// let log = fern::DateBased::new("my-program.log.", "%d%m%Y%H").utc_time();
/// ```
///
/// [`chrono::format::strftime`]: https://docs.rs/chrono/0.4.6/chrono/format/strftime/index.html
pub fn new<T, U>(file_prefix: T, file_suffix: U) -> Self
where
T: AsRef<Path>,
U: Into<Cow<'static, str>>,
{
DateBased {
utc_time: false,
file_prefix: file_prefix.as_ref().to_owned(),
file_suffix: file_suffix.into(),
line_sep: "\n".into(),
}
}
/// Changes the line separator this logger will use.
///
/// The default line separator is `\n`.
///
/// # Examples
///
/// Using a windows line separator:
///
/// ```
/// let log = fern::DateBased::new("logs", "%s.log").line_sep("\r\n");
/// ```
pub fn line_sep<T>(mut self, line_sep: T) -> Self
where
T: Into<Cow<'static, str>>,
{
self.line_sep = line_sep.into();
self
}
/// Orients this log file suffix formatting to use UTC time.
///
/// The default is local time.
///
/// # Examples
///
/// This will use UTC time to determine the date:
///
/// ```
/// // program.log.2310201923
/// let log = fern::DateBased::new("my-program.log.", "%d%m%Y%H").utc_time();
/// ```
pub fn utc_time(mut self) -> Self {
self.utc_time = true;
self
}
/// Orients this log file suffix formatting to use local time.
///
/// This is the default option.
///
/// # Examples
///
/// This log file will use local time - the latter method call overrides the
/// former.
///
/// ```
/// // program.log.2310201923
/// let log = fern::DateBased::new("my-program.log.", "%d%m%Y%H")
/// .utc_time()
/// .local_time();
/// ```
pub fn local_time(mut self) -> Self {
self.utc_time = false;
self
}
}
#[cfg(feature = "date-based")]
impl From<DateBased> for Output {
/// Create an output logger which defers to the given date-based logger. Use
/// configuration methods on [DateBased] to set line separator and filename.
fn from(config: DateBased) -> Self {
Output(OutputInner::DateBased { config })
}
}