summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Samuelsson <msamuelsson@storvix.eu>2023-02-24 14:31:43 +0100
committercos <cos>2023-03-02 07:40:47 +0100
commitc3e521e4ac4725daff075653c5c9163bfcb15526 (patch)
treef8bb8270b1d81079be065ca9c765f393fb5589e7
parent8ccef7cbe4c11c1638f82e1a9f4a36f5b643551a (diff)
downloadlibsyslog-rs-c3e521e4ac4725daff075653c5c9163bfcb15526.zip
Add actual implementationlibsyslog_v0.1.0libsyslog-sys_v0.1.0
-rw-r--r--README.md7
-rw-r--r--libsyslog-sys/Cargo.toml7
-rw-r--r--libsyslog-sys/README.md23
-rw-r--r--libsyslog-sys/build.rs29
-rw-r--r--libsyslog-sys/src/lib.rs46
-rw-r--r--libsyslog-sys/wrapper.h1
-rw-r--r--libsyslog/Cargo.toml13
-rw-r--r--libsyslog/README.md39
-rw-r--r--libsyslog/src/builder.rs127
-rw-r--r--libsyslog/src/facility.rs107
-rw-r--r--libsyslog/src/lib.rs67
-rw-r--r--libsyslog/src/logopt.rs29
-rw-r--r--libsyslog/src/syslog.rs92
13 files changed, 557 insertions, 30 deletions
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 <https://www.netizen.se/#contact>.
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: <https://doc.rust-lang.org/rustc/platform-support.html>
+// 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 <syslog.h>
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 <https://www.netizen.se/#contact>.
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<c_int>,
+ ident_opt: Option<CString>,
+ level_opt: Option<LevelFilter>,
+ logopt_opt: Option<c_int>,
+ 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<T: Into<Vec<u8>>>(mut self, ident: T) -> Result<Self, NulError> {
+ //! 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) {}
+}