flexi_logger 0.10.1

An easy-to-configure and flexible logger that writes logs to stderr and/or to files. It allows custom logline formats, and it allows changing the log specification at runtime. It also allows defining additional log streams, e.g. for alert or security messages.
Documentation
#[cfg(feature = "specfile")]
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
#[cfg(feature = "specfile")]
use std::path::Path;
#[cfg(feature = "specfile")]
use std::sync::mpsc::channel;
#[cfg(feature = "specfile")]
use std::thread;
#[cfg(feature = "specfile")]
use std::time::Duration;

use flexi_logger::{FlexiLogger, LogSpec};
use primary_writer::PrimaryWriter;
use reconfiguration_handle::reconfiguration_handle;
use writers::{FileLogWriter, FileLogWriterBuilder, LogWriter};
use FormatFunction;
use ReconfigurationHandle;
use {formats, FlexiLoggerError, LogSpecification};

use log;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};

/// The entry-point for using `flexi_logger`.
///
/// A simple example with file logging might look like this:
///
/// ```rust
/// use flexi_logger::{Duplicate,Logger};
///
/// Logger::with_str("info, mycrate = debug")
///         .log_to_file()
///         .duplicate_to_stderr(Duplicate::Warn)
///         .start()
///         .unwrap_or_else(|e| panic!("Logger initialization failed with {}", e));
///
/// ```
///
///
/// `Logger` is a builder class that allows you to
/// * specify your desired (initial) loglevel-specification
///   * either programmatically as a String
///    ([`Logger::with_str()`](struct.Logger.html#method.with_str))
///   * or by providing a String in the environment
///    ([`Logger::with_env()`](struct.Logger.html#method.with_env)),
///   * or by combining both options
///    ([`Logger::with_env_or_str()`](struct.Logger.html#method.with_env_or_str)),
///   * or by building a `LogSpecification` programmatically
///    ([`Logger::with()`](struct.Logger.html#method.with)),
/// * use the desired configuration methods,
/// * and finally start the logger with
///
///   * [`start()`](struct.Logger.html#method.start),
///   * or [`start_reconfigurable()`](struct.Logger.html#method.start_reconfigurable),
///   * or [`start_with_specfile()`](struct.Logger.html#method.start_with_specfile).
///
pub struct Logger {
    spec: LogSpecification,
    parse_errs: Option<Vec<String>>,
    log_to_file: bool,
    duplicate: Duplicate,
    format: FormatFunction,
    flwb: FileLogWriterBuilder,
    other_writers: HashMap<String, Box<LogWriter>>,
}

/// Choose a way to create a Logger instance and define how to access the (initial)
/// loglevel-specification.
impl Logger {
    /// Creates a Logger that you provide with an explicit LogSpecification.
    /// By default, logs are written with `default_format` to `stderr`.
    pub fn with(logspec: LogSpecification) -> Logger {
        Logger::from_spec_and_errs(logspec, None)
    }

    /// Creates a Logger that reads the LogSpecification from a String or &str.
    /// [See LogSpecification](struct.LogSpecification.html) for the syntax.
    pub fn with_str<S: AsRef<str>>(s: S) -> Logger {
        Logger::from_result(LogSpecification::parse(s.as_ref()))
    }

    /// Creates a Logger that reads the LogSpecification from the environment variable RUST_LOG.
    pub fn with_env() -> Logger {
        Logger::from_result(LogSpecification::env())
    }

    /// Creates a Logger that reads the LogSpecification from the environment variable RUST_LOG,
    /// or derives it from the given String, if RUST_LOG is not set.
    pub fn with_env_or_str<S: AsRef<str>>(s: S) -> Logger {
        Logger::from_result(LogSpecification::env_or_parse(s))
    }

    fn from_spec_and_errs(spec: LogSpecification, parse_errs: Option<Vec<String>>) -> Logger {
        Logger {
            spec,
            parse_errs,
            log_to_file: false,
            duplicate: Duplicate::None,
            format: formats::default_format,
            flwb: FileLogWriter::builder(),
            other_writers: HashMap::<String, Box<LogWriter>>::new(),
        }
    }

