From 48ac259a326f25a15d6e230181da1313a759c5ad Mon Sep 17 00:00:00 2001 From: BlackDex Date: Mon, 21 Oct 2024 22:01:46 +0200 Subject: [PATCH] Add syslog v7 support A while back the syslog crate was updated to v7. This PR adds compatibility with this new version in fern. Also added `.vscode` to the `.gitignore` and fixed a comment where it mentioned `syslog-4` where it should have been `syslog-6` Signed-off-by: BlackDex --- .github/workflows/rust.yml | 21 +++++++++ .gitignore | 1 + Cargo.toml | 8 ++++ examples/syslog7.rs | 54 +++++++++++++++++++++++ src/builders.rs | 88 +++++++++++++++++++++++++++++++++++++- src/lib.rs | 13 ++++++ src/log_impl.rs | 83 ++++++++++++++++++++++++++++++++++- 7 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 examples/syslog7.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 155341a..e9761a2 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -59,6 +59,10 @@ jobs: with: command: test args: --features=syslog-6 + - uses: actions-rs/cargo@v1 + with: + command: test + args: --features=syslog-7 - uses: actions-rs/cargo@v1 with: command: test @@ -103,6 +107,7 @@ jobs: - run: cargo run --example syslog3 --features syslog-3 - run: cargo run --example syslog4 --features syslog-4 - run: cargo run --example syslog --features syslog-6 + - run: cargo run --example syslog7 --features syslog-7 msrv: name: MSRV Compatability - fern runs-on: ${{ matrix.os }} @@ -169,6 +174,22 @@ jobs: toolchain: 1.67.0 override: true - run: cargo build --features syslog-6 + msrv_syslog_7: + name: MSRV Compatability - fern/syslog-7 + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + - windows-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.67.0 + override: true + - run: cargo build --features syslog-7 fmt_and_clippy: name: Optional Lints runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 6ac72f8..88d708a 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ nb-configuration.xml *.iws *.sublime-project *.sublime-workspace +.vscode diff --git a/Cargo.toml b/Cargo.toml index 9437e7f..cb11107 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ chrono = { version = "0.4", default-features = false, features = ["std", "clock" syslog3 = { version = "3", package = "syslog", optional = true } syslog4 = { version = "4", package = "syslog", optional = true } syslog6 = { version = "6", package = "syslog", optional = true } +syslog7 = { version = "7", package = "syslog", optional = true } reopen1 = { version = "~1", package = "reopen", features = ["signals"], optional = true } reopen03 = { version = "^0.3", package = "reopen", optional = true } libc = { version = "0.2.58", optional = true } @@ -35,11 +36,14 @@ libc = { version = "0.2.58", optional = true } syslog-3 = ["syslog3"] syslog-4 = ["syslog4"] syslog-6 = ["syslog6"] +syslog-7 = ["syslog7"] reopen-03 = ["reopen03", "libc"] reopen-1 = ["reopen1", "libc"] meta-logging-in-format = [] date-based = ["chrono"] +# default = ["syslog-7"] + [dev-dependencies] tempfile = "3" clap = "2.22" @@ -60,6 +64,10 @@ required-features = ["colored"] name = "pretty-colored" required-features = ["colored"] +[[example]] +name = "syslog7" +required-features = ["syslog-7"] + [[example]] name = "syslog" required-features = ["syslog-6"] diff --git a/examples/syslog7.rs b/examples/syslog7.rs new file mode 100644 index 0000000..71d38cf --- /dev/null +++ b/examples/syslog7.rs @@ -0,0 +1,54 @@ +#[cfg(not(windows))] +// This is necessary because `fern` depends on both version 3, 4 and 6 +use syslog7 as syslog; + +#[cfg(not(windows))] +use log::{debug, info, warn}; + +#[cfg(not(windows))] +fn setup_logging() -> Result<(), Box> { + let syslog_fmt = syslog::Formatter3164 { + facility: syslog::Facility::LOG_USER, + hostname: None, + process: "fern-syslog-example".into(), + pid: 0, + }; + fern::Dispatch::new() + // by default only accept warning messages so as not to spam + .level(log::LevelFilter::Warn) + // but accept Info if we explicitly mention it + .level_for("explicit-syslog", log::LevelFilter::Info) + .chain(syslog::unix(syslog_fmt)?) + .apply()?; + + Ok(()) +} + +#[cfg(not(windows))] +fn main() { + setup_logging().expect("failed to initialize logging."); + + // None of this will be shown in the syslog: + for i in 0..5 { + info!("executing section: {}", i); + + debug!("section {} 1/4 complete.", i); + + debug!("section {} 1/2 complete.", i); + + debug!("section {} 3/4 complete.", i); + + info!("section {} completed!", i); + } + + // these two *will* show. + + info!(target: "explicit-syslog", "hello to the syslog! this is rust."); + + warn!("AHHH something's on fire."); +} + +#[cfg(windows)] +fn main() { + panic!("this example does not work on Windows."); +} diff --git a/src/builders.rs b/src/builders.rs index 55bb8d4..fd7a075 100644 --- a/src/builders.rs +++ b/src/builders.rs @@ -11,6 +11,9 @@ use std::path::{Path, PathBuf}; #[cfg(all(not(windows), any(feature = "syslog-4", feature = "syslog-6")))] use std::collections::HashMap; +#[cfg(all(not(windows), feature = "syslog-7"))] +use std::collections::BTreeMap; + use log::Log; use crate::{log_impl, Filter, FormatCallback, Formatter}; @@ -24,6 +27,9 @@ use crate::{Syslog4Rfc3164Logger, Syslog4Rfc5424Logger, Syslog4TransformFn}; #[cfg(all(not(windows), feature = "syslog-6"))] use crate::{Syslog6Rfc3164Logger, Syslog6Rfc5424Logger, Syslog6TransformFn}; +#[cfg(all(not(windows), feature = "syslog-7"))] +use crate::{Syslog7Rfc3164Logger, Syslog7Rfc5424Logger, Syslog7TransformFn}; + /// The base dispatch logger. /// /// This allows for formatting log records, limiting what records can be passed @@ -504,6 +510,21 @@ impl Dispatch { transform, })) } + #[cfg(all(not(windows), feature = "syslog-7"))] + OutputInner::Syslog7Rfc3164(logger) => { + max_child_level = log::LevelFilter::Trace; + Some(log_impl::Output::Syslog7Rfc3164(log_impl::Syslog7Rfc3164 { + inner: Mutex::new(logger), + })) + } + #[cfg(all(not(windows), feature = "syslog-7"))] + OutputInner::Syslog7Rfc5424 { logger, transform } => { + max_child_level = log::LevelFilter::Trace; + Some(log_impl::Output::Syslog7Rfc5424(log_impl::Syslog7Rfc5424 { + inner: Mutex::new(logger), + transform, + })) + } OutputInner::Panic => { max_child_level = log::LevelFilter::Trace; Some(log_impl::Output::Panic(log_impl::Panic)) @@ -706,6 +727,14 @@ enum OutputInner { logger: Syslog6Rfc5424Logger, transform: Box, }, + #[cfg(all(not(windows), feature = "syslog-7"))] + Syslog7Rfc3164(Syslog7Rfc3164Logger), + /// Sends all messages through the transform then passes to the syslog. + #[cfg(all(not(windows), feature = "syslog-7"))] + Syslog7Rfc5424 { + logger: Syslog7Rfc5424Logger, + transform: Box, + }, /// Panics with messages text for all messages. Panic, /// File logger with custom date and timestamp suffix in file name. @@ -939,12 +968,31 @@ impl From for Output { /// This is for RFC 3164 loggers. To use an RFC 5424 logger, use the /// [`Output::syslog_5424`] helper method. /// - /// This requires the `"syslog-4"` feature. + /// This requires the `"syslog-6"` feature. fn from(log: Syslog6Rfc3164Logger) -> Self { Output(OutputInner::Syslog6Rfc3164(log)) } } +#[cfg(all(not(windows), feature = "syslog-7"))] +impl From for Output { + /// Creates an output logger which writes all messages to the given syslog. + /// + /// Log levels are translated trace => debug, debug => debug, info => + /// informational, warn => warning, and error => error. + /// + /// Note that due to , + /// logging to this backend requires one allocation per log call. + /// + /// This is for RFC 3164 loggers. To use an RFC 5424 logger, use the + /// [`Output::syslog_5424`] helper method. + /// + /// This requires the `"syslog-7"` feature. + fn from(log: Syslog7Rfc3164Logger) -> Self { + Output(OutputInner::Syslog7Rfc3164(log)) + } +} + impl From for Output { /// Creates an output logger which will panic with message text for all /// messages. @@ -1249,6 +1297,34 @@ impl Output { }) } + /// Returns a logger which logs into an RFC5424 syslog (using syslog version 6) + /// + /// This method takes an additional transform method to turn the log data + /// into RFC5424 data. + /// + /// I've honestly got no clue what the expected keys and values are for + /// this kind of logging, so I'm just going to link [the rfc] instead. + /// + /// If you're an expert on syslog logging and would like to contribute + /// an example to put here, it would be gladly accepted! + /// + /// This requires the `"syslog-7"` feature. + /// + /// [the rfc]: https://tools.ietf.org/html/rfc5424 + #[cfg(all(not(windows), feature = "syslog-7"))] + pub fn syslog7_5424(logger: Syslog7Rfc5424Logger, transform: F) -> Self + where + F: Fn(&log::Record) -> (u32, BTreeMap>, String) + + Sync + + Send + + 'static, + { + Output(OutputInner::Syslog7Rfc5424 { + logger, + transform: Box::new(transform), + }) + } + /// Returns a logger which simply calls the given function with each /// message. /// @@ -1407,6 +1483,16 @@ impl fmt::Debug for OutputInner { .debug_tuple("Output::Syslog6Rfc5424") .field(&"") .finish(), + #[cfg(all(not(windows), feature = "syslog-7"))] + OutputInner::Syslog7Rfc3164 { .. } => f + .debug_tuple("Output::Syslog7Rfc3164") + .field(&"") + .finish(), + #[cfg(all(not(windows), feature = "syslog-7"))] + OutputInner::Syslog7Rfc5424 { .. } => f + .debug_tuple("Output::Syslog7Rfc5424") + .field(&"") + .finish(), OutputInner::Dispatch(ref dispatch) => { f.debug_tuple("Output::Dispatch").field(dispatch).finish() } diff --git a/src/lib.rs b/src/lib.rs index 2e888f2..ee7f293 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -238,6 +238,9 @@ use std::{ #[cfg(all(not(windows), any(feature = "syslog-4", feature = "syslog-6")))] use std::collections::HashMap; +#[cfg(all(not(windows), feature = "syslog-7"))] +use std::collections::BTreeMap; + pub use crate::{ builders::{Dispatch, Output, Panic}, errors::InitError, @@ -291,6 +294,12 @@ type Syslog6Rfc3164Logger = syslog6::Logger; +#[cfg(all(not(windows), feature = "syslog-7"))] +type Syslog7Rfc3164Logger = syslog7::Logger; + +#[cfg(all(not(windows), feature = "syslog-7"))] +type Syslog7Rfc5424Logger = syslog7::Logger; + #[cfg(all(not(windows), feature = "syslog-4"))] type Syslog4TransformFn = dyn Fn(&log::Record) -> (i32, HashMap>, String) + Send + Sync; @@ -299,6 +308,10 @@ type Syslog4TransformFn = type Syslog6TransformFn = dyn Fn(&log::Record) -> (u32, HashMap>, String) + Send + Sync; +#[cfg(all(not(windows), feature = "syslog-7"))] +type Syslog7TransformFn = + dyn Fn(&log::Record) -> (u32, BTreeMap>, String) + Send + Sync; + /// Convenience method for opening a log file with common options. /// /// Equivalent to: diff --git a/src/log_impl.rs b/src/log_impl.rs index 65ac274..9b0d843 100644 --- a/src/log_impl.rs +++ b/src/log_impl.rs @@ -21,6 +21,8 @@ use crate::{Filter, Formatter}; use crate::{Syslog4Rfc3164Logger, Syslog4Rfc5424Logger, Syslog4TransformFn}; #[cfg(all(not(windows), feature = "syslog-6"))] use crate::{Syslog6Rfc3164Logger, Syslog6Rfc5424Logger, Syslog6TransformFn}; +#[cfg(all(not(windows), feature = "syslog-7"))] +use crate::{Syslog7Rfc3164Logger, Syslog7Rfc5424Logger, Syslog7TransformFn}; pub enum LevelConfiguration { JustDefault, @@ -72,6 +74,10 @@ pub enum Output { Syslog6Rfc3164(Syslog6Rfc3164), #[cfg(all(not(windows), feature = "syslog-6"))] Syslog6Rfc5424(Syslog6Rfc5424), + #[cfg(all(not(windows), feature = "syslog-7"))] + Syslog7Rfc3164(Syslog7Rfc3164), + #[cfg(all(not(windows), feature = "syslog-7"))] + Syslog7Rfc5424(Syslog7Rfc5424), Dispatch(Dispatch), SharedDispatch(Arc), OtherBoxed(Box), @@ -150,6 +156,17 @@ pub struct Syslog6Rfc5424 { pub transform: Box, } +#[cfg(all(not(windows), feature = "syslog-7"))] +pub struct Syslog7Rfc3164 { + pub inner: Mutex, +} + +#[cfg(all(not(windows), feature = "syslog-7"))] +pub struct Syslog7Rfc5424 { + pub inner: Mutex, + pub transform: Box, +} + pub struct Panic; pub struct Null; @@ -329,6 +346,10 @@ impl Log for Output { Output::Syslog6Rfc3164(ref s) => s.enabled(metadata), #[cfg(all(not(windows), feature = "syslog-6"))] Output::Syslog6Rfc5424(ref s) => s.enabled(metadata), + #[cfg(all(not(windows), feature = "syslog-7"))] + Output::Syslog7Rfc3164(ref s) => s.enabled(metadata), + #[cfg(all(not(windows), feature = "syslog-7"))] + Output::Syslog7Rfc5424(ref s) => s.enabled(metadata), Output::Panic(ref s) => s.enabled(metadata), Output::Writer(ref s) => s.enabled(metadata), #[cfg(feature = "date-based")] @@ -360,6 +381,10 @@ impl Log for Output { Output::Syslog6Rfc3164(ref s) => s.log(record), #[cfg(all(not(windows), feature = "syslog-6"))] Output::Syslog6Rfc5424(ref s) => s.log(record), + #[cfg(all(not(windows), feature = "syslog-7"))] + Output::Syslog7Rfc3164(ref s) => s.log(record), + #[cfg(all(not(windows), feature = "syslog-7"))] + Output::Syslog7Rfc5424(ref s) => s.log(record), Output::Panic(ref s) => s.log(record), Output::Writer(ref s) => s.log(record), #[cfg(feature = "date-based")] @@ -391,6 +416,10 @@ impl Log for Output { Output::Syslog6Rfc3164(ref s) => s.flush(), #[cfg(all(not(windows), feature = "syslog-6"))] Output::Syslog6Rfc5424(ref s) => s.flush(), + #[cfg(all(not(windows), feature = "syslog-7"))] + Output::Syslog7Rfc3164(ref s) => s.flush(), + #[cfg(all(not(windows), feature = "syslog-7"))] + Output::Syslog7Rfc5424(ref s) => s.flush(), Output::Panic(ref s) => s.flush(), Output::Writer(ref s) => s.flush(), #[cfg(feature = "date-based")] @@ -630,7 +659,12 @@ impl Log for Sender { #[cfg(all( not(windows), - any(feature = "syslog-3", feature = "syslog-4", feature = "syslog-6") + any( + feature = "syslog-3", + feature = "syslog-4", + feature = "syslog-6", + feature = "syslog-7" + ) ))] macro_rules! send_syslog { ($logger:expr, $level:expr, $message:expr) => { @@ -733,6 +767,42 @@ impl Log for Syslog6Rfc5424 { fn flush(&self) {} } +#[cfg(all(not(windows), feature = "syslog-7"))] +impl Log for Syslog7Rfc3164 { + fn enabled(&self, _: &log::Metadata) -> bool { + true + } + + fn log(&self, record: &log::Record) { + fallback_on_error(record, |record| { + let message = record.args().to_string(); + let mut log = self.inner.lock().unwrap_or_else(|e| e.into_inner()); + send_syslog!(log, record.level(), message); + + Ok(()) + }); + } + fn flush(&self) {} +} + +#[cfg(all(not(windows), feature = "syslog-7"))] +impl Log for Syslog7Rfc5424 { + fn enabled(&self, _: &log::Metadata) -> bool { + true + } + + fn log(&self, record: &log::Record) { + fallback_on_error(record, |record| { + let transformed = (self.transform)(record); + let mut log = self.inner.lock().unwrap_or_else(|e| e.into_inner()); + send_syslog!(log, record.level(), transformed); + + Ok(()) + }); + } + fn flush(&self) {} +} + impl Log for Panic { fn enabled(&self, _: &log::Metadata) -> bool { true @@ -845,6 +915,8 @@ enum LogError { Syslog4(syslog4::Error), #[cfg(all(not(windows), feature = "syslog-6"))] Syslog6(syslog6::Error), + #[cfg(all(not(windows), feature = "syslog-7"))] + Syslog7(syslog7::Error), } impl fmt::Display for LogError { @@ -856,6 +928,8 @@ impl fmt::Display for LogError { LogError::Syslog4(ref e) => write!(f, "{}", e), #[cfg(all(not(windows), feature = "syslog-6"))] LogError::Syslog6(ref e) => write!(f, "{}", e), + #[cfg(all(not(windows), feature = "syslog-7"))] + LogError::Syslog7(ref e) => write!(f, "{}", e), } } } @@ -886,6 +960,13 @@ impl From for LogError { } } +#[cfg(all(not(windows), feature = "syslog-7"))] +impl From for LogError { + fn from(error: syslog7::Error) -> Self { + LogError::Syslog7(error) + } +} + #[cfg(test)] mod test { use super::LevelConfiguration;