Skip to content

Commit d4ca988

Browse files
authoredJan 11, 2024
journald: make level mappings configurable (#2824)
This allows to manually map tracing levels to journald levels. It seems that @little-dude, who started the original PR, doesn't have time to finish this, so I picked it up. Reapplied the changes to the newest master branch and fixed the latest comments/issues. This will also fix/close: Closes #2649 Closes #2661 Closes #2347 (the original pr)
1 parent 07b4900 commit d4ca988

File tree

2 files changed

+210
-17
lines changed

2 files changed

+210
-17
lines changed
 

‎tracing-journald/src/lib.rs

+163-14
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,16 @@ mod socket;
6262
/// names by translating `.`s into `_`s, stripping leading `_`s and non-ascii-alphanumeric
6363
/// characters other than `_`, and upcasing.
6464
///
65-
/// Levels are mapped losslessly to journald `PRIORITY` values as follows:
65+
/// By default, levels are mapped losslessly to journald `PRIORITY` values as follows:
6666
///
6767
/// - `ERROR` => Error (3)
6868
/// - `WARN` => Warning (4)
6969
/// - `INFO` => Notice (5)
7070
/// - `DEBUG` => Informational (6)
7171
/// - `TRACE` => Debug (7)
7272
///
73+
/// These mappings can be changed with [`Subscriber::with_priority_mappings`].
74+
///
7375
/// The standard journald `CODE_LINE` and `CODE_FILE` fields are automatically emitted. A `TARGET`
7476
/// field is emitted containing the event's target.
7577
///
@@ -86,6 +88,7 @@ pub struct Subscriber {
8688
field_prefix: Option<String>,
8789
syslog_identifier: String,
8890
additional_fields: Vec<u8>,
91+
priority_mappings: PriorityMappings,
8992
}
9093

9194
#[cfg(unix)]
@@ -111,6 +114,7 @@ impl Subscriber {
111114
// If we fail to get the name of the current executable fall back to an empty string.
112115
.unwrap_or_default(),
113116
additional_fields: Vec::new(),
117+
priority_mappings: PriorityMappings::new(),
114118
};
115119
// Check that we can talk to journald, by sending empty payload which journald discards.
116120
// However if the socket didn't exist or if none listened we'd get an error here.
@@ -131,6 +135,41 @@ impl Subscriber {
131135
self
132136
}
133137

138+
/// Sets how [`tracing_core::Level`]s are mapped to [journald priorities](Priority).
139+
///
140+
/// # Examples
141+
///
142+
/// ```rust
143+
/// use tracing_journald::{Priority, PriorityMappings};
144+
/// use tracing_subscriber::prelude::*;
145+
/// use tracing::error;
146+
///
147+
/// let registry = tracing_subscriber::registry();
148+
/// match tracing_journald::subscriber() {
149+
/// Ok(subscriber) => {
150+
/// registry.with(
151+
/// subscriber
152+
/// // We can tweak the mappings between the trace level and
153+
/// // the journal priorities.
154+
/// .with_priority_mappings(PriorityMappings {
155+
/// info: Priority::Informational,
156+
/// ..PriorityMappings::new()
157+
/// }),
158+
/// );
159+
/// }
160+
/// // journald is typically available on Linux systems, but nowhere else. Portable software
161+
/// // should handle its absence gracefully.
162+
/// Err(e) => {
163+
/// registry.init();
164+
/// error!("couldn't connect to journald: {}", e);
165+
/// }
166+
/// }
167+
/// ```
168+
pub fn with_priority_mappings(mut self, mappings: PriorityMappings) -> Self {
169+
self.priority_mappings = mappings;
170+
self
171+
}
172+
134173
/// Sets the syslog identifier for this logger.
135174
///
136175
/// The syslog identifier comes from the classic syslog interface (`openlog()`
@@ -234,6 +273,20 @@ impl Subscriber {
234273
memfd::seal_fully(mem.as_raw_fd())?;
235274
socket::send_one_fd_to(&self.socket, mem.as_raw_fd(), JOURNALD_PATH)
236275
}
276+
277+
fn put_priority(&self, buf: &mut Vec<u8>, meta: &Metadata) {
278+
put_field_wellformed(
279+
buf,
280+
"PRIORITY",
281+
&[match *meta.level() {
282+
Level::ERROR => self.priority_mappings.error as u8,
283+
Level::WARN => self.priority_mappings.warn as u8,
284+
Level::INFO => self.priority_mappings.info as u8,
285+
Level::DEBUG => self.priority_mappings.debug as u8,
286+
Level::TRACE => self.priority_mappings.trace as u8,
287+
}],
288+
);
289+
}
237290
}
238291