    fn from_result(result: Result<LogSpecification, FlexiLoggerError>) -> Logger {
        match result {
            Ok(logspec) => Logger::from_spec_and_errs(logspec, None),
            Err(e) => match e {
                FlexiLoggerError::Parse(parse_errs, logspec) => {
                    Logger::from_spec_and_errs(logspec, Some(parse_errs))
                }
                _ => Logger::from_spec_and_errs(LogSpecification::off(), None),
            },
        }
    }
}

/// Choose a way how to start logging.
impl Logger {
    /// Consumes the Logger object and initializes `flexi_logger`.
    ///
    /// If started this way, the logger cannot be influenced anymore while the program is running.
    /// This is what you want in most of the cases.
    pub fn start(mut self) -> Result<(), FlexiLoggerError> {
        let max = self
            .spec
            .module_filters()
            .iter()
            .map(|d| d.level_filter)
            .max()
            .unwrap_or(log::LevelFilter::Off);

        log::set_boxed_logger(Box::new(FlexiLogger::new(
            LogSpec::STATIC(self.spec),
            Arc::new(if self.log_to_file {
                self.flwb = self.flwb.format(self.format);
                PrimaryWriter::file(self.duplicate, self.flwb.instantiate()?)
            } else {
                PrimaryWriter::stderr(self.format)
            }),
            self.other_writers,
        )))?;
        log::set_max_level(max);
        Ok(())
    }

    /// Consumes the Logger object and initializes `flexi_logger` in a way that
    /// subsequently the log specification can be updated programmatically.
    ///
    /// This allows e.g. to intensify logging for (buggy) parts of a (test) program, etc.
    ///
    /// ```rust
    /// use flexi_logger::Logger;
    ///
    /// let mut logger_handle = Logger::with_str("info")
    ///         .start_reconfigurable()
    ///         .unwrap_or_else(|e| panic!("Logger initialization failed with {}", e));
    ///
    /// // ...
    /// // intensify logging
    /// logger_handle.parse_new_spec("trace");
    ///
    /// // ... critical stuff happens here ...
    ///
    /// // switch logging back to normal
    /// logger_handle.parse_new_spec("info");
    ///
    /// // ...
    ///
    /// ```
    ///
    /// The resulting logger is still fast, but measurable slower for those log-calls (trace!() etc)
    /// that are on a deeper level than the deepest level in the LogSpecification.
    /// This is because the Log crate has an optimization for returning very fast from deep-level
    /// log calls, but the deepest level needs be given at initialization and cannot be updated
    /// later.
    ///
    /// Here is the output from a benchmark test, runnning on a windows laptop:
    ///
    ///  ```text
    ///   1  PS C:\projects\flexi_logger> cargo bench --bench bench_standard -- --nocapture
    ///   2      Finished release [optimized] target(s) in 0.4 secs
    ///   3       Running target\release\deps\bench_standard-20539c2be6d4f2e0.exe
    ///   4
    ///   5  running 4 tests
    ///   6  test b10_no_logger_active  ... bench:         118 ns/iter (+/- 19)
    ///   7  test b20_initialize_logger ... bench:           0 ns/iter (+/- 0)
    ///   8  test b30_relevant_logs     ... bench:     291,436 ns/iter (+/- 44,658)
    ///   9  test b40_suppressed_logs   ... bench:         123 ns/iter (+/- 5)
    ///  10
    ///  11  test result: ok. 0 passed; 0 failed; 0 ignored; 4 measured; 0 filtered out
    ///  12
    ///  13  PS C:\projects\flexi_logger> cargo bench --bench bench_reconfigurable -- --nocapture
    ///  14      Finished release [optimized] target(s) in 0.4 secs
    ///  15       Running target\release\deps\bench_reconfigurable-2e292a8d5c887d0d.exe
    ///  16
    ///  17  running 4 tests
    ///  18  test b10_no_logger_active  ... bench:         130 ns/iter (+/- 37)
    ///  19  test b20_initialize_logger ... bench:           0 ns/iter (+/- 0)
    ///  20  test b30_relevant_logs     ... bench:     301,092 ns/iter (+/- 87,452)
    ///  21  test b40_suppressed_logs   ... bench:       3,482 ns/iter (+/- 339)
    ///  22
    ///  23  test result: ok. 0 passed; 0 failed; 0 ignored; 4 measured; 0 filtered out
    ///  ```
    ///
    /// It shows that logging is fastest when no logger is active (lines 6 and 18).
    /// And it is just as fast when the above-mentioned optimization kicks in (line 9).
    ///
    /// Logging has measurable costs when logs are really written (line 8 and 20), independent
    /// of the reconfigurability feature of the flexi_logger.
    ///
    /// The measurable, but still in most cases not important, price for reconfigurability
    /// can be seen by comparing lines 9 and 21.
    ///
    pub fn start_reconfigurable(mut self) -> Result<ReconfigurationHandle, FlexiLoggerError> {
        let spec = Arc::new(RwLock::new(self.spec));

        let primary_writer = Arc::new(if self.log_to_file {
            self.flwb = self.flwb.format(self.format);
            PrimaryWriter::file(self.duplicate, self.flwb.instantiate()?)
        } else {
            PrimaryWriter::stderr(self.format)
        });

        let flexi_logger = FlexiLogger::new(
            LogSpec::DYNAMIC(Arc::clone(&spec)),
            Arc::clone(&primary_writer),
            self.other_writers,
        );

        log::set_boxed_logger(Box::new(flexi_logger))?;
        // no optimization possible, because the spec is dynamic, but max is not:
        log::set_max_level(log::LevelFilter::Trace);
        Ok(reconfiguration_handle(spec, primary_writer))
    }

