From c3e521e4ac4725daff075653c5c9163bfcb15526 Mon Sep 17 00:00:00 2001 From: Martin Samuelsson Date: Fri, 24 Feb 2023 14:31:43 +0100 Subject: Add actual implementation --- README.md | 7 ++- libsyslog-sys/Cargo.toml | 7 ++- libsyslog-sys/README.md | 23 ++++++--- libsyslog-sys/build.rs | 29 +++++++++++ libsyslog-sys/src/lib.rs | 46 +++++++++++++++++ libsyslog-sys/wrapper.h | 1 + libsyslog/Cargo.toml | 13 +++-- libsyslog/README.md | 39 +++++++++----- libsyslog/src/builder.rs | 127 ++++++++++++++++++++++++++++++++++++++++++++++ libsyslog/src/facility.rs | 107 ++++++++++++++++++++++++++++++++++++++ libsyslog/src/lib.rs | 67 ++++++++++++++++++++++++ libsyslog/src/logopt.rs | 29 +++++++++++ libsyslog/src/syslog.rs | 92 +++++++++++++++++++++++++++++++++ 13 files changed, 557 insertions(+), 30 deletions(-) create mode 100644 libsyslog-sys/build.rs create mode 100644 libsyslog-sys/wrapper.h create mode 100644 libsyslog/src/builder.rs create mode 100644 libsyslog/src/facility.rs create mode 100644 libsyslog/src/logopt.rs create mode 100644 libsyslog/src/syslog.rs diff --git a/README.md b/README.md index 798beaf..c186677 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ -This repository c̶o̶n̶t̶a̶i̶n̶s̶ will in a few days, once initial code review has -passed, contain libsyslog-sys and libsyslog, with the first being the low level -bindings to [syslog][wikipedia] and the second being the safe Rust bindings -likely of higher interest for most users. +This repository contain libsyslog-sys and libsyslog, with the first being the +low level bindings to [syslog][wikipedia] and the second being the safe Rust +bindings likely of higher interest for most users. Please see the README.md files in the corresponding subdirectories for more information. diff --git a/libsyslog-sys/Cargo.toml b/libsyslog-sys/Cargo.toml index ff5838d..160e575 100644 --- a/libsyslog-sys/Cargo.toml +++ b/libsyslog-sys/Cargo.toml @@ -1,9 +1,12 @@ [package] name = "libsyslog-sys" -version = "0.0.0" +version = "0.1.0" edition = "2021" license = "MIT OR Apache-2.0" description = "FFI bindings to system's native syslog" -repository = "https://git.netizen.se/libsyslog-rs" +repository = "https://git.netizen.se/libsyslog-rs/" keywords = [ "ffi", "logging", "syslog" ] categories = [ "external-ffi-bindings", "os" ] + +[build-dependencies] +bindgen = "0.64.0" diff --git a/libsyslog-sys/README.md b/libsyslog-sys/README.md index a0e6541..c5bd32f 100644 --- a/libsyslog-sys/README.md +++ b/libsyslog-sys/README.md @@ -1,11 +1,10 @@ libsyslog-sys ============= -The code (to be publiced shortly) in this crate provides a low level API to the -system's native syslog, the one typically implemented in C and residing in -libc. +The code in this crate provides a low level API to the system's native syslog, +the one typically implemented in C and residing in libc. Most users are likely more interested in the higher level safe API in the -libsyslog crate. +[libsyslog][] crate. Contact ------- @@ -13,9 +12,19 @@ Please see . Copyright and License --------------------- -This crate is Copyright 2023 Martin Samuelsson. It is distributed under the -terms of both the MIT license and the Apache License (Version 2.0). +This crate is Copyright 2023 Martin Samuelsson. It is licensed under either of -See LICENSE-APACHE and LICENSE-MIT for details. + * Apache License, Version 2.0 + ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license + ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. [libsyslog]: https://lib.rs/crates/libsyslog diff --git a/libsyslog-sys/build.rs b/libsyslog-sys/build.rs new file mode 100644 index 0000000..6cab3c4 --- /dev/null +++ b/libsyslog-sys/build.rs @@ -0,0 +1,29 @@ +extern crate bindgen; + +use { + bindgen::{ + Builder, + CargoCallbacks, + MacroTypeVariation, + }, + std::{ + env, + path::PathBuf, + }, +}; + +fn main() { + println!("cargo:rerun-if-changed=wrapper.h"); + + let bindings = Builder::default() + .header("wrapper.h") + .parse_callbacks(Box::new(CargoCallbacks)) + .default_macro_constant_type(MacroTypeVariation::Signed) + .generate() + .expect("Unable to generate bindings"); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} diff --git a/libsyslog-sys/src/lib.rs b/libsyslog-sys/src/lib.rs index e69de29..ed4ef3d 100644 --- a/libsyslog-sys/src/lib.rs +++ b/libsyslog-sys/src/lib.rs @@ -0,0 +1,46 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +//! The code in this crate contains the raw bindings for syslog, automatically +//! generated by [bindgen][]. Before continuing any further, please make sure +//! [libsyslog][] is not the crate you really are looking for. +//! +//! See [The Open Group Base Specifications Issue 7, 2018 edition][0] for +//! actual API documentation or [Wikipedia][1] for general context. +//! +//! Implementation specific documentation: (verified working platforms) +//! +//! * [FreeBSD][] +//! * [Haiku][] +//! * [illumos][] +//! * [Linux][] (with glibc) +//! * [NetBSD][] +//! * [OpenBSD][] +//! +//! [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=syslog +//! [Haiku]: https://www.haiku-os.org/documents/dev/system_logging +//! [illumos]: https://illumos.org/man/3C/syslog +//! [Linux]: https://www.man7.org/linux/man-pages/man3/syslog.3.html +//! [NetBSD]: https://man.netbsd.org/syslog.3 +//! [OpenBSD]: https://man.openbsd.org/syslog.3 +//! +//! Apple Inc. is advising to no longer use syslog on [macOS][] 10.12 and +//! later, yet this crate compiles and messages produced by it do appear in the +//! output of `log stream` on such platforms. +//! +//! [macOS]: https://developer.apple.com/documentation/os/logging +//! +//! [bindgen]: https://docs.rs/bindgen/latest/bindgen/ +//! [libsyslog]: https://docs.rs/bindgen/latest/libsyslog/ +//! [0]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/syslog.html +//! [1]: https://en.wikipedia.org/wiki/Syslog +// +// Rust tiers are at: +// It appears at least AIX and QNX should likely support syslog. Please get in +// touch if having access to such systems. +// +// [AIX]: https://www.ibm.com/docs/en/aix/latest?topic=s-syslog-openlog-closelog-setlogmask-subroutine +// [QNX]: https://www.qnx.com/developers/docs/7.1/com.qnx.doc.neutrino.lib_ref/topic/s/syslog.html + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/libsyslog-sys/wrapper.h b/libsyslog-sys/wrapper.h new file mode 100644 index 0000000..7761ece --- /dev/null +++ b/libsyslog-sys/wrapper.h @@ -0,0 +1 @@ +#include diff --git a/libsyslog/Cargo.toml b/libsyslog/Cargo.toml index 2554477..5d6e904 100644 --- a/libsyslog/Cargo.toml +++ b/libsyslog/Cargo.toml @@ -1,9 +1,16 @@ [package] name = "libsyslog" -version = "0.0.0" +version = "0.1.0" edition = "2021" license = "MIT OR Apache-2.0" description = "Rust `log` facade using system's native syslog" -repository = "https://git.netizen.se/libsyslog-rs" +repository = "https://git.netizen.se/libsyslog-rs/" keywords = [ "logging", "syslog" ] -categories = [ "external-ffi-bindings", "os" ] + +[dependencies] +bitflags = { version = "2.0.0-rc.3", optional = true } +libsyslog-sys = { version = "0.1.0", path = "../libsyslog-sys" } +log = { version = "0.4.17", features = [ "std" ] } + +[features] +bitflags = [ "dep:bitflags" ] diff --git a/libsyslog/README.md b/libsyslog/README.md index 5b4f64b..360f56f 100644 --- a/libsyslog/README.md +++ b/libsyslog/README.md @@ -1,24 +1,24 @@ libsyslog ========= -The code (to be publiced shortly) in this crate provides an API implementing -the standard Rust logging facade using the system's syslog. That is, it -implements the [Log][] trait of the [log] crate for native syslog, typically -implemented in C and residing in libc. +The code in this crate provides an API implementing the standard Rust logging +facade using the system's syslog. That is, it implements the [Log][] trait of +the [log crate][] for native syslog, typically implemented in C and residing in +libc. Why? ---- How does this differ from the handful of other pre-existing syslog crates? This -one uses the system library implementation rather attempting to rewrite it in -Rust. The hope is thus to have a crate that works on pretty much any platform -with syslog, avoiding bugs from making assumptions on implementation specific -details. +one uses the system library implementation rather than attempting to rewrite it +in Rust. The hope is thus to have a crate that works on pretty much any +platform with syslog, avoiding bugs from making assumptions on implementation +specific details. The need arose from a desire to use syslog on illumos, a platform where most of the other syslog crates fail to even build and other(s) instead fail at runtime. -So far this code has only been attempted on illumos. Reports on success or -failures to use it on other platforms are most welcome. +A list of attempted platforms is maintained in [libsyslog-sys][api]. Reports on +success or failures to use it on other platforms are most welcome. Contact ------- @@ -26,10 +26,21 @@ Please see . Copyright and License --------------------- -This crate is Copyright 2023 Martin Samuelsson. It is distributed under the -terms of both the MIT license and the Apache License (Version 2.0). +This crate is Copyright 2023 Martin Samuelsson. It is licensed under either of -See LICENSE-APACHE and LICENSE-MIT for details. + * Apache License, Version 2.0 + ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license + ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +at your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[api]: https://docs.rs/libsyslog-sys/latest/libsyslog_sys/ [Log]: https://docs.rs/log/latest/log/trait.Log.html -[log]: https://lib.rs/crates/log +[log crate]: https://lib.rs/crates/log diff --git a/libsyslog/src/builder.rs b/libsyslog/src/builder.rs new file mode 100644 index 0000000..c4b0781 --- /dev/null +++ b/libsyslog/src/builder.rs @@ -0,0 +1,127 @@ +use { + crate::{ + Facility, + Syslog, + }, + + libsyslog_sys::*, + log::{ + LevelFilter, + }, + std::{ + env::args, + ffi::{ + CString, + NulError, + }, + os::raw::c_int, + }, +}; + +#[cfg(feature = "bitflags")] +use crate::Logopt; + +/// Builder used to create and customize the [Syslog][Syslog] logger object. +#[derive(Debug,Default)] +pub struct SyslogBuilder { + facility_opt: Option, + ident_opt: Option, + level_opt: Option, + logopt_opt: Option, + module_levels: Vec<(String, LevelFilter)>, +} + +impl SyslogBuilder { + fn ident_from_argv() -> CString { + let argv0 = args().next().unwrap_or_default(); + let program = match argv0.rsplit_once('/') { + Some((_, filename)) => filename, + None => argv0.as_str(), + }; + CString::new(program).unwrap_or_default() + } + + #[must_use = "Syslog only gets active after calling .init() on it."] + pub fn build(self) -> Syslog { + //! Consumes `self` and returns a freshly created [`Syslog`] logger object. To be called at + //! the end of the builder chain, after all other methods on the [`SyslogBuilder`] object. + + let facility = self.facility_opt.unwrap_or(Facility::default() as c_int); + let ident = self.ident_opt.unwrap_or_else(Self::ident_from_argv); + let level = self.level_opt.unwrap_or(LevelFilter::Info); + let logopt = self.logopt_opt.unwrap_or(LOG_PID); + let module_levels = self.module_levels; + + Syslog { + facility, + ident, + level, + logopt, + module_levels, + } + } + + #[must_use = "No Syslog is constructed unless .build() is called."] + pub fn facility(mut self, facility: Facility) -> Self { + //! Sets logging facility to given [`Facility`] value. + + self.facility_opt = Some(facility as c_int); + self + } + + #[must_use = "No Syslog is constructed unless .build() is called."] + pub fn facility_raw(mut self, facility: c_int) -> Self { + //! Sets logging facility to unchecked `c_int` value. Can be used for + //! setting a raw [libsyslog-sys][libsyslog_sys] constant directly. + + self.facility_opt = Some(facility); + self + } + + #[must_use = "No Syslog is constructed unless .build() is called."] + pub fn ident>>(mut self, ident: T) -> Result { + //! Sets ident to the string like argument provided. By default ident is + //! derived from the name of the executable. + + self.ident_opt = Some(CString::new(ident)?); + Ok(self) + } + + #[must_use = "No Syslog is constructed unless .build() is called."] + pub fn level(mut self, level: LevelFilter) -> Self { + //! Sets global log level to be used when no + //! [module_level][`SyslogBuilder::module_level`] rule matches. + + self.level_opt = Some(level); + self + } + + #[cfg(feature = "bitflags")] + #[must_use = "No Syslog is constructed unless .build() is called."] + pub fn logopt(mut self, logopt: Logopt) -> Self { + //! Sets logopt to to given [`Logopt`] value. + + self.logopt_opt = Some(logopt.bits()); + self + } + + #[must_use = "No Syslog is constructed unless .build() is called."] + pub fn logopt_raw(mut self, logopt: c_int) -> Self { + //! Sets logopt to unchecked `c_int` value. Can be used for setting a raw + //! [libsyslog-sys][libsyslog_sys] constant directly. + + self.logopt_opt = Some(logopt); + self + } + + #[must_use = "No Syslog is constructed unless .build() is called."] + pub fn module_level(mut self, target: &str, level: LevelFilter) -> Self { + //! Sets log level for specified target Rust module. Multiple calls to this + //! method is possible and will set multiple filters. More specific filters + //! on children will override those of their parent modules. + + self.module_levels.push((target.to_string(), level)); + self + } +} + diff --git a/libsyslog/src/facility.rs b/libsyslog/src/facility.rs new file mode 100644 index 0000000..679403a --- /dev/null +++ b/libsyslog/src/facility.rs @@ -0,0 +1,107 @@ +use libsyslog_sys::*; + +/// Typesafe representation of syslog facility constants. +#[derive(Debug)] +pub enum Facility { + /// [LOG_KERN][`libsyslog_sys::LOG_KERN`] + /// Kernel messages. Not to sent from userland processes. + #[cfg(any(target_os = "freebsd", target_os="haiku", target_os = "linux", target_os = "netbsd", target_os = "illumos", target_os = "openbsd"))] + Kern = LOG_KERN as isize, + /// [LOG_MAIL][`libsyslog_sys::LOG_MAIL`] + /// Email system. + #[cfg(any(target_os = "freebsd", target_os="haiku", target_os = "linux", target_os = "netbsd", target_os = "illumos", target_os = "openbsd"))] + Mail = LOG_MAIL as isize, + /// [LOG_DAEMON][`libsyslog_sys::LOG_DAEMON`] + /// Daemons. + #[cfg(any(target_os = "freebsd", target_os="haiku", target_os = "linux", target_os = "netbsd", target_os = "illumos", target_os = "openbsd"))] + Daemon = LOG_DAEMON as isize, + /// [LOG_AUTH][`libsyslog_sys::LOG_AUTH`] + /// Authentication. + #[cfg(any(target_os = "freebsd", target_os="haiku", target_os = "linux", target_os = "netbsd", target_os = "illumos", target_os = "openbsd"))] + Auth = LOG_AUTH as isize, + /// [LOG_SYSLOG][`libsyslog_sys::LOG_SYSLOG`] + /// Internal messages from syslog. Not to be sent from other processes. + #[cfg(any(target_os = "freebsd", target_os="haiku", target_os = "linux", target_os = "netbsd", target_os = "illumos", target_os = "openbsd"))] + Syslog = LOG_SYSLOG as isize, + /// [LOG_LPR][`libsyslog_sys::LOG_LPR`] + /// Printing. + #[cfg(any(target_os = "freebsd", target_os="haiku", target_os = "linux", target_os = "netbsd", target_os = "illumos", target_os = "openbsd"))] + Lpr = LOG_LPR as isize, + /// [LOG_NEWS][`libsyslog_sys::LOG_NEWS`] + /// Usenet. + #[cfg(any(target_os = "freebsd", target_os="haiku", target_os = "linux", target_os = "netbsd", target_os = "illumos", target_os = "openbsd"))] + News = LOG_NEWS as isize, + /// [LOG_UUCP][`libsyslog_sys::LOG_UUCP`] + /// Unix to unix copy + #[cfg(any(target_os = "freebsd", target_os="haiku", target_os = "linux", target_os = "netbsd", target_os = "illumos", target_os = "openbsd"))] + Uucp = LOG_UUCP as isize, + /// [LOG_CRON][`libsyslog_sys::LOG_CRON`] + /// Scheduled jobs (at and cron). + #[cfg(any(target_os = "freebsd", target_os="haiku", target_os = "linux", target_os = "netbsd", target_os = "illumos", target_os = "openbsd"))] + Cron = LOG_CRON as isize, + /// [LOG_AUTHPRIV][`libsyslog_sys::LOG_AUTHPRIV`] + /// Authentication, sensitive data. + #[cfg(any(target_os = "freebsd", target_os="haiku", target_os = "linux", target_os = "netbsd", target_os = "illumos", target_os = "openbsd"))] + AuthPriv = LOG_AUTHPRIV as isize, + /// [LOG_FTP][`libsyslog_sys::LOG_FTP`] + /// File transfer protocol system. + #[cfg(any(target_os = "freebsd", target_os="haiku", target_os = "linux", target_os = "netbsd", target_os = "illumos", target_os = "openbsd"))] + Ftp = LOG_FTP as isize, + + /// [LOG_ALTCRON][`libsyslog_sys::LOG_ALTCRON`] + /// Scheduled jobs, BSD (at and cron). + #[cfg(target_os = "illumos")] + AltCron = LOG_ALTCRON as isize, + /// [LOG_NTP][`libsyslog_sys::LOG_NTP`] + /// Network time protocol. + #[cfg(any(target_os = "freebsd", target_os = "illumos"))] + Ntp = LOG_NTP as isize, + /// [LOG_AUDIT][`libsyslog_sys::LOG_AUDIT`] or LOG_SECURITY + /// Security/Audit. Low level constant varies between platforms. + #[cfg(target_os = "illumos")] + Security = LOG_AUDIT as isize, + /// LOG_AUDIT or [LOG_SECURITY][`libsyslog_sys::LOG_SECURITY`] + /// Security/Audit. Low level constant varies between platforms. + #[cfg(target_os = "freebsd")] + Security = LOG_SECURITY as isize, + /// [LOG_CONSOLE][`libsyslog_sys::LOG_CONSOLE`] + /// Console, BSD. + #[cfg(any(target_os = "freebsd", target_os = "illumos"))] + Console = LOG_CONSOLE as isize, + + /// [LOG_USER][`libsyslog_sys::LOG_USER`] + /// Default facility. + User = LOG_USER as isize, + /// [LOG_LOCAL0][`libsyslog_sys::LOG_LOCAL0`] + /// Site specific facility 0. + Local0 = LOG_LOCAL0 as isize, + /// [LOG_LOCAL1][`libsyslog_sys::LOG_LOCAL1`] + /// Site specific facility 1. + Local1 = LOG_LOCAL1 as isize, + /// [LOG_LOCAL2][`libsyslog_sys::LOG_LOCAL2`] + /// Site specific facility 2. + Local2 = LOG_LOCAL2 as isize, + /// [LOG_LOCAL3][`libsyslog_sys::LOG_LOCAL3`] + /// Site specific facility 3. + Local3 = LOG_LOCAL3 as isize, + /// [LOG_LOCAL4][`libsyslog_sys::LOG_LOCAL4`] + /// Site specific facility 4. + Local4 = LOG_LOCAL4 as isize, + /// [LOG_LOCAL5][`libsyslog_sys::LOG_LOCAL5`] + /// Site specific facility 5. + Local5 = LOG_LOCAL5 as isize, + /// [LOG_LOCAL6][`libsyslog_sys::LOG_LOCAL6`] + /// Site specific facility 6. + Local6 = LOG_LOCAL6 as isize, + /// [LOG_LOCAL7][`libsyslog_sys::LOG_LOCAL7`] + /// Site specific facility 7. + Local7 = LOG_LOCAL7 as isize, +} + +impl Default for Facility { + fn default() -> Facility { + //! Returns [`Facility::User`]. + + Facility::User + } +} diff --git a/libsyslog/src/lib.rs b/libsyslog/src/lib.rs index e69de29..7c253ae 100644 --- a/libsyslog/src/lib.rs +++ b/libsyslog/src/lib.rs @@ -0,0 +1,67 @@ +//! This crate provides an API implementing the standard Rust logging facade +//! to the system's syslog. That is, it implements the [Log][log::Log] trait +//! of the [log] crate for native syslog (the one typically implemented in C +//! and residing in libc). +//! +//! Syslog needs to be initialized prior to any [logging macros][log#macros] +//! being called. That is usally done through a single chained call of its +//! [builder][Syslog::builder], with optional +//! [methods][SyslogBuilder#implementations] to customize the logger before +//! finalizing the setup by calling [build][SyslogBuilder::build] and +//! [init][Syslog::init]. +//! +//! Typical use: (A.k.a. recommended snippet to copy'n'paste to the start of +//! `main()` of your daemon.) +//! ``` +//! # fn main() -> Result<()> { +//! libsyslog::Syslog::builder() +//! .module_level(std::module_path!(), log::LevelFilter::Info) +//! .level(log::LevelFilter::Off) +//! .build() +//! .init()?; +//! +//! error!("Hello libsyslog. I've failed you."); +//! # } +//! ``` +//! +//! It is likely reasonable for most users to configure the +//! [module_level][SyslogBuilder::module_level] and +//! [level][SyslogBuilder::level] filters to only have the application's own +//! log messages sent to syslog (ignoring potentially chatty libraries), as in +//! the above example. +//! +//! The scope of this crate is limited as described by the very first paragraph +//! above. Its intended use is during normal operation, in software with modest +//! requirements on low frequency logging. Note that syslog risks killing +//! performance. Consider using another additional facade, in combination with +//! a `--no-daemon` option, for when developing and debugging. And please +//! remember, if [libsyslog][`crate`] does not meet your requirements, there +//! are plenty of other crates for logging around. + +// According to [doc][], syslog() and friends are not thread safe on AIX. It +// appears there is a family or related functions with an _r suffix which +// should likely be used instead. Patches for adding AIX support are welcome. +// +// It should of course possible to locally disable the check below, but then +// it's up to you to ensure your application is single threaded. +// +#[cfg(target_os = "aix")] +compile_error!("AIX user, please help with the code comment above this error."); +// +// [doc]: https://www.ibm.com/docs/en/aix/latest?topic=s-syslog-openlog-closelog-setlogmask-subroutine + +mod builder; +mod facility; +// TODO Deprecate feature once the bitflags crate reaches 2.0.0. +#[cfg(feature = "bitflags")] +mod logopt; +mod syslog; + +#[cfg(feature = "bitflags")] +pub use logopt::*; + +pub use { + builder::*, + facility::*, + syslog::*, +}; diff --git a/libsyslog/src/logopt.rs b/libsyslog/src/logopt.rs new file mode 100644 index 0000000..381ab06 --- /dev/null +++ b/libsyslog/src/logopt.rs @@ -0,0 +1,29 @@ +use { + bitflags::bitflags, + libsyslog_sys::*, + std::os::raw::c_int, +}; + +bitflags! { + /// Typesafe representation of syslog logopt bitflags. + /// + /// Pid = [LOG_PID][`libsyslog_sys::LOG_PID`] + /// Cons = [LOG_CONS][`libsyslog_sys::LOG_CONS`] + /// ODelay = [LOG_ODELAY][`libsyslog_sys::LOG_ODELAY`] + /// NDelay = [LOG_NDELAY][`libsyslog_sys::LOG_NDELAY`] + /// NoWait = [LOG_NOWAIT][`libsyslog_sys::LOG_NOWAIT`] + #[derive(Debug,Default)] + pub struct Logopt: c_int { + const Pid = LOG_PID; + const Cons = LOG_CONS; + const ODelay = LOG_ODELAY; + const NDelay = LOG_NDELAY; + const NoWait = LOG_NOWAIT; + #[cfg(any(target_os = "freebsd", target_os = "netbsd"))] + const PError = LOG_PERROR; + #[cfg(target_os = "netbsd")] + const PTrim = LOG_PTRIM; + #[cfg(target_os = "netbsd")] + const NLogm = LOG_NLOG; + } +} diff --git a/libsyslog/src/syslog.rs b/libsyslog/src/syslog.rs new file mode 100644 index 0000000..6b36147 --- /dev/null +++ b/libsyslog/src/syslog.rs @@ -0,0 +1,92 @@ +use { + crate::SyslogBuilder, + + libsyslog_sys::*, + log::{ + Level, + LevelFilter, + Metadata, + Record, + SetLoggerError, + set_boxed_logger, + set_max_level, + }, + std::{ + ffi::CString, + os::raw::c_int, + }, +}; + +/// Logger object to make [log][log] output message to syslog. +/// +/// Creating and initializing a logger object will make [`log`]'s logging +/// macros go to syslog. By default a logger object gets created with facility +/// [Facility::User][`crate::Facility::User`], the name of the process as its ident, a global log +/// level of [`LevelFilter::Info`], no module loglevels and with logopt set to +/// [`libsyslog_sys::LOG_PID`]. The [`SyslogBuilder`] can be used to customize +/// those default values. +#[derive(Debug)] +pub struct Syslog { + pub(crate) facility: c_int, + pub(crate) ident: CString, + pub(crate) level: LevelFilter, + pub(crate) logopt: c_int, + pub(crate) module_levels: Vec<(String, LevelFilter)>, +} + +impl Syslog { + #[must_use = "No Syslog is constructed unless .build() is called."] + pub fn builder() -> SyslogBuilder { + //! Returns the [`SyslogBuilder`] used to create a [`Syslog`] logger object. + + SyslogBuilder::default() + } + + pub fn init(mut self) -> Result<(), SetLoggerError> { + //! Initialize logging by opening a connection to syslog and hand over `self` to be + //! [log's][`log::set_boxed_logger`] global logger. + //! + //! Passes the result of [set_boxed_logger][`log::set_boxed_logger`], where + //! [`SetLoggerError`] is returned if a global logger already is active. + + unsafe { openlog(self.ident.as_ptr(), self.logopt, self.facility); } + + self.module_levels.sort_by_key(|(modpath, _)| modpath.len().wrapping_neg()); + let max_module_level = self.module_levels.iter().map(|(_, level)| level).copied().max(); + let max_level = max_module_level.map(|level| level.max(self.level)).unwrap_or(self.level); + set_max_level(max_level); + set_boxed_logger(Box::new(self)) + } +} + +impl Drop for Syslog { + fn drop(&mut self) { + //! Closes the syslog connection. (Warning: Might potentially block.) + + unsafe { closelog(); } + } +} + +impl log::Log for Syslog { + fn enabled(&self, metadata: &Metadata) -> bool { + &metadata.level().to_level_filter() <= self.module_levels.iter() + .find(|(modpath, _)| metadata.target().starts_with(modpath) ).map(|(_, level)| level) + .unwrap_or(&self.level) + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + if let Ok(msg) = CString::new(format!("{}", record.args())) { + match record.level() { + Level::Debug => unsafe { syslog(LOG_DEBUG, msg.as_ptr()); } + Level::Error => unsafe { syslog(LOG_ERR, msg.as_ptr()); } + Level::Info => unsafe { syslog(LOG_INFO, msg.as_ptr()); } + Level::Warn => unsafe { syslog(LOG_WARNING, msg.as_ptr()); } + Level::Trace => unsafe { syslog(LOG_DEBUG, msg.as_ptr()); } + } + } + } + } + + fn flush(&self) {} +} -- cgit v1.2.3