From 1066a1820843ca530c8465f8c759b1dff49b9d5d Mon Sep 17 00:00:00 2001 From: little-dude Date: Wed, 26 Oct 2022 14:37:36 +0200 Subject: [PATCH 1/4] tracing-journald: make level mappings configurable --- tracing-journald/src/lib.rs | 146 ++++++++++++++++++++++++++++++++---- 1 file changed, 132 insertions(+), 14 deletions(-) diff --git a/tracing-journald/src/lib.rs b/tracing-journald/src/lib.rs index 2433418ce9..93769d9c96 100644 --- a/tracing-journald/src/lib.rs +++ b/tracing-journald/src/lib.rs @@ -62,7 +62,7 @@ mod socket; /// names by translating `.`s into `_`s, stripping leading `_`s and non-ascii-alphanumeric /// characters other than `_`, and upcasing. /// -/// Levels are mapped losslessly to journald `PRIORITY` values as follows: +/// By default, levels are mapped losslessly to journald `PRIORITY` values as follows: /// /// - `ERROR` => Error (3) /// - `WARN` => Warning (4) @@ -70,6 +70,8 @@ mod socket; /// - `DEBUG` => Informational (6) /// - `TRACE` => Debug (7) /// +/// These mappings can be changed with [`Subscriber::with_priority_mappings`]. +/// /// The standard journald `CODE_LINE` and `CODE_FILE` fields are automatically emitted. A `TARGET` /// field is emitted containing the event's target. /// @@ -85,6 +87,7 @@ pub struct Subscriber { socket: UnixDatagram, field_prefix: Option, syslog_identifier: String, + priority_mappings: PriorityMappings, } #[cfg(unix)] @@ -109,6 +112,7 @@ impl Subscriber { .map(|n| n.to_string_lossy().into_owned()) // If we fail to get the name of the current executable fall back to an empty string. .unwrap_or_else(String::new), + priority_mappings: PriorityMappings::new(), }; // Check that we can talk to journald, by sending empty payload which journald discards. // However if the socket didn't exist or if none listened we'd get an error here. @@ -129,6 +133,12 @@ impl Subscriber { self } + /// Sets the mappings from the tracing level to the journald priorities. + pub fn with_priority_mappings(mut self, mappings: PriorityMappings) -> Self { + self.priority_mappings = mappings; + self + } + /// Sets the syslog identifier for this logger. /// /// The syslog identifier comes from the classic syslog interface (`openlog()` @@ -198,6 +208,20 @@ impl Subscriber { memfd::seal_fully(mem.as_raw_fd())?; socket::send_one_fd_to(&self.socket, mem.as_raw_fd(), JOURNALD_PATH) } + + fn put_priority(&self, buf: &mut Vec, meta: &Metadata) { + put_field_wellformed( + buf, + "PRIORITY", + &[match *meta.level() { + Level::ERROR => self.priority_mappings.error as u8, + Level::WARN => self.priority_mappings.warn as u8, + Level::INFO => self.priority_mappings.info as u8, + Level::DEBUG => self.priority_mappings.debug as u8, + Level::TRACE => self.priority_mappings.trace as u8, + }], + ); + } } /// Construct a journald subscriber @@ -252,7 +276,7 @@ where } // Record event fields - put_priority(&mut buf, event.metadata()); + self.put_priority(&mut buf, event.metadata()); put_metadata(&mut buf, event.metadata(), None); put_field_length_encoded(&mut buf, "SYSLOG_IDENTIFIER", |buf| { write!(buf, "{}", self.syslog_identifier).unwrap() @@ -339,18 +363,112 @@ impl Visit for EventVisitor<'_> { } } -fn put_priority(buf: &mut Vec, meta: &Metadata) { - put_field_wellformed( - buf, - "PRIORITY", - match *meta.level() { - Level::ERROR => b"3", - Level::WARN => b"4", - Level::INFO => b"5", - Level::DEBUG => b"6", - Level::TRACE => b"7", - }, - ); +/// A priority (in syslog called severity code) is used to mark the +/// importance of a message. +/// +/// Descriptions and examples are taken from the [Arch Linux wiki]. +/// Priorities are also documented in the +/// [section 6.2.1 of the Syslog protocol RFC][syslog]. +/// +/// [Arch Linux wiki]: https://wiki.archlinux.org/title/Systemd/Journal#Priority_level +/// [syslog]: https://www.rfc-editor.org/rfc/rfc5424#section-6.2.1 +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +#[repr(u8)] +pub enum Priority { + /// System is unusable. + /// + /// Examples: + /// + /// - severe Kernel BUG + /// - systemd dumped core + /// + /// This level should not be used by applications. + Emergency = b'0', + /// Should be corrected immediately. + /// + /// Examples: + /// + /// - Vital subsystem goes out of work, data loss: + /// - `kernel: BUG: unable to handle kernel paging request at ffffc90403238ffc` + Alert = b'1', + /// Critical conditions + /// + /// Examples: + /// + /// - Crashe, coredumps + /// - `systemd-coredump[25319]: Process 25310 (plugin-container) of user 1000 dumped core` + Critical = b'2', + /// Error conditions + /// + /// Examples: + /// + /// - Not severe error reported + /// - `kernel: usb 1-3: 3:1: cannot get freq at ep 0x84, systemd[1]: Failed unmounting /var` + /// - `libvirtd[1720]: internal error: Failed to initialize a valid firewall backend` + Error = b'3', + /// May indicate that an error will occur if action is not taken. + /// + /// Examples: + /// + /// - a non-root file system has only 1GB free + /// - `org.freedesktop. Notifications[1860]: (process:5999): Gtk-WARNING **: Locale not supported by C library. Using the fallback 'C' locale` + Warning = b'4', + /// Events that are unusual, but not error conditions. + /// + /// Examples: + /// + /// - `systemd[1]: var.mount: Directory /var to mount over is not empty, mounting anyway` + /// - `gcr-prompter[4997]: Gtk: GtkDialog mapped without a transient parent. This is discouraged` + Notice = b'5', + /// Normal operational messages that require no action. + /// + /// Example: `lvm[585]: 7 logical volume(s) in volume group "archvg" now active` + Informational = b'6', + /// Information useful to developers for debugging the + /// application. + /// + /// Example: `kdeinit5[1900]: powerdevil: Scheduling inhibition from ":1.14" "firefox" with cookie 13 and reason "screen"` + Debug = b'7', +} + +/// Mappings from the tracing levels to the journald priorities. +#[derive(Debug, Clone)] +pub struct PriorityMappings { + /// Priority mapped to the `ERROR` level + pub error: Priority, + /// Priority mapped to the `WARN` level + pub warn: Priority, + /// Priority mapped to the `INFO` level + pub info: Priority, + /// Priority mapped to the `DEBUG` level + pub debug: Priority, + /// Priority mapped to the `TRACE` level + pub trace: Priority, +} + +impl PriorityMappings { + /// Create new default mappings: + /// + /// - `tracing::Level::ERROR`: Error (3) + /// - `tracing::Level::WARN`: Warning (4) + /// - `tracing::Level::INFO`: Notice (5) + /// - `tracing::Level::DEBUG`: Informational (6) + /// - `tracing::Level::TRACE`: Debug (7) + pub fn new() -> PriorityMappings { + Self { + error: Priority::Error, + warn: Priority::Warning, + info: Priority::Notice, + debug: Priority::Informational, + trace: Priority::Debug, + } + } +} + +impl Default for PriorityMappings { + fn default() -> Self { + Self::new() + } } fn put_metadata(buf: &mut Vec, meta: &Metadata, prefix: Option<&str>) { From f7cfd4b49ce515a884080eb0560cae3988bbf789 Mon Sep 17 00:00:00 2001 From: little-dude Date: Wed, 26 Oct 2022 14:37:36 +0200 Subject: [PATCH 2/4] tracing-journald: clean up documentation Co-authored-by: Eliza Weisman --- tracing-journald/src/lib.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tracing-journald/src/lib.rs b/tracing-journald/src/lib.rs index 93769d9c96..43057e8611 100644 --- a/tracing-journald/src/lib.rs +++ b/tracing-journald/src/lib.rs @@ -133,7 +133,7 @@ impl Subscriber { self } - /// Sets the mappings from the tracing level to the journald priorities. + /// Sets how [`tracing::Level`]s are mapped to [journald priorities](Priority). pub fn with_priority_mappings(mut self, mappings: PriorityMappings) -> Self { self.priority_mappings = mappings; self @@ -363,7 +363,7 @@ impl Visit for EventVisitor<'_> { } } -/// A priority (in syslog called severity code) is used to mark the +/// A priority (called "severity code" by syslog) is used to mark the /// importance of a message. /// /// Descriptions and examples are taken from the [Arch Linux wiki]. @@ -431,7 +431,9 @@ pub enum Priority { Debug = b'7', } -/// Mappings from the tracing levels to the journald priorities. +/// Mappings from tracing [`Level`]s to journald [priorities]. +/// +/// [priorities]: Priority #[derive(Debug, Clone)] pub struct PriorityMappings { /// Priority mapped to the `ERROR` level @@ -447,13 +449,13 @@ pub struct PriorityMappings { } impl PriorityMappings { - /// Create new default mappings: + /// Returns the default priority mappings: /// - /// - `tracing::Level::ERROR`: Error (3) - /// - `tracing::Level::WARN`: Warning (4) - /// - `tracing::Level::INFO`: Notice (5) - /// - `tracing::Level::DEBUG`: Informational (6) - /// - `tracing::Level::TRACE`: Debug (7) + /// - [`tracing::Level::ERROR`]: [`Priority::Error`] (3) + /// - [`tracing::Level::WARN`]: [`Priority::Warning`] (4) + /// - [`tracing::Level::INFO`]: [`Priority::Notice`] (5) + /// - [`tracing::Level::DEBUG`]: [`Priority::Informational`] (6) + /// - [`tracing::Level::TRACE`]: [`Priority::Debug`] (7) pub fn new() -> PriorityMappings { Self { error: Priority::Error, From cb359125a84a829db839dbaf544538d7a34f2531 Mon Sep 17 00:00:00 2001 From: little-dude Date: Wed, 26 Oct 2022 14:37:36 +0200 Subject: [PATCH 3/4] tracing-journald: add test for custom priorities --- tracing-journald/tests/journal.rs | 50 +++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/tracing-journald/tests/journal.rs b/tracing-journald/tests/journal.rs index dadb37aac3..92c1c85a8b 100644 --- a/tracing-journald/tests/journal.rs +++ b/tracing-journald/tests/journal.rs @@ -5,8 +5,8 @@ use std::process::Command; use std::time::Duration; use serde::Deserialize; -use tracing::{debug, error, info, info_span, warn}; -use tracing_journald::Subscriber; +use tracing::{debug, error, info, info_span, trace, warn}; +use tracing_journald::{Priority, PriorityMappings, Subscriber}; use tracing_subscriber::subscribe::CollectExt; use tracing_subscriber::Registry; @@ -16,7 +16,16 @@ fn journalctl_version() -> std::io::Result { } fn with_journald(f: impl FnOnce()) { - with_journald_subscriber(Subscriber::new().unwrap().with_field_prefix(None), f) + with_journald_subscriber( + Subscriber::new() + .unwrap() + .with_field_prefix(None) + .with_priority_mappings(PriorityMappings { + trace: Priority::Informational, + ..PriorityMappings::new() + }), + f, + ) } fn with_journald_subscriber(subscriber: Subscriber, f: impl FnOnce()) { @@ -167,6 +176,41 @@ fn simple_message() { }); } +#[test] +fn custom_priorities() { + fn check_message(level: &str, priority: &str) { + let entry = retry_read_one_line_from_journal(&format!("custom_priority.{}", level)); + assert_eq!(entry["MESSAGE"], format!("hello {}", level).as_str()); + assert_eq!(entry["PRIORITY"], priority); + } + + let priorities = PriorityMappings { + error: Priority::Critical, + warn: Priority::Error, + info: Priority::Warning, + debug: Priority::Notice, + trace: Priority::Informational, + }; + let subscriber = Subscriber::new() + .unwrap() + .with_field_prefix(None) + .with_priority_mappings(priorities); + let test = || { + trace!(test.name = "custom_priority.trace", "hello trace"); + check_message("trace", "6"); + debug!(test.name = "custom_priority.debug", "hello debug"); + check_message("debug", "5"); + info!(test.name = "custom_priority.info", "hello info"); + check_message("info", "4"); + warn!(test.name = "custom_priority.warn", "hello warn"); + check_message("warn", "3"); + error!(test.name = "custom_priority.error", "hello error"); + check_message("error", "2"); + }; + + with_journald_subscriber(subscriber, test); +} + #[test] fn multiline_message() { with_journald(|| { From 78e545e1843e11d6f22591002e9a1b2df55213d2 Mon Sep 17 00:00:00 2001 From: little-dude Date: Wed, 26 Oct 2022 14:37:36 +0200 Subject: [PATCH 4/4] tracing-journald: update example with custom priority mappings --- examples/examples/journald.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/examples/journald.rs b/examples/examples/journald.rs index ba811ac4a1..8f03b952de 100644 --- a/examples/examples/journald.rs +++ b/examples/examples/journald.rs @@ -1,5 +1,6 @@ #![deny(rust_2018_idioms)] use tracing::{error, info}; +use tracing_journald::{Priority, PriorityMappings}; use tracing_subscriber::prelude::*; #[path = "fmt/yak_shave.rs"] @@ -10,7 +11,17 @@ fn main() { .with(tracing_subscriber::fmt::subscriber().with_target(false)); match tracing_journald::subscriber() { Ok(subscriber) => { - registry.with(subscriber).init(); + registry + .with( + subscriber + // We can tweak the mappings between the trace level and + // the journal priorities. + .with_priority_mappings(PriorityMappings { + info: Priority::Informational, + ..PriorityMappings::new() + }), + ) + .init(); } // journald is typically available on Linux systems, but nowhere else. Portable software // should handle its absence gracefully.