    /// Consumes the Logger object and initializes `flexi_logger` in a way that
    /// subsequently the log specification can be updated manually.
    ///
    /// Uses the spec that was given to the factory method (`Logger::with()` etc)
    /// as initial spec and then tries to read the logspec from a file.
    ///
    /// If the file does not exist, `flexi_logger` creates the file and fills it
    /// with the initial spec (and in the respective file format, of course).
    ///
    /// ## Feature dependency
    ///
    /// The implementation of this configuration method uses some additional crates
    /// that you might not want to depend on with your program if you don't use this functionality.
    /// For that reason the method is only available if you activate the
    /// `specfile` feature. See `flexi_logger`'s [usage](index.html#usage) section for details.
    ///
    /// ## Usage
    ///
    /// A logger initialization like
    ///
    /// ```ignore
    /// use flexi_logger::Logger;
    ///     Logger::with_str("info")/*...*/.start_with_specfile("logspecification.toml");
    /// ```
    ///
    /// will create the file `logspecification.toml` (if it does not yet exist) with this content:
    ///
    /// ```toml
    /// ### Optional: Default log level
    /// global_level = 'info'
    /// ### Optional: specify a regular expression to suppress all messages that don't match
    /// #global_pattern = 'foo'
    ///
    /// ### Specific log levels per module are optionally defined in this section
    /// [modules]
    /// #'mod1' = 'warn'
    /// #'mod2' = 'debug'
    /// #'mod2::mod3' = 'trace'
    /// ```
    ///
    /// You can subsequently edit and modify the file according to your needs,
    /// while the program is running, and it will immediately take your changes into account.
    ///
    /// Currently only toml-files are supported, the file suffix thus must be `.toml`.
    ///
    /// The initial spec remains valid if the file cannot be read.
    ///
    /// If you update the specfile subsequently while the program is running, `flexi_logger`
    /// re-reads it automatically and adapts its behavior according to the new content.
    /// If the file cannot be read anymore, e.g. because the format is not correct, the
    /// previous logspec remains active.
    /// If the file is corrected subsequently, the log spec update will work again.
    #[cfg(feature = "specfile")]
    pub fn start_with_specfile<P: AsRef<Path>>(self, specfile: P) -> Result<(), FlexiLoggerError> {
        let specfile = specfile.as_ref().to_owned();
        self.spec.ensure_specfile_is_valid(&specfile)?;
        let mut handle = self.start_reconfigurable()?;

        // now setup fs notification to automatically reread the file, and initialize from the file
        thread::Builder::new().spawn(move || {
            // Create a channel to receive the events.
            let (tx, rx) = channel();
            // Create a watcher object, delivering debounced events
            let mut watcher = match watcher(tx, Duration::from_millis(800)) {
                Ok(w) => w,
                Err(e) => {
                    error!("watcher() failed with {:?}", e);
                    return;
                }
            };

            // watch the spec file
            match watcher.watch(&specfile, RecursiveMode::NonRecursive) {
                Err(e) => {
                    error!(
                        "watcher.watch() failed for the log specification file {:?}, caused by {:?}",
                        specfile, e
                    );
                    ::std::process::exit(-1);
                }
                Ok(_) => {
                    // initial read of the file: if that fails, just print an error and continue
                    match LogSpecification::file(&specfile) {
                        Ok(spec) => handle.set_new_spec(spec),
                        Err(e) => error!("Can't read the log specification file, due to {:?}", e),
                    }

                    loop {
                        match rx.recv() {
                            Ok(DebouncedEvent::Write(_)) => {
                                debug!("Got Write event");
                                match LogSpecification::file(&specfile) {
                                    Ok(spec) => handle.set_new_spec(spec),
                                    Err(e) => eprintln!(
                                        "Continuing with current log specification \
                                         because the log specification file is not readable, \
                                         due to {:?}",
                                        e
                                    ),
                                }
                            }
                            Ok(_event) => trace!("ignoring event {:?}", _event),
                            Err(e) => error!("watch error: {:?}", e),
                        }
                    }
                }
            }
        })?;

        Ok(())
    }
}

