-
Notifications
You must be signed in to change notification settings - Fork 848
tracing-syslog crate #1137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
tracing-syslog crate #1137
Changes from all commits
fe76218
490685d
7fa1cea
9f6e8f3
e25f734
62b56b3
5928308
a5ba26d
b088264
20f6cb4
c99dc57
763dca7
ab64898
4bba20e
203ecf3
1d2a389
612a148
8e75123
45339e6
748f823
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,5 +15,6 @@ members = [ | |
| "tracing-serde", | ||
| "tracing-appender", | ||
| "tracing-journald", | ||
| "tracing-syslog", | ||
| "examples" | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| [package] | ||
| name = "tracing-syslog" | ||
| version = "0.1.0" | ||
| authors = ["Max Heller <max.a.heller@gmail.com>"] | ||
| edition = "2018" | ||
| license = "MIT" | ||
| repository = "https://github.com/tokio-rs/tracing" | ||
| homepage = "https://tokio.rs" | ||
| description = "syslog subscriber for `tracing`" | ||
| categories = [ | ||
| "development-tools::debugging", | ||
| "development-tools::profiling", | ||
| ] | ||
| keywords = ["tracing", "syslog"] | ||
|
|
||
| [package.metadata.docs.rs] | ||
| all-features = true | ||
| rustdoc-args = ["--cfg", "docsrs"] | ||
|
|
||
| [dependencies] | ||
| libc = "0.2" | ||
| tracing-core = { path = "../tracing-core", version = "0.2" } | ||
| tracing-subscriber = { path = "../tracing-subscriber", version = "0.3" } | ||
|
|
||
| [dev-dependencies] | ||
| tracing = { path = "../tracing", version = "0.2" } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| //! `syslog` logging support for `tracing` backed by `libc`'s | ||
| //! [`syslog()`](libc::syslog) function. | ||
| //! | ||
| //! See [`Syslog`] for documentation and examples. | ||
|
|
||
| #[cfg(unix)] | ||
| mod syslog; | ||
| #[cfg(unix)] | ||
| pub use syslog::*; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,321 @@ | ||
| use std::{borrow::Cow, cell::RefCell, ffi::CStr, io}; | ||
| use tracing_core::{Level, Metadata}; | ||
| use tracing_subscriber::fmt::MakeWriter; | ||
|
|
||
| /// `syslog` options. | ||
| /// | ||
| /// # Examples | ||
| /// ``` | ||
| /// use tracing_syslog::Options; | ||
| /// // Log PID with messages and log to stderr as well as `syslog`. | ||
| /// let opts = Options::LOG_PID | Options::LOG_PERROR; | ||
| /// ``` | ||
| #[derive(Copy, Clone, Debug, Default)] | ||
| pub struct Options(libc::c_int); | ||
|
|
||
| impl Options { | ||
| /// Log the pid with each message. | ||
| pub const LOG_PID: Self = Self(libc::LOG_PID); | ||
| /// Log on the console if errors in sending. | ||
| pub const LOG_CONS: Self = Self(libc::LOG_CONS); | ||
| /// Delay open until first syslog() (default). | ||
| pub const LOG_ODELAY: Self = Self(libc::LOG_ODELAY); | ||
| /// Don't delay open. | ||
| pub const LOG_NDELAY: Self = Self(libc::LOG_NDELAY); | ||
| /// Don't wait for console forks: DEPRECATED. | ||
| pub const LOG_NOWAIT: Self = Self(libc::LOG_NOWAIT); | ||
| /// Log to stderr as well. | ||
| pub const LOG_PERROR: Self = Self(libc::LOG_PERROR); | ||
| } | ||
|
|
||
| impl std::ops::BitOr for Options { | ||
| type Output = Self; | ||
| fn bitor(self, rhs: Self) -> Self::Output { | ||
| Self(self.0 | rhs.0) | ||
| } | ||
| } | ||
|
Comment on lines
+31
to
+36
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, not sure if this carries its weight. Why do you think this is valuable?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So you can set multiple options at the same time: use tracing_syslog::Options;
// Log PID with messages and log to stderr as well as `syslog`.
let opts = Options::LOG_PID | Options::LOG_PERROR;(added this example to |
||
|
|
||
| /// `syslog` facility. | ||
| #[derive(Copy, Clone, Debug)] | ||
| #[repr(i32)] | ||
| pub enum Facility { | ||
| /// Generic user-level messages. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_USER"))] | ||
| User = libc::LOG_USER, | ||
| /// Mail subsystem. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_MAIL"))] | ||
| Mail = libc::LOG_MAIL, | ||
| /// System daemons without separate facility value. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_DAEMON"))] | ||
| Daemon = libc::LOG_DAEMON, | ||
| /// Security/authorization messages. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_AUTH"))] | ||
| Auth = libc::LOG_AUTH, | ||
| /// Line printer subsystem. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_LPR"))] | ||
| Lpr = libc::LOG_LPR, | ||
| /// USENET news subsystem. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_NEWS"))] | ||
| News = libc::LOG_NEWS, | ||
| /// UUCP subsystem. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_UUCP"))] | ||
| Uucp = libc::LOG_UUCP, | ||
| /// Clock daemon (`cron` and `at`). | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_CRON"))] | ||
| Cron = libc::LOG_CRON, | ||
| /// Security/authorization messages (private). | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_AUTHPRIV"))] | ||
| AuthPriv = libc::LOG_AUTHPRIV, | ||
| /// FTP daemon. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_FTP"))] | ||
| Ftp = libc::LOG_FTP, | ||
| /// Reserved for local use. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_LOCAL0"))] | ||
| Local0 = libc::LOG_LOCAL0, | ||
| /// Reserved for local use. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_LOCAL1"))] | ||
| Local1 = libc::LOG_LOCAL1, | ||
| /// Reserved for local use. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_LOCAL2"))] | ||
| Local2 = libc::LOG_LOCAL2, | ||
| /// Reserved for local use. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_LOCAL3"))] | ||
| Local3 = libc::LOG_LOCAL3, | ||
| /// Reserved for local use. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_LOCAL4"))] | ||
| Local4 = libc::LOG_LOCAL4, | ||
| /// Reserved for local use. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_LOCAL5"))] | ||
| Local5 = libc::LOG_LOCAL5, | ||
| /// Reserved for local use. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_LOCAL6"))] | ||
| Local6 = libc::LOG_LOCAL6, | ||
| /// Reserved for local use. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_LOCAL7"))] | ||
| Local7 = libc::LOG_LOCAL7, | ||
| } | ||
|
|
||
| impl Default for Facility { | ||
| fn default() -> Self { | ||
| Self::User | ||
| } | ||
| } | ||
|
|
||
| /// `syslog` severity. | ||
| #[derive(Copy, Clone)] | ||
| #[repr(i32)] | ||
| // There are more `syslog` severities than `tracing` levels, so some severities | ||
| // aren't used. They're included here for completeness and so the level mapping | ||
| // could easily change to include them. | ||
| #[allow(dead_code)] | ||
| enum Severity { | ||
| /// System is unusable. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_EMERG"))] | ||
| Emergency = libc::LOG_EMERG, | ||
| /// Action must be taken immediately. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_ALERT"))] | ||
| Alert = libc::LOG_ALERT, | ||
| /// Critical conditions. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_CRIT"))] | ||
| Critical = libc::LOG_CRIT, | ||
| /// Error conditions. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_ERR"))] | ||
| Error = libc::LOG_ERR, | ||
| /// Warning conditions. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_WARNING"))] | ||
| Warning = libc::LOG_WARNING, | ||
| /// Normal, but significant, condition. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_NOTICE"))] | ||
| Notice = libc::LOG_NOTICE, | ||
| /// Informational message. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_INFO"))] | ||
| Info = libc::LOG_INFO, | ||
| /// Debug-level message. | ||
| #[cfg_attr(docsrs, doc(alias = "LOG_DEBUG"))] | ||
| Debug = libc::LOG_DEBUG, | ||
| } | ||
|
|
||
| impl From<Level> for Severity { | ||
| fn from(level: Level) -> Self { | ||
| match level { | ||
| Level::ERROR => Self::Error, | ||
| Level::WARN => Self::Warning, | ||
| Level::INFO => Self::Notice, | ||
| Level::DEBUG => Self::Info, | ||
| Level::TRACE => Self::Debug, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// `syslog` priority. | ||
| #[derive(Copy, Clone, Debug)] | ||
| struct Priority(libc::c_int); | ||
|
|
||
| impl Priority { | ||
| fn new(facility: Facility, level: Level) -> Self { | ||
| let severity = Severity::from(level); | ||
| Self((facility as libc::c_int) | (severity as libc::c_int)) | ||
| } | ||
| } | ||
|
|
||
| fn syslog(priority: Priority, msg: &CStr) { | ||
| // SAFETY: the second argument must be a valid pointer to a nul-terminated | ||
| // format string and formatting placeholders e.g. %s must correspond to | ||
| // one of the variable-length arguments. By construction, the format string | ||
| // is nul-terminated, and the only string formatting placeholder corresponds | ||
| // to `msg.as_ptr()`, which is a valid, nul-terminated string in C world | ||
| // because `msg` is a `CStr`. | ||
| unsafe { libc::syslog(priority.0, "%s\0".as_ptr().cast(), msg.as_ptr()) } | ||
max-heller marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /// [`MakeWriter`] that logs to `syslog` via `libc`'s [`syslog()`](libc::syslog) function. | ||
| /// | ||
| /// # Level Mapping | ||
| /// | ||
| /// `tracing` [`Level`]s are mapped to `syslog` severities as follows: | ||
| /// | ||
| /// ```raw | ||
| /// Level::ERROR => Severity::LOG_ERR, | ||
| /// Level::WARN => Severity::LOG_WARNING, | ||
| /// Level::INFO => Severity::LOG_NOTICE, | ||
| /// Level::DEBUG => Severity::LOG_INFO, | ||
| /// Level::TRACE => Severity::LOG_DEBUG, | ||
| /// ``` | ||
| /// | ||
| /// **Note:** the mapping is lossless, but the corresponding `syslog` severity | ||
| /// names differ from `tracing`'s level names towards the bottom. `syslog` | ||
| /// does not have a level lower than `LOG_DEBUG`, so this is unavoidable. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// Initializing a global logger that writes to `syslog` with an identity of `example-program` | ||
| /// and the default `syslog` options and facility: | ||
| /// | ||
| /// ``` | ||
| /// let identity = std::ffi::CStr::from_bytes_with_nul(b"example-program\0").unwrap(); | ||
| /// let (options, facility) = Default::default(); | ||
| /// let syslog = tracing_syslog::Syslog::new(identity, options, facility); | ||
| /// tracing_subscriber::fmt().with_writer(syslog).init(); | ||
| /// ``` | ||
| pub struct Syslog { | ||
| /// Identity e.g. program name. Referenced by syslog, so we store it here to | ||
| /// ensure it lives until we are done logging. | ||
| #[allow(dead_code)] | ||
| identity: Cow<'static, CStr>, | ||
| facility: Facility, | ||
| } | ||
|
|
||
| impl Syslog { | ||
| /// Creates a [`MakeWriter`] that writes to `syslog`. | ||
| /// | ||
| /// This calls [`libc::openlog()`] to initialize the logger. The corresponding | ||
| /// [`libc::closelog()`] call happens when the returned logger is dropped. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// Creating a `syslog` subscriber with an identity of `example-program` and | ||
| /// the default `syslog` options and facility: | ||
| /// | ||
| /// ``` | ||
| /// use tracing_syslog::Syslog; | ||
| /// let identity = std::ffi::CStr::from_bytes_with_nul(b"example-program\0").unwrap(); | ||
| /// let (options, facility) = Default::default(); | ||
| /// let subscriber = Syslog::new(identity, options, facility); | ||
| /// ``` | ||
| pub fn new( | ||
| identity: impl Into<Cow<'static, CStr>>, | ||
| options: Options, | ||
| facility: Facility, | ||
| ) -> Self { | ||
| let identity = identity.into(); | ||
| // SAFETY: identity will remain alive until the returned struct's fields | ||
| // are dropped, by which point `closelog` will have been called by the | ||
| // `Drop` implementation. | ||
| unsafe { libc::openlog(identity.as_ptr(), options.0, facility as libc::c_int) }; | ||
| Syslog { identity, facility } | ||
| } | ||
| } | ||
|
|
||
| impl Drop for Syslog { | ||
| /// Calls [`libc::closelog()`]. | ||
| fn drop(&mut self) { | ||
| unsafe { libc::closelog() }; | ||
| } | ||
|
Comment on lines
+241
to
+243
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why's this safe? What invarients need to be upheld? If you can, can you add comments explaining why?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't decided yet how to best handle the case where multiple |
||
| } | ||
|
|
||
| impl<'a> MakeWriter<'a> for Syslog { | ||
| type Writer = SyslogWriter; | ||
|
|
||
| fn make_writer(&'a self) -> Self::Writer { | ||
| // TODO: is `INFO` a good default? | ||
| SyslogWriter::new(self.facility, Level::INFO) | ||
| } | ||
|
|
||
| fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer { | ||
| SyslogWriter::new(self.facility, *meta.level()) | ||
| } | ||
| } | ||
|
|
||
| /// [Writer](io::Write) to `syslog` produced by [`MakeWriter`]. | ||
| pub struct SyslogWriter { | ||
| flushed: bool, | ||
| facility: Facility, | ||
| level: Level, | ||
| } | ||
|
|
||
| impl SyslogWriter { | ||
| fn new(facility: Facility, level: Level) -> Self { | ||
| SyslogWriter { | ||
| flushed: false, | ||
| facility, | ||
| level, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| thread_local! { static BUF: RefCell<Vec<u8>> = RefCell::new(Vec::with_capacity(256)) } | ||
|
|
||
| impl io::Write for SyslogWriter { | ||
| fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { | ||
| BUF.with(|buf| buf.borrow_mut().extend(bytes)); | ||
| Ok(bytes.len()) | ||
| } | ||
|
|
||
| fn flush(&mut self) -> io::Result<()> { | ||
| BUF.with(|buf| { | ||
| let mut buf = buf.borrow_mut(); | ||
|
|
||
| // Append nul-terminator | ||
| buf.push(0); | ||
|
|
||
| // Parse log message as C string | ||
| let msg = CStr::from_bytes_with_nul(&buf); | ||
|
|
||
| // In debug mode, panic when log message contains interior nul | ||
| #[cfg(debug_assertions)] | ||
| msg.as_ref().expect("logs free of interior nul-terminators"); | ||
| // ... but in non-debug mode, just print an error | ||
| #[cfg(not(debug_assertions))] | ||
| let msg = msg.map_err(|res| eprintln!("log contained interior nul byte: {}", res)); | ||
|
|
||
| // Send the message to `syslog` if the message is valid | ||
| if let Ok(msg) = msg { | ||
| let priority = Priority::new(self.facility, self.level); | ||
| syslog(priority, msg) | ||
| } | ||
|
|
||
| // Clear buffer | ||
| buf.clear(); | ||
| }); | ||
| self.flushed = true; | ||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| impl Drop for SyslogWriter { | ||
| fn drop(&mut self) { | ||
| if !self.flushed { | ||
| let _ = io::Write::flush(self); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be great to have doc comments with examples and tests here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've never been sure about this: for a crate that has a primary type (like
Syslogin this case), should the majority of the docs go with that type or inlib.rs?