239292
/// Construct a journald subscriber
@@ -288,7 +341,7 @@ where
288341
}
289342

290343
// Record event fields
291-
put_priority(&mut buf, event.metadata());
344+
self.put_priority(&mut buf, event.metadata());
292345
put_metadata(&mut buf, event.metadata(), None);
293346
put_field_length_encoded(&mut buf, "SYSLOG_IDENTIFIER", |buf| {
294347
write!(buf, "{}", self.syslog_identifier).unwrap()
@@ -376,18 +429,114 @@ impl Visit for EventVisitor<'_> {
376429
}
377430
}
378431

379-
fn put_priority(buf: &mut Vec<u8>, meta: &Metadata) {
380-
put_field_wellformed(
381-
buf,
382-
"PRIORITY",
383-
match *meta.level() {
384-
Level::ERROR => b"3",
385-
Level::WARN => b"4",
386-
Level::INFO => b"5",
387-
Level::DEBUG => b"6",
388-
Level::TRACE => b"7",
389-
},
390-
);
432+
/// A priority (called "severity code" by syslog) is used to mark the
433+
/// importance of a message.
434+
///
435+
/// Descriptions and examples are taken from the [Arch Linux wiki].
436+
/// Priorities are also documented in the
437+
/// [section 6.2.1 of the Syslog protocol RFC][syslog].
438+
///
439+
/// [Arch Linux wiki]: https://wiki.archlinux.org/title/Systemd/Journal#Priority_level
440+
/// [syslog]: https://www.rfc-editor.org/rfc/rfc5424#section-6.2.1
441+
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
442+
#[repr(u8)]
443+
pub enum Priority {
444+
/// System is unusable.
445+
///
446+
/// Examples:
447+
///
448+
/// - severe Kernel BUG
449+
/// - systemd dumped core
450+
///
451+
/// This level should not be used by applications.
452+
Emergency = b'0',
453+
/// Should be corrected immediately.
454+
///
455+
/// Examples:
456+
///
457+
/// - Vital subsystem goes out of work, data loss:
458+
/// - `kernel: BUG: unable to handle kernel paging request at ffffc90403238ffc`
459+
Alert = b'1',
460+
/// Critical conditions
461+
///
462+
/// Examples:
463+
///
464+
/// - Crashe, coredumps
465+
/// - `systemd-coredump[25319]: Process 25310 (plugin-container) of user 1000 dumped core`
466+
Critical = b'2',
467+
/// Error conditions
468+
///
469+
/// Examples:
470+
///
471+
/// - Not severe error reported
472+
/// - `kernel: usb 1-3: 3:1: cannot get freq at ep 0x84, systemd[1]: Failed unmounting /var`
473+
/// - `libvirtd[1720]: internal error: Failed to initialize a valid firewall backend`
474+
Error = b'3',
475+
/// May indicate that an error will occur if action is not taken.
476+
///
477+
/// Examples:
478+
///
479+
/// - a non-root file system has only 1GB free
480+
/// - `org.freedesktop. Notifications[1860]: (process:5999): Gtk-WARNING **: Locale not supported by C library. Using the fallback 'C' locale`
481+
Warning = b'4',
482+
/// Events that are unusual, but not error conditions.
483+
///
484+
/// Examples:
485+
///
486+
/// - `systemd[1]: var.mount: Directory /var to mount over is not empty, mounting anyway`
487+
/// - `gcr-prompter[4997]: Gtk: GtkDialog mapped without a transient parent. This is discouraged`
488+
Notice = b'5',
489+
/// Normal operational messages that require no action.
490+
///
491+
/// Example: `lvm[585]: 7 logical volume(s) in volume group "archvg" now active`
492+
Informational = b'6',
493+
/// Information useful to developers for debugging the
494+
/// application.
495+
///
496+
/// Example: `kdeinit5[1900]: powerdevil: Scheduling inhibition from ":1.14" "firefox" with cookie 13 and reason "screen"`
497+
Debug = b'7',
498+
}
499+
500+
/// Mappings from tracing [`Level`]s to journald [priorities].
501+
///
502+
/// [priorities]: Priority
503+
#[derive(Debug, Clone)]
504+
pub struct PriorityMappings {
505+
/// Priority mapped to the `ERROR` level
506+
pub error: Priority,
507+
/// Priority mapped to the `WARN` level
508+
pub warn: Priority,
509+
/// Priority mapped to the `INFO` level
510+
pub info: Priority,
511+
/// Priority mapped to the `DEBUG` level
512+
pub debug: Priority,
513+
/// Priority mapped to the `TRACE` level
514+
pub trace: Priority,
515+
}
516+
517+
impl PriorityMappings {
518+
/// Returns the default priority mappings:
519+
///
520+
/// - [`tracing::Level::ERROR`]: [`Priority::Error`] (3)
521+
/// - [`tracing::Level::WARN`]: [`Priority::Warning`] (4)
522+
/// - [`tracing::Level::INFO`]: [`Priority::Notice`] (5)
523+
/// - [`tracing::Level::DEBUG`]: [`Priority::Informational`] (6)
524+
/// - [`tracing::Level::TRACE`]: [`Priority::Debug`] (7)
525+
pub fn new() -> PriorityMappings {
526+
Self {
527+
error: Priority::Error,
528+
warn: Priority::Warning,
529+
info: Priority::Notice,
530+
debug: Priority::Informational,
531+
trace: Priority::Debug,
532+
}
533+
}
534+
}
535+
536+
impl Default for PriorityMappings {
537+
fn default() -> Self {
538+
Self::new()
539+
}
391540
}
392541

393542
fn put_metadata(buf: &mut Vec<u8>, meta: &Metadata, prefix: Option<&str>) {

‎tracing-journald/tests/journal.rs

+47-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use std::process::Command;
55
use std::time::Duration;
66

77
use serde::Deserialize;
8-
use tracing::{debug, error, info, info_span, warn};
9-
use tracing_journald::Subscriber;
8+
use tracing::{debug, error, info, info_span, trace, warn};
9+
use tracing_journald::{Priority, PriorityMappings, Subscriber};
1010
use tracing_subscriber::subscribe::CollectExt;
1111
use tracing_subscriber::Registry;
1212

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

1818
fn with_journald(f: impl FnOnce()) {
19-
with_journald_subscriber(Subscriber::new().unwrap().with_field_prefix(None), f)
19+
with_journald_subscriber(
20+
Subscriber::new()
21+
.unwrap()
22+
.with_field_prefix(None)
23+
.with_priority_mappings(PriorityMappings {
24+
trace: Priority::Informational,
25+
..PriorityMappings::new()
26+
}),
27+
f,
28+
)
2029
}
2130

2231
fn with_journald_subscriber(subscriber: Subscriber, f: impl FnOnce()) {
@@ -167,6 +176,41 @@ fn simple_message() {
167176
});
168177
}
169178

179+
#[test]
180+
fn custom_priorities() {
181+
fn check_message(level: &str, priority: &str) {
182+
let entry = retry_read_one_line_from_journal(&format!("custom_priority.{}", level));
183+
assert_eq!(entry["MESSAGE"], format!("hello {}", level).as_str());
184+
assert_eq!(entry["PRIORITY"], priority);
185+
}
186+
187+
let priorities = PriorityMappings {
188+
error: Priority::Critical,
189+
warn: Priority::Error,
190+
info: Priority::Warning,
191+
debug: Priority::Notice,
192+
trace: Priority::Informational,
193+
};
194+
let subscriber = Subscriber::new()
195+
.unwrap()
196+
.with_field_prefix(None)
197+
.with_priority_mappings(priorities);
198+
let test = || {
199+
trace!(test.name = "custom_priority.trace", "hello trace");
200+
check_message("trace", "6");
201+
debug!(test.name = "custom_priority.debug", "hello debug");
202+
check_message("debug", "5");
203+
info!(test.name = "custom_priority.info", "hello info");
204+
check_message("info", "4");
205+
warn!(test.name = "custom_priority.warn", "hello warn");
206+
check_message("warn", "3");
207+
error!(test.name = "custom_priority.error", "hello error");
208+
check_message("error", "2");
209+
};
210+
211+
with_journald_subscriber(subscriber, test);
212+
}
213+
170214
#[test]
171215
fn multiline_message() {
172216
with_journald(|| {

0 commit comments

Comments
 (0)
Please sign in to comment.