/// Simple methods for influencing the behavior of the Logger.
impl Logger {
    /// Allows verifying that no parsing errors have occured in the used factory method,
    /// and examining the parse error.
    ///
    /// The factory methods `Logger::with_str()`, `Logger::with_env()`,
    /// and `Logger::with_env_or_str()`,
    /// parse a log specification String, and deduce from it a `LogSpecification` object.
    /// Parsing errors are reported to stdout, but effectively ignored; in worst case, a
    /// LogSpecification might be used that turns off logging completely!
    ///
    /// This method gives programmatic access to parse errors, if there were any.
    ///
    /// In the following example we just panic if the spec was not free of errors:
    ///
    /// ```rust
    /// # use flexi_logger::Logger;
    /// # let some_log_spec_string = "hello";
    /// Logger::with_str(some_log_spec_string)
    /// .check_parser_error()
    /// .unwrap()
    /// .log_to_file()
    /// .start();
    /// ```
    pub fn check_parser_error(self) -> Result<Logger, FlexiLoggerError> {
        match self.parse_errs {
            Some(parse_errs) => Err(FlexiLoggerError::Parse(parse_errs, self.spec)),
            None => Ok(self),
        }
    }

    /// Makes the logger write all logs to a file, rather than to stderr.
    ///
    /// The default pattern for the filename is '\<program_name\>\_\<date\>\_\<time\>.\<suffix\>',
    ///  e.g. `myprog_2015-07-08_10-44-11.log`.
    pub fn log_to_file(mut self) -> Logger {
        self.log_to_file = true;
        self
    }

    /// Makes the logger print an info message to stdout with the name of the logfile
    /// when a logfile is opened for writing.
    pub fn print_message(mut self) -> Logger {
        self.flwb = self.flwb.print_message();
        self
    }

    /// Makes the logger write messages with the specified minimum severity additionally to stderr.
    pub fn duplicate_to_stderr(mut self, dup: Duplicate) -> Logger {
        self.duplicate = dup;
        self
    }

    /// Makes the logger write all logged error messages additionally to stderr.
    #[deprecated(note = "use duplicate_to_stderr(dup: Duplicate)")]
    pub fn duplicate_error(mut self) -> Logger {
        self.duplicate = Duplicate::Error;
        self
    }

