From 5c3c88d7a789a3c04b8760aa8116a33ef8acc104 Mon Sep 17 00:00:00 2001 From: Richard M Date: Tue, 10 Mar 2020 22:05:41 -0700 Subject: [PATCH 01/35] Expose errors as failure::Error (#138) --- Cargo.toml | 2 + src/append/console.rs | 7 +- src/append/file.rs | 7 +- src/append/mod.rs | 8 +- src/append/rolling_file/mod.rs | 12 +- .../rolling_file/policy/compound/mod.rs | 7 +- .../policy/compound/roll/delete.rs | 8 +- .../policy/compound/roll/fixed_window.rs | 19 ++- .../rolling_file/policy/compound/roll/mod.rs | 6 +- .../policy/compound/trigger/mod.rs | 6 +- .../policy/compound/trigger/size.rs | 8 +- src/append/rolling_file/policy/mod.rs | 6 +- src/config.rs | 57 +++------ src/encode/json.rs | 14 +-- src/encode/mod.rs | 10 +- src/encode/pattern/mod.rs | 13 +-- src/file.rs | 68 ++++------- src/filter/threshold.rs | 4 +- src/lib.rs | 10 +- src/priv_file.rs | 108 +++++++----------- 20 files changed, 162 insertions(+), 218 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 177a8de1..7f25438f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,8 @@ harness = false [dependencies] arc-swap = "0.4" chrono = { version = "0.4", optional = true } +failure_derive = "0.1.6" +failure = "0.1.6" flate2 = { version = "1.0", optional = true } fnv = "1.0" humantime = { version = "1.0", optional = true } diff --git a/src/append/console.rs b/src/append/console.rs index 2292e9b7..a2667aab 100644 --- a/src/append/console.rs +++ b/src/append/console.rs @@ -6,11 +6,12 @@ use log::Record; #[cfg(feature = "file")] use serde_derive::Deserialize; use std::{ - error::Error, fmt, io::{self, Write}, }; +use failure::Error; + #[cfg(feature = "file")] use crate::encode::EncoderConfig; #[cfg(feature = "file")] @@ -123,7 +124,7 @@ impl fmt::Debug for ConsoleAppender { } impl Append for ConsoleAppender { - fn append(&self, record: &Record) -> Result<(), Box> { + fn append(&self, record: &Record) -> Result<(), Error> { let mut writer = self.writer.lock(); self.encoder.encode(&mut writer, record)?; writer.flush()?; @@ -221,7 +222,7 @@ impl Deserialize for ConsoleAppenderDeserializer { &self, config: ConsoleAppenderConfig, deserializers: &Deserializers, - ) -> Result, Box> { + ) -> Result, failure::Error> { let mut appender = ConsoleAppender::builder(); if let Some(target) = config.target { let target = match target { diff --git a/src/append/file.rs b/src/append/file.rs index 6ceae436..f5d006df 100644 --- a/src/append/file.rs +++ b/src/append/file.rs @@ -7,13 +7,14 @@ use parking_lot::Mutex; #[cfg(feature = "file")] use serde_derive::Deserialize; use std::{ - error::Error, fmt, fs::{self, File, OpenOptions}, io::{self, BufWriter, Write}, path::{Path, PathBuf}, }; +use failure::Error; + #[cfg(feature = "file")] use crate::encode::EncoderConfig; #[cfg(feature = "file")] @@ -50,7 +51,7 @@ impl fmt::Debug for FileAppender { } impl Append for FileAppender { - fn append(&self, record: &Record) -> Result<(), Box> { + fn append(&self, record: &Record) -> Result<(), Error> { let mut file = self.file.lock(); self.encoder.encode(&mut *file, record)?; file.flush()?; @@ -145,7 +146,7 @@ impl Deserialize for FileAppenderDeserializer { &self, config: FileAppenderConfig, deserializers: &Deserializers, - ) -> Result, Box> { + ) -> Result, Error> { let mut appender = FileAppender::builder(); if let Some(append) = config.append { appender = appender.append(append); diff --git a/src/append/mod.rs b/src/append/mod.rs index a5207c41..5d300699 100644 --- a/src/append/mod.rs +++ b/src/append/mod.rs @@ -7,7 +7,9 @@ use serde::{de, Deserialize, Deserializer}; use serde_value::Value; #[cfg(feature = "file")] use std::collections::BTreeMap; -use std::{error::Error, fmt}; +use std::fmt; + +use failure::Error; #[cfg(feature = "file")] use crate::file::Deserializable; @@ -27,7 +29,7 @@ pub mod rolling_file; /// to a file or the console. pub trait Append: fmt::Debug + Send + Sync + 'static { /// Processes the provided `Record`. - fn append(&self, record: &Record) -> Result<(), Box>; + fn append(&self, record: &Record) -> Result<(), Error>; /// Flushes all in-flight records. fn flush(&self); @@ -41,7 +43,7 @@ impl Deserializable for dyn Append { } impl Append for T { - fn append(&self, record: &Record) -> Result<(), Box> { + fn append(&self, record: &Record) -> Result<(), Error> { self.log(record); Ok(()) } diff --git a/src/append/rolling_file/mod.rs b/src/append/rolling_file/mod.rs index 4f252ed2..4685bc23 100644 --- a/src/append/rolling_file/mod.rs +++ b/src/append/rolling_file/mod.rs @@ -25,13 +25,14 @@ use serde_value::Value; #[cfg(feature = "file")] use std::collections::BTreeMap; use std::{ - error::Error, fmt, fs::{self, File, OpenOptions}, io::{self, BufWriter, Write}, path::{Path, PathBuf}, }; +use failure::Error; + #[cfg(feature = "file")] use crate::encode::EncoderConfig; #[cfg(feature = "file")] @@ -169,7 +170,7 @@ impl fmt::Debug for RollingFileAppender { } impl Append for RollingFileAppender { - fn append(&self, record: &Record) -> Result<(), Box> { + fn append(&self, record: &Record) -> Result<(), Error> { // TODO(eas): Perhaps this is better as a concurrent queue? let mut writer = self.writer.lock(); @@ -326,7 +327,7 @@ impl Deserialize for RollingFileAppenderDeserializer { &self, config: RollingFileAppenderConfig, deserializers: &Deserializers, - ) -> Result, Box> { + ) -> Result, Error> { let mut builder = RollingFileAppender::builder(); if let Some(append) = config.append { builder = builder.append(append); @@ -345,11 +346,12 @@ impl Deserialize for RollingFileAppenderDeserializer { #[cfg(test)] mod test { use std::{ - error::Error, fs::File, io::{Read, Write}, }; + use failure::Error; + use super::*; use crate::append::rolling_file::policy::Policy; @@ -399,7 +401,7 @@ appenders: struct NopPolicy; impl Policy for NopPolicy { - fn process(&self, _: &mut LogFile) -> Result<(), Box> { + fn process(&self, _: &mut LogFile) -> Result<(), Error> { Ok(()) } } diff --git a/src/append/rolling_file/policy/compound/mod.rs b/src/append/rolling_file/policy/compound/mod.rs index 038db83d..9c0e4116 100644 --- a/src/append/rolling_file/policy/compound/mod.rs +++ b/src/append/rolling_file/policy/compound/mod.rs @@ -9,7 +9,8 @@ use serde_derive::Deserialize; use serde_value::Value; #[cfg(feature = "file")] use std::collections::BTreeMap; -use std::error::Error; + +use failure::Error; use crate::append::rolling_file::{ policy::{compound::roll::Roll, Policy}, @@ -101,7 +102,7 @@ impl CompoundPolicy { } impl Policy for CompoundPolicy { - fn process(&self, log: &mut LogFile) -> Result<(), Box> { + fn process(&self, log: &mut LogFile) -> Result<(), Error> { if self.trigger.trigger(log)? { log.roll(); self.roller.roll(log.path())?; @@ -149,7 +150,7 @@ impl Deserialize for CompoundPolicyDeserializer { &self, config: CompoundPolicyConfig, deserializers: &Deserializers, - ) -> Result, Box> { + ) -> Result, Error> { let trigger = deserializers.deserialize(&config.trigger.kind, config.trigger.config)?; let roller = deserializers.deserialize(&config.roller.kind, config.roller.config)?; Ok(Box::new(CompoundPolicy::new(trigger, roller))) diff --git a/src/append/rolling_file/policy/compound/roll/delete.rs b/src/append/rolling_file/policy/compound/roll/delete.rs index 92956a0c..fe053da5 100644 --- a/src/append/rolling_file/policy/compound/roll/delete.rs +++ b/src/append/rolling_file/policy/compound/roll/delete.rs @@ -4,7 +4,9 @@ #[cfg(feature = "file")] use serde_derive::Deserialize; -use std::{error::Error, fs, path::Path}; +use std::{fs, path::Path}; + +use failure::Error; use crate::append::rolling_file::policy::compound::roll::Roll; #[cfg(feature = "file")] @@ -24,7 +26,7 @@ pub struct DeleteRollerConfig { pub struct DeleteRoller(()); impl Roll for DeleteRoller { - fn roll(&self, file: &Path) -> Result<(), Box> { + fn roll(&self, file: &Path) -> Result<(), Error> { fs::remove_file(file).map_err(Into::into) } } @@ -56,7 +58,7 @@ impl Deserialize for DeleteRollerDeserializer { &self, _: DeleteRollerConfig, _: &Deserializers, - ) -> Result, Box> { + ) -> Result, Error> { Ok(Box::new(DeleteRoller::default())) } } diff --git a/src/append/rolling_file/policy/compound/roll/fixed_window.rs b/src/append/rolling_file/policy/compound/roll/fixed_window.rs index c1524c5b..6c61013f 100644 --- a/src/append/rolling_file/policy/compound/roll/fixed_window.rs +++ b/src/append/rolling_file/policy/compound/roll/fixed_window.rs @@ -9,11 +9,12 @@ use serde_derive::Deserialize; #[cfg(feature = "background_rotation")] use std::sync::Arc; use std::{ - error::Error, fs, io, path::{Path, PathBuf}, }; +use failure::{err_msg, Error}; + use crate::append::rolling_file::policy::compound::roll::Roll; #[cfg(feature = "file")] use crate::file::{Deserialize, Deserializers}; @@ -102,7 +103,7 @@ impl FixedWindowRoller { impl Roll for FixedWindowRoller { #[cfg(not(feature = "background_rotation"))] - fn roll(&self, file: &Path) -> Result<(), Box> { + fn roll(&self, file: &Path) -> Result<(), Error> { if self.count == 0 { return fs::remove_file(file).map_err(Into::into); } @@ -119,7 +120,7 @@ impl Roll for FixedWindowRoller { } #[cfg(feature = "background_rotation")] - fn roll(&self, file: &Path) -> Result<(), Box> { + fn roll(&self, file: &Path) -> Result<(), Error> { if self.count == 0 { return fs::remove_file(file).map_err(Into::into); } @@ -257,13 +258,9 @@ impl FixedWindowRollerBuilder { /// If the file extension of the pattern is `.gz` and the `gzip` Cargo /// feature is enabled, the archive files will be gzip-compressed. /// If the extension is `.gz` and the `gzip` feature is *not* enabled, an error will be returned. - pub fn build( - self, - pattern: &str, - count: u32, - ) -> Result> { + pub fn build(self, pattern: &str, count: u32) -> Result { if !pattern.contains("{}") { - return Err("pattern does not contain `{}`".into()); + return Err(err_msg("pattern does not contain `{}`")); } let compression = match Path::new(pattern).extension() { @@ -271,7 +268,7 @@ impl FixedWindowRollerBuilder { Some(e) if e == "gz" => Compression::Gzip, #[cfg(not(feature = "gzip"))] Some(e) if e == "gz" => { - return Err("gzip compression requires the `gzip` feature".into()); + return Err(err_msg("gzip compression requires the `gzip` feature")); } _ => Compression::None, }; @@ -321,7 +318,7 @@ impl Deserialize for FixedWindowRollerDeserializer { &self, config: FixedWindowRollerConfig, _: &Deserializers, - ) -> Result, Box> { + ) -> Result, Error> { let mut builder = FixedWindowRoller::builder(); if let Some(base) = config.base { builder = builder.base(base); diff --git a/src/append/rolling_file/policy/compound/roll/mod.rs b/src/append/rolling_file/policy/compound/roll/mod.rs index 82ddba46..957c47e3 100644 --- a/src/append/rolling_file/policy/compound/roll/mod.rs +++ b/src/append/rolling_file/policy/compound/roll/mod.rs @@ -1,6 +1,8 @@ //! Rollers -use std::{error::Error, fmt, path::Path}; +use std::{fmt, path::Path}; + +use failure::Error; #[cfg(feature = "file")] use crate::file::Deserializable; @@ -19,7 +21,7 @@ pub trait Roll: fmt::Debug + Send + Sync + 'static { /// /// If this method returns successfully, there *must* no longer be a file /// at the specified location. - fn roll(&self, file: &Path) -> Result<(), Box>; + fn roll(&self, file: &Path) -> Result<(), Error>; } #[cfg(feature = "file")] diff --git a/src/append/rolling_file/policy/compound/trigger/mod.rs b/src/append/rolling_file/policy/compound/trigger/mod.rs index 5cd8c227..f1ef109c 100644 --- a/src/append/rolling_file/policy/compound/trigger/mod.rs +++ b/src/append/rolling_file/policy/compound/trigger/mod.rs @@ -1,6 +1,8 @@ //! Triggers -use std::{error::Error, fmt}; +use std::fmt; + +use failure::Error; use crate::append::rolling_file::LogFile; #[cfg(feature = "file")] @@ -12,7 +14,7 @@ pub mod size; /// A trait which identifies if the active log file should be rolled over. pub trait Trigger: fmt::Debug + Send + Sync + 'static { /// Determines if the active log file should be rolled over. - fn trigger(&self, file: &LogFile) -> Result>; + fn trigger(&self, file: &LogFile) -> Result; } #[cfg(feature = "file")] diff --git a/src/append/rolling_file/policy/compound/trigger/size.rs b/src/append/rolling_file/policy/compound/trigger/size.rs index 7f9ead81..506cf9a2 100644 --- a/src/append/rolling_file/policy/compound/trigger/size.rs +++ b/src/append/rolling_file/policy/compound/trigger/size.rs @@ -6,11 +6,13 @@ use serde::de; #[cfg(feature = "file")] use serde_derive::Deserialize; -use std::error::Error; #[cfg(feature = "file")] use std::fmt; use crate::append::rolling_file::{policy::compound::trigger::Trigger, LogFile}; + +use failure::Error; + #[cfg(feature = "file")] use crate::file::{Deserialize, Deserializers}; @@ -116,7 +118,7 @@ impl SizeTrigger { } impl Trigger for SizeTrigger { - fn trigger(&self, file: &LogFile) -> Result> { + fn trigger(&self, file: &LogFile) -> Result { Ok(file.len_estimate() > self.limit) } } @@ -146,7 +148,7 @@ impl Deserialize for SizeTriggerDeserializer { &self, config: SizeTriggerConfig, _: &Deserializers, - ) -> Result, Box> { + ) -> Result, Error> { Ok(Box::new(SizeTrigger::new(config.limit))) } } diff --git a/src/append/rolling_file/policy/mod.rs b/src/append/rolling_file/policy/mod.rs index 536e7ba3..abf1487a 100644 --- a/src/append/rolling_file/policy/mod.rs +++ b/src/append/rolling_file/policy/mod.rs @@ -1,5 +1,7 @@ //! Policies. -use std::{error::Error, fmt}; +use std::fmt; + +use failure::Error; use crate::append::rolling_file::LogFile; #[cfg(feature = "file")] @@ -14,7 +16,7 @@ pub trait Policy: Sync + Send + 'static + fmt::Debug { /// /// This method is called after each log event. It is provided a reference /// to the current log file. - fn process(&self, log: &mut LogFile) -> Result<(), Box>; + fn process(&self, log: &mut LogFile) -> Result<(), Error>; } #[cfg(feature = "file")] diff --git a/src/config.rs b/src/config.rs index 023fb80c..6e581220 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,10 +1,12 @@ //! log4rs configuration use log::LevelFilter; -use std::{collections::HashSet, error, fmt, iter::IntoIterator}; +use std::{collections::HashSet, fmt, iter::IntoIterator}; use crate::{append::Append, filter::Filter, ConfigPrivateExt, PrivateConfigAppenderExt}; +use failure::{Error, Fail}; + /// Configuration for the root logger. #[derive(Debug)] pub struct Root { @@ -323,7 +325,7 @@ impl ConfigBuilder { if appender_names.insert(appender.name.clone()) { ok_appenders.push(appender); } else { - errors.push(Error::DuplicateAppenderName(appender.name)); + errors.push(ConfigError::DuplicateAppenderName(appender.name).into()); } } @@ -332,7 +334,7 @@ impl ConfigBuilder { if appender_names.contains(&appender) { ok_root_appenders.push(appender); } else { - errors.push(Error::NonexistentAppender(appender)); + errors.push(ConfigError::NonexistentAppender(appender).into()); } } root.appenders = ok_root_appenders; @@ -341,7 +343,7 @@ impl ConfigBuilder { let mut logger_names = HashSet::new(); for mut logger in loggers { if !logger_names.insert(logger.name.clone()) { - errors.push(Error::DuplicateLoggerName(logger.name)); + errors.push(ConfigError::DuplicateLoggerName(logger.name).into()); continue; } @@ -355,7 +357,7 @@ impl ConfigBuilder { if appender_names.contains(&appender) { ok_logger_appenders.push(appender); } else { - errors.push(Error::NonexistentAppender(appender)); + errors.push(ConfigError::NonexistentAppender(appender).into()); } } logger.appenders = ok_logger_appenders; @@ -385,7 +387,7 @@ impl ConfigBuilder { fn check_logger_name(name: &str) -> Result<(), Error> { if name.is_empty() { - return Err(Error::InvalidLoggerName(name.to_owned())); + return Err(ConfigError::InvalidLoggerName(name.to_owned()).into()); } let mut streak = 0; @@ -393,18 +395,18 @@ fn check_logger_name(name: &str) -> Result<(), Error> { if ch == ':' { streak += 1; if streak > 2 { - return Err(Error::InvalidLoggerName(name.to_owned())); + return Err(ConfigError::InvalidLoggerName(name.to_owned()).into()); } } else { if streak > 0 && streak != 2 { - return Err(Error::InvalidLoggerName(name.to_owned())); + return Err(ConfigError::InvalidLoggerName(name.to_owned()).into()); } streak = 0; } } if streak > 0 { - Err(Error::InvalidLoggerName(name.to_owned())) + Err(ConfigError::InvalidLoggerName(name.to_owned().into()).into()) } else { Ok(()) } @@ -422,7 +424,7 @@ impl ConfigPrivateExt for Config { } /// Errors encountered when validating a log4rs `Config`. -#[derive(Debug)] +#[derive(Debug, Fail)] pub struct Errors { errors: Vec, } @@ -443,47 +445,26 @@ impl fmt::Display for Errors { } } -impl error::Error for Errors { - fn description(&self) -> &str { - "Errors encountered when validating a log4rs `Config`" - } -} - /// An error validating a log4rs `Config`. -#[derive(Debug)] -pub enum Error { +#[derive(Debug, Fail)] +pub enum ConfigError { /// Multiple appenders were registered with the same name. + #[fail(display = "Duplicate appender name `{}`", 0)] DuplicateAppenderName(String), /// A reference to a nonexistant appender. + #[fail(display = "Reference to nonexistent appender: `{}`", 0)] NonexistentAppender(String), /// Multiple loggers were registered with the same name. + #[fail(display = "Duplicate logger name `{}`", 0)] DuplicateLoggerName(String), /// A logger name was invalid. + #[fail(display = "Invalid logger name `{}`", 0)] InvalidLoggerName(String), #[doc(hidden)] + #[fail(display = "Reserved for future use")] __Extensible, } -impl fmt::Display for Error { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - match *self { - Error::DuplicateAppenderName(ref n) => write!(fmt, "Duplicate appender name `{}`", n), - Error::NonexistentAppender(ref n) => { - write!(fmt, "Reference to nonexistent appender: `{}`", n) - } - Error::DuplicateLoggerName(ref n) => write!(fmt, "Duplicate logger name `{}`", n), - Error::InvalidLoggerName(ref n) => write!(fmt, "Invalid logger name `{}`", n), - Error::__Extensible => unreachable!(), - } - } -} - -impl error::Error for Error { - fn description(&self) -> &str { - "An error constructing a log4rs `Config`" - } -} - #[cfg(test)] mod test { #[test] diff --git a/src/encode/json.rs b/src/encode/json.rs index 0224dc89..13b81f37 100644 --- a/src/encode/json.rs +++ b/src/encode/json.rs @@ -34,7 +34,9 @@ use serde::ser::{self, Serialize, SerializeMap}; #[cfg(feature = "file")] use serde_derive::Deserialize; use serde_derive::Serialize; -use std::{error::Error, fmt, option, thread}; +use std::{fmt, option, thread}; + +use failure::Error; use crate::encode::{Encode, Write, NEWLINE}; #[cfg(feature = "file")] @@ -66,7 +68,7 @@ impl JsonEncoder { w: &mut dyn Write, time: DateTime, record: &Record, - ) -> Result<(), Box> { + ) -> Result<(), Error> { let thread = thread::current(); let message = Message { time: time.format_with_items(Some(Item::Fixed(Fixed::RFC3339)).into_iter()), @@ -87,11 +89,7 @@ impl JsonEncoder { } impl Encode for JsonEncoder { - fn encode( - &self, - w: &mut dyn Write, - record: &Record, - ) -> Result<(), Box> { + fn encode(&self, w: &mut dyn Write, record: &Record) -> Result<(), Error> { self.encode_inner(w, Local::now(), record) } } @@ -164,7 +162,7 @@ impl Deserialize for JsonEncoderDeserializer { &self, _: JsonEncoderConfig, _: &Deserializers, - ) -> Result, Box> { + ) -> Result, Error> { Ok(Box::new(JsonEncoder::default())) } } diff --git a/src/encode/mod.rs b/src/encode/mod.rs index 8e4297ba..f11bb37e 100644 --- a/src/encode/mod.rs +++ b/src/encode/mod.rs @@ -7,7 +7,9 @@ use serde::de; use serde_value::Value; #[cfg(feature = "file")] use std::collections::BTreeMap; -use std::{error::Error, fmt, io}; +use std::{fmt, io}; + +use failure::Error; #[cfg(feature = "file")] use crate::file::Deserializable; @@ -32,11 +34,7 @@ const NEWLINE: &str = "\n"; /// output. pub trait Encode: fmt::Debug + Send + Sync + 'static { /// Encodes the `Record` into bytes and writes them. - fn encode( - &self, - w: &mut dyn Write, - record: &Record, - ) -> Result<(), Box>; + fn encode(&self, w: &mut dyn Write, record: &Record) -> Result<(), Error>; } #[cfg(feature = "file")] diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index e1b43329..967d8980 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -122,13 +122,16 @@ use chrono::{Local, Utc}; use log::{Level, Record}; #[cfg(feature = "file")] use serde_derive::Deserialize; -use std::{default::Default, error::Error, fmt, io, process, thread}; +use std::{default::Default, fmt, io, process, thread}; use crate::encode::{ self, pattern::parser::{Alignment, Parameters, Parser, Piece}, Color, Encode, Style, NEWLINE, }; + +use failure::Error; + #[cfg(feature = "file")] use crate::file::{Deserialize, Deserializers}; @@ -618,11 +621,7 @@ impl Default for PatternEncoder { } impl Encode for PatternEncoder { - fn encode( - &self, - w: &mut dyn encode::Write, - record: &Record, - ) -> Result<(), Box> { + fn encode(&self, w: &mut dyn encode::Write, record: &Record) -> Result<(), Error> { for chunk in &self.chunks { chunk.encode(w, record)?; } @@ -666,7 +665,7 @@ impl Deserialize for PatternEncoderDeserializer { &self, config: PatternEncoderConfig, _: &Deserializers, - ) -> Result, Box> { + ) -> Result, Error> { let encoder = match config.pattern { Some(pattern) => PatternEncoder::new(&pattern), None => PatternEncoder::default(), diff --git a/src/file.rs b/src/file.rs index 1c9989cf..4d0b757b 100644 --- a/src/file.rs +++ b/src/file.rs @@ -95,9 +95,12 @@ use serde::de::{self, Deserialize as SerdeDeserialize, DeserializeOwned}; use serde_derive::Deserialize; use serde_value::Value; use std::{ - borrow::ToOwned, collections::HashMap, error, fmt, marker::PhantomData, sync::Arc, + borrow::ToOwned, collections::HashMap, fmt, marker::PhantomData, sync::Arc, time::Duration, }; + +use failure::{err_msg, Error, Fail}; + use typemap::{Key, ShareCloneMap}; use crate::{append::AppenderConfig, config}; @@ -132,7 +135,7 @@ pub trait Deserialize: Send + Sync + 'static { &self, config: Self::Config, deserializers: &Deserializers, - ) -> Result, Box>; + ) -> Result, Error>; } trait ErasedDeserialize: Send + Sync + 'static { @@ -142,7 +145,7 @@ trait ErasedDeserialize: Send + Sync + 'static { &self, config: Value, deserializers: &Deserializers, - ) -> Result, Box>; + ) -> Result, Error>; } struct DeserializeEraser(T); @@ -157,7 +160,7 @@ where &self, config: Value, deserializers: &Deserializers, - ) -> Result, Box> { + ) -> Result, Error> { let config = config.deserialize_into()?; self.0.deserialize(config, deserializers) } @@ -279,59 +282,30 @@ impl Deserializers { } /// Deserializes a value of a specific type and kind. - pub fn deserialize( - &self, - kind: &str, - config: Value, - ) -> Result, Box> + pub fn deserialize(&self, kind: &str, config: Value) -> Result, Error> where T: Deserializable, { match self.0.get::>().and_then(|m| m.get(kind)) { Some(b) => b.deserialize(config, self), - None => Err(format!( + None => Err(err_msg(format!( "no {} deserializer for kind `{}` registered", T::name(), kind - ) - .into()), + ))), } } } -/// An error deserializing a configuration into a log4rs `Config`. -#[derive(Debug)] -pub struct Error(ErrorKind, Box); - -#[derive(Debug)] -enum ErrorKind { - Appender(String), - Filter(String), -} - -impl fmt::Display for Error { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - match self.0 { - ErrorKind::Appender(ref name) => { - write!(fmt, "error deserializing appender {}: {}", name, self.1) - } - ErrorKind::Filter(ref name) => write!( - fmt, - "error deserializing filter attached to appender {}: {}", - name, self.1 - ), - } - } -} - -impl error::Error for Error { - fn description(&self) -> &str { - "error deserializing a log4rs `Config`" - } - - fn cause(&self) -> Option<&dyn error::Error> { - Some(&*self.1) - } +#[derive(Debug, Fail)] +enum DeserializingConfigError { + #[fail(display = "error deserializing appender {}: {}", 0, 1)] + Appender(String, Error), + #[fail( + display = "error deserializing filter attached to appender {}: {}", + 0, 1 + )] + Filter(String, Error), } /// A raw deserializable log4rs configuration for xml. @@ -418,12 +392,12 @@ impl RawConfig { for filter in &appender.filters { match deserializers.deserialize(&filter.kind, filter.config.clone()) { Ok(filter) => builder = builder.filter(filter), - Err(e) => errors.push(Error(ErrorKind::Filter(name.clone()), e)), + Err(e) => errors.push(DeserializingConfigError::Filter(name.clone(), e).into()), } } match deserializers.deserialize(&appender.kind, appender.config.clone()) { Ok(appender) => appenders.push(builder.build(name.clone(), appender)), - Err(e) => errors.push(Error(ErrorKind::Appender(name.clone()), e)), + Err(e) => errors.push(DeserializingConfigError::Appender(name.clone(), e).into()), } } diff --git a/src/filter/threshold.rs b/src/filter/threshold.rs index 396fbcf5..5bdda4d3 100644 --- a/src/filter/threshold.rs +++ b/src/filter/threshold.rs @@ -5,8 +5,6 @@ use log::{LevelFilter, Record}; #[cfg(feature = "file")] use serde_derive::Deserialize; -#[cfg(feature = "file")] -use std::error::Error; #[cfg(feature = "file")] use crate::file::{Deserialize, Deserializers}; @@ -65,7 +63,7 @@ impl Deserialize for ThresholdFilterDeserializer { &self, config: ThresholdFilterConfig, _: &Deserializers, - ) -> Result, Box> { + ) -> Result, failure::Error> { Ok(Box::new(ThresholdFilter::new(config.level))) } } diff --git a/src/lib.rs b/src/lib.rs index 06b8cb55..8f007b5e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,11 +190,11 @@ use arc_swap::ArcSwap; use fnv::FnvHasher; use log::{Level, LevelFilter, Metadata, Record, SetLoggerError}; use std::{ - cmp, collections::HashMap, error, hash::BuildHasherDefault, io, io::prelude::*, sync::Arc, + cmp, collections::HashMap, hash::BuildHasherDefault, io, io::prelude::*, sync::Arc, }; #[cfg(feature = "file")] -pub use crate::priv_file::{init_file, load_config_file, Error}; +pub use crate::priv_file::{init_file, load_config_file, FormatError}; use crate::{append::Append, config::Config, filter::Filter}; @@ -281,7 +281,7 @@ impl ConfiguredLogger { if self.enabled(record.level()) { for &idx in &self.appenders { if let Err(err) = appenders[idx].append(record) { - handle_error(&*err); + handle_error(&err); } } } @@ -294,7 +294,7 @@ struct Appender { } impl Appender { - fn append(&self, record: &Record) -> Result<(), Box> { + fn append(&self, record: &Record) -> Result<(), failure::Error> { for filter in &self.filters { match filter.filter(record) { filter::Response::Accept => break, @@ -403,7 +403,7 @@ impl log::Log for Logger { } } -fn handle_error(e: &E) { +fn handle_error(e: &failure::Error) { let _ = writeln!(io::stderr(), "log4rs: {}", e); } diff --git a/src/priv_file.rs b/src/priv_file.rs index 68d7f885..c7512396 100644 --- a/src/priv_file.rs +++ b/src/priv_file.rs @@ -1,13 +1,13 @@ -#![allow(deprecated)] - -use log::SetLoggerError; use std::{ - error, fmt, fs, + fs::{self, File}, + io::Read, path::{Path, PathBuf}, thread, time::{Duration, SystemTime}, }; +use failure::{err_msg, Error, Fail}; + #[cfg(feature = "xml_format")] use crate::file::RawConfigXml; use crate::{ @@ -25,7 +25,7 @@ use crate::{ /// reported to stderr. /// /// Requires the `file` feature (enabled by default). -pub fn init_file

(path: P, deserializers: Deserializers) -> Result<(), Error> +pub fn init_file

(path: P, deserializers: Deserializers) -> Result<(), failure::Error> where P: AsRef, { @@ -62,7 +62,7 @@ where /// /// Unlike `init_file`, this function does not initialize the logger; it only /// loads the `Config` and returns it. -pub fn load_config_file

(path: P, deserializers: Deserializers) -> Result +pub fn load_config_file

(path: P, deserializers: Deserializers) -> Result where P: AsRef, { @@ -74,52 +74,35 @@ where Ok(deserialize(&config, &deserializers)) } -/// An error initializing the logging framework from a file. -#[derive(Debug)] -pub enum Error { - /// An error from the log crate - Log(SetLoggerError), - /// A fatal error initializing the log4rs config. - Log4rs(Box), -} +/// The various types of formatting errors that can be generated. +#[derive(Debug, Fail)] +pub enum FormatError { + /// The YAML feature flag was missing. + #[fail(display = "the `yaml_format` feature is required for YAML support")] + YamlFeatureFlagRequired, -impl fmt::Display for Error { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - match *self { - Error::Log(ref e) => fmt::Display::fmt(e, fmt), - Error::Log4rs(ref e) => fmt::Display::fmt(e, fmt), - } - } -} + /// The JSON feature flag was missing. + #[fail(display = "the `json_format` feature is required for JSON support")] + JsonFeatureFlagRequired, -impl error::Error for Error { - fn description(&self) -> &str { - match *self { - Error::Log(ref e) => error::Error::description(e), - Error::Log4rs(ref e) => error::Error::description(&**e), - } - } + /// The TOML feature flag was missing. + #[fail(display = "the `toml_format` feature is required for TOML support")] + TomlFeatureFlagRequired, - fn cause(&self) -> Option<&dyn error::Error> { - match *self { - Error::Log(ref e) => Some(e), - Error::Log4rs(ref e) => Some(&**e), - } - } -} + /// The XML feature flag was missing. + #[fail(display = "the `xml_format` feature is required for XML support")] + XmlFeatureFlagRequired, -impl From for Error { - fn from(t: SetLoggerError) -> Error { - Error::Log(t) - } -} + /// An unsupported format was specified. + #[fail(display = "unsupported file format `{}`", 0)] + UnsupportedFormat(String), -impl From> for Error { - fn from(t: Box) -> Error { - Error::Log4rs(t) - } + /// Log4rs could not determine the file format. + #[fail(display = "unable to determine the file format")] + UnknownFormat, } +#[derive(Debug)] enum Format { #[cfg(feature = "yaml_format")] Yaml, @@ -133,36 +116,34 @@ enum Format { } impl Format { - fn from_path(path: &Path) -> Result> { + fn from_path(path: &Path) -> Result { match path.extension().and_then(|s| s.to_str()) { #[cfg(feature = "yaml_format")] Some("yaml") | Some("yml") => Ok(Format::Yaml), #[cfg(not(feature = "yaml_format"))] - Some("yaml") | Some("yml") => { - Err("the `yaml_format` feature is required for YAML support".into()) - } + Some("yaml") | Some("yml") => Err(FormatError::YamlFeatureFlagRequired.into()), + #[cfg(feature = "json_format")] Some("json") => Ok(Format::Json), #[cfg(not(feature = "json_format"))] - Some("json") => Err("the `json_format` feature is required for JSON support".into()), + Some("json") => Err(FormatError::JsonFeatureFlagRequired.into()), #[cfg(feature = "toml_format")] Some("toml") => Ok(Format::Toml), #[cfg(not(feature = "toml_format"))] - Some("toml") => Err("the `toml_format` feature is required for TOML support".into()), + Some("toml") => Err(FormatError::TomlFeatureFlagRequired.into()), #[cfg(feature = "xml_format")] Some("xml") => Ok(Format::Xml), #[cfg(not(feature = "xml_format"))] - Some("xml") => Err("the `xml_format` feature is required for XML support".into()), + Some("xml") => Err(FormatError::XmlFeatureFlagRequired.into()), - Some(f) => Err(format!("unsupported file format `{}`", f).into()), - None => Err("unable to determine the file format".into()), + Some(f) => Err(FormatError::UnsupportedFormat(f.to_string()).into()), + None => Err(FormatError::UnknownFormat.into()), } } - #[allow(unused_variables)] - fn parse(&self, source: &str) -> Result> { + fn parse(&self, source: &str) -> Result { match *self { #[cfg(feature = "yaml_format")] Format::Yaml => ::serde_yaml::from_str(source).map_err(Into::into), @@ -173,13 +154,15 @@ impl Format { #[cfg(feature = "xml_format")] Format::Xml => ::serde_xml_rs::from_reader::<_, RawConfigXml>(source.as_bytes()) .map(Into::into) - .map_err(|e| e.to_string().into()), + .map_err(|e| err_msg(e.to_string())), } } } -fn read_config(path: &Path) -> Result> { - let s = fs::read_to_string(path)?; +fn read_config(path: &Path) -> Result { + let mut file = File::open(path)?; + let mut s = String::new(); + file.read_to_string(&mut s)?; Ok(s) } @@ -241,15 +224,12 @@ impl ConfigReloader { match self.run_once(rate) { Ok(Some(r)) => rate = r, Ok(None) => break, - Err(e) => handle_error(&*e), + Err(e) => handle_error(&e), } } } - fn run_once( - &mut self, - rate: Duration, - ) -> Result, Box> { + fn run_once(&mut self, rate: Duration) -> Result, failure::Error> { if let Some(last_modified) = self.modified { let modified = fs::metadata(&self.path).and_then(|m| m.modified())?; if last_modified == modified { From 905183e0ef69a412ee6b7577f054f8b91bed5cbe Mon Sep 17 00:00:00 2001 From: Richard M Date: Wed, 11 Mar 2020 09:26:41 -0700 Subject: [PATCH 02/35] Issue #129: Drop XML Config support (#137) --- Cargo.toml | 1 - src/file.rs | 113 ----------------------------------------------- src/priv_file.rs | 16 +------ 3 files changed, 1 insertion(+), 129 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7f25438f..f1112425 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ file = ["humantime", "serde", "serde_derive", "serde-value", "typemap", "log/ser yaml_format = ["serde_yaml"] json_format = ["serde_json"] toml_format = ["toml"] -xml_format = ["serde-xml-rs"] console_appender = ["console_writer", "simple_writer", "pattern_encoder"] file_appender = ["parking_lot", "simple_writer", "pattern_encoder"] diff --git a/src/file.rs b/src/file.rs index 4d0b757b..3c1bac6f 100644 --- a/src/file.rs +++ b/src/file.rs @@ -308,40 +308,6 @@ enum DeserializingConfigError { Filter(String, Error), } -/// A raw deserializable log4rs configuration for xml. -#[cfg(feature = "xml_format")] -#[deprecated(since = "0.11.0")] -#[derive(Deserialize, Clone, Debug)] -#[serde(deny_unknown_fields)] -pub struct RawConfigXml { - #[serde(deserialize_with = "de_duration", default)] - refresh_rate: Option, - #[serde(default)] - root: Root, - #[serde(default)] - appenders: HashMap, - #[serde(rename = "loggers", default)] - loggers: LoggersXml, -} - -/// Loggers section wrapper for xml configuration -#[cfg(feature = "xml_format")] -#[deprecated(since = "0.11.0")] -#[derive(Deserialize, Debug, Clone)] -#[serde(deny_unknown_fields)] -pub struct LoggersXml { - #[serde(rename = "logger", default)] - loggers: Vec, -} - -#[cfg(feature = "xml_format")] -#[deprecated(since = "0.11.0")] -impl Default for LoggersXml { - fn default() -> Self { - Self { loggers: vec![] } - } -} - /// A raw deserializable log4rs configuration. #[derive(Deserialize, Debug, Clone)] #[serde(deny_unknown_fields)] @@ -410,23 +376,6 @@ impl RawConfig { } } -#[cfg(feature = "xml_format")] -impl ::std::convert::From for RawConfig { - fn from(cfg: RawConfigXml) -> Self { - Self { - refresh_rate: cfg.refresh_rate, - root: cfg.root, - appenders: cfg.appenders, - loggers: cfg - .loggers - .loggers - .into_iter() - .map(|l| (l.name.clone(), l.into())) - .collect(), - } - } -} - fn de_duration<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -484,24 +433,6 @@ fn root_level_default() -> LevelFilter { LevelFilter::Debug } -/// logger struct for xml configuration -#[cfg(feature = "xml_format")] -#[deprecated(since = "0.11.0")] -#[derive(Deserialize, Debug, Clone)] -#[serde(deny_unknown_fields)] -struct LoggerXml { - /// explicit field "name" for xml config - name: String, - - level: LevelFilter, - - #[serde(default)] - appenders: Vec, - - #[serde(default = "logger_additive_default")] - additive: bool, -} - #[derive(Deserialize, Debug, Clone)] #[serde(deny_unknown_fields)] struct Logger { @@ -512,17 +443,6 @@ struct Logger { additive: bool, } -#[cfg(feature = "xml_format")] -impl ::std::convert::From for Logger { - fn from(logger_xml: LoggerXml) -> Self { - Logger { - level: logger_xml.level, - appenders: logger_xml.appenders, - additive: logger_xml.additive, - } - } -} - fn logger_additive_default() -> bool { true } @@ -573,37 +493,4 @@ loggers: fn empty() { ::serde_yaml::from_str::("{}").unwrap(); } - - #[test] - #[cfg(feature = "xml_format")] - fn full_deserialize_xml() { - let cfg = r#" - - - - - - - - - - stdout - - - - requests - - - -"#; - let config: RawConfigXml = ::serde_xml_rs::from_reader(cfg.as_bytes()).unwrap(); - let config: RawConfig = config.into(); - let errors = config.appenders_lossy(&Deserializers::new()).1; - println!("{:?}", errors); - assert!(errors.is_empty()); - assert_eq!(config.refresh_rate, Some(Duration::from_secs(30))); - - let logger = config.loggers.get("foo::bar::baz").unwrap(); - assert_eq!(logger.appenders[0], "requests"); - } } diff --git a/src/priv_file.rs b/src/priv_file.rs index c7512396..1a95f504 100644 --- a/src/priv_file.rs +++ b/src/priv_file.rs @@ -6,10 +6,8 @@ use std::{ time::{Duration, SystemTime}, }; -use failure::{err_msg, Error, Fail}; +use failure::{Error, Fail}; -#[cfg(feature = "xml_format")] -use crate::file::RawConfigXml; use crate::{ config::Config, file::{Deserializers, RawConfig}, @@ -110,9 +108,6 @@ enum Format { Json, #[cfg(feature = "toml_format")] Toml, - #[cfg(feature = "xml_format")] - #[deprecated(since = "0.11.0")] - Xml, } impl Format { @@ -133,11 +128,6 @@ impl Format { #[cfg(not(feature = "toml_format"))] Some("toml") => Err(FormatError::TomlFeatureFlagRequired.into()), - #[cfg(feature = "xml_format")] - Some("xml") => Ok(Format::Xml), - #[cfg(not(feature = "xml_format"))] - Some("xml") => Err(FormatError::XmlFeatureFlagRequired.into()), - Some(f) => Err(FormatError::UnsupportedFormat(f.to_string()).into()), None => Err(FormatError::UnknownFormat.into()), } @@ -151,10 +141,6 @@ impl Format { Format::Json => ::serde_json::from_str(source).map_err(Into::into), #[cfg(feature = "toml_format")] Format::Toml => ::toml::from_str(source).map_err(Into::into), - #[cfg(feature = "xml_format")] - Format::Xml => ::serde_xml_rs::from_reader::<_, RawConfigXml>(source.as_bytes()) - .map(Into::into) - .map_err(|e| err_msg(e.to_string())), } } } From f59f4e58d84ba2c29d9dfdd01b4afa51fd09f41d Mon Sep 17 00:00:00 2001 From: estk Date: Wed, 11 Mar 2020 10:01:17 -0700 Subject: [PATCH 03/35] Rename file feature (#147) * Rename to config_parsing * Add workflows for devel --- .github/workflows/main.yml | 2 ++ Cargo.toml | 15 ++++------ src/append/console.rs | 20 ++++++------- src/append/file.rs | 17 ++++++----- src/append/mod.rs | 18 ++++++------ src/append/rolling_file/mod.rs | 26 ++++++++--------- .../rolling_file/policy/compound/mod.rs | 28 +++++++++---------- .../policy/compound/roll/delete.rs | 14 ++++------ .../policy/compound/roll/fixed_window.rs | 14 ++++------ .../rolling_file/policy/compound/roll/mod.rs | 6 ++-- .../policy/compound/trigger/mod.rs | 6 ++-- .../policy/compound/trigger/size.rs | 24 ++++++++-------- src/append/rolling_file/policy/mod.rs | 6 ++-- src/{file.rs => config_parsing.rs} | 10 +++---- src/encode/json.rs | 17 +++++------ src/encode/mod.rs | 16 +++++------ src/encode/pattern/mod.rs | 17 +++++------ src/filter/mod.rs | 16 +++++------ src/filter/threshold.rs | 14 ++++------ src/lib.rs | 16 +++++------ src/priv_file.rs | 2 +- 21 files changed, 140 insertions(+), 164 deletions(-) rename src/{file.rs => config_parsing.rs} (98%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 06d660a4..49da54c6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,9 +4,11 @@ on: push: branches: - master + - devel pull_request: branches: - master + - devel jobs: lint: diff --git a/Cargo.toml b/Cargo.toml index f1112425..f8e30c9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,9 @@ keywords = ["log", "logger", "logging", "log4"] edition = '2018' [features] -default = ["all_components", "file", "yaml_format", "gzip"] +default = ["all_components", "config_parsing", "yaml_format"] -file = ["humantime", "serde", "serde_derive", "serde-value", "typemap", "log/serde"] +config_parsing = ["humantime", "serde", "serde-value", "typemap", "log/serde"] yaml_format = ["serde_yaml"] json_format = ["serde_json"] toml_format = ["toml"] @@ -24,7 +24,7 @@ compound_policy = [] delete_roller = [] fixed_window_roller = [] size_trigger = [] -json_encoder = ["serde", "serde_json", "chrono", "log-mdc", "serde_derive", "log/serde", "thread-id"] +json_encoder = ["serde", "serde_json", "chrono", "log-mdc", "log/serde", "thread-id"] pattern_encoder = ["chrono", "log-mdc", "thread-id"] ansi_writer = [] console_writer = ["ansi_writer", "libc", "winapi"] @@ -54,22 +54,19 @@ harness = false [dependencies] arc-swap = "0.4" chrono = { version = "0.4", optional = true } -failure_derive = "0.1.6" failure = "0.1.6" flate2 = { version = "1.0", optional = true } fnv = "1.0" -humantime = { version = "1.0", optional = true } +humantime = { version = "2.0", optional = true } log = { version = "0.4.0", features = ["std"] } log-mdc = { version = "0.1", optional = true } -serde = { version = "1.0", optional = true } -serde_derive = { version = "1.0", optional = true } +serde = { version = "1.0", optional = true, features = ["derive"] } serde-value = { version = "0.6", optional = true } thread-id = { version = "3.3", optional = true } typemap = { version = "0.3", optional = true } serde_json = { version = "1.0", optional = true } serde_yaml = { version = "0.8.4", optional = true } toml = { version = "0.5", optional = true } -serde-xml-rs = { version = "0.4", optional = true } parking_lot = { version = "0.11.0", optional = true } [target.'cfg(windows)'.dependencies] @@ -81,7 +78,7 @@ libc = { version = "0.2", optional = true } [dev-dependencies] lazy_static = "1.4" streaming-stats = "0.2.3" -humantime = "1.0.0" +humantime = "2.0" tempfile = "3.1.0" [[example]] diff --git a/src/append/console.rs b/src/append/console.rs index a2667aab..15f5aab4 100644 --- a/src/append/console.rs +++ b/src/append/console.rs @@ -3,8 +3,6 @@ //! Requires the `console_appender` feature. use log::Record; -#[cfg(feature = "file")] -use serde_derive::Deserialize; use std::{ fmt, io::{self, Write}, @@ -12,10 +10,10 @@ use std::{ use failure::Error; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] +use crate::config_parsing::{Deserialize, Deserializers}; +#[cfg(feature = "config_parsing")] use crate::encode::EncoderConfig; -#[cfg(feature = "file")] -use crate::file::{Deserialize, Deserializers}; use crate::{ append::Append, encode::{ @@ -31,16 +29,16 @@ use crate::{ }; /// The console appender's configuration. -#[cfg(feature = "file")] -#[derive(Deserialize)] +#[cfg(feature = "config_parsing")] +#[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct ConsoleAppenderConfig { target: Option, encoder: Option, } -#[cfg(feature = "file")] -#[derive(Deserialize)] +#[cfg(feature = "config_parsing")] +#[derive(serde::Deserialize)] enum ConfigTarget { #[serde(rename = "stdout")] Stdout, @@ -209,10 +207,10 @@ pub enum Target { /// encoder: /// kind: pattern /// ``` -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] pub struct ConsoleAppenderDeserializer; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl Deserialize for ConsoleAppenderDeserializer { type Trait = dyn Append; diff --git a/src/append/file.rs b/src/append/file.rs index f5d006df..5db55b39 100644 --- a/src/append/file.rs +++ b/src/append/file.rs @@ -4,8 +4,6 @@ use log::Record; use parking_lot::Mutex; -#[cfg(feature = "file")] -use serde_derive::Deserialize; use std::{ fmt, fs::{self, File, OpenOptions}, @@ -15,18 +13,19 @@ use std::{ use failure::Error; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] +use crate::config_parsing::{Deserialize, Deserializers}; +#[cfg(feature = "config_parsing")] use crate::encode::EncoderConfig; -#[cfg(feature = "file")] -use crate::file::{Deserialize, Deserializers}; + use crate::{ append::Append, encode::{pattern::PatternEncoder, writer::simple::SimpleWriter, Encode}, }; /// The file appender's configuration. -#[cfg(feature = "file")] -#[derive(Deserialize)] +#[cfg(feature = "config_parsing")] +#[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct FileAppenderConfig { path: String, @@ -133,10 +132,10 @@ impl FileAppenderBuilder { /// encoder: /// kind: pattern /// ``` -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] pub struct FileAppenderDeserializer; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl Deserialize for FileAppenderDeserializer { type Trait = dyn Append; diff --git a/src/append/mod.rs b/src/append/mod.rs index 5d300699..1856cae6 100644 --- a/src/append/mod.rs +++ b/src/append/mod.rs @@ -1,19 +1,19 @@ //! Appenders use log::{Log, Record}; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] use serde::{de, Deserialize, Deserializer}; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] use serde_value::Value; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] use std::collections::BTreeMap; use std::fmt; use failure::Error; -#[cfg(feature = "file")] -use crate::file::Deserializable; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] +use crate::config_parsing::Deserializable; +#[cfg(feature = "config_parsing")] use crate::filter::FilterConfig; #[cfg(feature = "console_appender")] @@ -35,7 +35,7 @@ pub trait Append: fmt::Debug + Send + Sync + 'static { fn flush(&self); } -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl Deserializable for dyn Append { fn name() -> &'static str { "appender" @@ -54,7 +54,7 @@ impl Append for T { } /// Configuration for an appender. -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] #[derive(PartialEq, Eq, Debug, Clone)] pub struct AppenderConfig { /// The appender kind. @@ -65,7 +65,7 @@ pub struct AppenderConfig { pub config: Value, } -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl<'de> Deserialize<'de> for AppenderConfig { fn deserialize(d: D) -> Result where diff --git a/src/append/rolling_file/mod.rs b/src/append/rolling_file/mod.rs index 4685bc23..65dd1f5e 100644 --- a/src/append/rolling_file/mod.rs +++ b/src/append/rolling_file/mod.rs @@ -18,11 +18,9 @@ use log::Record; use parking_lot::Mutex; -#[cfg(feature = "file")] -use serde_derive::Deserialize; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] use serde_value::Value; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] use std::collections::BTreeMap; use std::{ fmt, @@ -33,10 +31,10 @@ use std::{ use failure::Error; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] +use crate::config_parsing::{Deserialize, Deserializers}; +#[cfg(feature = "config_parsing")] use crate::encode::EncoderConfig; -#[cfg(feature = "file")] -use crate::file::{Deserialize, Deserializers}; use crate::{ append::Append, encode::{self, pattern::PatternEncoder, Encode}, @@ -45,8 +43,8 @@ use crate::{ pub mod policy; /// Configuration for the rolling file appender. -#[cfg(feature = "file")] -#[derive(Deserialize)] +#[cfg(feature = "config_parsing")] +#[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct RollingFileAppenderConfig { path: String, @@ -55,13 +53,13 @@ pub struct RollingFileAppenderConfig { policy: Policy, } -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] struct Policy { kind: String, config: Value, } -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl<'de> serde::Deserialize<'de> for Policy { fn deserialize(d: D) -> Result where @@ -314,10 +312,10 @@ impl RollingFileAppenderBuilder { /// roller: /// kind: delete /// ``` -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] pub struct RollingFileAppenderDeserializer; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl Deserialize for RollingFileAppenderDeserializer { type Trait = dyn Append; @@ -358,7 +356,7 @@ mod test { #[test] #[cfg(feature = "yaml_format")] fn deserialize() { - use crate::file::{Deserializers, RawConfig}; + use crate::config_parsing::{Deserializers, RawConfig}; let dir = tempfile::tempdir().unwrap(); diff --git a/src/append/rolling_file/policy/compound/mod.rs b/src/append/rolling_file/policy/compound/mod.rs index 9c0e4116..3f65729b 100644 --- a/src/append/rolling_file/policy/compound/mod.rs +++ b/src/append/rolling_file/policy/compound/mod.rs @@ -1,13 +1,11 @@ //! The compound rolling policy. //! //! Requires the `compound_policy` feature. -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] use serde::{self, de}; -#[cfg(feature = "file")] -use serde_derive::Deserialize; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] use serde_value::Value; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] use std::collections::BTreeMap; use failure::Error; @@ -16,28 +14,28 @@ use crate::append::rolling_file::{ policy::{compound::roll::Roll, Policy}, LogFile, }; -#[cfg(feature = "file")] -use crate::file::{Deserialize, Deserializers}; +#[cfg(feature = "config_parsing")] +use crate::config_parsing::{Deserialize, Deserializers}; pub mod roll; pub mod trigger; /// Configuration for the compound policy. -#[cfg(feature = "file")] -#[derive(Deserialize)] +#[cfg(feature = "config_parsing")] +#[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct CompoundPolicyConfig { trigger: Trigger, roller: Roller, } -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] struct Trigger { kind: String, config: Value, } -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl<'de> serde::Deserialize<'de> for Trigger { fn deserialize(d: D) -> Result where @@ -57,13 +55,13 @@ impl<'de> serde::Deserialize<'de> for Trigger { } } -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] struct Roller { kind: String, config: Value, } -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl<'de> serde::Deserialize<'de> for Roller { fn deserialize(d: D) -> Result where @@ -137,10 +135,10 @@ impl Policy for CompoundPolicy { /// # The remainder of the configuration is passed to the roller's /// # deserializer, and will vary based on the kind of roller. /// ``` -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] pub struct CompoundPolicyDeserializer; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl Deserialize for CompoundPolicyDeserializer { type Trait = dyn Policy; diff --git a/src/append/rolling_file/policy/compound/roll/delete.rs b/src/append/rolling_file/policy/compound/roll/delete.rs index fe053da5..91e176ca 100644 --- a/src/append/rolling_file/policy/compound/roll/delete.rs +++ b/src/append/rolling_file/policy/compound/roll/delete.rs @@ -2,19 +2,17 @@ //! //! Requires the `delete_roller` feature. -#[cfg(feature = "file")] -use serde_derive::Deserialize; use std::{fs, path::Path}; use failure::Error; use crate::append::rolling_file::policy::compound::roll::Roll; -#[cfg(feature = "file")] -use crate::file::{Deserialize, Deserializers}; +#[cfg(feature = "config_parsing")] +use crate::config_parsing::{Deserialize, Deserializers}; /// Configuration for the delete roller. -#[cfg(feature = "file")] -#[derive(Deserialize, Clone)] +#[cfg(feature = "config_parsing")] +#[derive(serde::Deserialize, Clone)] #[serde(deny_unknown_fields)] pub struct DeleteRollerConfig { #[serde(skip_deserializing)] @@ -45,10 +43,10 @@ impl DeleteRoller { /// ```yaml /// kind: delete /// ``` -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] pub struct DeleteRollerDeserializer; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl Deserialize for DeleteRollerDeserializer { type Trait = dyn Roll; diff --git a/src/append/rolling_file/policy/compound/roll/fixed_window.rs b/src/append/rolling_file/policy/compound/roll/fixed_window.rs index 6c61013f..d8ed9512 100644 --- a/src/append/rolling_file/policy/compound/roll/fixed_window.rs +++ b/src/append/rolling_file/policy/compound/roll/fixed_window.rs @@ -4,8 +4,6 @@ #[cfg(feature = "background_rotation")] use parking_lot::{Condvar, Mutex}; -#[cfg(feature = "file")] -use serde_derive::Deserialize; #[cfg(feature = "background_rotation")] use std::sync::Arc; use std::{ @@ -16,12 +14,12 @@ use std::{ use failure::{err_msg, Error}; use crate::append::rolling_file::policy::compound::roll::Roll; -#[cfg(feature = "file")] -use crate::file::{Deserialize, Deserializers}; +#[cfg(feature = "config_parsing")] +use crate::config_parsing::{Deserialize, Deserializers}; /// Configuration for the fixed window roller. -#[cfg(feature = "file")] -#[derive(Deserialize)] +#[cfg(feature = "config_parsing")] +#[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct FixedWindowRollerConfig { pattern: String, @@ -305,10 +303,10 @@ impl FixedWindowRollerBuilder { /// # The base value for archived log indices. Defaults to 0. /// base: 1 /// ``` -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] pub struct FixedWindowRollerDeserializer; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl Deserialize for FixedWindowRollerDeserializer { type Trait = dyn Roll; diff --git a/src/append/rolling_file/policy/compound/roll/mod.rs b/src/append/rolling_file/policy/compound/roll/mod.rs index 957c47e3..e84788d3 100644 --- a/src/append/rolling_file/policy/compound/roll/mod.rs +++ b/src/append/rolling_file/policy/compound/roll/mod.rs @@ -4,8 +4,8 @@ use std::{fmt, path::Path}; use failure::Error; -#[cfg(feature = "file")] -use crate::file::Deserializable; +#[cfg(feature = "config_parsing")] +use crate::config_parsing::Deserializable; #[cfg(feature = "delete_roller")] pub mod delete; @@ -24,7 +24,7 @@ pub trait Roll: fmt::Debug + Send + Sync + 'static { fn roll(&self, file: &Path) -> Result<(), Error>; } -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl Deserializable for dyn Roll { fn name() -> &'static str { "roller" diff --git a/src/append/rolling_file/policy/compound/trigger/mod.rs b/src/append/rolling_file/policy/compound/trigger/mod.rs index f1ef109c..b7f1a61c 100644 --- a/src/append/rolling_file/policy/compound/trigger/mod.rs +++ b/src/append/rolling_file/policy/compound/trigger/mod.rs @@ -5,8 +5,8 @@ use std::fmt; use failure::Error; use crate::append::rolling_file::LogFile; -#[cfg(feature = "file")] -use crate::file::Deserializable; +#[cfg(feature = "config_parsing")] +use crate::config_parsing::Deserializable; #[cfg(feature = "size_trigger")] pub mod size; @@ -17,7 +17,7 @@ pub trait Trigger: fmt::Debug + Send + Sync + 'static { fn trigger(&self, file: &LogFile) -> Result; } -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl Deserializable for dyn Trigger { fn name() -> &'static str { "trigger" diff --git a/src/append/rolling_file/policy/compound/trigger/size.rs b/src/append/rolling_file/policy/compound/trigger/size.rs index 506cf9a2..e733e31e 100644 --- a/src/append/rolling_file/policy/compound/trigger/size.rs +++ b/src/append/rolling_file/policy/compound/trigger/size.rs @@ -2,30 +2,28 @@ //! //! Requires the `size_trigger` feature. -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] use serde::de; -#[cfg(feature = "file")] -use serde_derive::Deserialize; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] use std::fmt; -use crate::append::rolling_file::{policy::compound::trigger::Trigger, LogFile}; - use failure::Error; -#[cfg(feature = "file")] -use crate::file::{Deserialize, Deserializers}; +use crate::append::rolling_file::{policy::compound::trigger::Trigger, LogFile}; + +#[cfg(feature = "config_parsing")] +use crate::config_parsing::{Deserialize, Deserializers}; /// Configuration for the size trigger. -#[cfg(feature = "file")] -#[derive(Deserialize)] +#[cfg(feature = "config_parsing")] +#[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct SizeTriggerConfig { #[serde(deserialize_with = "deserialize_limit")] limit: u64, } -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] fn deserialize_limit<'de, D>(d: D) -> Result where D: de::Deserializer<'de>, @@ -135,10 +133,10 @@ impl Trigger for SizeTrigger { /// # bytes if not specified. Required. /// limit: 10 mb /// ``` -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] pub struct SizeTriggerDeserializer; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl Deserialize for SizeTriggerDeserializer { type Trait = dyn Trigger; diff --git a/src/append/rolling_file/policy/mod.rs b/src/append/rolling_file/policy/mod.rs index abf1487a..b12b1aca 100644 --- a/src/append/rolling_file/policy/mod.rs +++ b/src/append/rolling_file/policy/mod.rs @@ -4,8 +4,8 @@ use std::fmt; use failure::Error; use crate::append::rolling_file::LogFile; -#[cfg(feature = "file")] -use crate::file::Deserializable; +#[cfg(feature = "config_parsing")] +use crate::config_parsing::Deserializable; #[cfg(feature = "compound_policy")] pub mod compound; @@ -19,7 +19,7 @@ pub trait Policy: Sync + Send + 'static + fmt::Debug { fn process(&self, log: &mut LogFile) -> Result<(), Error>; } -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl Deserializable for dyn Policy { fn name() -> &'static str { "policy" diff --git a/src/file.rs b/src/config_parsing.rs similarity index 98% rename from src/file.rs rename to src/config_parsing.rs index 3c1bac6f..f5829b5d 100644 --- a/src/file.rs +++ b/src/config_parsing.rs @@ -92,11 +92,9 @@ use log::LevelFilter; use serde::de::{self, Deserialize as SerdeDeserialize, DeserializeOwned}; -use serde_derive::Deserialize; use serde_value::Value; use std::{ - borrow::ToOwned, collections::HashMap, fmt, marker::PhantomData, sync::Arc, - time::Duration, + borrow::ToOwned, collections::HashMap, fmt, marker::PhantomData, sync::Arc, time::Duration, }; use failure::{err_msg, Error, Fail}; @@ -309,7 +307,7 @@ enum DeserializingConfigError { } /// A raw deserializable log4rs configuration. -#[derive(Deserialize, Debug, Clone)] +#[derive(serde::Deserialize, Debug, Clone)] #[serde(deny_unknown_fields)] pub struct RawConfig { #[serde(deserialize_with = "de_duration", default)] @@ -411,7 +409,7 @@ where Option::::deserialize(d).map(|r| r.map(|s| s.0)) } -#[derive(Deserialize, Debug, Clone)] +#[derive(serde::Deserialize, Debug, Clone)] #[serde(deny_unknown_fields)] struct Root { #[serde(default = "root_level_default")] @@ -433,7 +431,7 @@ fn root_level_default() -> LevelFilter { LevelFilter::Debug } -#[derive(Deserialize, Debug, Clone)] +#[derive(serde::Deserialize, Debug, Clone)] #[serde(deny_unknown_fields)] struct Logger { level: LevelFilter, diff --git a/src/encode/json.rs b/src/encode/json.rs index 13b81f37..8b8b1b96 100644 --- a/src/encode/json.rs +++ b/src/encode/json.rs @@ -31,20 +31,17 @@ use chrono::{ }; use log::{Level, Record}; use serde::ser::{self, Serialize, SerializeMap}; -#[cfg(feature = "file")] -use serde_derive::Deserialize; -use serde_derive::Serialize; use std::{fmt, option, thread}; use failure::Error; +#[cfg(feature = "config_parsing")] +use crate::config_parsing::{Deserialize, Deserializers}; use crate::encode::{Encode, Write, NEWLINE}; -#[cfg(feature = "file")] -use crate::file::{Deserialize, Deserializers}; /// The JSON encoder's configuration -#[cfg(feature = "file")] -#[derive(Deserialize, Clone)] +#[cfg(feature = "config_parsing")] +#[derive(serde::Deserialize, Clone)] #[serde(deny_unknown_fields)] pub struct JsonEncoderConfig { #[serde(skip_deserializing)] @@ -94,7 +91,7 @@ impl Encode for JsonEncoder { } } -#[derive(Serialize)] +#[derive(serde::Serialize)] struct Message<'a> { #[serde(serialize_with = "ser_display")] time: DelayedFormat>>, @@ -149,10 +146,10 @@ impl ser::Serialize for Mdc { /// ```yaml /// kind: json /// ``` -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] pub struct JsonEncoderDeserializer; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl Deserialize for JsonEncoderDeserializer { type Trait = dyn Encode; diff --git a/src/encode/mod.rs b/src/encode/mod.rs index f11bb37e..accedb62 100644 --- a/src/encode/mod.rs +++ b/src/encode/mod.rs @@ -1,18 +1,18 @@ //! Encoders use log::Record; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] use serde::de; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] use serde_value::Value; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] use std::collections::BTreeMap; use std::{fmt, io}; use failure::Error; -#[cfg(feature = "file")] -use crate::file::Deserializable; +#[cfg(feature = "config_parsing")] +use crate::config_parsing::Deserializable; #[cfg(feature = "json_encoder")] pub mod json; @@ -37,7 +37,7 @@ pub trait Encode: fmt::Debug + Send + Sync + 'static { fn encode(&self, w: &mut dyn Write, record: &Record) -> Result<(), Error>; } -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl Deserializable for dyn Encode { fn name() -> &'static str { "encoder" @@ -45,7 +45,7 @@ impl Deserializable for dyn Encode { } /// Configuration for an encoder. -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] pub struct EncoderConfig { /// The encoder's kind. pub kind: String, @@ -54,7 +54,7 @@ pub struct EncoderConfig { pub config: Value, } -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl<'de> de::Deserialize<'de> for EncoderConfig { fn deserialize(d: D) -> Result where diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index 967d8980..6410954b 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -119,9 +119,8 @@ //! [MDC]: https://crates.io/crates/log-mdc use chrono::{Local, Utc}; +use failure::Error; use log::{Level, Record}; -#[cfg(feature = "file")] -use serde_derive::Deserialize; use std::{default::Default, fmt, io, process, thread}; use crate::encode::{ @@ -130,16 +129,14 @@ use crate::encode::{ Color, Encode, Style, NEWLINE, }; -use failure::Error; - -#[cfg(feature = "file")] -use crate::file::{Deserialize, Deserializers}; +#[cfg(feature = "config_parsing")] +use crate::config_parsing::{Deserialize, Deserializers}; mod parser; /// The pattern encoder's configuration. -#[cfg(feature = "file")] -#[derive(Deserialize)] +#[cfg(feature = "config_parsing")] +#[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct PatternEncoderConfig { pattern: Option, @@ -652,10 +649,10 @@ impl PatternEncoder { /// # "{d} {l} {t} - {m}{n}". /// pattern: "{d} {l} {t} - {m}{n}" /// ``` -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] pub struct PatternEncoderDeserializer; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl Deserialize for PatternEncoderDeserializer { type Trait = dyn Encode; diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 35286d55..1aa222c4 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -1,16 +1,16 @@ //! Filters use log::Record; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] use serde::de; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] use serde_value::Value; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] use std::collections::BTreeMap; use std::fmt; -#[cfg(feature = "file")] -use crate::file::Deserializable; +#[cfg(feature = "config_parsing")] +use crate::config_parsing::Deserializable; #[cfg(feature = "threshold_filter")] pub mod threshold; @@ -24,7 +24,7 @@ pub trait Filter: fmt::Debug + Send + Sync + 'static { fn filter(&self, record: &Record) -> Response; } -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl Deserializable for dyn Filter { fn name() -> &'static str { "filter" @@ -51,7 +51,7 @@ pub enum Response { /// Configuration for a filter. #[derive(PartialEq, Eq, Debug, Clone)] -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] pub struct FilterConfig { /// The filter kind. pub kind: String, @@ -59,7 +59,7 @@ pub struct FilterConfig { pub config: Value, } -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl<'de> de::Deserialize<'de> for FilterConfig { fn deserialize(d: D) -> Result where diff --git a/src/filter/threshold.rs b/src/filter/threshold.rs index 5bdda4d3..f6d7c4d6 100644 --- a/src/filter/threshold.rs +++ b/src/filter/threshold.rs @@ -3,16 +3,14 @@ //! Requires the `threshold_filter` feature. use log::{LevelFilter, Record}; -#[cfg(feature = "file")] -use serde_derive::Deserialize; -#[cfg(feature = "file")] -use crate::file::{Deserialize, Deserializers}; +#[cfg(feature = "config_parsing")] +use crate::config_parsing::{Deserialize, Deserializers}; use crate::filter::{Filter, Response}; /// The threshold filter's configuration. -#[cfg(feature = "file")] -#[derive(Deserialize)] +#[cfg(feature = "config_parsing")] +#[derive(serde::Deserialize)] pub struct ThresholdFilterConfig { level: LevelFilter, } @@ -50,10 +48,10 @@ impl Filter for ThresholdFilter { /// # The threshold log level to filter at. Required /// level: warn /// ``` -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] pub struct ThresholdFilterDeserializer; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] impl Deserialize for ThresholdFilterDeserializer { type Trait = dyn Filter; diff --git a/src/lib.rs b/src/lib.rs index 8f007b5e..ac123443 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,7 +134,7 @@ //! Add the following in your application initialization. //! //! ```no_run -//! # #[cfg(feature = "file")] +//! # #[cfg(feature = "config_parsing")] //! # fn f() { //! log4rs::init_file("log4rs.yml", Default::default()).unwrap(); //! # } @@ -189,11 +189,9 @@ use arc_swap::ArcSwap; use fnv::FnvHasher; use log::{Level, LevelFilter, Metadata, Record, SetLoggerError}; -use std::{ - cmp, collections::HashMap, hash::BuildHasherDefault, io, io::prelude::*, sync::Arc, -}; +use std::{cmp, collections::HashMap, hash::BuildHasherDefault, io, io::prelude::*, sync::Arc}; -#[cfg(feature = "file")] +#[cfg(feature = "config_parsing")] pub use crate::priv_file::{init_file, load_config_file, FormatError}; use crate::{append::Append, config::Config, filter::Filter}; @@ -201,11 +199,13 @@ use crate::{append::Append, config::Config, filter::Filter}; pub mod append; pub mod config; pub mod encode; -#[cfg(feature = "file")] -pub mod file; pub mod filter; -#[cfg(feature = "file")] + +#[cfg(feature = "config_parsing")] +pub mod config_parsing; +#[cfg(feature = "config_parsing")] mod priv_file; + #[cfg(feature = "console_writer")] mod priv_io; diff --git a/src/priv_file.rs b/src/priv_file.rs index 1a95f504..6b8b52b4 100644 --- a/src/priv_file.rs +++ b/src/priv_file.rs @@ -10,7 +10,7 @@ use failure::{Error, Fail}; use crate::{ config::Config, - file::{Deserializers, RawConfig}, + config_parsing::{Deserializers, RawConfig}, handle_error, init_config, Handle, }; From 9c5b945a35616f98cff7b925206792e9e6bf2178 Mon Sep 17 00:00:00 2001 From: Evan Simmons Date: Wed, 11 Mar 2020 11:01:02 -0700 Subject: [PATCH 04/35] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9a0b506..02ac465a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ ### Changed +* Drop XML config support +* Rename feature `file` to `config_parsing` +* Expose errors as `failure::Error` + ### Fixed ## [0.13.0] From 0f4085e8ee431b053f1558b74b331227c496316a Mon Sep 17 00:00:00 2001 From: Richard M Date: Mon, 30 Mar 2020 09:17:14 -0700 Subject: [PATCH 05/35] Add an init function which takes a RawConfig (#150) --- src/lib.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index ac123443..11f9aa66 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -191,9 +191,14 @@ use fnv::FnvHasher; use log::{Level, LevelFilter, Metadata, Record, SetLoggerError}; use std::{cmp, collections::HashMap, hash::BuildHasherDefault, io, io::prelude::*, sync::Arc}; +use failure::{Error, Fail}; + #[cfg(feature = "config_parsing")] pub use crate::priv_file::{init_file, load_config_file, FormatError}; +#[cfg(feature = "config_parsing")] +pub use crate::config_parsing::{Deserializers, RawConfig}; + use crate::{append::Append, config::Config, filter::Filter}; pub mod append; @@ -209,6 +214,11 @@ mod priv_file; #[cfg(feature = "console_writer")] mod priv_io; +/// Collects the set of errors that occur when deserializing the appenders. +#[derive(Debug, Fail)] +#[fail(display = "Errors on initialization: {:#?}", _0)] +pub struct InitErrors(Vec); + type FnvHashMap = HashMap>; struct ConfiguredLogger { @@ -420,6 +430,26 @@ pub fn init_config(config: config::Config) -> Result { log::set_boxed_logger(Box::new(logger)).map(|()| handle) } +/// Initializes the global logger as a log4rs logger using the provided raw config. +/// +/// This will return errors if the appenders configuration is malformed or if we fail to set the global logger. +#[cfg(feature = "config_parsing")] +pub fn init_raw_config(config: RawConfig) -> Result<(), Error> { + let (appenders, errors) = config.appenders_lossy(&Deserializers::default()); + if errors.len() > 0 { + return Err(InitErrors(errors).into()); + } + let config = Config::builder() + .appenders(appenders) + .loggers(config.loggers()) + .build(config.root())?; + let logger = Logger::new(config); + let logger = Box::new(logger); + log::set_max_level(log::LevelFilter::Info); + log::set_boxed_logger(logger)?; + Ok(()) +} + /// A handle to the active logger. #[derive(Clone)] pub struct Handle { @@ -453,6 +483,31 @@ mod test { use super::*; + #[test] + #[cfg(all(feature = "config_parsing", feature = "json_format"))] + #[cfg(target_os = "linux")] + fn init_from_raw_config() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("append.log"); + + let cfg = format!( + "{{\"refresh_rate\":\"60 seconds\",\"appenders\":{{\"baz\":{{\"kind\":\"file\",\"path\":\"{}\",\"encoder\":{{\"pattern\":\"{{m}}\"}}}}}},\"root\":{{\"appenders\":[\"baz\"],\"level\":\"info\"}}}}", + path.display()); + let config = ::serde_json::from_str::(&cfg).unwrap(); + if let Err(e) = init_raw_config(config) { + panic!(e); + } + assert!(path.exists()); + log::info!("init_from_raw_config"); + + let mut contents = String::new(); + std::fs::File::open(&path) + .unwrap() + .read_to_string(&mut contents) + .unwrap(); + assert_eq!(contents, "init_from_raw_config"); + } + #[test] fn enabled() { let root = config::Root::builder().build(LevelFilter::Debug); From 6c75bc052f8d3e05b58203093072fc8e2b7a7b19 Mon Sep 17 00:00:00 2001 From: Evan Simmons Date: Sat, 4 Apr 2020 10:43:45 -0700 Subject: [PATCH 06/35] Clippy --- src/config.rs | 2 +- src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config.rs b/src/config.rs index 6e581220..fb380449 100644 --- a/src/config.rs +++ b/src/config.rs @@ -406,7 +406,7 @@ fn check_logger_name(name: &str) -> Result<(), Error> { } if streak > 0 { - Err(ConfigError::InvalidLoggerName(name.to_owned().into()).into()) + Err(ConfigError::InvalidLoggerName(name.to_owned()).into()) } else { Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 11f9aa66..bbfda5e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -436,7 +436,7 @@ pub fn init_config(config: config::Config) -> Result { #[cfg(feature = "config_parsing")] pub fn init_raw_config(config: RawConfig) -> Result<(), Error> { let (appenders, errors) = config.appenders_lossy(&Deserializers::default()); - if errors.len() > 0 { + if !errors.is_empty() { return Err(InitErrors(errors).into()); } let config = Config::builder() @@ -491,7 +491,7 @@ mod test { let path = dir.path().join("append.log"); let cfg = format!( - "{{\"refresh_rate\":\"60 seconds\",\"appenders\":{{\"baz\":{{\"kind\":\"file\",\"path\":\"{}\",\"encoder\":{{\"pattern\":\"{{m}}\"}}}}}},\"root\":{{\"appenders\":[\"baz\"],\"level\":\"info\"}}}}", + "{{\"refresh_rate\":\"60 seconds\",\"appenders\":{{\"baz\":{{\"kind\":\"file\",\"path\":\"{}\",\"encoder\":{{\"pattern\":\"{{m}}\"}}}}}},\"root\":{{\"appenders\":[\"baz\"],\"level\":\"info\"}}}}", path.display()); let config = ::serde_json::from_str::(&cfg).unwrap(); if let Err(e) = init_raw_config(config) { From 9824445f66d411e4a204b611e9e9ac9a719247a0 Mon Sep 17 00:00:00 2001 From: Evan Simmons Date: Sat, 4 Apr 2020 11:44:30 -0700 Subject: [PATCH 07/35] Changelog, cleanup --- CHANGELOG.md | 2 ++ src/lib.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02ac465a..b48ba821 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### New +* Allow parsing of config from string + ### Changed * Drop XML config support diff --git a/src/lib.rs b/src/lib.rs index bbfda5e0..f401a883 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -443,10 +443,10 @@ pub fn init_raw_config(config: RawConfig) -> Result<(), Error> { .appenders(appenders) .loggers(config.loggers()) .build(config.root())?; + let logger = Logger::new(config); - let logger = Box::new(logger); log::set_max_level(log::LevelFilter::Info); - log::set_boxed_logger(logger)?; + log::set_boxed_logger(Box::new(logger))?; Ok(()) } From 461e4c692165b277480d4bc0b33c58f6075c949b Mon Sep 17 00:00:00 2001 From: Evan Simmons Date: Sat, 4 Apr 2020 11:51:41 -0700 Subject: [PATCH 08/35] json macro --- src/lib.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f401a883..0eb468dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -490,9 +490,22 @@ mod test { let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("append.log"); - let cfg = format!( - "{{\"refresh_rate\":\"60 seconds\",\"appenders\":{{\"baz\":{{\"kind\":\"file\",\"path\":\"{}\",\"encoder\":{{\"pattern\":\"{{m}}\"}}}}}},\"root\":{{\"appenders\":[\"baz\"],\"level\":\"info\"}}}}", - path.display()); + let cfg = json!({ + "refresh_rate": "60 seconds", + "root" : { + "appenders": ["baz"], + "level": "info", + } + "appenders": { + "baz": { + "kind": "file", + "path": path, + "encoder": { + "pattern": "{m}" + } + } + }, + }); let config = ::serde_json::from_str::(&cfg).unwrap(); if let Err(e) = init_raw_config(config) { panic!(e); From fedf2df4bc90ecc0ab75e237a845d7e6e93fa92f Mon Sep 17 00:00:00 2001 From: Evan Simmons Date: Sat, 4 Apr 2020 11:54:46 -0700 Subject: [PATCH 09/35] qualify --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0eb468dc..b3920e75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -490,7 +490,7 @@ mod test { let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("append.log"); - let cfg = json!({ + let cfg = serde_json::json!({ "refresh_rate": "60 seconds", "root" : { "appenders": ["baz"], @@ -506,7 +506,7 @@ mod test { } }, }); - let config = ::serde_json::from_str::(&cfg).unwrap(); + let config = serde_json::from_str::(&cfg).unwrap(); if let Err(e) = init_raw_config(config) { panic!(e); } From f6dfdc30199d3553b553ed92e0f147c9f8416733 Mon Sep 17 00:00:00 2001 From: Evan Simmons Date: Sat, 4 Apr 2020 11:57:54 -0700 Subject: [PATCH 10/35] typo --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index b3920e75..5018ce35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -495,7 +495,7 @@ mod test { "root" : { "appenders": ["baz"], "level": "info", - } + }, "appenders": { "baz": { "kind": "file", From e6d5c6fd6d0506bcebeed9bc361e1e8a6a487e6e Mon Sep 17 00:00:00 2001 From: Evan Simmons Date: Sat, 4 Apr 2020 12:01:51 -0700 Subject: [PATCH 11/35] Remove unused dir from .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8be72874..a0db182e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ target/ Cargo.lock -!codegen/Cargo.lock .idea/ *.iml .vscode/ From b190d48868ffd7b0e444a86bea8722d99a184977 Mon Sep 17 00:00:00 2001 From: Evan Simmons Date: Sat, 4 Apr 2020 12:06:12 -0700 Subject: [PATCH 12/35] Init raw on all platforms --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5018ce35..a69b09b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -485,7 +485,6 @@ mod test { #[test] #[cfg(all(feature = "config_parsing", feature = "json_format"))] - #[cfg(target_os = "linux")] fn init_from_raw_config() { let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("append.log"); @@ -506,7 +505,7 @@ mod test { } }, }); - let config = serde_json::from_str::(&cfg).unwrap(); + let config = serde_json::from_str::(&cfg.to_string()).unwrap(); if let Err(e) = init_raw_config(config) { panic!(e); } From 1e8114fe2ba63c34f0aac5246eafe7f5740b9179 Mon Sep 17 00:00:00 2001 From: Richard M Date: Sat, 4 Apr 2020 20:28:25 -0700 Subject: [PATCH 13/35] Remove log4rs::FormatError::XmlFeatureFlagRequired (#156) --- src/priv_file.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/priv_file.rs b/src/priv_file.rs index 6b8b52b4..b230d5fa 100644 --- a/src/priv_file.rs +++ b/src/priv_file.rs @@ -87,10 +87,6 @@ pub enum FormatError { #[fail(display = "the `toml_format` feature is required for TOML support")] TomlFeatureFlagRequired, - /// The XML feature flag was missing. - #[fail(display = "the `xml_format` feature is required for XML support")] - XmlFeatureFlagRequired, - /// An unsupported format was specified. #[fail(display = "unsupported file format `{}`", 0)] UnsupportedFormat(String), From e9da9481fdbe8652ddb08ce7e24de08810f235c5 Mon Sep 17 00:00:00 2001 From: Richard M Date: Sun, 5 Apr 2020 22:28:47 -0700 Subject: [PATCH 14/35] Expand env vars in the path for File and RollingFile appenders (#155) --- Cargo.toml | 5 +- src/append/file.rs | 10 ++- src/append/mod.rs | 129 +++++++++++++++++++++++++++++++++ src/append/rolling_file/mod.rs | 11 ++- 4 files changed, 151 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f8e30c9a..7363f0b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,8 +18,8 @@ json_format = ["serde_json"] toml_format = ["toml"] console_appender = ["console_writer", "simple_writer", "pattern_encoder"] -file_appender = ["parking_lot", "simple_writer", "pattern_encoder"] -rolling_file_appender = ["parking_lot", "simple_writer", "pattern_encoder"] +file_appender = ["parking_lot", "simple_writer", "pattern_encoder", "regex"] +rolling_file_appender = ["parking_lot", "simple_writer", "pattern_encoder", "regex"] compound_policy = [] delete_roller = [] fixed_window_roller = [] @@ -68,6 +68,7 @@ serde_json = { version = "1.0", optional = true } serde_yaml = { version = "0.8.4", optional = true } toml = { version = "0.5", optional = true } parking_lot = { version = "0.11.0", optional = true } +regex = { version = "1", optional = true } [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", optional = true, features = ["handleapi", "minwindef", "processenv", "winbase", "wincon"] } diff --git a/src/append/file.rs b/src/append/file.rs index 5db55b39..b795bc03 100644 --- a/src/append/file.rs +++ b/src/append/file.rs @@ -92,8 +92,12 @@ impl FileAppenderBuilder { } /// Consumes the `FileAppenderBuilder`, producing a `FileAppender`. + /// The path argument can contain environment variables of the form $ENV{name_here}, + /// where 'name_here' will be the name of the environment variable that + /// will be resolved. Note that if the variable fails to resolve, + /// $ENV{name_here} will NOT be replaced in the path. pub fn build>(self, path: P) -> io::Result { - let path = path.as_ref().to_owned(); + let path = super::env_util::expand_env_vars(path.as_ref().to_path_buf()); if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; } @@ -122,6 +126,10 @@ impl FileAppenderBuilder { /// kind: file /// /// # The path of the log file. Required. +/// # The path can contain environment variables of the form $ENV{name_here}, +/// # where 'name_here' will be the name of the environment variable that +/// # will be resolved. Note that if the variable fails to resolve, +/// # $ENV{name_here} will NOT be replaced in the path. /// path: log/foo.log /// /// # Specifies if the appender should append to or truncate the log file if it diff --git a/src/append/mod.rs b/src/append/mod.rs index 1856cae6..80b22b0e 100644 --- a/src/append/mod.rs +++ b/src/append/mod.rs @@ -23,6 +23,20 @@ pub mod file; #[cfg(feature = "rolling_file_appender")] pub mod rolling_file; +#[cfg(any(feature = "file_appender", feature = "rolling_file_appender"))] +mod env_util { + pub fn expand_env_vars(path: std::path::PathBuf) -> std::path::PathBuf { + let mut path: String = path.to_string_lossy().into(); + let matcher = regex::Regex::new(r#"\$ENV\{([\w][\w|\d|\.|_]*)\}"#).unwrap(); + matcher.captures_iter(&path.clone()).for_each(|c| { + if let Ok(s) = std::env::var(&c[1]) { + path = path.replace(&c[0], &s); + } + }); + path.into() + } +} + /// A trait implemented by log4rs appenders. /// /// Appenders take a log record and processes them, for example, by writing it @@ -90,3 +104,118 @@ impl<'de> Deserialize<'de> for AppenderConfig { }) } } + +#[cfg(test)] +mod test { + #[cfg(any(feature = "file_appender", feature = "rolling_file_appender"))] + use std::{ + env::{set_var, var}, + path::PathBuf, + }; + + #[test] + #[cfg(any(feature = "file_appender", feature = "rolling_file_appender"))] + fn expand_env_vars_tests() { + set_var("HELLO_WORLD", "GOOD BYE"); + #[cfg(target_os = "linux")] + let test_cases = vec![ + ("$ENV{HOME}", PathBuf::from(var("HOME").unwrap())), + ( + "$ENV{HELLO_WORLD}", + PathBuf::from(var("HELLO_WORLD").unwrap()), + ), + ( + "$ENV{HOME}/test", + PathBuf::from(format!("{}/test", var("HOME").unwrap())), + ), + ( + "/test/$ENV{HOME}", + PathBuf::from(format!("/test/{}", var("HOME").unwrap())), + ), + ( + "/test/$ENV{HOME}/test", + PathBuf::from(format!("/test/{}/test", var("HOME").unwrap())), + ), + ( + "/test$ENV{HOME}/test", + PathBuf::from(format!("/test{}/test", var("HOME").unwrap())), + ), + ( + "test/$ENV{HOME}/test", + PathBuf::from(format!("test/{}/test", var("HOME").unwrap())), + ), + ( + "/$ENV{HOME}/test/$ENV{USER}", + PathBuf::from(format!( + "/{}/test/{}", + var("HOME").unwrap(), + var("USER").unwrap() + )), + ), + ( + "$ENV{SHOULD_NOT_EXIST}", + PathBuf::from("$ENV{SHOULD_NOT_EXIST}"), + ), + ( + "/$ENV{HOME}/test/$ENV{SHOULD_NOT_EXIST}", + PathBuf::from(format!( + "/{}/test/$ENV{{SHOULD_NOT_EXIST}}", + var("HOME").unwrap() + )), + ), + ]; + + #[cfg(target_os = "windows")] + let test_cases = vec![ + ("$ENV{HOMEPATH}", PathBuf::from(var("HOMEPATH").unwrap())), + ( + "$ENV{HELLO_WORLD}", + PathBuf::from(var("HELLO_WORLD").unwrap()), + ), + ( + "$ENV{HOMEPATH}/test", + PathBuf::from(format!("{}/test", var("HOMEPATH").unwrap())), + ), + ( + "/test/$ENV{USERNAME}", + PathBuf::from(format!("/test/{}", var("USERNAME").unwrap())), + ), + ( + "/test/$ENV{USERNAME}/test", + PathBuf::from(format!("/test/{}/test", var("USERNAME").unwrap())), + ), + ( + "/test$ENV{USERNAME}/test", + PathBuf::from(format!("/test{}/test", var("USERNAME").unwrap())), + ), + ( + "test/$ENV{USERNAME}/test", + PathBuf::from(format!("test/{}/test", var("USERNAME").unwrap())), + ), + ( + "$ENV{HOMEPATH}/test/$ENV{USERNAME}", + PathBuf::from(format!( + "{}/test/{}", + var("HOMEPATH").unwrap(), + var("USERNAME").unwrap() + )), + ), + ( + "$ENV{SHOULD_NOT_EXIST}", + PathBuf::from("$ENV{SHOULD_NOT_EXIST}"), + ), + ( + "$ENV{HOMEPATH}/test/$ENV{SHOULD_NOT_EXIST}", + PathBuf::from(format!( + "{}/test/$ENV{{SHOULD_NOT_EXIST}}", + var("HOMEPATH").unwrap() + )), + ), + ]; + + for (input, expected) in test_cases { + let res = super::env_util::expand_env_vars(input.into()); + assert_eq!(res, expected) + } + } +} diff --git a/src/append/rolling_file/mod.rs b/src/append/rolling_file/mod.rs index 65dd1f5e..b54467b1 100644 --- a/src/append/rolling_file/mod.rs +++ b/src/append/rolling_file/mod.rs @@ -250,6 +250,10 @@ impl RollingFileAppenderBuilder { } /// Constructs a `RollingFileAppender`. + /// The path argument can contain environment variables of the form $ENV{name_here}, + /// where 'name_here' will be the name of the environment variable that + /// will be resolved. Note that if the variable fails to resolve, + /// $ENV{name_here} will NOT be replaced in the path. pub fn build

( self, path: P, @@ -258,9 +262,10 @@ impl RollingFileAppenderBuilder { where P: AsRef, { + let path = super::env_util::expand_env_vars(path.as_ref().to_path_buf()); let appender = RollingFileAppender { writer: Mutex::new(None), - path: path.as_ref().to_owned(), + path, append: self.append, encoder: self .encoder @@ -287,6 +292,10 @@ impl RollingFileAppenderBuilder { /// kind: rolling_file /// /// # The path of the log file. Required. +/// # The path can contain environment variables of the form $ENV{name_here}, +/// # where 'name_here' will be the name of the environment variable that +/// # will be resolved. Note that if the variable fails to resolve, +/// # $ENV{name_here} will NOT be replaced in the path. /// path: log/foo.log /// /// # Specifies if the appender should append to or truncate the log file if it From 4b1b830a0083791430b183d41cc91d84b16d27ac Mon Sep 17 00:00:00 2001 From: estk Date: Fri, 24 Apr 2020 09:39:51 -0700 Subject: [PATCH 15/35] Reorganize config (#157) * Reorganize config * allow missing docs so tests run * lint abatement * some renames --- CHANGELOG.md | 1 + src/append/console.rs | 2 +- src/append/file.rs | 2 +- src/append/mod.rs | 4 +- src/append/rolling_file/mod.rs | 4 +- .../rolling_file/policy/compound/mod.rs | 2 +- .../policy/compound/roll/delete.rs | 2 +- .../policy/compound/roll/fixed_window.rs | 2 +- .../rolling_file/policy/compound/roll/mod.rs | 2 +- .../policy/compound/trigger/mod.rs | 2 +- .../policy/compound/trigger/size.rs | 2 +- src/append/rolling_file/policy/mod.rs | 2 +- src/{priv_file.rs => config/file.rs} | 7 +- src/config/mod.rs | 55 +++ src/{config_parsing.rs => config/raw.rs} | 0 src/{config.rs => config/runtime.rs} | 327 +++++++++--------- src/encode/json.rs | 2 +- src/encode/mod.rs | 2 +- src/encode/pattern/mod.rs | 2 +- src/filter/mod.rs | 2 +- src/filter/threshold.rs | 2 +- src/lib.rs | 77 +---- 22 files changed, 248 insertions(+), 255 deletions(-) rename src/{priv_file.rs => config/file.rs} (98%) create mode 100644 src/config/mod.rs rename src/{config_parsing.rs => config/raw.rs} (100%) rename src/{config.rs => config/runtime.rs} (97%) diff --git a/CHANGELOG.md b/CHANGELOG.md index b48ba821..7d46198a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### New * Allow parsing of config from string +* Expand env vars in file path of file and RollingFile appenders PR#155 ### Changed diff --git a/src/append/console.rs b/src/append/console.rs index 15f5aab4..08669b20 100644 --- a/src/append/console.rs +++ b/src/append/console.rs @@ -11,7 +11,7 @@ use std::{ use failure::Error; #[cfg(feature = "config_parsing")] -use crate::config_parsing::{Deserialize, Deserializers}; +use crate::config::{Deserialize, Deserializers}; #[cfg(feature = "config_parsing")] use crate::encode::EncoderConfig; use crate::{ diff --git a/src/append/file.rs b/src/append/file.rs index b795bc03..a51a5eb3 100644 --- a/src/append/file.rs +++ b/src/append/file.rs @@ -14,7 +14,7 @@ use std::{ use failure::Error; #[cfg(feature = "config_parsing")] -use crate::config_parsing::{Deserialize, Deserializers}; +use crate::config::{Deserialize, Deserializers}; #[cfg(feature = "config_parsing")] use crate::encode::EncoderConfig; diff --git a/src/append/mod.rs b/src/append/mod.rs index 80b22b0e..992aea06 100644 --- a/src/append/mod.rs +++ b/src/append/mod.rs @@ -12,7 +12,7 @@ use std::fmt; use failure::Error; #[cfg(feature = "config_parsing")] -use crate::config_parsing::Deserializable; +use crate::config::Deserializable; #[cfg(feature = "config_parsing")] use crate::filter::FilterConfig; @@ -117,7 +117,7 @@ mod test { #[cfg(any(feature = "file_appender", feature = "rolling_file_appender"))] fn expand_env_vars_tests() { set_var("HELLO_WORLD", "GOOD BYE"); - #[cfg(target_os = "linux")] + #[cfg(not(target_os = "windows"))] let test_cases = vec![ ("$ENV{HOME}", PathBuf::from(var("HOME").unwrap())), ( diff --git a/src/append/rolling_file/mod.rs b/src/append/rolling_file/mod.rs index b54467b1..2e7afae2 100644 --- a/src/append/rolling_file/mod.rs +++ b/src/append/rolling_file/mod.rs @@ -32,7 +32,7 @@ use std::{ use failure::Error; #[cfg(feature = "config_parsing")] -use crate::config_parsing::{Deserialize, Deserializers}; +use crate::config::{Deserialize, Deserializers}; #[cfg(feature = "config_parsing")] use crate::encode::EncoderConfig; use crate::{ @@ -365,7 +365,7 @@ mod test { #[test] #[cfg(feature = "yaml_format")] fn deserialize() { - use crate::config_parsing::{Deserializers, RawConfig}; + use crate::config::{Deserializers, RawConfig}; let dir = tempfile::tempdir().unwrap(); diff --git a/src/append/rolling_file/policy/compound/mod.rs b/src/append/rolling_file/policy/compound/mod.rs index 3f65729b..cb89e05b 100644 --- a/src/append/rolling_file/policy/compound/mod.rs +++ b/src/append/rolling_file/policy/compound/mod.rs @@ -15,7 +15,7 @@ use crate::append::rolling_file::{ LogFile, }; #[cfg(feature = "config_parsing")] -use crate::config_parsing::{Deserialize, Deserializers}; +use crate::config::{Deserialize, Deserializers}; pub mod roll; pub mod trigger; diff --git a/src/append/rolling_file/policy/compound/roll/delete.rs b/src/append/rolling_file/policy/compound/roll/delete.rs index 91e176ca..947d7bf7 100644 --- a/src/append/rolling_file/policy/compound/roll/delete.rs +++ b/src/append/rolling_file/policy/compound/roll/delete.rs @@ -8,7 +8,7 @@ use failure::Error; use crate::append::rolling_file::policy::compound::roll::Roll; #[cfg(feature = "config_parsing")] -use crate::config_parsing::{Deserialize, Deserializers}; +use crate::config::{Deserialize, Deserializers}; /// Configuration for the delete roller. #[cfg(feature = "config_parsing")] diff --git a/src/append/rolling_file/policy/compound/roll/fixed_window.rs b/src/append/rolling_file/policy/compound/roll/fixed_window.rs index d8ed9512..1b989fd3 100644 --- a/src/append/rolling_file/policy/compound/roll/fixed_window.rs +++ b/src/append/rolling_file/policy/compound/roll/fixed_window.rs @@ -15,7 +15,7 @@ use failure::{err_msg, Error}; use crate::append::rolling_file::policy::compound::roll::Roll; #[cfg(feature = "config_parsing")] -use crate::config_parsing::{Deserialize, Deserializers}; +use crate::config::{Deserialize, Deserializers}; /// Configuration for the fixed window roller. #[cfg(feature = "config_parsing")] diff --git a/src/append/rolling_file/policy/compound/roll/mod.rs b/src/append/rolling_file/policy/compound/roll/mod.rs index e84788d3..862e74de 100644 --- a/src/append/rolling_file/policy/compound/roll/mod.rs +++ b/src/append/rolling_file/policy/compound/roll/mod.rs @@ -5,7 +5,7 @@ use std::{fmt, path::Path}; use failure::Error; #[cfg(feature = "config_parsing")] -use crate::config_parsing::Deserializable; +use crate::config::Deserializable; #[cfg(feature = "delete_roller")] pub mod delete; diff --git a/src/append/rolling_file/policy/compound/trigger/mod.rs b/src/append/rolling_file/policy/compound/trigger/mod.rs index b7f1a61c..91971b80 100644 --- a/src/append/rolling_file/policy/compound/trigger/mod.rs +++ b/src/append/rolling_file/policy/compound/trigger/mod.rs @@ -6,7 +6,7 @@ use failure::Error; use crate::append::rolling_file::LogFile; #[cfg(feature = "config_parsing")] -use crate::config_parsing::Deserializable; +use crate::config::Deserializable; #[cfg(feature = "size_trigger")] pub mod size; diff --git a/src/append/rolling_file/policy/compound/trigger/size.rs b/src/append/rolling_file/policy/compound/trigger/size.rs index e733e31e..0d49f316 100644 --- a/src/append/rolling_file/policy/compound/trigger/size.rs +++ b/src/append/rolling_file/policy/compound/trigger/size.rs @@ -12,7 +12,7 @@ use failure::Error; use crate::append::rolling_file::{policy::compound::trigger::Trigger, LogFile}; #[cfg(feature = "config_parsing")] -use crate::config_parsing::{Deserialize, Deserializers}; +use crate::config::{Deserialize, Deserializers}; /// Configuration for the size trigger. #[cfg(feature = "config_parsing")] diff --git a/src/append/rolling_file/policy/mod.rs b/src/append/rolling_file/policy/mod.rs index b12b1aca..13f94af0 100644 --- a/src/append/rolling_file/policy/mod.rs +++ b/src/append/rolling_file/policy/mod.rs @@ -5,7 +5,7 @@ use failure::Error; use crate::append::rolling_file::LogFile; #[cfg(feature = "config_parsing")] -use crate::config_parsing::Deserializable; +use crate::config::Deserializable; #[cfg(feature = "compound_policy")] pub mod compound; diff --git a/src/priv_file.rs b/src/config/file.rs similarity index 98% rename from src/priv_file.rs rename to src/config/file.rs index b230d5fa..6e979c12 100644 --- a/src/priv_file.rs +++ b/src/config/file.rs @@ -8,11 +8,8 @@ use std::{ use failure::{Error, Fail}; -use crate::{ - config::Config, - config_parsing::{Deserializers, RawConfig}, - handle_error, init_config, Handle, -}; +use super::{init_config, Config, Deserializers, Handle, RawConfig}; +use crate::handle_error; /// Initializes the global logger as a log4rs logger configured via a file. /// diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 00000000..f7ec8d1a --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,55 @@ +use crate::Handle; +use failure::{Error, Fail}; +use log::SetLoggerError; + +pub mod runtime; + +#[cfg(feature = "config_parsing")] +mod file; +#[cfg(feature = "config_parsing")] +mod raw; + +pub use runtime::{Appender, Config, Logger, Root}; + +#[cfg(feature = "config_parsing")] +pub use self::file::{init_file, load_config_file, FormatError}; +#[cfg(feature = "config_parsing")] +pub use self::raw::{Deserializable, Deserialize, Deserializers, RawConfig}; + +/// Initializes the global logger as a log4rs logger with the provided config. +/// +/// A `Handle` object is returned which can be used to adjust the logging +/// configuration. +pub fn init_config(config: runtime::Config) -> Result { + let logger = crate::Logger::new(config); + log::set_max_level(logger.max_log_level()); + let handle = Handle { + shared: logger.0.clone(), + }; + log::set_boxed_logger(Box::new(logger)).map(|()| handle) +} + +/// Initializes the global logger as a log4rs logger using the provided raw config. +/// +/// This will return errors if the appenders configuration is malformed or if we fail to set the global logger. +#[cfg(feature = "config_parsing")] +pub fn init_raw_config(config: RawConfig) -> Result<(), Error> { + let (appenders, errors) = config.appenders_lossy(&Deserializers::default()); + if !errors.is_empty() { + return Err(InitErrors(errors).into()); + } + let config = Config::builder() + .appenders(appenders) + .loggers(config.loggers()) + .build(config.root())?; + + let logger = crate::Logger::new(config); + log::set_max_level(log::LevelFilter::Info); + log::set_boxed_logger(Box::new(logger))?; + Ok(()) +} + +/// Collects the set of errors that occur when deserializing the appenders. +#[derive(Debug, Fail)] +#[fail(display = "Errors on initialization: {:#?}", _0)] +pub struct InitErrors(Vec); diff --git a/src/config_parsing.rs b/src/config/raw.rs similarity index 100% rename from src/config_parsing.rs rename to src/config/raw.rs diff --git a/src/config.rs b/src/config/runtime.rs similarity index 97% rename from src/config.rs rename to src/config/runtime.rs index fb380449..a78a4797 100644 --- a/src/config.rs +++ b/src/config/runtime.rs @@ -1,11 +1,169 @@ //! log4rs configuration +use failure::{Error, Fail}; use log::LevelFilter; use std::{collections::HashSet, fmt, iter::IntoIterator}; -use crate::{append::Append, filter::Filter, ConfigPrivateExt, PrivateConfigAppenderExt}; +use crate::{append::Append, filter::Filter}; -use failure::{Error, Fail}; +/// A log4rs configuration. +#[derive(Debug)] +pub struct Config { + appenders: Vec, + root: Root, + loggers: Vec, +} + +impl Config { + /// Creates a new `ConfigBuilder`. + pub fn builder() -> ConfigBuilder { + ConfigBuilder { + appenders: vec![], + loggers: vec![], + } + } + + /// Returns the `Appender`s associated with the `Config`. + pub fn appenders(&self) -> &[Appender] { + &self.appenders + } + + /// Returns the `Root` associated with the `Config`. + pub fn root(&self) -> &Root { + &self.root + } + + /// Returns a mutable handle for the `Root` associated with the `Config`. + pub fn root_mut(&mut self) -> &mut Root { + &mut self.root + } + + /// Returns the `Logger`s associated with the `Config`. + pub fn loggers(&self) -> &[Logger] { + &self.loggers + } + + pub(crate) fn unpack(self) -> (Vec, Root, Vec) { + let Config { + appenders, + root, + loggers, + } = self; + (appenders, root, loggers) + } +} + +/// A builder for `Config`s. +pub struct ConfigBuilder { + appenders: Vec, + loggers: Vec, +} + +impl ConfigBuilder { + /// Adds an appender. + pub fn appender(mut self, appender: Appender) -> ConfigBuilder { + self.appenders.push(appender); + self + } + + /// Adds appenders. + pub fn appenders(mut self, appenders: I) -> ConfigBuilder + where + I: IntoIterator, + { + self.appenders.extend(appenders); + self + } + + /// Adds a logger. + pub fn logger(mut self, logger: Logger) -> ConfigBuilder { + self.loggers.push(logger); + self + } + + /// Adds loggers. + pub fn loggers(mut self, loggers: I) -> ConfigBuilder + where + I: IntoIterator, + { + self.loggers.extend(loggers); + self + } + + /// Consumes the `ConfigBuilder`, returning the `Config`. + /// + /// Unlike `build`, this method will always return a `Config` by stripping + /// portions of the configuration that are incorrect. + pub fn build_lossy(self, mut root: Root) -> (Config, Vec) { + let mut errors = vec![]; + + let ConfigBuilder { appenders, loggers } = self; + + let mut ok_appenders = vec![]; + let mut appender_names = HashSet::new(); + for appender in appenders { + if appender_names.insert(appender.name.clone()) { + ok_appenders.push(appender); + } else { + errors.push(ConfigError::DuplicateAppenderName(appender.name).into()); + } + } + + let mut ok_root_appenders = vec![]; + for appender in root.appenders { + if appender_names.contains(&appender) { + ok_root_appenders.push(appender); + } else { + errors.push(ConfigError::NonexistentAppender(appender).into()); + } + } + root.appenders = ok_root_appenders; + + let mut ok_loggers = vec![]; + let mut logger_names = HashSet::new(); + for mut logger in loggers { + if !logger_names.insert(logger.name.clone()) { + errors.push(ConfigError::DuplicateLoggerName(logger.name).into()); + continue; + } + + if let Err(err) = check_logger_name(&logger.name) { + errors.push(err); + continue; + } + + let mut ok_logger_appenders = vec![]; + for appender in logger.appenders { + if appender_names.contains(&appender) { + ok_logger_appenders.push(appender); + } else { + errors.push(ConfigError::NonexistentAppender(appender).into()); + } + } + logger.appenders = ok_logger_appenders; + + ok_loggers.push(logger); + } + + let config = Config { + appenders: ok_appenders, + root, + loggers: ok_loggers, + }; + + (config, errors) + } + + /// Consumes the `ConfigBuilder`, returning the `Config`. + pub fn build(self, root: Root) -> Result { + let (config, errors) = self.build_lossy(root); + if errors.is_empty() { + Ok(config) + } else { + Err(Errors { errors }) + } + } +} /// Configuration for the root logger. #[derive(Debug)] @@ -99,10 +257,8 @@ impl Appender { pub fn filters(&self) -> &[Box] { &self.filters } -} -impl PrivateConfigAppenderExt for Appender { - fn unpack(self) -> (String, Box, Vec>) { + pub(crate) fn unpack(self) -> (String, Box, Vec>) { let Appender { name, appender, @@ -235,156 +391,6 @@ impl LoggerBuilder { } } -/// A log4rs configuration. -#[derive(Debug)] -pub struct Config { - appenders: Vec, - root: Root, - loggers: Vec, -} - -impl Config { - /// Creates a new `ConfigBuilder`. - pub fn builder() -> ConfigBuilder { - ConfigBuilder { - appenders: vec![], - loggers: vec![], - } - } - - /// Returns the `Appender`s associated with the `Config`. - pub fn appenders(&self) -> &[Appender] { - &self.appenders - } - - /// Returns the `Root` associated with the `Config`. - pub fn root(&self) -> &Root { - &self.root - } - - /// Returns a mutable handle for the `Root` associated with the `Config`. - pub fn root_mut(&mut self) -> &mut Root { - &mut self.root - } - - /// Returns the `Logger`s associated with the `Config`. - pub fn loggers(&self) -> &[Logger] { - &self.loggers - } -} - -/// A builder for `Config`s. -pub struct ConfigBuilder { - appenders: Vec, - loggers: Vec, -} - -impl ConfigBuilder { - /// Adds an appender. - pub fn appender(mut self, appender: Appender) -> ConfigBuilder { - self.appenders.push(appender); - self - } - - /// Adds appenders. - pub fn appenders(mut self, appenders: I) -> ConfigBuilder - where - I: IntoIterator, - { - self.appenders.extend(appenders); - self - } - - /// Adds a logger. - pub fn logger(mut self, logger: Logger) -> ConfigBuilder { - self.loggers.push(logger); - self - } - - /// Adds loggers. - pub fn loggers(mut self, loggers: I) -> ConfigBuilder - where - I: IntoIterator, - { - self.loggers.extend(loggers); - self - } - - /// Consumes the `ConfigBuilder`, returning the `Config`. - /// - /// Unlike `build`, this method will always return a `Config` by stripping - /// portions of the configuration that are incorrect. - pub fn build_lossy(self, mut root: Root) -> (Config, Vec) { - let mut errors = vec![]; - - let ConfigBuilder { appenders, loggers } = self; - - let mut ok_appenders = vec![]; - let mut appender_names = HashSet::new(); - for appender in appenders { - if appender_names.insert(appender.name.clone()) { - ok_appenders.push(appender); - } else { - errors.push(ConfigError::DuplicateAppenderName(appender.name).into()); - } - } - - let mut ok_root_appenders = vec![]; - for appender in root.appenders { - if appender_names.contains(&appender) { - ok_root_appenders.push(appender); - } else { - errors.push(ConfigError::NonexistentAppender(appender).into()); - } - } - root.appenders = ok_root_appenders; - - let mut ok_loggers = vec![]; - let mut logger_names = HashSet::new(); - for mut logger in loggers { - if !logger_names.insert(logger.name.clone()) { - errors.push(ConfigError::DuplicateLoggerName(logger.name).into()); - continue; - } - - if let Err(err) = check_logger_name(&logger.name) { - errors.push(err); - continue; - } - - let mut ok_logger_appenders = vec![]; - for appender in logger.appenders { - if appender_names.contains(&appender) { - ok_logger_appenders.push(appender); - } else { - errors.push(ConfigError::NonexistentAppender(appender).into()); - } - } - logger.appenders = ok_logger_appenders; - - ok_loggers.push(logger); - } - - let config = Config { - appenders: ok_appenders, - root, - loggers: ok_loggers, - }; - - (config, errors) - } - - /// Consumes the `ConfigBuilder`, returning the `Config`. - pub fn build(self, root: Root) -> Result { - let (config, errors) = self.build_lossy(root); - if errors.is_empty() { - Ok(config) - } else { - Err(Errors { errors }) - } - } -} - fn check_logger_name(name: &str) -> Result<(), Error> { if name.is_empty() { return Err(ConfigError::InvalidLoggerName(name.to_owned()).into()); @@ -412,17 +418,6 @@ fn check_logger_name(name: &str) -> Result<(), Error> { } } -impl ConfigPrivateExt for Config { - fn unpack(self) -> (Vec, Root, Vec) { - let Config { - appenders, - root, - loggers, - } = self; - (appenders, root, loggers) - } -} - /// Errors encountered when validating a log4rs `Config`. #[derive(Debug, Fail)] pub struct Errors { diff --git a/src/encode/json.rs b/src/encode/json.rs index 8b8b1b96..8c4c162f 100644 --- a/src/encode/json.rs +++ b/src/encode/json.rs @@ -36,7 +36,7 @@ use std::{fmt, option, thread}; use failure::Error; #[cfg(feature = "config_parsing")] -use crate::config_parsing::{Deserialize, Deserializers}; +use crate::config::{Deserialize, Deserializers}; use crate::encode::{Encode, Write, NEWLINE}; /// The JSON encoder's configuration diff --git a/src/encode/mod.rs b/src/encode/mod.rs index accedb62..aa2f020e 100644 --- a/src/encode/mod.rs +++ b/src/encode/mod.rs @@ -12,7 +12,7 @@ use std::{fmt, io}; use failure::Error; #[cfg(feature = "config_parsing")] -use crate::config_parsing::Deserializable; +use crate::config::Deserializable; #[cfg(feature = "json_encoder")] pub mod json; diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index 6410954b..284877d6 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -130,7 +130,7 @@ use crate::encode::{ }; #[cfg(feature = "config_parsing")] -use crate::config_parsing::{Deserialize, Deserializers}; +use crate::config::{Deserialize, Deserializers}; mod parser; diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 1aa222c4..ba350ac8 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -10,7 +10,7 @@ use std::collections::BTreeMap; use std::fmt; #[cfg(feature = "config_parsing")] -use crate::config_parsing::Deserializable; +use crate::config::Deserializable; #[cfg(feature = "threshold_filter")] pub mod threshold; diff --git a/src/filter/threshold.rs b/src/filter/threshold.rs index f6d7c4d6..bd2dd7b6 100644 --- a/src/filter/threshold.rs +++ b/src/filter/threshold.rs @@ -5,7 +5,7 @@ use log::{LevelFilter, Record}; #[cfg(feature = "config_parsing")] -use crate::config_parsing::{Deserialize, Deserializers}; +use crate::config::{Deserialize, Deserializers}; use crate::filter::{Filter, Response}; /// The threshold filter's configuration. diff --git a/src/lib.rs b/src/lib.rs index a69b09b6..76ff5dc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -185,39 +185,27 @@ #![allow(where_clauses_object_safety, clippy::manual_non_exhaustive)] #![warn(missing_docs)] +// TODO: need to remove before merge +#![allow(missing_docs, clippy::module_inception)] use arc_swap::ArcSwap; use fnv::FnvHasher; -use log::{Level, LevelFilter, Metadata, Record, SetLoggerError}; +use log::{Level, LevelFilter, Metadata, Record}; use std::{cmp, collections::HashMap, hash::BuildHasherDefault, io, io::prelude::*, sync::Arc}; -use failure::{Error, Fail}; - -#[cfg(feature = "config_parsing")] -pub use crate::priv_file::{init_file, load_config_file, FormatError}; - -#[cfg(feature = "config_parsing")] -pub use crate::config_parsing::{Deserializers, RawConfig}; - -use crate::{append::Append, config::Config, filter::Filter}; - pub mod append; pub mod config; pub mod encode; pub mod filter; - -#[cfg(feature = "config_parsing")] -pub mod config_parsing; -#[cfg(feature = "config_parsing")] -mod priv_file; - #[cfg(feature = "console_writer")] mod priv_io; -/// Collects the set of errors that occur when deserializing the appenders. -#[derive(Debug, Fail)] -#[fail(display = "Errors on initialization: {:#?}", _0)] -pub struct InitErrors(Vec); +pub use config::{init_config, Config}; + +#[cfg(feature = "config_parsing")] +pub use config::{init_file, init_raw_config}; + +use self::{append::Append, filter::Filter}; type FnvHashMap = HashMap>; @@ -325,7 +313,6 @@ struct SharedLogger { root: ConfiguredLogger, appenders: Vec, } - impl SharedLogger { fn new(config: config::Config) -> SharedLogger { let (appenders, root, mut loggers) = config.unpack(); @@ -413,43 +400,9 @@ impl log::Log for Logger { } } -fn handle_error(e: &failure::Error) { +pub(crate) fn handle_error(e: &failure::Error) { let _ = writeln!(io::stderr(), "log4rs: {}", e); } - -/// Initializes the global logger as a log4rs logger with the provided config. -/// -/// A `Handle` object is returned which can be used to adjust the logging -/// configuration. -pub fn init_config(config: config::Config) -> Result { - let logger = Logger::new(config); - log::set_max_level(logger.max_log_level()); - let handle = Handle { - shared: logger.0.clone(), - }; - log::set_boxed_logger(Box::new(logger)).map(|()| handle) -} - -/// Initializes the global logger as a log4rs logger using the provided raw config. -/// -/// This will return errors if the appenders configuration is malformed or if we fail to set the global logger. -#[cfg(feature = "config_parsing")] -pub fn init_raw_config(config: RawConfig) -> Result<(), Error> { - let (appenders, errors) = config.appenders_lossy(&Deserializers::default()); - if !errors.is_empty() { - return Err(InitErrors(errors).into()); - } - let config = Config::builder() - .appenders(appenders) - .loggers(config.loggers()) - .build(config.root())?; - - let logger = Logger::new(config); - log::set_max_level(log::LevelFilter::Info); - log::set_boxed_logger(Box::new(logger))?; - Ok(()) -} - /// A handle to the active logger. #[derive(Clone)] pub struct Handle { @@ -469,14 +422,6 @@ trait ErrorInternals { fn new(message: String) -> Self; } -trait ConfigPrivateExt { - fn unpack(self) -> (Vec, config::Root, Vec); -} - -trait PrivateConfigAppenderExt { - fn unpack(self) -> (String, Box, Vec>); -} - #[cfg(test)] mod test { use log::{Level, LevelFilter, Log}; @@ -505,7 +450,7 @@ mod test { } }, }); - let config = serde_json::from_str::(&cfg.to_string()).unwrap(); + let config = serde_json::from_str::(&cfg.to_string()).unwrap(); if let Err(e) = init_raw_config(config) { panic!(e); } From 5953720d90ce35f99042b5e5506b319261681e81 Mon Sep 17 00:00:00 2001 From: estk Date: Fri, 24 Apr 2020 09:54:07 -0700 Subject: [PATCH 16/35] Use anyhow/thiserror (#159) * Use anyhow/thiserror --- CHANGELOG.md | 3 +- Cargo.toml | 3 +- src/append/console.rs | 6 +-- src/append/file.rs | 6 +-- src/append/mod.rs | 6 +-- src/append/rolling_file/mod.rs | 10 ++--- .../rolling_file/policy/compound/mod.rs | 6 +-- .../policy/compound/roll/delete.rs | 6 +-- .../policy/compound/roll/fixed_window.rs | 15 ++++---- .../rolling_file/policy/compound/roll/mod.rs | 4 +- .../policy/compound/trigger/mod.rs | 4 +- .../policy/compound/trigger/size.rs | 6 +-- src/append/rolling_file/policy/mod.rs | 5 +-- src/config/file.rs | 31 ++++++++-------- src/config/mod.rs | 10 ++--- src/config/raw.rs | 37 +++++++++---------- src/config/runtime.rs | 24 ++++++------ src/encode/json.rs | 8 ++-- src/encode/mod.rs | 4 +- src/encode/pattern/mod.rs | 5 +-- src/filter/threshold.rs | 2 +- src/lib.rs | 8 ++-- 22 files changed, 91 insertions(+), 118 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d46198a..cc20efad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [Unreleased] + ### New * Allow parsing of config from string @@ -11,7 +12,7 @@ * Drop XML config support * Rename feature `file` to `config_parsing` -* Expose errors as `failure::Error` +* Use `thiserror`/`anyhow` for errors ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 7363f0b6..8333d789 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,6 @@ harness = false [dependencies] arc-swap = "0.4" chrono = { version = "0.4", optional = true } -failure = "0.1.6" flate2 = { version = "1.0", optional = true } fnv = "1.0" humantime = { version = "2.0", optional = true } @@ -69,6 +68,8 @@ serde_yaml = { version = "0.8.4", optional = true } toml = { version = "0.5", optional = true } parking_lot = { version = "0.11.0", optional = true } regex = { version = "1", optional = true } +thiserror = "1.0.15" +anyhow = "1.0.28" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", optional = true, features = ["handleapi", "minwindef", "processenv", "winbase", "wincon"] } diff --git a/src/append/console.rs b/src/append/console.rs index 08669b20..446a497f 100644 --- a/src/append/console.rs +++ b/src/append/console.rs @@ -8,8 +8,6 @@ use std::{ io::{self, Write}, }; -use failure::Error; - #[cfg(feature = "config_parsing")] use crate::config::{Deserialize, Deserializers}; #[cfg(feature = "config_parsing")] @@ -122,7 +120,7 @@ impl fmt::Debug for ConsoleAppender { } impl Append for ConsoleAppender { - fn append(&self, record: &Record) -> Result<(), Error> { + fn append(&self, record: &Record) -> anyhow::Result<()> { let mut writer = self.writer.lock(); self.encoder.encode(&mut writer, record)?; writer.flush()?; @@ -220,7 +218,7 @@ impl Deserialize for ConsoleAppenderDeserializer { &self, config: ConsoleAppenderConfig, deserializers: &Deserializers, - ) -> Result, failure::Error> { + ) -> anyhow::Result> { let mut appender = ConsoleAppender::builder(); if let Some(target) = config.target { let target = match target { diff --git a/src/append/file.rs b/src/append/file.rs index a51a5eb3..89f41298 100644 --- a/src/append/file.rs +++ b/src/append/file.rs @@ -11,8 +11,6 @@ use std::{ path::{Path, PathBuf}, }; -use failure::Error; - #[cfg(feature = "config_parsing")] use crate::config::{Deserialize, Deserializers}; #[cfg(feature = "config_parsing")] @@ -50,7 +48,7 @@ impl fmt::Debug for FileAppender { } impl Append for FileAppender { - fn append(&self, record: &Record) -> Result<(), Error> { + fn append(&self, record: &Record) -> anyhow::Result<()> { let mut file = self.file.lock(); self.encoder.encode(&mut *file, record)?; file.flush()?; @@ -153,7 +151,7 @@ impl Deserialize for FileAppenderDeserializer { &self, config: FileAppenderConfig, deserializers: &Deserializers, - ) -> Result, Error> { + ) -> anyhow::Result> { let mut appender = FileAppender::builder(); if let Some(append) = config.append { appender = appender.append(append); diff --git a/src/append/mod.rs b/src/append/mod.rs index 992aea06..9f33838d 100644 --- a/src/append/mod.rs +++ b/src/append/mod.rs @@ -9,8 +9,6 @@ use serde_value::Value; use std::collections::BTreeMap; use std::fmt; -use failure::Error; - #[cfg(feature = "config_parsing")] use crate::config::Deserializable; #[cfg(feature = "config_parsing")] @@ -43,7 +41,7 @@ mod env_util { /// to a file or the console. pub trait Append: fmt::Debug + Send + Sync + 'static { /// Processes the provided `Record`. - fn append(&self, record: &Record) -> Result<(), Error>; + fn append(&self, record: &Record) -> anyhow::Result<()>; /// Flushes all in-flight records. fn flush(&self); @@ -57,7 +55,7 @@ impl Deserializable for dyn Append { } impl Append for T { - fn append(&self, record: &Record) -> Result<(), Error> { + fn append(&self, record: &Record) -> anyhow::Result<()> { self.log(record); Ok(()) } diff --git a/src/append/rolling_file/mod.rs b/src/append/rolling_file/mod.rs index 2e7afae2..b950f652 100644 --- a/src/append/rolling_file/mod.rs +++ b/src/append/rolling_file/mod.rs @@ -29,8 +29,6 @@ use std::{ path::{Path, PathBuf}, }; -use failure::Error; - #[cfg(feature = "config_parsing")] use crate::config::{Deserialize, Deserializers}; #[cfg(feature = "config_parsing")] @@ -168,7 +166,7 @@ impl fmt::Debug for RollingFileAppender { } impl Append for RollingFileAppender { - fn append(&self, record: &Record) -> Result<(), Error> { + fn append(&self, record: &Record) -> anyhow::Result<()> { // TODO(eas): Perhaps this is better as a concurrent queue? let mut writer = self.writer.lock(); @@ -334,7 +332,7 @@ impl Deserialize for RollingFileAppenderDeserializer { &self, config: RollingFileAppenderConfig, deserializers: &Deserializers, - ) -> Result, Error> { + ) -> anyhow::Result> { let mut builder = RollingFileAppender::builder(); if let Some(append) = config.append { builder = builder.append(append); @@ -357,8 +355,6 @@ mod test { io::{Read, Write}, }; - use failure::Error; - use super::*; use crate::append::rolling_file::policy::Policy; @@ -408,7 +404,7 @@ appenders: struct NopPolicy; impl Policy for NopPolicy { - fn process(&self, _: &mut LogFile) -> Result<(), Error> { + fn process(&self, _: &mut LogFile) -> anyhow::Result<()> { Ok(()) } } diff --git a/src/append/rolling_file/policy/compound/mod.rs b/src/append/rolling_file/policy/compound/mod.rs index cb89e05b..24b8be41 100644 --- a/src/append/rolling_file/policy/compound/mod.rs +++ b/src/append/rolling_file/policy/compound/mod.rs @@ -8,8 +8,6 @@ use serde_value::Value; #[cfg(feature = "config_parsing")] use std::collections::BTreeMap; -use failure::Error; - use crate::append::rolling_file::{ policy::{compound::roll::Roll, Policy}, LogFile, @@ -100,7 +98,7 @@ impl CompoundPolicy { } impl Policy for CompoundPolicy { - fn process(&self, log: &mut LogFile) -> Result<(), Error> { + fn process(&self, log: &mut LogFile) -> anyhow::Result<()> { if self.trigger.trigger(log)? { log.roll(); self.roller.roll(log.path())?; @@ -148,7 +146,7 @@ impl Deserialize for CompoundPolicyDeserializer { &self, config: CompoundPolicyConfig, deserializers: &Deserializers, - ) -> Result, Error> { + ) -> anyhow::Result> { let trigger = deserializers.deserialize(&config.trigger.kind, config.trigger.config)?; let roller = deserializers.deserialize(&config.roller.kind, config.roller.config)?; Ok(Box::new(CompoundPolicy::new(trigger, roller))) diff --git a/src/append/rolling_file/policy/compound/roll/delete.rs b/src/append/rolling_file/policy/compound/roll/delete.rs index 947d7bf7..e8a6f10d 100644 --- a/src/append/rolling_file/policy/compound/roll/delete.rs +++ b/src/append/rolling_file/policy/compound/roll/delete.rs @@ -4,8 +4,6 @@ use std::{fs, path::Path}; -use failure::Error; - use crate::append::rolling_file::policy::compound::roll::Roll; #[cfg(feature = "config_parsing")] use crate::config::{Deserialize, Deserializers}; @@ -24,7 +22,7 @@ pub struct DeleteRollerConfig { pub struct DeleteRoller(()); impl Roll for DeleteRoller { - fn roll(&self, file: &Path) -> Result<(), Error> { + fn roll(&self, file: &Path) -> anyhow::Result<()> { fs::remove_file(file).map_err(Into::into) } } @@ -56,7 +54,7 @@ impl Deserialize for DeleteRollerDeserializer { &self, _: DeleteRollerConfig, _: &Deserializers, - ) -> Result, Error> { + ) -> anyhow::Result> { Ok(Box::new(DeleteRoller::default())) } } diff --git a/src/append/rolling_file/policy/compound/roll/fixed_window.rs b/src/append/rolling_file/policy/compound/roll/fixed_window.rs index 1b989fd3..6e58b754 100644 --- a/src/append/rolling_file/policy/compound/roll/fixed_window.rs +++ b/src/append/rolling_file/policy/compound/roll/fixed_window.rs @@ -2,6 +2,7 @@ //! //! Requires the `fixed_window_roller` feature. +use anyhow::bail; #[cfg(feature = "background_rotation")] use parking_lot::{Condvar, Mutex}; #[cfg(feature = "background_rotation")] @@ -11,8 +12,6 @@ use std::{ path::{Path, PathBuf}, }; -use failure::{err_msg, Error}; - use crate::append::rolling_file::policy::compound::roll::Roll; #[cfg(feature = "config_parsing")] use crate::config::{Deserialize, Deserializers}; @@ -101,7 +100,7 @@ impl FixedWindowRoller { impl Roll for FixedWindowRoller { #[cfg(not(feature = "background_rotation"))] - fn roll(&self, file: &Path) -> Result<(), Error> { + fn roll(&self, file: &Path) -> anyhow::Result<()> { if self.count == 0 { return fs::remove_file(file).map_err(Into::into); } @@ -118,7 +117,7 @@ impl Roll for FixedWindowRoller { } #[cfg(feature = "background_rotation")] - fn roll(&self, file: &Path) -> Result<(), Error> { + fn roll(&self, file: &Path) -> anyhow::Result<()> { if self.count == 0 { return fs::remove_file(file).map_err(Into::into); } @@ -256,9 +255,9 @@ impl FixedWindowRollerBuilder { /// If the file extension of the pattern is `.gz` and the `gzip` Cargo /// feature is enabled, the archive files will be gzip-compressed. /// If the extension is `.gz` and the `gzip` feature is *not* enabled, an error will be returned. - pub fn build(self, pattern: &str, count: u32) -> Result { + pub fn build(self, pattern: &str, count: u32) -> anyhow::Result { if !pattern.contains("{}") { - return Err(err_msg("pattern does not contain `{}`")); + bail!("pattern does not contain `{}`"); } let compression = match Path::new(pattern).extension() { @@ -266,7 +265,7 @@ impl FixedWindowRollerBuilder { Some(e) if e == "gz" => Compression::Gzip, #[cfg(not(feature = "gzip"))] Some(e) if e == "gz" => { - return Err(err_msg("gzip compression requires the `gzip` feature")); + bail!("gzip compression requires the `gzip` feature"); } _ => Compression::None, }; @@ -316,7 +315,7 @@ impl Deserialize for FixedWindowRollerDeserializer { &self, config: FixedWindowRollerConfig, _: &Deserializers, - ) -> Result, Error> { + ) -> anyhow::Result> { let mut builder = FixedWindowRoller::builder(); if let Some(base) = config.base { builder = builder.base(base); diff --git a/src/append/rolling_file/policy/compound/roll/mod.rs b/src/append/rolling_file/policy/compound/roll/mod.rs index 862e74de..3b53f578 100644 --- a/src/append/rolling_file/policy/compound/roll/mod.rs +++ b/src/append/rolling_file/policy/compound/roll/mod.rs @@ -2,8 +2,6 @@ use std::{fmt, path::Path}; -use failure::Error; - #[cfg(feature = "config_parsing")] use crate::config::Deserializable; @@ -21,7 +19,7 @@ pub trait Roll: fmt::Debug + Send + Sync + 'static { /// /// If this method returns successfully, there *must* no longer be a file /// at the specified location. - fn roll(&self, file: &Path) -> Result<(), Error>; + fn roll(&self, file: &Path) -> anyhow::Result<()>; } #[cfg(feature = "config_parsing")] diff --git a/src/append/rolling_file/policy/compound/trigger/mod.rs b/src/append/rolling_file/policy/compound/trigger/mod.rs index 91971b80..76e67e74 100644 --- a/src/append/rolling_file/policy/compound/trigger/mod.rs +++ b/src/append/rolling_file/policy/compound/trigger/mod.rs @@ -2,8 +2,6 @@ use std::fmt; -use failure::Error; - use crate::append::rolling_file::LogFile; #[cfg(feature = "config_parsing")] use crate::config::Deserializable; @@ -14,7 +12,7 @@ pub mod size; /// A trait which identifies if the active log file should be rolled over. pub trait Trigger: fmt::Debug + Send + Sync + 'static { /// Determines if the active log file should be rolled over. - fn trigger(&self, file: &LogFile) -> Result; + fn trigger(&self, file: &LogFile) -> anyhow::Result; } #[cfg(feature = "config_parsing")] diff --git a/src/append/rolling_file/policy/compound/trigger/size.rs b/src/append/rolling_file/policy/compound/trigger/size.rs index 0d49f316..9477ebbb 100644 --- a/src/append/rolling_file/policy/compound/trigger/size.rs +++ b/src/append/rolling_file/policy/compound/trigger/size.rs @@ -7,8 +7,6 @@ use serde::de; #[cfg(feature = "config_parsing")] use std::fmt; -use failure::Error; - use crate::append::rolling_file::{policy::compound::trigger::Trigger, LogFile}; #[cfg(feature = "config_parsing")] @@ -116,7 +114,7 @@ impl SizeTrigger { } impl Trigger for SizeTrigger { - fn trigger(&self, file: &LogFile) -> Result { + fn trigger(&self, file: &LogFile) -> anyhow::Result { Ok(file.len_estimate() > self.limit) } } @@ -146,7 +144,7 @@ impl Deserialize for SizeTriggerDeserializer { &self, config: SizeTriggerConfig, _: &Deserializers, - ) -> Result, Error> { + ) -> anyhow::Result> { Ok(Box::new(SizeTrigger::new(config.limit))) } } diff --git a/src/append/rolling_file/policy/mod.rs b/src/append/rolling_file/policy/mod.rs index 13f94af0..8c1e6b2d 100644 --- a/src/append/rolling_file/policy/mod.rs +++ b/src/append/rolling_file/policy/mod.rs @@ -1,9 +1,8 @@ //! Policies. use std::fmt; -use failure::Error; - use crate::append::rolling_file::LogFile; + #[cfg(feature = "config_parsing")] use crate::config::Deserializable; @@ -16,7 +15,7 @@ pub trait Policy: Sync + Send + 'static + fmt::Debug { /// /// This method is called after each log event. It is provided a reference /// to the current log file. - fn process(&self, log: &mut LogFile) -> Result<(), Error>; + fn process(&self, log: &mut LogFile) -> anyhow::Result<()>; } #[cfg(feature = "config_parsing")] diff --git a/src/config/file.rs b/src/config/file.rs index 6e979c12..b47e10fe 100644 --- a/src/config/file.rs +++ b/src/config/file.rs @@ -6,7 +6,7 @@ use std::{ time::{Duration, SystemTime}, }; -use failure::{Error, Fail}; +use thiserror::Error; use super::{init_config, Config, Deserializers, Handle, RawConfig}; use crate::handle_error; @@ -20,7 +20,7 @@ use crate::handle_error; /// reported to stderr. /// /// Requires the `file` feature (enabled by default). -pub fn init_file

(path: P, deserializers: Deserializers) -> Result<(), failure::Error> +pub fn init_file

(path: P, deserializers: Deserializers) -> anyhow::Result<()> where P: AsRef, { @@ -57,7 +57,7 @@ where /// /// Unlike `init_file`, this function does not initialize the logger; it only /// loads the `Config` and returns it. -pub fn load_config_file

(path: P, deserializers: Deserializers) -> Result +pub fn load_config_file

(path: P, deserializers: Deserializers) -> anyhow::Result where P: AsRef, { @@ -70,26 +70,26 @@ where } /// The various types of formatting errors that can be generated. -#[derive(Debug, Fail)] +#[derive(Debug, Error)] pub enum FormatError { /// The YAML feature flag was missing. - #[fail(display = "the `yaml_format` feature is required for YAML support")] + #[error("the `yaml_format` feature is required for YAML support")] YamlFeatureFlagRequired, /// The JSON feature flag was missing. - #[fail(display = "the `json_format` feature is required for JSON support")] + #[error("the `json_format` feature is required for JSON support")] JsonFeatureFlagRequired, /// The TOML feature flag was missing. - #[fail(display = "the `toml_format` feature is required for TOML support")] + #[error("the `toml_format` feature is required for TOML support")] TomlFeatureFlagRequired, /// An unsupported format was specified. - #[fail(display = "unsupported file format `{}`", 0)] + #[error("unsupported file format `{0}`")] UnsupportedFormat(String), /// Log4rs could not determine the file format. - #[fail(display = "unable to determine the file format")] + #[error("unable to determine the file format")] UnknownFormat, } @@ -104,7 +104,7 @@ enum Format { } impl Format { - fn from_path(path: &Path) -> Result { + fn from_path(path: &Path) -> anyhow::Result { match path.extension().and_then(|s| s.to_str()) { #[cfg(feature = "yaml_format")] Some("yaml") | Some("yml") => Ok(Format::Yaml), @@ -126,7 +126,8 @@ impl Format { } } - fn parse(&self, source: &str) -> Result { + #[allow(unused_variables)] + fn parse(&self, source: &str) -> anyhow::Result { match *self { #[cfg(feature = "yaml_format")] Format::Yaml => ::serde_yaml::from_str(source).map_err(Into::into), @@ -138,10 +139,8 @@ impl Format { } } -fn read_config(path: &Path) -> Result { - let mut file = File::open(path)?; - let mut s = String::new(); - file.read_to_string(&mut s)?; +fn read_config(path: &Path) -> anyhow::Result { + let s = fs::read_to_string(path)?; Ok(s) } @@ -208,7 +207,7 @@ impl ConfigReloader { } } - fn run_once(&mut self, rate: Duration) -> Result, failure::Error> { + fn run_once(&mut self, rate: Duration) -> anyhow::Result> { if let Some(last_modified) = self.modified { let modified = fs::metadata(&self.path).and_then(|m| m.modified())?; if last_modified == modified { diff --git a/src/config/mod.rs b/src/config/mod.rs index f7ec8d1a..236b3c18 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,6 +1,6 @@ use crate::Handle; -use failure::{Error, Fail}; use log::SetLoggerError; +use thiserror::Error; pub mod runtime; @@ -33,7 +33,7 @@ pub fn init_config(config: runtime::Config) -> Result Result<(), Error> { +pub fn init_raw_config(config: RawConfig) -> anyhow::Result<()> { let (appenders, errors) = config.appenders_lossy(&Deserializers::default()); if !errors.is_empty() { return Err(InitErrors(errors).into()); @@ -50,6 +50,6 @@ pub fn init_raw_config(config: RawConfig) -> Result<(), Error> { } /// Collects the set of errors that occur when deserializing the appenders. -#[derive(Debug, Fail)] -#[fail(display = "Errors on initialization: {:#?}", _0)] -pub struct InitErrors(Vec); +#[derive(Debug, Error)] +#[error("Errors on initialization: {0:#?}")] +pub struct InitErrors(Vec); diff --git a/src/config/raw.rs b/src/config/raw.rs index f5829b5d..3edcfc31 100644 --- a/src/config/raw.rs +++ b/src/config/raw.rs @@ -90,15 +90,15 @@ //! ``` #![allow(deprecated)] -use log::LevelFilter; -use serde::de::{self, Deserialize as SerdeDeserialize, DeserializeOwned}; -use serde_value::Value; use std::{ borrow::ToOwned, collections::HashMap, fmt, marker::PhantomData, sync::Arc, time::Duration, }; -use failure::{err_msg, Error, Fail}; - +use anyhow::anyhow; +use log::LevelFilter; +use serde::de::{self, Deserialize as SerdeDeserialize, DeserializeOwned}; +use serde_value::Value; +use thiserror::Error; use typemap::{Key, ShareCloneMap}; use crate::{append::AppenderConfig, config}; @@ -133,7 +133,7 @@ pub trait Deserialize: Send + Sync + 'static { &self, config: Self::Config, deserializers: &Deserializers, - ) -> Result, Error>; + ) -> anyhow::Result>; } trait ErasedDeserialize: Send + Sync + 'static { @@ -143,7 +143,7 @@ trait ErasedDeserialize: Send + Sync + 'static { &self, config: Value, deserializers: &Deserializers, - ) -> Result, Error>; + ) -> anyhow::Result>; } struct DeserializeEraser(T); @@ -158,7 +158,7 @@ where &self, config: Value, deserializers: &Deserializers, - ) -> Result, Error> { + ) -> anyhow::Result> { let config = config.deserialize_into()?; self.0.deserialize(config, deserializers) } @@ -280,30 +280,27 @@ impl Deserializers { } /// Deserializes a value of a specific type and kind. - pub fn deserialize(&self, kind: &str, config: Value) -> Result, Error> + pub fn deserialize(&self, kind: &str, config: Value) -> anyhow::Result> where T: Deserializable, { match self.0.get::>().and_then(|m| m.get(kind)) { Some(b) => b.deserialize(config, self), - None => Err(err_msg(format!( + None => Err(anyhow!( "no {} deserializer for kind `{}` registered", T::name(), kind - ))), + )), } } } -#[derive(Debug, Fail)] +#[derive(Debug, Error)] enum DeserializingConfigError { - #[fail(display = "error deserializing appender {}: {}", 0, 1)] - Appender(String, Error), - #[fail( - display = "error deserializing filter attached to appender {}: {}", - 0, 1 - )] - Filter(String, Error), + #[error("error deserializing appender {0}: {1}")] + Appender(String, anyhow::Error), + #[error("error deserializing filter attached to appender {0}: {1}")] + Filter(String, anyhow::Error), } /// A raw deserializable log4rs configuration. @@ -347,7 +344,7 @@ impl RawConfig { pub fn appenders_lossy( &self, deserializers: &Deserializers, - ) -> (Vec, Vec) { + ) -> (Vec, Vec) { let mut appenders = vec![]; let mut errors = vec![]; diff --git a/src/config/runtime.rs b/src/config/runtime.rs index a78a4797..d88e4fd4 100644 --- a/src/config/runtime.rs +++ b/src/config/runtime.rs @@ -1,8 +1,8 @@ //! log4rs configuration -use failure::{Error, Fail}; use log::LevelFilter; use std::{collections::HashSet, fmt, iter::IntoIterator}; +use thiserror::Error; use crate::{append::Append, filter::Filter}; @@ -94,7 +94,7 @@ impl ConfigBuilder { /// /// Unlike `build`, this method will always return a `Config` by stripping /// portions of the configuration that are incorrect. - pub fn build_lossy(self, mut root: Root) -> (Config, Vec) { + pub fn build_lossy(self, mut root: Root) -> (Config, Vec) { let mut errors = vec![]; let ConfigBuilder { appenders, loggers } = self; @@ -391,7 +391,7 @@ impl LoggerBuilder { } } -fn check_logger_name(name: &str) -> Result<(), Error> { +fn check_logger_name(name: &str) -> Result<(), anyhow::Error> { if name.is_empty() { return Err(ConfigError::InvalidLoggerName(name.to_owned()).into()); } @@ -419,14 +419,14 @@ fn check_logger_name(name: &str) -> Result<(), Error> { } /// Errors encountered when validating a log4rs `Config`. -#[derive(Debug, Fail)] +#[derive(Debug, Error)] pub struct Errors { - errors: Vec, + errors: Vec, } impl Errors { /// Returns a slice of `Error`s. - pub fn errors(&self) -> &[Error] { + pub fn errors(&self) -> &[anyhow::Error] { &self.errors } } @@ -441,22 +441,22 @@ impl fmt::Display for Errors { } /// An error validating a log4rs `Config`. -#[derive(Debug, Fail)] +#[derive(Debug, Error)] pub enum ConfigError { /// Multiple appenders were registered with the same name. - #[fail(display = "Duplicate appender name `{}`", 0)] + #[error("Duplicate appender name `{0}`")] DuplicateAppenderName(String), /// A reference to a nonexistant appender. - #[fail(display = "Reference to nonexistent appender: `{}`", 0)] + #[error("Reference to nonexistent appender: `{0}`")] NonexistentAppender(String), /// Multiple loggers were registered with the same name. - #[fail(display = "Duplicate logger name `{}`", 0)] + #[error("Duplicate logger name `{0}`")] DuplicateLoggerName(String), /// A logger name was invalid. - #[fail(display = "Invalid logger name `{}`", 0)] + #[error("Invalid logger name `{0}`")] InvalidLoggerName(String), #[doc(hidden)] - #[fail(display = "Reserved for future use")] + #[error("Reserved for future use")] __Extensible, } diff --git a/src/encode/json.rs b/src/encode/json.rs index 8c4c162f..d5f28a56 100644 --- a/src/encode/json.rs +++ b/src/encode/json.rs @@ -33,8 +33,6 @@ use log::{Level, Record}; use serde::ser::{self, Serialize, SerializeMap}; use std::{fmt, option, thread}; -use failure::Error; - #[cfg(feature = "config_parsing")] use crate::config::{Deserialize, Deserializers}; use crate::encode::{Encode, Write, NEWLINE}; @@ -65,7 +63,7 @@ impl JsonEncoder { w: &mut dyn Write, time: DateTime, record: &Record, - ) -> Result<(), Error> { + ) -> anyhow::Result<()> { let thread = thread::current(); let message = Message { time: time.format_with_items(Some(Item::Fixed(Fixed::RFC3339)).into_iter()), @@ -86,7 +84,7 @@ impl JsonEncoder { } impl Encode for JsonEncoder { - fn encode(&self, w: &mut dyn Write, record: &Record) -> Result<(), Error> { + fn encode(&self, w: &mut dyn Write, record: &Record) -> anyhow::Result<()> { self.encode_inner(w, Local::now(), record) } } @@ -159,7 +157,7 @@ impl Deserialize for JsonEncoderDeserializer { &self, _: JsonEncoderConfig, _: &Deserializers, - ) -> Result, Error> { + ) -> anyhow::Result> { Ok(Box::new(JsonEncoder::default())) } } diff --git a/src/encode/mod.rs b/src/encode/mod.rs index aa2f020e..2ed19edd 100644 --- a/src/encode/mod.rs +++ b/src/encode/mod.rs @@ -9,8 +9,6 @@ use serde_value::Value; use std::collections::BTreeMap; use std::{fmt, io}; -use failure::Error; - #[cfg(feature = "config_parsing")] use crate::config::Deserializable; @@ -34,7 +32,7 @@ const NEWLINE: &str = "\n"; /// output. pub trait Encode: fmt::Debug + Send + Sync + 'static { /// Encodes the `Record` into bytes and writes them. - fn encode(&self, w: &mut dyn Write, record: &Record) -> Result<(), Error>; + fn encode(&self, w: &mut dyn Write, record: &Record) -> anyhow::Result<()>; } #[cfg(feature = "config_parsing")] diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index 284877d6..8a94f943 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -119,7 +119,6 @@ //! [MDC]: https://crates.io/crates/log-mdc use chrono::{Local, Utc}; -use failure::Error; use log::{Level, Record}; use std::{default::Default, fmt, io, process, thread}; @@ -618,7 +617,7 @@ impl Default for PatternEncoder { } impl Encode for PatternEncoder { - fn encode(&self, w: &mut dyn encode::Write, record: &Record) -> Result<(), Error> { + fn encode(&self, w: &mut dyn encode::Write, record: &Record) -> anyhow::Result<()> { for chunk in &self.chunks { chunk.encode(w, record)?; } @@ -662,7 +661,7 @@ impl Deserialize for PatternEncoderDeserializer { &self, config: PatternEncoderConfig, _: &Deserializers, - ) -> Result, Error> { + ) -> anyhow::Result> { let encoder = match config.pattern { Some(pattern) => PatternEncoder::new(&pattern), None => PatternEncoder::default(), diff --git a/src/filter/threshold.rs b/src/filter/threshold.rs index bd2dd7b6..6726bfc9 100644 --- a/src/filter/threshold.rs +++ b/src/filter/threshold.rs @@ -61,7 +61,7 @@ impl Deserialize for ThresholdFilterDeserializer { &self, config: ThresholdFilterConfig, _: &Deserializers, - ) -> Result, failure::Error> { + ) -> anyhow::Result> { Ok(Box::new(ThresholdFilter::new(config.level))) } } diff --git a/src/lib.rs b/src/lib.rs index 76ff5dc1..cbe4fdc3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -188,10 +188,11 @@ // TODO: need to remove before merge #![allow(missing_docs, clippy::module_inception)] +use std::{cmp, collections::HashMap, hash::BuildHasherDefault, io, io::prelude::*, sync::Arc}; + use arc_swap::ArcSwap; use fnv::FnvHasher; use log::{Level, LevelFilter, Metadata, Record}; -use std::{cmp, collections::HashMap, hash::BuildHasherDefault, io, io::prelude::*, sync::Arc}; pub mod append; pub mod config; @@ -292,7 +293,7 @@ struct Appender { } impl Appender { - fn append(&self, record: &Record) -> Result<(), failure::Error> { + fn append(&self, record: &Record) -> anyhow::Result<()> { for filter in &self.filters { match filter.filter(record) { filter::Response::Accept => break, @@ -400,9 +401,10 @@ impl log::Log for Logger { } } -pub(crate) fn handle_error(e: &failure::Error) { +pub(crate) fn handle_error(e: &anyhow::Error) { let _ = writeln!(io::stderr(), "log4rs: {}", e); } + /// A handle to the active logger. #[derive(Clone)] pub struct Handle { From a674389b0cae6fd4a345f67d82ed11d7eb854d0f Mon Sep 17 00:00:00 2001 From: Richard M Date: Mon, 20 Jul 2020 10:02:36 -0700 Subject: [PATCH 17/35] Change addtivity to additive to match the actual code (#163) --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index cbe4fdc3..a50829ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,7 @@ //! Loggers are also associated with a set of appenders. Appenders can be //! associated directly with a logger. In addition, the appenders of the //! logger's parent will be associated with the logger unless the logger has -//! its *additivity* set to `false`. Log events sent to the logger that are not +//! its *additive* set to `false`. Log events sent to the logger that are not //! filtered out by the logger's maximum log level will be sent to all //! associated appenders. //! From b59a728d5bb901f34f77112bfac1761275b51dea Mon Sep 17 00:00:00 2001 From: estk Date: Wed, 22 Jul 2020 11:39:46 -0700 Subject: [PATCH 18/35] Errors 1.0 (#160) Co-authored-by: shmapdy --- src/append/mod.rs | 1 + src/config/file.rs | 13 ++++----- src/config/mod.rs | 21 ++++++++++---- src/config/raw.rs | 28 +++++++++++++++---- src/config/runtime.rs | 58 ++++++++++++++++++++------------------- src/encode/pattern/mod.rs | 49 +++++++++++++++++---------------- 6 files changed, 100 insertions(+), 70 deletions(-) diff --git a/src/append/mod.rs b/src/append/mod.rs index 9f33838d..8d9a1c9b 100644 --- a/src/append/mod.rs +++ b/src/append/mod.rs @@ -23,6 +23,7 @@ pub mod rolling_file; #[cfg(any(feature = "file_appender", feature = "rolling_file_appender"))] mod env_util { + #[allow(clippy::redundant_clone)] pub fn expand_env_vars(path: std::path::PathBuf) -> std::path::PathBuf { let mut path: String = path.to_string_lossy().into(); let matcher = regex::Regex::new(r#"\$ENV\{([\w][\w|\d|\.|_]*)\}"#).unwrap(); diff --git a/src/config/file.rs b/src/config/file.rs index b47e10fe..0f82c6fd 100644 --- a/src/config/file.rs +++ b/src/config/file.rs @@ -145,18 +145,15 @@ fn read_config(path: &Path) -> anyhow::Result { } fn deserialize(config: &RawConfig, deserializers: &Deserializers) -> Config { - let (appenders, errors) = config.appenders_lossy(deserializers); - for error in &errors { - handle_error(error); - } + let (appenders, mut errors) = config.appenders_lossy(deserializers); + errors.handle(); - let (config, errors) = Config::builder() + let (config, mut errors) = Config::builder() .appenders(appenders) .loggers(config.loggers()) .build_lossy(config.root()); - for error in &errors { - handle_error(error); - } + + errors.handle(); config } diff --git a/src/config/mod.rs b/src/config/mod.rs index 236b3c18..4565c2b9 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,7 +1,8 @@ -use crate::Handle; use log::SetLoggerError; use thiserror::Error; +use crate::Handle; + pub mod runtime; #[cfg(feature = "config_parsing")] @@ -33,10 +34,10 @@ pub fn init_config(config: runtime::Config) -> Result anyhow::Result<()> { +pub fn init_raw_config(config: RawConfig) -> Result<(), InitError> { let (appenders, errors) = config.appenders_lossy(&Deserializers::default()); if !errors.is_empty() { - return Err(InitErrors(errors).into()); + return Err(InitError::Deserializing(errors)); } let config = Config::builder() .appenders(appenders) @@ -49,7 +50,15 @@ pub fn init_raw_config(config: RawConfig) -> anyhow::Result<()> { Ok(()) } -/// Collects the set of errors that occur when deserializing the appenders. #[derive(Debug, Error)] -#[error("Errors on initialization: {0:#?}")] -pub struct InitErrors(Vec); +pub enum InitError { + #[error("Errors found when deserializing the config: {0:#?}")] + #[cfg(feature = "config_parsing")] + Deserializing(#[from] raw::AppenderErrors), + + #[error("Config building errors: {0:#?}")] + BuildConfig(#[from] runtime::ConfigErrors), + + #[error("Error setting the logger: {0:#?}")] + SetLogger(#[from] log::SetLoggerError), +} diff --git a/src/config/raw.rs b/src/config/raw.rs index 3edcfc31..696e327b 100644 --- a/src/config/raw.rs +++ b/src/config/raw.rs @@ -296,7 +296,7 @@ impl Deserializers { } #[derive(Debug, Error)] -enum DeserializingConfigError { +pub enum DeserializingConfigError { #[error("error deserializing appender {0}: {1}")] Appender(String, anyhow::Error), #[error("error deserializing filter attached to appender {0}: {1}")] @@ -309,14 +309,32 @@ enum DeserializingConfigError { pub struct RawConfig { #[serde(deserialize_with = "de_duration", default)] refresh_rate: Option, + #[serde(default)] root: Root, + #[serde(default)] appenders: HashMap, + #[serde(default)] loggers: HashMap, } +#[derive(Debug, Error)] +#[error("errors deserializing appenders {0:#?}")] +pub struct AppenderErrors(Vec); + +impl AppenderErrors { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + pub fn handle(&mut self) { + for error in self.0.drain(..) { + crate::handle_error(&error.into()); + } + } +} + impl RawConfig { /// Returns the root. pub fn root(&self) -> config::Root { @@ -344,7 +362,7 @@ impl RawConfig { pub fn appenders_lossy( &self, deserializers: &Deserializers, - ) -> (Vec, Vec) { + ) -> (Vec, AppenderErrors) { let mut appenders = vec![]; let mut errors = vec![]; @@ -353,16 +371,16 @@ impl RawConfig { for filter in &appender.filters { match deserializers.deserialize(&filter.kind, filter.config.clone()) { Ok(filter) => builder = builder.filter(filter), - Err(e) => errors.push(DeserializingConfigError::Filter(name.clone(), e).into()), + Err(e) => errors.push(DeserializingConfigError::Filter(name.clone(), e)), } } match deserializers.deserialize(&appender.kind, appender.config.clone()) { Ok(appender) => appenders.push(builder.build(name.clone(), appender)), - Err(e) => errors.push(DeserializingConfigError::Appender(name.clone(), e).into()), + Err(e) => errors.push(DeserializingConfigError::Appender(name.clone(), e)), } } - (appenders, errors) + (appenders, AppenderErrors(errors)) } /// Returns the requested refresh rate. diff --git a/src/config/runtime.rs b/src/config/runtime.rs index d88e4fd4..ac241add 100644 --- a/src/config/runtime.rs +++ b/src/config/runtime.rs @@ -1,7 +1,7 @@ //! log4rs configuration use log::LevelFilter; -use std::{collections::HashSet, fmt, iter::IntoIterator}; +use std::{collections::HashSet, iter::IntoIterator}; use thiserror::Error; use crate::{append::Append, filter::Filter}; @@ -94,8 +94,8 @@ impl ConfigBuilder { /// /// Unlike `build`, this method will always return a `Config` by stripping /// portions of the configuration that are incorrect. - pub fn build_lossy(self, mut root: Root) -> (Config, Vec) { - let mut errors = vec![]; + pub fn build_lossy(self, mut root: Root) -> (Config, ConfigErrors) { + let mut errors: Vec = vec![]; let ConfigBuilder { appenders, loggers } = self; @@ -105,7 +105,7 @@ impl ConfigBuilder { if appender_names.insert(appender.name.clone()) { ok_appenders.push(appender); } else { - errors.push(ConfigError::DuplicateAppenderName(appender.name).into()); + errors.push(ConfigError::DuplicateAppenderName(appender.name)); } } @@ -114,7 +114,7 @@ impl ConfigBuilder { if appender_names.contains(&appender) { ok_root_appenders.push(appender); } else { - errors.push(ConfigError::NonexistentAppender(appender).into()); + errors.push(ConfigError::NonexistentAppender(appender)); } } root.appenders = ok_root_appenders; @@ -123,7 +123,7 @@ impl ConfigBuilder { let mut logger_names = HashSet::new(); for mut logger in loggers { if !logger_names.insert(logger.name.clone()) { - errors.push(ConfigError::DuplicateLoggerName(logger.name).into()); + errors.push(ConfigError::DuplicateLoggerName(logger.name)); continue; } @@ -137,7 +137,7 @@ impl ConfigBuilder { if appender_names.contains(&appender) { ok_logger_appenders.push(appender); } else { - errors.push(ConfigError::NonexistentAppender(appender).into()); + errors.push(ConfigError::NonexistentAppender(appender)); } } logger.appenders = ok_logger_appenders; @@ -151,16 +151,16 @@ impl ConfigBuilder { loggers: ok_loggers, }; - (config, errors) + (config, ConfigErrors(errors)) } /// Consumes the `ConfigBuilder`, returning the `Config`. - pub fn build(self, root: Root) -> Result { + pub fn build(self, root: Root) -> Result { let (config, errors) = self.build_lossy(root); if errors.is_empty() { Ok(config) } else { - Err(Errors { errors }) + Err(errors) } } } @@ -391,9 +391,9 @@ impl LoggerBuilder { } } -fn check_logger_name(name: &str) -> Result<(), anyhow::Error> { +fn check_logger_name(name: &str) -> Result<(), ConfigError> { if name.is_empty() { - return Err(ConfigError::InvalidLoggerName(name.to_owned()).into()); + return Err(ConfigError::InvalidLoggerName(name.to_owned())); } let mut streak = 0; @@ -401,18 +401,18 @@ fn check_logger_name(name: &str) -> Result<(), anyhow::Error> { if ch == ':' { streak += 1; if streak > 2 { - return Err(ConfigError::InvalidLoggerName(name.to_owned()).into()); + return Err(ConfigError::InvalidLoggerName(name.to_owned())); } } else { if streak > 0 && streak != 2 { - return Err(ConfigError::InvalidLoggerName(name.to_owned()).into()); + return Err(ConfigError::InvalidLoggerName(name.to_owned())); } streak = 0; } } if streak > 0 { - Err(ConfigError::InvalidLoggerName(name.to_owned()).into()) + Err(ConfigError::InvalidLoggerName(name.to_owned())) } else { Ok(()) } @@ -420,23 +420,21 @@ fn check_logger_name(name: &str) -> Result<(), anyhow::Error> { /// Errors encountered when validating a log4rs `Config`. #[derive(Debug, Error)] -pub struct Errors { - errors: Vec, -} +#[error("Configuration errors: {0:#?}")] +pub struct ConfigErrors(Vec); -impl Errors { +impl ConfigErrors { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } /// Returns a slice of `Error`s. - pub fn errors(&self) -> &[anyhow::Error] { - &self.errors + pub fn errors(&self) -> &[ConfigError] { + &self.0 } -} - -impl fmt::Display for Errors { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - for error in &self.errors { - writeln!(fmt, "{}", error)?; + pub fn handle(&mut self) { + for e in self.0.drain(..) { + crate::handle_error(&e.into()); } - Ok(()) } } @@ -446,15 +444,19 @@ pub enum ConfigError { /// Multiple appenders were registered with the same name. #[error("Duplicate appender name `{0}`")] DuplicateAppenderName(String), + /// A reference to a nonexistant appender. #[error("Reference to nonexistent appender: `{0}`")] NonexistentAppender(String), + /// Multiple loggers were registered with the same name. #[error("Duplicate logger name `{0}`")] DuplicateLoggerName(String), + /// A logger name was invalid. #[error("Invalid logger name `{0}`")] InvalidLoggerName(String), + #[doc(hidden)] #[error("Reserved for future use")] __Extensible, diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index 8a94f943..e356508a 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -403,16 +403,17 @@ impl<'a> From> for Chunk { let timezone = match formatter.args.get(1) { Some(arg) => { - if arg.len() != 1 { - return Chunk::Error("invalid timezone".to_owned()); - } - match arg[0] { - Piece::Text(ref z) if *z == "utc" => Timezone::Utc, - Piece::Text(ref z) if *z == "local" => Timezone::Local, - Piece::Text(ref z) => { - return Chunk::Error(format!("invalid timezone `{}`", z)); + if let Some(arg) = arg.get(0) { + match arg { + Piece::Text(ref z) if *z == "utc" => Timezone::Utc, + Piece::Text(ref z) if *z == "local" => Timezone::Local, + Piece::Text(ref z) => { + return Chunk::Error(format!("invalid timezone `{}`", z)); + } + _ => return Chunk::Error("invalid timezone".to_owned()), } - _ => return Chunk::Error("invalid timezone".to_owned()), + } else { + return Chunk::Error("invalid timezone".to_owned()); } } None => Timezone::Local, @@ -457,34 +458,36 @@ impl<'a> From> for Chunk { let key = match formatter.args.get(0) { Some(arg) => { - if arg.len() != 1 { + if let Some(arg) = arg.get(0) { + match arg { + Piece::Text(key) => key.to_owned(), + Piece::Error(ref e) => return Chunk::Error(e.clone()), + _ => return Chunk::Error("invalid MDC key".to_owned()), + } + } else { return Chunk::Error("invalid MDC key".to_owned()); } - match arg[0] { - Piece::Text(key) => key.to_owned(), - Piece::Error(ref e) => return Chunk::Error(e.clone()), - _ => return Chunk::Error("invalid MDC key".to_owned()), - } } None => return Chunk::Error("missing MDC key".to_owned()), }; let default = match formatter.args.get(1) { Some(arg) => { - if arg.len() != 1 { + if let Some(arg) = arg.get(0) { + match arg { + Piece::Text(key) => key.to_owned(), + Piece::Error(ref e) => return Chunk::Error(e.clone()), + _ => return Chunk::Error("invalid MDC default".to_owned()), + } + } else { return Chunk::Error("invalid MDC default".to_owned()); } - match arg[0] { - Piece::Text(key) => key.to_owned(), - Piece::Error(ref e) => return Chunk::Error(e.clone()), - _ => return Chunk::Error("invalid MDC default".to_owned()), - } } - None => "".to_owned(), + None => "", }; Chunk::Formatted { - chunk: FormattedChunk::Mdc(key, default), + chunk: FormattedChunk::Mdc(key.into(), default.into()), params: parameters, } } From ea3f2f3c0fd0adc91fe1fe37604ca26d05f09fc2 Mon Sep 17 00:00:00 2001 From: Evan Simmons Date: Wed, 22 Jul 2020 13:49:14 -0700 Subject: [PATCH 19/35] clippy --- src/config/file.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/config/file.rs b/src/config/file.rs index 0f82c6fd..288ab4fc 100644 --- a/src/config/file.rs +++ b/src/config/file.rs @@ -1,6 +1,5 @@ use std::{ - fs::{self, File}, - io::Read, + fs, path::{Path, PathBuf}, thread, time::{Duration, SystemTime}, From dfec821f2377eda1f7a155b8bc88fdcd2487ef28 Mon Sep 17 00:00:00 2001 From: estk Date: Wed, 22 Jul 2020 14:25:49 -0700 Subject: [PATCH 20/35] Standard derives rebase (#175) --- Cargo.toml | 1 + src/append/console.rs | 18 +++++----- src/append/file.rs | 17 ++++----- src/append/mod.rs | 2 +- src/append/rolling_file/mod.rs | 36 +++++++++---------- .../rolling_file/policy/compound/mod.rs | 5 ++- .../policy/compound/roll/delete.rs | 5 +-- .../policy/compound/roll/fixed_window.rs | 12 ++++--- .../policy/compound/trigger/size.rs | 5 +-- src/config/mod.rs | 6 ++++ src/config/raw.rs | 16 +++------ src/config/runtime.rs | 9 +++-- src/encode/json.rs | 5 +-- src/encode/mod.rs | 23 ++++++------ src/encode/pattern/mod.rs | 21 +++++------ src/encode/pattern/parser.rs | 6 +++- src/encode/writer/ansi.rs | 2 +- src/encode/writer/simple.rs | 2 +- src/filter/mod.rs | 2 +- src/filter/threshold.rs | 5 +-- src/lib.rs | 8 +++-- 21 files changed, 107 insertions(+), 99 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8333d789..3478e89b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,6 +70,7 @@ parking_lot = { version = "0.11.0", optional = true } regex = { version = "1", optional = true } thiserror = "1.0.15" anyhow = "1.0.28" +derivative = "2.1.1" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", optional = true, features = ["handleapi", "minwindef", "processenv", "winbase", "wincon"] } diff --git a/src/append/console.rs b/src/append/console.rs index 446a497f..3877ce33 100644 --- a/src/append/console.rs +++ b/src/append/console.rs @@ -2,6 +2,7 @@ //! //! Requires the `console_appender` feature. +use derivative::Derivative; use log::Record; use std::{ fmt, @@ -28,15 +29,15 @@ use crate::{ /// The console appender's configuration. #[cfg(feature = "config_parsing")] -#[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] +#[derive(Debug, serde::Deserialize)] pub struct ConsoleAppenderConfig { target: Option, encoder: Option, } #[cfg(feature = "config_parsing")] -#[derive(serde::Deserialize)] +#[derive(Debug, serde::Deserialize)] enum ConfigTarget { #[serde(rename = "stdout")] Stdout, @@ -106,19 +107,14 @@ impl<'a> encode::Write for WriterLock<'a> { /// /// It supports output styling if standard out is a console buffer on Windows /// or is a TTY on Unix. +#[derive(Derivative)] +#[derivative(Debug)] pub struct ConsoleAppender { + #[derivative(Debug = "ignore")] writer: Writer, encoder: Box, } -impl fmt::Debug for ConsoleAppender { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("ConsoleAppender") - .field("encoder", &self.encoder) - .finish() - } -} - impl Append for ConsoleAppender { fn append(&self, record: &Record) -> anyhow::Result<()> { let mut writer = self.writer.lock(); @@ -184,6 +180,7 @@ impl ConsoleAppenderBuilder { } /// The stream to log to. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum Target { /// Standard output. Stdout, @@ -206,6 +203,7 @@ pub enum Target { /// kind: pattern /// ``` #[cfg(feature = "config_parsing")] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct ConsoleAppenderDeserializer; #[cfg(feature = "config_parsing")] diff --git a/src/append/file.rs b/src/append/file.rs index 89f41298..2021aa33 100644 --- a/src/append/file.rs +++ b/src/append/file.rs @@ -2,10 +2,10 @@ //! //! Requires the `file_appender` feature. +use derivative::Derivative; use log::Record; use parking_lot::Mutex; use std::{ - fmt, fs::{self, File, OpenOptions}, io::{self, BufWriter, Write}, path::{Path, PathBuf}, @@ -23,8 +23,8 @@ use crate::{ /// The file appender's configuration. #[cfg(feature = "config_parsing")] -#[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] +#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, serde::Deserialize)] pub struct FileAppenderConfig { path: String, encoder: Option, @@ -32,21 +32,15 @@ pub struct FileAppenderConfig { } /// An appender which logs to a file. +#[derive(Derivative)] +#[derivative(Debug)] pub struct FileAppender { path: PathBuf, + #[derivative(Debug = "ignore")] file: Mutex>>, encoder: Box, } -impl fmt::Debug for FileAppender { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("FileAppender") - .field("file", &self.path) - .field("encoder", &self.encoder) - .finish() - } -} - impl Append for FileAppender { fn append(&self, record: &Record) -> anyhow::Result<()> { let mut file = self.file.lock(); @@ -139,6 +133,7 @@ impl FileAppenderBuilder { /// kind: pattern /// ``` #[cfg(feature = "config_parsing")] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct FileAppenderDeserializer; #[cfg(feature = "config_parsing")] diff --git a/src/append/mod.rs b/src/append/mod.rs index 8d9a1c9b..eafa6cc0 100644 --- a/src/append/mod.rs +++ b/src/append/mod.rs @@ -68,7 +68,7 @@ impl Append for T { /// Configuration for an appender. #[cfg(feature = "config_parsing")] -#[derive(PartialEq, Eq, Debug, Clone)] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct AppenderConfig { /// The appender kind. pub kind: String, diff --git a/src/append/rolling_file/mod.rs b/src/append/rolling_file/mod.rs index b950f652..a7c6c74f 100644 --- a/src/append/rolling_file/mod.rs +++ b/src/append/rolling_file/mod.rs @@ -16,34 +16,36 @@ //! //! Requires the `rolling_file_appender` feature. +use derivative::Derivative; use log::Record; use parking_lot::Mutex; -#[cfg(feature = "config_parsing")] -use serde_value::Value; -#[cfg(feature = "config_parsing")] -use std::collections::BTreeMap; use std::{ - fmt, fs::{self, File, OpenOptions}, io::{self, BufWriter, Write}, path::{Path, PathBuf}, }; #[cfg(feature = "config_parsing")] -use crate::config::{Deserialize, Deserializers}; +use serde_value::Value; #[cfg(feature = "config_parsing")] -use crate::encode::EncoderConfig; +use std::collections::BTreeMap; + use crate::{ append::Append, encode::{self, pattern::PatternEncoder, Encode}, }; +#[cfg(feature = "config_parsing")] +use crate::config::{Deserialize, Deserializers}; +#[cfg(feature = "config_parsing")] +use crate::encode::EncoderConfig; + pub mod policy; /// Configuration for the rolling file appender. #[cfg(feature = "config_parsing")] -#[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] +#[derive(Clone, Eq, PartialEq, Hash, Debug, serde::Deserialize)] pub struct RollingFileAppenderConfig { path: String, append: Option, @@ -52,6 +54,7 @@ pub struct RollingFileAppenderConfig { } #[cfg(feature = "config_parsing")] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] struct Policy { kind: String, config: Value, @@ -77,6 +80,7 @@ impl<'de> serde::Deserialize<'de> for Policy { } } +#[derive(Debug)] struct LogWriter { file: BufWriter, len: u64, @@ -98,6 +102,7 @@ impl io::Write for LogWriter { impl encode::Write for LogWriter {} /// Information about the active log file. +#[derive(Debug)] pub struct LogFile<'a> { writer: &'a mut Option, path: &'a Path, @@ -146,7 +151,10 @@ impl<'a> LogFile<'a> { } /// An appender which archives log files in a configurable strategy. +#[derive(Derivative)] +#[derivative(Debug)] pub struct RollingFileAppender { + #[derivative(Debug = "ignore")] writer: Mutex>, path: PathBuf, append: bool, @@ -154,17 +162,6 @@ pub struct RollingFileAppender { policy: Box, } -impl fmt::Debug for RollingFileAppender { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("RollingFileAppender") - .field("path", &self.path) - .field("append", &self.append) - .field("encoder", &self.encoder) - .field("policy", &self.policy) - .finish() - } -} - impl Append for RollingFileAppender { fn append(&self, record: &Record) -> anyhow::Result<()> { // TODO(eas): Perhaps this is better as a concurrent queue? @@ -320,6 +317,7 @@ impl RollingFileAppenderBuilder { /// kind: delete /// ``` #[cfg(feature = "config_parsing")] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct RollingFileAppenderDeserializer; #[cfg(feature = "config_parsing")] diff --git a/src/append/rolling_file/policy/compound/mod.rs b/src/append/rolling_file/policy/compound/mod.rs index 24b8be41..4a689c51 100644 --- a/src/append/rolling_file/policy/compound/mod.rs +++ b/src/append/rolling_file/policy/compound/mod.rs @@ -20,14 +20,15 @@ pub mod trigger; /// Configuration for the compound policy. #[cfg(feature = "config_parsing")] -#[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] +#[derive(Clone, Eq, PartialEq, Hash, Debug, serde::Deserialize)] pub struct CompoundPolicyConfig { trigger: Trigger, roller: Roller, } #[cfg(feature = "config_parsing")] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] struct Trigger { kind: String, config: Value, @@ -54,6 +55,7 @@ impl<'de> serde::Deserialize<'de> for Trigger { } #[cfg(feature = "config_parsing")] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] struct Roller { kind: String, config: Value, @@ -134,6 +136,7 @@ impl Policy for CompoundPolicy { /// # deserializer, and will vary based on the kind of roller. /// ``` #[cfg(feature = "config_parsing")] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct CompoundPolicyDeserializer; #[cfg(feature = "config_parsing")] diff --git a/src/append/rolling_file/policy/compound/roll/delete.rs b/src/append/rolling_file/policy/compound/roll/delete.rs index e8a6f10d..34c4b5d9 100644 --- a/src/append/rolling_file/policy/compound/roll/delete.rs +++ b/src/append/rolling_file/policy/compound/roll/delete.rs @@ -10,15 +10,15 @@ use crate::config::{Deserialize, Deserializers}; /// Configuration for the delete roller. #[cfg(feature = "config_parsing")] -#[derive(serde::Deserialize, Clone)] #[serde(deny_unknown_fields)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default, serde::Deserialize)] pub struct DeleteRollerConfig { #[serde(skip_deserializing)] _p: (), } /// A roller which deletes the log file. -#[derive(Debug, Default)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct DeleteRoller(()); impl Roll for DeleteRoller { @@ -42,6 +42,7 @@ impl DeleteRoller { /// kind: delete /// ``` #[cfg(feature = "config_parsing")] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct DeleteRollerDeserializer; #[cfg(feature = "config_parsing")] diff --git a/src/append/rolling_file/policy/compound/roll/fixed_window.rs b/src/append/rolling_file/policy/compound/roll/fixed_window.rs index 6e58b754..fa2f3dee 100644 --- a/src/append/rolling_file/policy/compound/roll/fixed_window.rs +++ b/src/append/rolling_file/policy/compound/roll/fixed_window.rs @@ -18,15 +18,15 @@ use crate::config::{Deserialize, Deserializers}; /// Configuration for the fixed window roller. #[cfg(feature = "config_parsing")] -#[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] +#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, serde::Deserialize)] pub struct FixedWindowRollerConfig { pattern: String, base: Option, count: u32, } -#[derive(Clone, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] enum Compression { None, #[cfg(feature = "gzip")] @@ -81,7 +81,7 @@ impl Compression { /// Note that this roller will have to rename every archived file every time the /// log rolls over. Performance may be negatively impacted by specifying a large /// count. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct FixedWindowRoller { pattern: String, compression: Compression, @@ -107,7 +107,7 @@ impl Roll for FixedWindowRoller { rotate( self.pattern.clone(), - self.compression.clone(), + self.compression, self.base, self.count, file.to_path_buf(), @@ -136,7 +136,7 @@ impl Roll for FixedWindowRoller { drop(ready); let pattern = self.pattern.clone(); - let compression = self.compression.clone(); + let compression = self.compression; let base = self.base; let count = self.count; let cond_pair = self.cond_pair.clone(); @@ -233,6 +233,7 @@ fn rotate( } /// A builder for the `FixedWindowRoller`. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct FixedWindowRollerBuilder { base: u32, } @@ -303,6 +304,7 @@ impl FixedWindowRollerBuilder { /// base: 1 /// ``` #[cfg(feature = "config_parsing")] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct FixedWindowRollerDeserializer; #[cfg(feature = "config_parsing")] diff --git a/src/append/rolling_file/policy/compound/trigger/size.rs b/src/append/rolling_file/policy/compound/trigger/size.rs index 9477ebbb..c668549e 100644 --- a/src/append/rolling_file/policy/compound/trigger/size.rs +++ b/src/append/rolling_file/policy/compound/trigger/size.rs @@ -14,8 +14,8 @@ use crate::config::{Deserialize, Deserializers}; /// Configuration for the size trigger. #[cfg(feature = "config_parsing")] -#[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default, serde::Deserialize)] pub struct SizeTriggerConfig { #[serde(deserialize_with = "deserialize_limit")] limit: u64, @@ -100,7 +100,7 @@ where } /// A trigger which rolls the log once it has passed a certain size. -#[derive(Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct SizeTrigger { limit: u64, } @@ -132,6 +132,7 @@ impl Trigger for SizeTrigger { /// limit: 10 mb /// ``` #[cfg(feature = "config_parsing")] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct SizeTriggerDeserializer; #[cfg(feature = "config_parsing")] diff --git a/src/config/mod.rs b/src/config/mod.rs index 4565c2b9..1725d3a9 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,3 +1,5 @@ +//! All things pertaining to log4rs config. See the docs root for examples. + use log::SetLoggerError; use thiserror::Error; @@ -50,15 +52,19 @@ pub fn init_raw_config(config: RawConfig) -> Result<(), InitError> { Ok(()) } +/// Errors found when initializing. #[derive(Debug, Error)] pub enum InitError { + /// There was an error deserializing. #[error("Errors found when deserializing the config: {0:#?}")] #[cfg(feature = "config_parsing")] Deserializing(#[from] raw::AppenderErrors), + /// There was an error building the handle. #[error("Config building errors: {0:#?}")] BuildConfig(#[from] runtime::ConfigErrors), + /// There was an error setting the global logger. #[error("Error setting the logger: {0:#?}")] SetLogger(#[from] log::SetLoggerError), } diff --git a/src/config/raw.rs b/src/config/raw.rs index 696e327b..a1539849 100644 --- a/src/config/raw.rs +++ b/src/config/raw.rs @@ -95,6 +95,7 @@ use std::{ }; use anyhow::anyhow; +use derivative::Derivative; use log::LevelFilter; use serde::de::{self, Deserialize as SerdeDeserialize, DeserializeOwned}; use serde_value::Value; @@ -304,8 +305,8 @@ pub enum DeserializingConfigError { } /// A raw deserializable log4rs configuration. -#[derive(serde::Deserialize, Debug, Clone)] #[serde(deny_unknown_fields)] +#[derive(Clone, Debug, Default, serde::Deserialize)] pub struct RawConfig { #[serde(deserialize_with = "de_duration", default)] refresh_rate: Option, @@ -424,24 +425,17 @@ where Option::::deserialize(d).map(|r| r.map(|s| s.0)) } -#[derive(serde::Deserialize, Debug, Clone)] +#[derive(Clone, Debug, Derivative, serde::Deserialize)] +#[derivative(Default)] #[serde(deny_unknown_fields)] struct Root { #[serde(default = "root_level_default")] + #[derivative(Default(value = "root_level_default()"))] level: LevelFilter, #[serde(default)] appenders: Vec, } -impl Default for Root { - fn default() -> Root { - Root { - level: root_level_default(), - appenders: vec![], - } - } -} - fn root_level_default() -> LevelFilter { LevelFilter::Debug } diff --git a/src/config/runtime.rs b/src/config/runtime.rs index ac241add..6b80019f 100644 --- a/src/config/runtime.rs +++ b/src/config/runtime.rs @@ -54,6 +54,7 @@ impl Config { } /// A builder for `Config`s. +#[derive(Debug, Default)] pub struct ConfigBuilder { appenders: Vec, loggers: Vec, @@ -195,7 +196,7 @@ impl Root { } /// A builder for `Root`s. -#[derive(Debug)] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct RootBuilder { appenders: Vec, } @@ -304,7 +305,7 @@ impl AppenderBuilder { } /// Configuration for a logger. -#[derive(Debug)] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct Logger { name: String, level: LevelFilter, @@ -345,7 +346,7 @@ impl Logger { } /// A builder for `Logger`s. -#[derive(Debug)] +#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct LoggerBuilder { appenders: Vec, additive: bool, @@ -424,6 +425,7 @@ fn check_logger_name(name: &str) -> Result<(), ConfigError> { pub struct ConfigErrors(Vec); impl ConfigErrors { + /// There were no config errors. pub fn is_empty(&self) -> bool { self.0.is_empty() } @@ -431,6 +433,7 @@ impl ConfigErrors { pub fn errors(&self) -> &[ConfigError] { &self.0 } + /// Handle non-fatal errors (by logging them to stderr.) pub fn handle(&mut self) { for e in self.0.drain(..) { crate::handle_error(&e.into()); diff --git a/src/encode/json.rs b/src/encode/json.rs index d5f28a56..0ac336df 100644 --- a/src/encode/json.rs +++ b/src/encode/json.rs @@ -39,7 +39,7 @@ use crate::encode::{Encode, Write, NEWLINE}; /// The JSON encoder's configuration #[cfg(feature = "config_parsing")] -#[derive(serde::Deserialize, Clone)] +#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct JsonEncoderConfig { #[serde(skip_deserializing)] @@ -47,7 +47,7 @@ pub struct JsonEncoderConfig { } /// An `Encode`r which writes a JSON object. -#[derive(Debug, Default)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct JsonEncoder(()); impl JsonEncoder { @@ -145,6 +145,7 @@ impl ser::Serialize for Mdc { /// kind: json /// ``` #[cfg(feature = "config_parsing")] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct JsonEncoderDeserializer; #[cfg(feature = "config_parsing")] diff --git a/src/encode/mod.rs b/src/encode/mod.rs index 2ed19edd..aa290b3c 100644 --- a/src/encode/mod.rs +++ b/src/encode/mod.rs @@ -1,13 +1,15 @@ //! Encoders +use derivative::Derivative; use log::Record; +use std::{fmt, io}; + #[cfg(feature = "config_parsing")] use serde::de; #[cfg(feature = "config_parsing")] use serde_value::Value; #[cfg(feature = "config_parsing")] use std::collections::BTreeMap; -use std::{fmt, io}; #[cfg(feature = "config_parsing")] use crate::config::Deserializable; @@ -21,6 +23,7 @@ pub mod writer; #[allow(dead_code)] #[cfg(windows)] const NEWLINE: &'static str = "\r\n"; + #[allow(dead_code)] #[cfg(not(windows))] const NEWLINE: &str = "\n"; @@ -44,6 +47,7 @@ impl Deserializable for dyn Encode { /// Configuration for an encoder. #[cfg(feature = "config_parsing")] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct EncoderConfig { /// The encoder's kind. pub kind: String, @@ -73,8 +77,8 @@ impl<'de> de::Deserialize<'de> for EncoderConfig { } /// A text or background color. -#[derive(Copy, Clone, Debug)] #[allow(missing_docs)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum Color { Black, Red, @@ -90,7 +94,9 @@ pub enum Color { /// /// Any fields set to `None` will be set to their default format, as defined /// by the `Write`r. -#[derive(Clone, Default)] +#[derive(Derivative)] +#[derivative(Debug)] +#[derive(Clone, Eq, PartialEq, Hash, Default)] pub struct Style { /// The text (or foreground) color. pub text: Option, @@ -98,19 +104,10 @@ pub struct Style { pub background: Option, /// True if the text should have increased intensity. pub intense: Option, + #[derivative(Debug = "ignore")] _p: (), } -impl fmt::Debug for Style { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("Style") - .field("text", &self.text) - .field("background", &self.background) - .field("intense", &self.intense) - .finish() - } -} - impl Style { /// Returns a `Style` with all fields set to their defaults. pub fn new() -> Style { diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index e356508a..32a6240d 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -119,8 +119,9 @@ //! [MDC]: https://crates.io/crates/log-mdc use chrono::{Local, Utc}; +use derivative::Derivative; use log::{Level, Record}; -use std::{default::Default, fmt, io, process, thread}; +use std::{default::Default, io, process, thread}; use crate::encode::{ self, @@ -135,8 +136,8 @@ mod parser; /// The pattern encoder's configuration. #[cfg(feature = "config_parsing")] -#[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] +#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, serde::Deserialize)] pub struct PatternEncoderConfig { pattern: Option, } @@ -292,6 +293,7 @@ impl encode::Write for RightAlignWriter { } } +#[derive(Clone, Eq, PartialEq, Hash, Debug)] enum Chunk { Text(String), Formatted { @@ -523,11 +525,13 @@ fn no_args(arg: &[Vec], params: Parameters, chunk: FormattedChunk) -> Chu } } +#[derive(Clone, Eq, PartialEq, Hash, Debug)] enum Timezone { Utc, Local, } +#[derive(Clone, Eq, PartialEq, Hash, Debug)] enum FormattedChunk { Time(String, Timezone), Level, @@ -599,19 +603,15 @@ impl FormattedChunk { } /// An `Encode`r configured via a format string. +#[derive(Derivative)] +#[derivative(Debug)] +#[derive(Clone, Eq, PartialEq, Hash)] pub struct PatternEncoder { + #[derivative(Debug = "ignore")] chunks: Vec, pattern: String, } -impl fmt::Debug for PatternEncoder { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("PatternEncoder") - .field("pattern", &self.pattern) - .finish() - } -} - /// Returns a `PatternEncoder` using the default pattern of `{d} {l} {t} - {m}{n}`. impl Default for PatternEncoder { fn default() -> PatternEncoder { @@ -652,6 +652,7 @@ impl PatternEncoder { /// pattern: "{d} {l} {t} - {m}{n}" /// ``` #[cfg(feature = "config_parsing")] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct PatternEncoderDeserializer; #[cfg(feature = "config_parsing")] diff --git a/src/encode/pattern/parser.rs b/src/encode/pattern/parser.rs index 445cee76..8e91e8ec 100644 --- a/src/encode/pattern/parser.rs +++ b/src/encode/pattern/parser.rs @@ -1,6 +1,7 @@ // cribbed to a large extent from libfmt_macros use std::{iter::Peekable, str::CharIndices}; +#[derive(Clone, Eq, PartialEq, Hash, Debug)] pub enum Piece<'a> { Text(&'a str), Argument { @@ -10,11 +11,13 @@ pub enum Piece<'a> { Error(String), } +#[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct Formatter<'a> { pub name: &'a str, pub args: Vec>>, } +#[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct Parameters { pub fill: char, pub align: Alignment, @@ -22,12 +25,13 @@ pub struct Parameters { pub max_width: Option, } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum Alignment { Left, Right, } +#[derive(Clone, Debug)] pub struct Parser<'a> { pattern: &'a str, it: Peekable>, diff --git a/src/encode/writer/ansi.rs b/src/encode/writer/ansi.rs index 18deef93..8b8b4226 100644 --- a/src/encode/writer/ansi.rs +++ b/src/encode/writer/ansi.rs @@ -7,7 +7,7 @@ use std::{fmt, io}; /// An `encode::Write`r that wraps an `io::Write`r, emitting ANSI escape codes /// for text style. -#[derive(Debug)] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct AnsiWriter(pub W); impl io::Write for AnsiWriter { diff --git a/src/encode/writer/simple.rs b/src/encode/writer/simple.rs index 08757650..db29a6d9 100644 --- a/src/encode/writer/simple.rs +++ b/src/encode/writer/simple.rs @@ -7,7 +7,7 @@ use std::{fmt, io}; /// An `encode::Write`r that simply delegates to an `io::Write`r and relies /// on the default implementations of `encode::Write`r methods. -#[derive(Debug)] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct SimpleWriter(pub W); impl io::Write for SimpleWriter { diff --git a/src/filter/mod.rs b/src/filter/mod.rs index ba350ac8..093dd2d1 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -50,8 +50,8 @@ pub enum Response { } /// Configuration for a filter. -#[derive(PartialEq, Eq, Debug, Clone)] #[cfg(feature = "config_parsing")] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct FilterConfig { /// The filter kind. pub kind: String, diff --git a/src/filter/threshold.rs b/src/filter/threshold.rs index 6726bfc9..86c6e289 100644 --- a/src/filter/threshold.rs +++ b/src/filter/threshold.rs @@ -10,13 +10,13 @@ use crate::filter::{Filter, Response}; /// The threshold filter's configuration. #[cfg(feature = "config_parsing")] -#[derive(serde::Deserialize)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, serde::Deserialize)] pub struct ThresholdFilterConfig { level: LevelFilter, } /// A filter that rejects all events at a level below a provided threshold. -#[derive(Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct ThresholdFilter { level: LevelFilter, } @@ -49,6 +49,7 @@ impl Filter for ThresholdFilter { /// level: warn /// ``` #[cfg(feature = "config_parsing")] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct ThresholdFilterDeserializer; #[cfg(feature = "config_parsing")] diff --git a/src/lib.rs b/src/lib.rs index a50829ed..ba0474cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -185,8 +185,6 @@ #![allow(where_clauses_object_safety, clippy::manual_non_exhaustive)] #![warn(missing_docs)] -// TODO: need to remove before merge -#![allow(missing_docs, clippy::module_inception)] use std::{cmp, collections::HashMap, hash::BuildHasherDefault, io, io::prelude::*, sync::Arc}; @@ -210,6 +208,7 @@ use self::{append::Append, filter::Filter}; type FnvHashMap = HashMap>; +#[derive(Debug)] struct ConfiguredLogger { level: LevelFilter, appenders: Vec, @@ -287,6 +286,7 @@ impl ConfiguredLogger { } } +#[derive(Debug)] struct Appender { appender: Box, filters: Vec>, @@ -310,6 +310,7 @@ impl Appender { } } +#[derive(Debug)] struct SharedLogger { root: ConfiguredLogger, appenders: Vec, @@ -363,6 +364,7 @@ impl SharedLogger { /// The fully configured log4rs Logger which is appropriate /// to use with the `log::set_boxed_logger` function. +#[derive(Debug)] pub struct Logger(Arc>); impl Logger { @@ -406,7 +408,7 @@ pub(crate) fn handle_error(e: &anyhow::Error) { } /// A handle to the active logger. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Handle { shared: Arc>, } From 9a02da9853a0f1cf5ef83c6e3b957fbee23a5699 Mon Sep 17 00:00:00 2001 From: Evan Simmons Date: Wed, 22 Jul 2020 14:26:25 -0700 Subject: [PATCH 21/35] Bump serde-value --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3478e89b..c0a261a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ humantime = { version = "2.0", optional = true } log = { version = "0.4.0", features = ["std"] } log-mdc = { version = "0.1", optional = true } serde = { version = "1.0", optional = true, features = ["derive"] } -serde-value = { version = "0.6", optional = true } +serde-value = { version = "0.7", optional = true } thread-id = { version = "3.3", optional = true } typemap = { version = "0.3", optional = true } serde_json = { version = "1.0", optional = true } From 3e09e60c16d3fc689c570217d2011a50f78bfa0f Mon Sep 17 00:00:00 2001 From: Evan Simmons Date: Thu, 23 Jul 2020 09:59:44 -0700 Subject: [PATCH 22/35] Alpha 1 version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c0a261a5..d5fa2240 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "log4rs" -version = "0.13.0" +version = "1.0.0-alpha-1" authors = ["Steven Fackler ", "Evan Simmons "] description = "A highly configurable multi-output logging implementation for the `log` facade" license = "MIT/Apache-2.0" From 467b85d719e369b2fe31a92b82ec35c9f3b0af20 Mon Sep 17 00:00:00 2001 From: Charles Giguere Date: Mon, 28 Sep 2020 18:25:10 -0400 Subject: [PATCH 23/35] Update highlight colors to be the same as env_logger (#167) --- src/encode/pattern/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index 32a6240d..2a620a5a 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -582,8 +582,9 @@ impl FormattedChunk { Level::Error => { w.set_style(Style::new().text(Color::Red).intense(true))?; } - Level::Warn => w.set_style(Style::new().text(Color::Red))?, - Level::Info => w.set_style(Style::new().text(Color::Blue))?, + Level::Warn => w.set_style(Style::new().text(Color::Yellow))?, + Level::Info => w.set_style(Style::new().text(Color::Green))?, + Level::Trace => w.set_style(Style::new().text(Color::Black).intense(true))?, _ => {} } for chunk in chunks { From c997e0eacea8489c796a6984a2ffd37191cb34ea Mon Sep 17 00:00:00 2001 From: estk Date: Tue, 29 Sep 2020 16:58:21 -0700 Subject: [PATCH 24/35] Custom err handler (#183) * WIP custom err handling * Compiling * Add an method to the public api * re-enable Debug --- CHANGELOG.md | 2 ++ src/config/mod.rs | 16 ++++++++++++ src/lib.rs | 62 +++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 73 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc20efad..540902ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,13 @@ ### New +* Custom error handling * Allow parsing of config from string * Expand env vars in file path of file and RollingFile appenders PR#155 ### Changed +* Colors changed to match `env_logger` * Drop XML config support * Rename feature `file` to `config_parsing` * Use `thiserror`/`anyhow` for errors diff --git a/src/config/mod.rs b/src/config/mod.rs index 1725d3a9..d82a6ab5 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -32,6 +32,22 @@ pub fn init_config(config: runtime::Config) -> Result, +) -> Result { + let logger = crate::Logger::new_with_err_handler(config, err_handler); + log::set_max_level(logger.max_log_level()); + let handle = Handle { + shared: logger.0.clone(), + }; + log::set_boxed_logger(Box::new(logger)).map(|()| handle) +} + /// Initializes the global logger as a log4rs logger using the provided raw config. /// /// This will return errors if the appenders configuration is malformed or if we fail to set the global logger. diff --git a/src/lib.rs b/src/lib.rs index ba0474cc..589b9ae8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -186,7 +186,9 @@ #![allow(where_clauses_object_safety, clippy::manual_non_exhaustive)] #![warn(missing_docs)] -use std::{cmp, collections::HashMap, hash::BuildHasherDefault, io, io::prelude::*, sync::Arc}; +use std::{ + cmp, collections::HashMap, fmt, hash::BuildHasherDefault, io, io::prelude::*, sync::Arc, +}; use arc_swap::ArcSwap; use fnv::FnvHasher; @@ -275,14 +277,21 @@ impl ConfiguredLogger { self.level >= level } - fn log(&self, record: &log::Record, appenders: &[Appender]) { + fn log(&self, record: &log::Record, appenders: &[Appender]) -> Result<(), Vec> { + let mut errors = vec![]; if self.enabled(record.level()) { for &idx in &self.appenders { if let Err(err) = appenders[idx].append(record) { - handle_error(&err); + errors.push(err); } } } + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } } } @@ -310,13 +319,34 @@ impl Appender { } } -#[derive(Debug)] struct SharedLogger { root: ConfiguredLogger, appenders: Vec, + err_handler: Box, +} + +impl fmt::Debug for SharedLogger { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SharedLogger") + .field("root", &self.root) + .field("appenders", &self.appenders) + .finish() + } } + impl SharedLogger { fn new(config: config::Config) -> SharedLogger { + Self::new_with_err_handler( + config, + Box::new(|e: &anyhow::Error| { + let _ = writeln!(io::stderr(), "log4rs: {}", e); + }), + ) + } + fn new_with_err_handler( + config: config::Config, + err_handler: Box, + ) -> SharedLogger { let (appenders, root, mut loggers) = config.unpack(); let root = { @@ -358,7 +388,11 @@ impl SharedLogger { }) .collect(); - SharedLogger { root, appenders } + SharedLogger { + root, + appenders, + err_handler, + } } } @@ -372,6 +406,15 @@ impl Logger { pub fn new(config: config::Config) -> Logger { Logger(Arc::new(ArcSwap::new(Arc::new(SharedLogger::new(config))))) } + /// Create a new `Logger` given a configuration and err handler. + pub fn new_with_err_handler( + config: config::Config, + err_handler: Box, + ) -> Logger { + Logger(Arc::new(ArcSwap::new(Arc::new( + SharedLogger::new_with_err_handler(config, err_handler), + )))) + } /// Set the max log level above which everything will be filtered. pub fn max_log_level(&self) -> LevelFilter { @@ -390,10 +433,15 @@ impl log::Log for Logger { fn log(&self, record: &log::Record) { let shared = self.0.load(); - shared + if let Err(errs) = shared .root .find(record.target()) - .log(record, &shared.appenders); + .log(record, &shared.appenders) + { + for e in errs { + (shared.err_handler)(&e) + } + } } fn flush(&self) { From b25c9f0c6f6fee719f4bd12a476b01283adbf046 Mon Sep 17 00:00:00 2001 From: Evan Simmons Date: Tue, 29 Sep 2020 17:00:22 -0700 Subject: [PATCH 25/35] bump ver --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d5fa2240..658d1246 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "log4rs" -version = "1.0.0-alpha-1" +version = "1.0.0-alpha-2" authors = ["Steven Fackler ", "Evan Simmons "] description = "A highly configurable multi-output logging implementation for the `log` facade" license = "MIT/Apache-2.0" From d6ddbb128815a41b44853e5942f03ab7c7c2a495 Mon Sep 17 00:00:00 2001 From: 1c7718e7 Date: Wed, 16 Dec 2020 04:52:32 +0200 Subject: [PATCH 26/35] fix: init_raw_config forcing max_log_level to Info (#200) Co-authored-by: braindead --- src/config/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index d82a6ab5..08cf5f73 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -63,7 +63,7 @@ pub fn init_raw_config(config: RawConfig) -> Result<(), InitError> { .build(config.root())?; let logger = crate::Logger::new(config); - log::set_max_level(log::LevelFilter::Info); + log::set_max_level(logger.max_log_level()); log::set_boxed_logger(Box::new(logger))?; Ok(()) } From 36627ba76a2507496e1e1f4f9784a10db53609f4 Mon Sep 17 00:00:00 2001 From: Julia DeMille <8127111+judemille@users.noreply.github.com> Date: Tue, 15 Dec 2020 21:00:32 -0600 Subject: [PATCH 27/35] pattern encoder: Set trace to default color, reset formatting after (#186) * pattern encoder: Set trace to default color, reset formatting after * Fix formatting of last commit * lol apparently rustfmt doesn't like commas? idk if this will work * Set trace color to cyan --- src/encode/pattern/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index 2a620a5a..5fef409b 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -584,14 +584,16 @@ impl FormattedChunk { } Level::Warn => w.set_style(Style::new().text(Color::Yellow))?, Level::Info => w.set_style(Style::new().text(Color::Green))?, - Level::Trace => w.set_style(Style::new().text(Color::Black).intense(true))?, + Level::Trace => w.set_style(Style::new().text(Color::Cyan))?, _ => {} } for chunk in chunks { chunk.encode(w, record)?; } match record.level() { - Level::Error | Level::Warn | Level::Info => w.set_style(&Style::new())?, + Level::Error | Level::Warn | Level::Info | Level::Trace => { + w.set_style(&Style::new())? + } _ => {} } Ok(()) From 5459ec35ee73cea087bfef67770cd2f66db8982b Mon Sep 17 00:00:00 2001 From: Bradley Noyes Date: Mon, 28 Dec 2020 16:18:55 -0500 Subject: [PATCH 28/35] WIP: Adding ability to configure colors for console Specifically this useful for dark console backgrounds where the default for 'Blue' for log level INFO is difficult to read. Most of the changes are in `src/encode/patter/mod.rs`. I added default parameters for deserializing. I added a function to describe the default pattern string, and defined that at the top of the source file. I added a `color_map` to the Encoder structs which is a HashMap that defines the LogLovel->Color mapping. The color_map is of type `HashMap>`, if the Option is None, then no styling is applied. TODO: Adding a feature means adding documentation, which isn't complete. I can do this and add to the PR, however before i continue i want to make sure this is the direction that the maintiner wants to take. TODO: Other testing may need to be done. I was testing with `cargo test --features file` with a modified config option in the yaml test config. As i am not familiar with this code base there may be other cases that need to be tested. TODO/Future: This patch only allows foreground color to be specified. There are other aspects of styling like bold,italics,background color. Does it make sense to allow for more detailed styling? That involves more work and more work for the maintiner. I think forground color will probably cover most everyone's cases. --- src/config/raw.rs | 3 ++ src/encode/mod.rs | 6 ++- src/encode/pattern/mod.rs | 104 ++++++++++++++++++++++++++++++-------- 3 files changed, 90 insertions(+), 23 deletions(-) diff --git a/src/config/raw.rs b/src/config/raw.rs index a1539849..d4018c19 100644 --- a/src/config/raw.rs +++ b/src/config/raw.rs @@ -476,6 +476,9 @@ appenders: path: /tmp/baz.log encoder: pattern: "%m" + color_map: + INFO: Blue + TRACE: Black root: appenders: diff --git a/src/encode/mod.rs b/src/encode/mod.rs index aa290b3c..25888b0d 100644 --- a/src/encode/mod.rs +++ b/src/encode/mod.rs @@ -13,6 +13,8 @@ use std::collections::BTreeMap; #[cfg(feature = "config_parsing")] use crate::config::Deserializable; +#[cfg(feature = "file")] +use crate::file::Deserializable; #[cfg(feature = "json_encoder")] pub mod json; @@ -77,8 +79,10 @@ impl<'de> de::Deserialize<'de> for EncoderConfig { } /// A text or background color. +#[cfg_attr(feature = "config_parsing", derive(serde::Deserialize))] +//#[cfg(feature = "config_parsing")] +#[derive(Copy, Clone, Debug, Eq, PartialEq,Hash)] #[allow(missing_docs)] -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum Color { Black, Red, diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index 2a620a5a..dfc70c82 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -121,7 +121,9 @@ use chrono::{Local, Utc}; use derivative::Derivative; use log::{Level, Record}; -use std::{default::Default, io, process, thread}; +use std::{collections::HashMap, default::Default, io, process, thread}; +#[cfg(feature = "file")] +use serde_derive::Deserialize; use crate::encode::{ self, @@ -134,12 +136,30 @@ use crate::config::{Deserialize, Deserializers}; mod parser; +const DEFAULT_PATTERN_ENCODER: &str = "{d} {l} {t} - {m}{n}"; + +#[allow(dead_code)] +fn default_pattern() -> Option { + Some(DEFAULT_PATTERN_ENCODER.to_owned()) +} +fn default_color_map() -> HashMap> { + let mut color_map = HashMap::new(); + color_map.insert(Level::Info, Some(Color::Blue)); + color_map.insert(Level::Debug, None); + color_map.insert(Level::Warn, Some(Color::Red)); + color_map.insert(Level::Error, Some(Color::Red)); + color_map +} + /// The pattern encoder's configuration. #[cfg(feature = "config_parsing")] #[serde(deny_unknown_fields)] -#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, serde::Deserialize)] +#[derive(Clone, Eq, PartialEq,/* Hash, */ Debug, Default, serde::Deserialize)] pub struct PatternEncoderConfig { + #[serde(default = "default_pattern")] pattern: Option, + #[serde(default = "default_color_map")] + color_map: HashMap>, } fn is_char_boundary(b: u8) -> bool { @@ -304,20 +324,25 @@ enum Chunk { } impl Chunk { - fn encode(&self, w: &mut dyn encode::Write, record: &Record) -> io::Result<()> { + fn encode( + &self, + w: &mut dyn encode::Write, + record: &Record, + color_map: &HashMap>, + ) -> io::Result<()> { match *self { Chunk::Text(ref s) => w.write_all(s.as_bytes()), Chunk::Formatted { ref chunk, ref params, } => match (params.min_width, params.max_width, params.align) { - (None, None, _) => chunk.encode(w, record), + (None, None, _) => chunk.encode(w, record, color_map), (None, Some(max_width), _) => { let mut w = MaxWidthWriter { remaining: max_width, w, }; - chunk.encode(&mut w, record) + chunk.encode(&mut w, record, color_map) } (Some(min_width), None, Alignment::Left) => { let mut w = LeftAlignWriter { @@ -325,7 +350,7 @@ impl Chunk { fill: params.fill, w, }; - chunk.encode(&mut w, record)?; + chunk.encode(&mut w, record, color_map)?; w.finish() } (Some(min_width), None, Alignment::Right) => { @@ -335,7 +360,7 @@ impl Chunk { w, buf: vec![], }; - chunk.encode(&mut w, record)?; + chunk.encode(&mut w, record, color_map)?; w.finish() } (Some(min_width), Some(max_width), Alignment::Left) => { @@ -347,7 +372,7 @@ impl Chunk { w, }, }; - chunk.encode(&mut w, record)?; + chunk.encode(&mut w, record, color_map)?; w.finish() } (Some(min_width), Some(max_width), Alignment::Right) => { @@ -360,7 +385,7 @@ impl Chunk { }, buf: vec![], }; - chunk.encode(&mut w, record)?; + chunk.encode(&mut w, record, color_map)?; w.finish() } }, @@ -550,7 +575,12 @@ enum FormattedChunk { } impl FormattedChunk { - fn encode(&self, w: &mut dyn encode::Write, record: &Record) -> io::Result<()> { + fn encode( + &self, + w: &mut dyn encode::Write, + record: &Record, + color_map: &HashMap>, + ) -> io::Result<()> { match *self { FormattedChunk::Time(ref fmt, Timezone::Utc) => write!(w, "{}", Utc::now().format(fmt)), FormattedChunk::Time(ref fmt, Timezone::Local) => { @@ -573,22 +603,24 @@ impl FormattedChunk { FormattedChunk::Newline => w.write_all(NEWLINE.as_bytes()), FormattedChunk::Align(ref chunks) => { for chunk in chunks { - chunk.encode(w, record)?; + chunk.encode(w, record, color_map)?; } Ok(()) } FormattedChunk::Highlight(ref chunks) => { - match record.level() { - Level::Error => { - w.set_style(Style::new().text(Color::Red).intense(true))?; + if let Some(Some(color)) = color_map.get(&record.level()) { + match record.level() { + Level::Error => { + w.set_style(Style::new().text(*color).intense(true))?; + } + Level::Warn => w.set_style(Style::new().text(*color))?, + Level::Info => w.set_style(Style::new().text(*color))?, + Level::Debug => w.set_style(Style::new().text(*color))?, + _ => {} } - Level::Warn => w.set_style(Style::new().text(Color::Yellow))?, - Level::Info => w.set_style(Style::new().text(Color::Green))?, - Level::Trace => w.set_style(Style::new().text(Color::Black).intense(true))?, - _ => {} } for chunk in chunks { - chunk.encode(w, record)?; + chunk.encode(w, record, color_map)?; } match record.level() { Level::Error | Level::Warn | Level::Info => w.set_style(&Style::new())?, @@ -606,29 +638,42 @@ impl FormattedChunk { /// An `Encode`r configured via a format string. #[derive(Derivative)] #[derivative(Debug)] -#[derive(Clone, Eq, PartialEq, Hash)] +#[derive(Clone, Eq, PartialEq, /*Hash*/)] pub struct PatternEncoder { #[derivative(Debug = "ignore")] chunks: Vec, pattern: String, + color_map: HashMap>, } /// Returns a `PatternEncoder` using the default pattern of `{d} {l} {t} - {m}{n}`. impl Default for PatternEncoder { fn default() -> PatternEncoder { - PatternEncoder::new("{d} {l} {t} - {m}{n}") + PatternEncoder::new_with_colormap(DEFAULT_PATTERN_ENCODER, default_color_map()) } } impl Encode for PatternEncoder { fn encode(&self, w: &mut dyn encode::Write, record: &Record) -> anyhow::Result<()> { for chunk in &self.chunks { - chunk.encode(w, record)?; + chunk.encode(w, record, &self.color_map)?; } Ok(()) } } +#[cfg(feature = "file")] +impl From for PatternEncoder { + fn from(pattern_config: PatternEncoderConfig) -> Self { + // Merge default color_map with user-configured color_map + let mut color_map = default_color_map(); + for (k, v) in pattern_config.color_map { + color_map.insert(k, v); + } + PatternEncoder::new_with_colormap(&pattern_config.pattern, color_map) + } +} + impl PatternEncoder { /// Creates a `PatternEncoder` from a pattern string. /// @@ -637,6 +682,21 @@ impl PatternEncoder { PatternEncoder { chunks: Parser::new(pattern).map(From::from).collect(), pattern: pattern.to_owned(), + color_map: default_color_map(), + } + } + + /// Creates a `PatternEncoder` from a pattern string and color hashmap. + /// + /// The pattern string syntax is documented in the `pattern` module. + pub fn new_with_colormap( + pattern: &str, + color_map: HashMap>, + ) -> PatternEncoder { + PatternEncoder { + chunks: Parser::new(pattern).map(From::from).collect(), + pattern: pattern.to_owned(), + color_map, } } } From b58d7b08201b780f6fb4f6675a404382af753b2b Mon Sep 17 00:00:00 2001 From: Bradley Noyes Date: Mon, 28 Dec 2020 20:33:16 -0500 Subject: [PATCH 29/35] fixing rustfmt errors --- src/encode/mod.rs | 4 ++-- src/encode/pattern/mod.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/encode/mod.rs b/src/encode/mod.rs index 25888b0d..ede0640c 100644 --- a/src/encode/mod.rs +++ b/src/encode/mod.rs @@ -79,9 +79,9 @@ impl<'de> de::Deserialize<'de> for EncoderConfig { } /// A text or background color. -#[cfg_attr(feature = "config_parsing", derive(serde::Deserialize))] +#[cfg_attr(feature = "config_parsing", derive(serde::Deserialize))] //#[cfg(feature = "config_parsing")] -#[derive(Copy, Clone, Debug, Eq, PartialEq,Hash)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[allow(missing_docs)] pub enum Color { Black, diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index dfc70c82..5479a63b 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -121,9 +121,9 @@ use chrono::{Local, Utc}; use derivative::Derivative; use log::{Level, Record}; -use std::{collections::HashMap, default::Default, io, process, thread}; #[cfg(feature = "file")] use serde_derive::Deserialize; +use std::{collections::HashMap, default::Default, io, process, thread}; use crate::encode::{ self, @@ -154,7 +154,7 @@ fn default_color_map() -> HashMap> { /// The pattern encoder's configuration. #[cfg(feature = "config_parsing")] #[serde(deny_unknown_fields)] -#[derive(Clone, Eq, PartialEq,/* Hash, */ Debug, Default, serde::Deserialize)] +#[derive(Clone, Eq, PartialEq, Debug, Default, serde::Deserialize)] pub struct PatternEncoderConfig { #[serde(default = "default_pattern")] pattern: Option, @@ -638,7 +638,7 @@ impl FormattedChunk { /// An `Encode`r configured via a format string. #[derive(Derivative)] #[derivative(Debug)] -#[derive(Clone, Eq, PartialEq, /*Hash*/)] +#[derive(Clone, Eq, PartialEq)] pub struct PatternEncoder { #[derivative(Debug = "ignore")] chunks: Vec, From 10e9f2e9b5426f9450ace324ff524498d45d5e57 Mon Sep 17 00:00:00 2001 From: Bradley Noyes Date: Thu, 14 Jan 2021 09:14:09 -0500 Subject: [PATCH 30/35] Edited `PatternEncoder::new_with_colormap()` function to merge user's chosen color map with the default. This is for better usage when using rust code for log4rs configurations Example to change only Info level log color: ``` // Change color of Info level log msgs let mut log_color_map = HashMap::new(); log_color_map.insert(log::Level::Info, Some(log4rs::encode::Color::Cyan)); let pattern_encoder = PatternEncoder::new_with_colormap("{d} - {m}{n}", log_color_map); ``` --- src/encode/pattern/mod.rs | 28 ++++++++++++++++++++++------ test/log.yml | 3 +++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index 59af39a7..a2fedff3 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -667,11 +667,6 @@ impl Encode for PatternEncoder { #[cfg(feature = "file")] impl From for PatternEncoder { fn from(pattern_config: PatternEncoderConfig) -> Self { - // Merge default color_map with user-configured color_map - let mut color_map = default_color_map(); - for (k, v) in pattern_config.color_map { - color_map.insert(k, v); - } PatternEncoder::new_with_colormap(&pattern_config.pattern, color_map) } } @@ -691,14 +686,35 @@ impl PatternEncoder { /// Creates a `PatternEncoder` from a pattern string and color hashmap. /// /// The pattern string syntax is documented in the `pattern` module. + /// + /// + ///! ```no_run + ///! # #[cfg(all(feature = "console_appender", + ///! # feature = "file_appender", + ///! # feature = "pattern_encoder"))] + ///! # fn f() { + ///! // Change color of Info level msgs, other msgs will be the default color + ///! let mut log_color_map = HashMap::new(); + ///! log_color_map.insert(log::Level::Info, Some(log4rs::encode::Color::Cyan)); + ///! let pattern_encoder = PatternEncoder::new_with_colormap("{d} - {m}{n}", log_color_map); + ///! + ///! } + ///! # } + ///! # fn main() {} + ///! ``` pub fn new_with_colormap( pattern: &str, color_map: HashMap>, ) -> PatternEncoder { + // Merge default color_map with user-configured color_map + let mut color_map_def = default_color_map(); + for (k, v) in color_map { + color_map_def.insert(k, v); + } PatternEncoder { chunks: Parser::new(pattern).map(From::from).collect(), pattern: pattern.to_owned(), - color_map, + color_map: color_map_def, } } } diff --git a/test/log.yml b/test/log.yml index b8c5aee3..3dee86cc 100644 --- a/test/log.yml +++ b/test/log.yml @@ -5,6 +5,9 @@ appenders: kind: console encoder: pattern: "{d(%+)(local)} [{t}] {h({l})} {M}:{m}{n}" + color_map: + Info: Cyan + Warn: Red filters: - kind: threshold level: error From f28ca6796d9141990ad0c99029631f0757fe1181 Mon Sep 17 00:00:00 2001 From: Bradley Noyes Date: Mon, 17 May 2021 14:52:39 -0400 Subject: [PATCH 31/35] Adding test --- src/encode/mod.rs | 3 +-- src/encode/pattern/mod.rs | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/encode/mod.rs b/src/encode/mod.rs index 66f8c3bd..9c0f9c1e 100644 --- a/src/encode/mod.rs +++ b/src/encode/mod.rs @@ -80,9 +80,8 @@ impl<'de> de::Deserialize<'de> for EncoderConfig { /// A text or background color. #[cfg_attr(feature = "config_parsing", derive(serde::Deserialize))] -//#[cfg(feature = "config_parsing")] #[allow(missing_docs)] -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum Color { Black, Red, diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index 80e4ac8b..e1f826a9 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -152,7 +152,7 @@ fn default_color_map() -> HashMap> { /// The pattern encoder's configuration. #[cfg(feature = "config_parsing")] #[serde(deny_unknown_fields)] -#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, serde::Deserialize)] +#[derive(Clone, Eq, PartialEq, Debug, Default, serde::Deserialize)] pub struct PatternEncoderConfig { #[serde(default = "default_pattern")] pattern: Option, @@ -638,7 +638,7 @@ impl FormattedChunk { /// An `Encode`r configured via a format string. #[derive(Derivative)] #[derivative(Debug)] -#[derive(Clone, Eq, PartialEq, Hash)] +#[derive(Clone, Eq, PartialEq)] pub struct PatternEncoder { #[derivative(Debug = "ignore")] chunks: Vec, @@ -1027,4 +1027,22 @@ mod tests { assert_eq!(buf, b"missing value"); } + #[test] + fn check_color_hash() { + // purpose of this test is to specify a single custom color. + // - test the custom color + // - test that default colors were intact + use std::collections::HashMap; + use crate::encode::Color; + use crate::encode::pattern::default_color_map; + let mut log_color_map = HashMap::new(); + log_color_map.insert(log::Level::Info, Some( Color::Cyan )); + let encoder = Box::new(PatternEncoder::new_with_colormap( + "{d(%Y-%m-%d %H:%M:%S)(local)} {h({l} [{f}:{L} {T} {t}] {m})}{n}", + log_color_map )); + assert_eq!(encoder.color_map.get(&log::Level::Info), Some(&Some(Color::Cyan))); + assert_eq!(encoder.color_map.get(&log::Level::Warn), default_color_map().get(&log::Level::Warn)); + assert_eq!(encoder.color_map.get(&log::Level::Error), default_color_map().get(&log::Level::Error)); + assert_eq!(encoder.color_map.get(&log::Level::Debug), default_color_map().get(&log::Level::Debug)); + } } From 884087dd38b55c342f00d5a018d237003891eab3 Mon Sep 17 00:00:00 2001 From: Bradley Noyes Date: Sat, 30 Apr 2022 10:48:35 -0400 Subject: [PATCH 32/35] Fixing for LINT test w/ --- src/encode/pattern/mod.rs | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index 8603a5eb..0d825d8c 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -1055,17 +1055,30 @@ mod tests { // purpose of this test is to specify a single custom color. // - test the custom color // - test that default colors were intact - use std::collections::HashMap; - use crate::encode::Color; use crate::encode::pattern::default_color_map; + use crate::encode::Color; + use std::collections::HashMap; let mut log_color_map = HashMap::new(); - log_color_map.insert(log::Level::Info, Some( Color::Cyan )); + log_color_map.insert(log::Level::Info, Some(Color::Cyan)); let encoder = Box::new(PatternEncoder::new_with_colormap( - "{d(%Y-%m-%d %H:%M:%S)(local)} {h({l} [{f}:{L} {T} {t}] {m})}{n}", - log_color_map )); - assert_eq!(encoder.color_map.get(&log::Level::Info), Some(&Some(Color::Cyan))); - assert_eq!(encoder.color_map.get(&log::Level::Warn), default_color_map().get(&log::Level::Warn)); - assert_eq!(encoder.color_map.get(&log::Level::Error), default_color_map().get(&log::Level::Error)); - assert_eq!(encoder.color_map.get(&log::Level::Debug), default_color_map().get(&log::Level::Debug)); + "{d(%Y-%m-%d %H:%M:%S)(local)} {h({l} [{f}:{L} {T} {t}] {m})}{n}", + log_color_map, + )); + assert_eq!( + encoder.color_map.get(&log::Level::Info), + Some(&Some(Color::Cyan)) + ); + assert_eq!( + encoder.color_map.get(&log::Level::Warn), + default_color_map().get(&log::Level::Warn) + ); + assert_eq!( + encoder.color_map.get(&log::Level::Error), + default_color_map().get(&log::Level::Error) + ); + assert_eq!( + encoder.color_map.get(&log::Level::Debug), + default_color_map().get(&log::Level::Debug) + ); } } From 225c5eebc70f7f0a1c42a81c6857cc6ce50d1e6d Mon Sep 17 00:00:00 2001 From: Bradley Noyes Date: Wed, 4 May 2022 20:21:12 -0400 Subject: [PATCH 33/35] Removing color_map as HashMap and adding ColorMap Struct. --- src/encode/mod.rs | 2 + src/encode/pattern/mod.rs | 152 ++++++++++++++++++++++++++++++-------- 2 files changed, 124 insertions(+), 30 deletions(-) diff --git a/src/encode/mod.rs b/src/encode/mod.rs index 9c0f9c1e..f291e577 100644 --- a/src/encode/mod.rs +++ b/src/encode/mod.rs @@ -80,6 +80,7 @@ impl<'de> de::Deserialize<'de> for EncoderConfig { /// A text or background color. #[cfg_attr(feature = "config_parsing", derive(serde::Deserialize))] +#[cfg_attr(feature = "config_parsing", derive(serde::Serialize))] #[allow(missing_docs)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum Color { @@ -92,6 +93,7 @@ pub enum Color { Cyan, White, } +impl Default for Color { fn default() -> Self { Color::Black } } /// The style applied to text output. /// diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index 0d825d8c..8f9f5ece 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -122,7 +122,7 @@ use chrono::{Local, Utc}; use derivative::Derivative; use log::{Level, Record}; -use std::{collections::HashMap, default::Default, io, process, thread}; +use std::{default::Default, io, process, thread}; use crate::encode::{ self, @@ -141,15 +141,95 @@ const DEFAULT_PATTERN_ENCODER: &str = "{d} {l} {t} - {m}{n}"; fn default_pattern() -> Option { Some(DEFAULT_PATTERN_ENCODER.to_owned()) } -fn default_color_map() -> HashMap> { - let mut color_map = HashMap::new(); - color_map.insert(Level::Info, Some(Color::Blue)); - color_map.insert(Level::Debug, None); - color_map.insert(Level::Warn, Some(Color::Red)); - color_map.insert(Level::Error, Some(Color::Red)); - color_map +fn default_color_map() -> ColorMap { + ColorMap::default() } + +/// A simple color map struct +/// +/// You can use this struct to define a custom color map. +/// This can be done using a serializer (i.e. config file) or pragmatically. +/// +/// It is part of the encoder +/// +/// ```not_rust +/// pattern: "%m" +/// color_map: +/// INFO: Blue +/// TRACE: Black +/// ``` +/// +/// Or progmatically in conjuction with PatternEncoder +///! ```no_run +///! # #[cfg(all(feature = "console_appender", +///! # feature = "file_appender", +///! # feature = "pattern_encoder"))] +///! # fn f() { +///! let mut log_color_map = ColorMap::default() +///! log_color_map.info_color = Some(log4rs::encode::Color::Cyan); +///! } +///! # } +///! # fn main() {} +/// ``` +#[cfg_attr(feature = "config_parsing", derive(serde::Deserialize))] +#[cfg_attr(feature = "config_parsing", serde(deny_unknown_fields))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ColorMap { + trace: Option, + debug: Option, + info: Option, + warn: Option, + error: Option, +} +impl Default for ColorMap { + fn default() -> Self { + ColorMap { + trace: None, + debug: None, + info: None, + warn: None, + error: None, + } + } +} + +impl ColorMap { + /// Clear color styling for given log sevarity + pub fn unset(&mut self, level: &Level) { + match level { + Level::Trace => { self.trace = None; } + Level::Debug => { self.debug = None; } + Level::Info => { self.info = None; } + Level::Warn => { self.warn = None; } + Level::Error => { self.error = None; } + } + } + + /// Set color styling for given log sevarity + pub fn set(&mut self, level: &Level, color: Color) { + match level { + Level::Trace => { self.trace = Some(color); } + Level::Debug => { self.debug = Some(color); } + Level::Info => { self.info = Some(color); } + Level::Warn => { self.warn = Some(color); } + Level::Error => { self.error = Some(color); } + } + } + + /// Get color styling for given log sevarity + pub fn get(&self, level: &Level) -> Option { + match level { + Level::Trace => { self.trace } + Level::Debug => { self.debug } + Level::Info => { self.info } + Level::Warn => { self.warn } + Level::Error => { self.error } + } + } +} + + thread_local!( /// Thread-locally cached thread ID. static TID: usize = thread_id::get() @@ -163,7 +243,7 @@ pub struct PatternEncoderConfig { #[serde(default = "default_pattern")] pattern: Option, #[serde(default = "default_color_map")] - color_map: HashMap>, + color_map: ColorMap, } fn is_char_boundary(b: u8) -> bool { @@ -332,7 +412,7 @@ impl Chunk { &self, w: &mut dyn encode::Write, record: &Record, - color_map: &HashMap>, + color_map: &ColorMap, ) -> io::Result<()> { match *self { Chunk::Text(ref s) => w.write_all(s.as_bytes()), @@ -585,7 +665,7 @@ impl FormattedChunk { &self, w: &mut dyn encode::Write, record: &Record, - color_map: &HashMap>, + color_map: &ColorMap, ) -> io::Result<()> { match *self { FormattedChunk::Time(ref fmt, Timezone::Utc) => write!(w, "{}", Utc::now().format(fmt)), @@ -617,14 +697,14 @@ impl FormattedChunk { Ok(()) } FormattedChunk::Highlight(ref chunks) => { - if let Some(Some(color)) = color_map.get(&record.level()) { + if let Some(color) = color_map.get(&record.level()) { match record.level() { Level::Error => { - w.set_style(Style::new().text(*color).intense(true))?; + w.set_style(Style::new().text(color).intense(true))?; } - Level::Warn => w.set_style(Style::new().text(*color))?, - Level::Info => w.set_style(Style::new().text(*color))?, - Level::Debug => w.set_style(Style::new().text(*color))?, + Level::Warn => w.set_style(Style::new().text(color))?, + Level::Info => w.set_style(Style::new().text(color))?, + Level::Debug => w.set_style(Style::new().text(color))?, _ => {} } } @@ -654,13 +734,13 @@ pub struct PatternEncoder { #[derivative(Debug = "ignore")] chunks: Vec, pattern: String, - color_map: HashMap>, + color_map: ColorMap, } /// Returns a `PatternEncoder` using the default pattern of `{d} {l} {t} - {m}{n}`. impl Default for PatternEncoder { fn default() -> PatternEncoder { - PatternEncoder::new_with_colormap(DEFAULT_PATTERN_ENCODER, default_color_map()) + PatternEncoder::new(DEFAULT_PATTERN_ENCODER) } } @@ -703,8 +783,8 @@ impl PatternEncoder { ///! # feature = "pattern_encoder"))] ///! # fn f() { ///! // Change color of Info level msgs, other msgs will be the default color - ///! let mut log_color_map = HashMap::new(); - ///! log_color_map.insert(log::Level::Info, Some(log4rs::encode::Color::Cyan)); + ///! let mut log_color_map = ColorMap::default() + ///! log_color_map.info_color = Some(log4rs::encode::Color::Cyan); ///! let pattern_encoder = PatternEncoder::new_with_colormap("{d} - {m}{n}", log_color_map); ///! ///! } @@ -713,17 +793,17 @@ impl PatternEncoder { ///! ``` pub fn new_with_colormap( pattern: &str, - color_map: HashMap>, + color_map: ColorMap, ) -> PatternEncoder { // Merge default color_map with user-configured color_map - let mut color_map_def = default_color_map(); - for (k, v) in color_map { - color_map_def.insert(k, v); - } + //let mut color_map_def = default_color_map(); + //for (k, v) in color_map { + // color_map_def.insert(k, v); + // } PatternEncoder { chunks: Parser::new(pattern).map(From::from).collect(), pattern: pattern.to_owned(), - color_map: color_map_def, + color_map, } } } @@ -1050,6 +1130,17 @@ mod tests { assert_eq!(buf, b"missing value"); } + #[cfg(feature = "config_parsing")] + #[test] + fn check_deserialize_color_hash() { + + use crate::encode::pattern::ColorMap; + let serialized = r" + info: Blue + trace: Black + "; + let _deserialized: ColorMap = serde_yaml::from_str(&serialized).unwrap(); + } #[test] fn check_color_hash() { // purpose of this test is to specify a single custom color. @@ -1057,16 +1148,17 @@ mod tests { // - test that default colors were intact use crate::encode::pattern::default_color_map; use crate::encode::Color; - use std::collections::HashMap; - let mut log_color_map = HashMap::new(); - log_color_map.insert(log::Level::Info, Some(Color::Cyan)); + use crate::encode::pattern::ColorMap; + let mut log_color_map = ColorMap::default(); + + log_color_map.info = Some(Color::Cyan); let encoder = Box::new(PatternEncoder::new_with_colormap( "{d(%Y-%m-%d %H:%M:%S)(local)} {h({l} [{f}:{L} {T} {t}] {m})}{n}", log_color_map, )); assert_eq!( encoder.color_map.get(&log::Level::Info), - Some(&Some(Color::Cyan)) + Some(Color::Cyan) ); assert_eq!( encoder.color_map.get(&log::Level::Warn), From 82bb5e5cb656a50be32807b2afe08b020c5847dc Mon Sep 17 00:00:00 2001 From: Bradley Noyes Date: Wed, 4 May 2022 20:21:12 -0400 Subject: [PATCH 34/35] Removing color_map as HashMap and adding ColorMap Struct. --- src/encode/mod.rs | 2 + src/encode/pattern/mod.rs | 153 ++++++++++++++++++++++++++++++-------- 2 files changed, 125 insertions(+), 30 deletions(-) diff --git a/src/encode/mod.rs b/src/encode/mod.rs index 9c0f9c1e..f291e577 100644 --- a/src/encode/mod.rs +++ b/src/encode/mod.rs @@ -80,6 +80,7 @@ impl<'de> de::Deserialize<'de> for EncoderConfig { /// A text or background color. #[cfg_attr(feature = "config_parsing", derive(serde::Deserialize))] +#[cfg_attr(feature = "config_parsing", derive(serde::Serialize))] #[allow(missing_docs)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum Color { @@ -92,6 +93,7 @@ pub enum Color { Cyan, White, } +impl Default for Color { fn default() -> Self { Color::Black } } /// The style applied to text output. /// diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index 0d825d8c..05f464df 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -122,7 +122,7 @@ use chrono::{Local, Utc}; use derivative::Derivative; use log::{Level, Record}; -use std::{collections::HashMap, default::Default, io, process, thread}; +use std::{default::Default, io, process, thread}; use crate::encode::{ self, @@ -141,15 +141,96 @@ const DEFAULT_PATTERN_ENCODER: &str = "{d} {l} {t} - {m}{n}"; fn default_pattern() -> Option { Some(DEFAULT_PATTERN_ENCODER.to_owned()) } -fn default_color_map() -> HashMap> { - let mut color_map = HashMap::new(); - color_map.insert(Level::Info, Some(Color::Blue)); - color_map.insert(Level::Debug, None); - color_map.insert(Level::Warn, Some(Color::Red)); - color_map.insert(Level::Error, Some(Color::Red)); - color_map +fn default_color_map() -> ColorMap { + ColorMap::default() } + +/// A simple color map struct +/// +/// You can use this struct to define a custom color map. +/// This can be done using a serializer (i.e. config file) or pragmatically. +/// +/// It is part of the encoder +/// +/// ```not_rust +/// pattern: "%m" +/// color_map: +/// INFO: Blue +/// TRACE: Black +/// ``` +/// +/// Or progmatically in conjuction with PatternEncoder +///! ```no_run +///! # #[cfg(all(feature = "console_appender", +///! # feature = "file_appender", +///! # feature = "pattern_encoder"))] +///! # fn f() { +///! let mut log_color_map = ColorMap::default() +///! log_color_map.info_color = Some(log4rs::encode::Color::Cyan); +///! } +///! # } +///! # fn main() {} +/// ``` +#[cfg_attr(feature = "config_parsing", derive(serde::Deserialize))] +#[cfg_attr(feature = "config_parsing", serde(deny_unknown_fields))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ColorMap { + // in future this represent other aspects of style, like bold/italics, etc. + trace: Option, + debug: Option, + info: Option, + warn: Option, + error: Option, +} +impl Default for ColorMap { + fn default() -> Self { + ColorMap { + trace: None, + debug: None, + info: Color::Blue, + warn: Color::Red, + error: Color::Red, + } + } +} + +impl ColorMap { + /// Clear color styling for given log sevarity + pub fn unset(&mut self, level: &Level) { + match level { + Level::Trace => { self.trace = None; } + Level::Debug => { self.debug = None; } + Level::Info => { self.info = None; } + Level::Warn => { self.warn = None; } + Level::Error => { self.error = None; } + } + } + + /// Set color styling for given log sevarity + pub fn set(&mut self, level: &Level, color: Color) { + match level { + Level::Trace => { self.trace = Some(color); } + Level::Debug => { self.debug = Some(color); } + Level::Info => { self.info = Some(color); } + Level::Warn => { self.warn = Some(color); } + Level::Error => { self.error = Some(color); } + } + } + + /// Get color styling for given log sevarity + pub fn get(&self, level: &Level) -> Option { + match level { + Level::Trace => { self.trace } + Level::Debug => { self.debug } + Level::Info => { self.info } + Level::Warn => { self.warn } + Level::Error => { self.error } + } + } +} + + thread_local!( /// Thread-locally cached thread ID. static TID: usize = thread_id::get() @@ -163,7 +244,7 @@ pub struct PatternEncoderConfig { #[serde(default = "default_pattern")] pattern: Option, #[serde(default = "default_color_map")] - color_map: HashMap>, + color_map: ColorMap, } fn is_char_boundary(b: u8) -> bool { @@ -332,7 +413,7 @@ impl Chunk { &self, w: &mut dyn encode::Write, record: &Record, - color_map: &HashMap>, + color_map: &ColorMap, ) -> io::Result<()> { match *self { Chunk::Text(ref s) => w.write_all(s.as_bytes()), @@ -585,7 +666,7 @@ impl FormattedChunk { &self, w: &mut dyn encode::Write, record: &Record, - color_map: &HashMap>, + color_map: &ColorMap, ) -> io::Result<()> { match *self { FormattedChunk::Time(ref fmt, Timezone::Utc) => write!(w, "{}", Utc::now().format(fmt)), @@ -617,14 +698,14 @@ impl FormattedChunk { Ok(()) } FormattedChunk::Highlight(ref chunks) => { - if let Some(Some(color)) = color_map.get(&record.level()) { + if let Some(color) = color_map.get(&record.level()) { match record.level() { Level::Error => { - w.set_style(Style::new().text(*color).intense(true))?; + w.set_style(Style::new().text(color).intense(true))?; } - Level::Warn => w.set_style(Style::new().text(*color))?, - Level::Info => w.set_style(Style::new().text(*color))?, - Level::Debug => w.set_style(Style::new().text(*color))?, + Level::Warn => w.set_style(Style::new().text(color))?, + Level::Info => w.set_style(Style::new().text(color))?, + Level::Debug => w.set_style(Style::new().text(color))?, _ => {} } } @@ -654,13 +735,13 @@ pub struct PatternEncoder { #[derivative(Debug = "ignore")] chunks: Vec, pattern: String, - color_map: HashMap>, + color_map: ColorMap, } /// Returns a `PatternEncoder` using the default pattern of `{d} {l} {t} - {m}{n}`. impl Default for PatternEncoder { fn default() -> PatternEncoder { - PatternEncoder::new_with_colormap(DEFAULT_PATTERN_ENCODER, default_color_map()) + PatternEncoder::new(DEFAULT_PATTERN_ENCODER) } } @@ -703,8 +784,8 @@ impl PatternEncoder { ///! # feature = "pattern_encoder"))] ///! # fn f() { ///! // Change color of Info level msgs, other msgs will be the default color - ///! let mut log_color_map = HashMap::new(); - ///! log_color_map.insert(log::Level::Info, Some(log4rs::encode::Color::Cyan)); + ///! let mut log_color_map = ColorMap::default() + ///! log_color_map.info_color = Some(log4rs::encode::Color::Cyan); ///! let pattern_encoder = PatternEncoder::new_with_colormap("{d} - {m}{n}", log_color_map); ///! ///! } @@ -713,17 +794,17 @@ impl PatternEncoder { ///! ``` pub fn new_with_colormap( pattern: &str, - color_map: HashMap>, + color_map: ColorMap, ) -> PatternEncoder { // Merge default color_map with user-configured color_map - let mut color_map_def = default_color_map(); - for (k, v) in color_map { - color_map_def.insert(k, v); - } + //let mut color_map_def = default_color_map(); + //for (k, v) in color_map { + // color_map_def.insert(k, v); + // } PatternEncoder { chunks: Parser::new(pattern).map(From::from).collect(), pattern: pattern.to_owned(), - color_map: color_map_def, + color_map, } } } @@ -1050,6 +1131,17 @@ mod tests { assert_eq!(buf, b"missing value"); } + #[cfg(feature = "config_parsing")] + #[test] + fn check_deserialize_color_hash() { + + use crate::encode::pattern::ColorMap; + let serialized = r" + info: Blue + trace: Black + "; + let _deserialized: ColorMap = serde_yaml::from_str(&serialized).unwrap(); + } #[test] fn check_color_hash() { // purpose of this test is to specify a single custom color. @@ -1057,16 +1149,17 @@ mod tests { // - test that default colors were intact use crate::encode::pattern::default_color_map; use crate::encode::Color; - use std::collections::HashMap; - let mut log_color_map = HashMap::new(); - log_color_map.insert(log::Level::Info, Some(Color::Cyan)); + use crate::encode::pattern::ColorMap; + let mut log_color_map = ColorMap::default(); + + log_color_map.info = Some(Color::Cyan); let encoder = Box::new(PatternEncoder::new_with_colormap( "{d(%Y-%m-%d %H:%M:%S)(local)} {h({l} [{f}:{L} {T} {t}] {m})}{n}", log_color_map, )); assert_eq!( encoder.color_map.get(&log::Level::Info), - Some(&Some(Color::Cyan)) + Some(Color::Cyan) ); assert_eq!( encoder.color_map.get(&log::Level::Warn), From 5bd8586a1553fea6b2d493ed6d223caee57ef146 Mon Sep 17 00:00:00 2001 From: Bradley Noyes Date: Thu, 5 May 2022 16:59:24 -0400 Subject: [PATCH 35/35] Removing default_color_map in favor of Default trait. --- src/encode/pattern/mod.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index 5f6dfd37..78903c3a 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -141,9 +141,6 @@ const DEFAULT_PATTERN_ENCODER: &str = "{d} {l} {t} - {m}{n}"; fn default_pattern() -> Option { Some(DEFAULT_PATTERN_ENCODER.to_owned()) } -fn default_color_map() -> ColorMap { - ColorMap::default() -} /// A simple color map struct /// @@ -261,7 +258,7 @@ thread_local!( pub struct PatternEncoderConfig { #[serde(default = "default_pattern")] pattern: Option, - #[serde(default = "default_color_map")] + #[serde(default = "ColorMap::default")] color_map: ColorMap, } @@ -787,7 +784,7 @@ impl PatternEncoder { PatternEncoder { chunks: Parser::new(pattern).map(From::from).collect(), pattern: pattern.to_owned(), - color_map: default_color_map(), + color_map: ColorMap::default(), } } @@ -811,11 +808,6 @@ impl PatternEncoder { ///! # fn main() {} ///! ``` pub fn new_with_colormap(pattern: &str, color_map: ColorMap) -> PatternEncoder { - // Merge default color_map with user-configured color_map - //let mut color_map_def = default_color_map(); - //for (k, v) in color_map { - // color_map_def.insert(k, v); - // } PatternEncoder { chunks: Parser::new(pattern).map(From::from).collect(), pattern: pattern.to_owned(), @@ -1161,7 +1153,6 @@ mod tests { // purpose of this test is to specify a single custom color. // - test the custom color // - test that default colors were intact - use crate::encode::pattern::default_color_map; use crate::encode::pattern::ColorMap; use crate::encode::Color; let mut log_color_map = ColorMap::default(); @@ -1174,15 +1165,15 @@ mod tests { assert_eq!(encoder.color_map.get(&log::Level::Info), Some(Color::Cyan)); assert_eq!( encoder.color_map.get(&log::Level::Warn), - default_color_map().get(&log::Level::Warn) + ColorMap::default().get(&log::Level::Warn) ); assert_eq!( encoder.color_map.get(&log::Level::Error), - default_color_map().get(&log::Level::Error) + ColorMap::default().get(&log::Level::Error) ); assert_eq!( encoder.color_map.get(&log::Level::Debug), - default_color_map().get(&log::Level::Debug) + ColorMap::default().get(&log::Level::Debug) ); } }