Skip to content
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

journald: make level mappings configurable #2824

Merged
merged 4 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 163 additions & 14 deletions tracing-journald/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,16 @@ 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)
/// - `INFO` => Notice (5)
/// - `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.
///
Expand All @@ -86,6 +88,7 @@ pub struct Subscriber {
field_prefix: Option<String>,
syslog_identifier: String,
additional_fields: Vec<u8>,
priority_mappings: PriorityMappings,
}

#[cfg(unix)]
Expand All @@ -111,6 +114,7 @@ impl Subscriber {
// If we fail to get the name of the current executable fall back to an empty string.
.unwrap_or_default(),
additional_fields: Vec::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.
Expand All @@ -131,6 +135,41 @@ impl Subscriber {
self
}

/// Sets how [`tracing_core::Level`]s are mapped to [journald priorities](Priority).
///
/// # Examples
///
/// ```rust
/// use tracing_journald::{Priority, PriorityMappings};
/// use tracing_subscriber::prelude::*;
/// use tracing::error;
///
///let registry = tracing_subscriber::registry();
///match tracing_journald::subscriber() {
kaffarell marked this conversation as resolved.
Show resolved Hide resolved
/// Ok(subscriber) => {
/// 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()
/// }),
/// );
/// }
/// // journald is typically available on Linux systems, but nowhere else. Portable software
/// // should handle its absence gracefully.
/// Err(e) => {
/// registry.init();
/// error!("couldn't connect to journald: {}", e);
/// }
///}
/// ```
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()`
Expand Down Expand Up @@ -234,6 +273,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<u8>, 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
Expand Down Expand Up @@ -288,7 +341,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()
Expand Down Expand Up @@ -376,18 +429,114 @@ impl Visit for EventVisitor<'_> {
}
}

fn put_priority(buf: &mut Vec<u8>, 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 (called "severity code" by syslog) 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 tracing [`Level`]s to journald [priorities].
///
/// [priorities]: Priority
#[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 {
/// Returns the default priority mappings:
///
/// - [`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,
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<u8>, meta: &Metadata, prefix: Option<&str>) {
Expand Down
50 changes: 47 additions & 3 deletions tracing-journald/tests/journal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -16,7 +16,16 @@ fn journalctl_version() -> std::io::Result<String> {
}

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()) {
Expand Down Expand Up @@ -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(|| {
Expand Down
Loading