    /// Makes the logger write all logged error, warning, and info messages additionally to stderr.
    #[deprecated(note = "use duplicate_to_stderr(dup: Duplicate)")]
    pub fn duplicate_info(mut self) -> Logger {
        self.duplicate = Duplicate::Info;
        self
    }

    /// Makes the logger use the provided format function for the log entries,
    /// rather than [formats::default_format](fn.default_format.html).
    ///
    /// You can either choose between some predefined variants,
    /// ```default_format```, ```opt_format```, ```detailed_format```, ```with_thread```,
    /// or you create and use your own format function
    /// with the signature ```fn(&Record) -> String```.
    pub fn format(mut self, format: FormatFunction) -> Logger {
        self.format = format;
        self
    }

    /// Specifies a folder for the log files.
    ///
    /// This parameter only has an effect if `log_to_file()` is used, too.
    /// If the specified folder does not exist, the initialization will fail.
    /// By default, the log files are created in the folder where the program was started.
    pub fn directory<S: Into<String>>(mut self, directory: S) -> Logger {
        self.flwb = self.flwb.directory(directory);
        self
    }

    /// Specifies a suffix for the log files.
    ///
    /// This parameter only has an effect if `log_to_file()` is used, too.
    pub fn suffix<S: Into<String>>(mut self, suffix: S) -> Logger {
        self.flwb = self.flwb.suffix(suffix);
        self
    }

    /// Makes the logger not include a timestamp into the names of the log files.
    ///
    /// This option only has an effect if `log_to_file()` is used, too.
    pub fn suppress_timestamp(mut self) -> Logger {
        self.flwb = self.flwb.suppress_timestamp();
        self
    }

    /// Prevents indefinite growth of log files.
    ///
    /// For details, see
    /// [FileLogWriterBuilder::rotate_over_size](./writers/struct.FileLogWriterBuilder.html#method.rotate_over_size).
    pub fn rotate_over_size(mut self, rotate_over_size: usize) -> Logger {
        self.flwb = self
            .flwb
            .rotate_over_size(rotate_over_size)
            .o_timestamp(false);
        self
    }

    /// Makes the logger append to the specified output file, if it exists already;
    /// by default, the file would be truncated.
    ///
    /// This option only has an effect if `log_to_file()` is used, too.
    /// This option will hardly make an effect if `suppress_timestamp()` is not used.
    pub fn append(mut self) -> Logger {
        self.flwb = self.flwb.append();
        self
    }

    /// The specified String is added to the log file name after the program name.
    ///
    /// This option only has an effect if `log_to_file()` is used, too.
    pub fn discriminant<S: Into<String>>(mut self, discriminant: S) -> Logger {
        self.flwb = self.flwb.discriminant(discriminant);
        self
    }

    /// The specified String will be used on linux systems to create in the current folder
    /// a symbolic link to the current log file.
    ///
    /// This option only has an effect if `log_to_file()` is used, too.
    pub fn create_symlink<S: Into<String>>(mut self, symlink: S) -> Logger {
        self.flwb = self.flwb.create_symlink(symlink);
        self
    }

    /// Registers a LogWriter implementation under the given target name.
    ///
    /// The target name should not start with an underscore.
    ///
    /// See [the module documentation of `writers`](writers/index.html).
    pub fn add_writer<S: Into<String>>(mut self, name: S, writer: Box<LogWriter>) -> Logger {
        self.other_writers.insert(name.into(), writer);
        self
    }
}

/// Alternative set of methods to control the behavior of the Logger.
/// Use these methods when you want to control the settings flexibly,
/// e.g. with commandline arguments via `docopts` or `clap`.
impl Logger {
    /// With true, makes the logger write all logs to a file, otherwise to stderr.
    pub fn o_log_to_file(mut self, log_to_file: bool) -> Logger {
        self.log_to_file = log_to_file;
        self
    }

    /// With true, makes the logger print an info message to stdout, each time
    /// when a new file is used for log-output.
    pub fn o_print_message(mut self, print_message: bool) -> Logger {
        self.flwb = self.flwb.o_print_message(print_message);
        self
    }

