|
| 1 | +use log::{Level, Log, Metadata, Record, SetLoggerError}; |
| 2 | + |
| 3 | +// This is a special kind of logger that allows sending logs to different |
| 4 | +// targets depending on the log level. |
| 5 | +// |
| 6 | +// By default, if nothing is set, it acts as a regular env_log logger, |
| 7 | +// it sends everything to standard error. |
| 8 | +// |
| 9 | +// If the Env variable `STDOUT_LOG` is defined, it will be used for |
| 10 | +// configuring the standard out logger. |
| 11 | +// |
| 12 | +// The behavior is: |
| 13 | +// - If it is an error, the message is written to standard error. |
| 14 | +// - If it is not, and it matches the log level of the standard output logger (`STDOUT_LOG` env var), it will be send to standard output. |
| 15 | +// - If the above is not true, it is sent to the stderr logger that will log it or not depending on the value |
| 16 | +// of the RUST_LOG env var. |
| 17 | +// |
| 18 | +// So to summarize, if no `STDOUT_LOG` env var is present, the logger is the default logger. If `STDOUT_LOG` is set, everything |
| 19 | +// but errors, that matches the log level set in the `STDOUT_LOG` env var is sent to stdout. You can have also some esoteric configuration |
| 20 | +// where you set `RUST_LOG=debug` and `STDOUT_LOG=info`, in here, erros will go to stderr, warns and infos to stdout and debugs to stderr. |
| 21 | +// |
| 22 | +pub struct MultiLogger { |
| 23 | + stderr_logger: env_logger::Logger, |
| 24 | + stdout_logger: env_logger::Logger, |
| 25 | +} |
| 26 | + |
| 27 | +impl MultiLogger { |
| 28 | + fn new() -> Self { |
| 29 | + let stderr_logger = env_logger::builder().format_timestamp_micros().build(); |
| 30 | + let stdout_logger = env_logger::Builder::from_env("STDOUT_LOG") |
| 31 | + .format_timestamp_micros() |
| 32 | + .target(env_logger::Target::Stdout) |
| 33 | + .build(); |
| 34 | + |
| 35 | + Self { |
| 36 | + stderr_logger, |
| 37 | + stdout_logger, |
| 38 | + } |
| 39 | + } |
| 40 | + |
| 41 | + pub fn init() -> Result<(), SetLoggerError> { |
| 42 | + let logger = Self::new(); |
| 43 | + |
| 44 | + log::set_max_level(logger.stderr_logger.filter()); |
| 45 | + log::set_boxed_logger(Box::new(logger)) |
| 46 | + } |
| 47 | +} |
| 48 | + |
| 49 | +impl Log for MultiLogger { |
| 50 | + fn enabled(&self, metadata: &Metadata) -> bool { |
| 51 | + self.stderr_logger.enabled(metadata) && self.stdout_logger.enabled(metadata) |
| 52 | + } |
| 53 | + |
| 54 | + fn log(&self, record: &Record) { |
| 55 | + if record.level() == Level::Error { |
| 56 | + self.stderr_logger.log(record); |
| 57 | + } else { |
| 58 | + if self.stdout_logger.matches(record) { |
| 59 | + self.stdout_logger.log(record); |
| 60 | + } else { |
| 61 | + self.stderr_logger.log(record); |
| 62 | + } |
| 63 | + } |
| 64 | + } |
| 65 | + |
| 66 | + fn flush(&self) { |
| 67 | + self.stderr_logger.flush(); |
| 68 | + self.stdout_logger.flush(); |
| 69 | + } |
| 70 | +} |
| 71 | + |
| 72 | +#[cfg(test)] |
| 73 | +mod test { |
| 74 | + use super::*; |
| 75 | + |
| 76 | + #[test] |
| 77 | + fn test_init() { |
| 78 | + MultiLogger::init().unwrap(); |
| 79 | + } |
| 80 | +} |
0 commit comments