    /// With true, makes the logger write all logged error messages additionally to stderr;
    /// with false, no messages are duplicated.
    #[deprecated(note = "use duplicate_to_stderr(dup: Duplicate)")]
    pub fn o_duplicate_error(mut self, duplicate_error: bool) -> Logger {
        if duplicate_error {
            self.duplicate = Duplicate::Error;
        } else {
            self.duplicate = Duplicate::None;
        }
        self
    }

    /// With true, makes the logger write all logged error, warning,
    /// and info messages additionally to stderr;
    /// with false, no messages are duplicated.
    #[deprecated(note = "use duplicate_to_stderr(dup: Duplicate)")]
    pub fn o_duplicate_info(mut self, duplicate_info: bool) -> Logger {
        if duplicate_info {
            self.duplicate = Duplicate::Info;
        } else {
            self.duplicate = Duplicate::None;
        }
        self
    }

    /// Specifies a folder for the log files.
    ///
    /// This parameter only has an effect if `log_to_file` is set to true.
    /// If the specified folder does not exist, the initialization will fail.
    /// With None, the log files are created in the folder where the program was started.
    pub fn o_directory<S: Into<String>>(mut self, directory: Option<S>) -> Logger {
        self.flwb = self.flwb.o_directory(directory);
        self
    }

    /// This option only has an effect if `log_to_file` is set to true.
    ///
    /// By default, and with None, the log file will grow indefinitely.
    /// If a size is set, when the log file reaches or exceeds the specified size,
    /// the file will be closed and a new file will be opened.
    /// Also the filename pattern changes: instead of the timestamp, a serial number
    /// is included into the filename.
    ///
    /// The size is given in bytes, e.g. `o_rotate_over_size(Some(1_000))` will rotate
    /// files once they reach a size of 1 kB.
    pub fn o_rotate_over_size(mut self, rotate_over_size: Option<usize>) -> Logger {
        self.flwb = self
            .flwb
            .o_rotate_over_size(rotate_over_size)
            .o_timestamp(rotate_over_size.is_none());
        self
    }

    /// With true, makes the logger include a timestamp into the names of the log files.
    /// `true` is the default, but `rotate_over_size` sets it to `false`.
    /// With this method you can set it to `true` again.
    ///
    /// This parameter only has an effect if `log_to_file` is set to true.
    pub fn o_timestamp(mut self, timestamp: bool) -> Logger {
        self.flwb = self.flwb.o_timestamp(timestamp);
        self
    }

    /// This option only has an effect if `log_to_file` is set to true.
    ///
    /// If append is set to true, makes the logger append to the specified output file, if it exists.
    /// By default, or with false, the file would be truncated.
    ///
    /// This option will hardly make an effect if `suppress_timestamp()` is not used.

    pub fn o_append(mut self, append: bool) -> Logger {
        self.flwb = self.flwb.o_append(append);
        self
    }

    /// This option only has an effect if `log_to_file` is set to true.
    ///
    /// The specified String is added to the log file name.
    pub fn o_discriminant<S: Into<String>>(mut self, discriminant: Option<S>) -> Logger {
        self.flwb = self.flwb.o_discriminant(discriminant);
        self
    }

    /// This option only has an effect if `log_to_file` is set to true.
    ///
    /// If a String is specified, it will be used on linux systems to create in the current folder
    /// a symbolic link with this name to the current log file.
    pub fn o_create_symlink<S: Into<String>>(mut self, symlink: Option<S>) -> Logger {
        self.flwb = self.flwb.o_create_symlink(symlink);
        self
    }
}

/// Used to control which messages are to be duplicated to stderr, when log_to_file() is used.
pub enum Duplicate {
    /// No messages are duplicated.
    None,
    /// Only error messages are duplicated.
    Error,
    /// Error and warn messages are duplicated.
    Warn,
    /// Error, warn, and info messages are duplicated.
    Info,
    /// Error, warn, info, and debug messages are duplicated.
    Debug,
    /// All messages are duplicated.
    Trace,
    /// All messages are duplicated.
    All,
}