Skip to content

Commit 07d3404

Browse files
kaffarellhds
authored andcommitted
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 af54fc9 commit 07d3404

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
@@ -60,14 +60,16 @@ mod socket;
6060
/// names by translating `.`s into `_`s, stripping leading `_`s and non-ascii-alphanumeric
6161
/// characters other than `_`, and upcasing.
6262
///
63-
/// Levels are mapped losslessly to journald `PRIORITY` values as follows:
63+
/// By default, levels are mapped losslessly to journald `PRIORITY` values as follows:
6464
///
6565
/// - `ERROR` => Error (3)
6666
/// - `WARN` => Warning (4)
6767
/// - `INFO` => Notice (5)
6868
/// - `DEBUG` => Informational (6)
6969
/// - `TRACE` => Debug (7)
7070
///
71+
/// These mappings can be changed with [`Subscriber::with_priority_mappings`].
72+
///
7173
/// The standard journald `CODE_LINE` and `CODE_FILE` fields are automatically emitted. A `TARGET`
7274
/// field is emitted containing the event's target.
7375
///
@@ -84,6 +86,7 @@ pub struct Layer {
8486
field_prefix: Option<String>,
8587
syslog_identifier: String,
8688
additional_fields: Vec<u8>,
89+
priority_mappings: PriorityMappings,
8790
}
8891

8992
#[cfg(unix)]
@@ -109,6 +112,7 @@ impl Layer {
109112
// If we fail to get the name of the current executable fall back to an empty string.
110113
.unwrap_or_default(),
111114
additional_fields: Vec::new(),
115+
priority_mappings: PriorityMappings::new(),
112116
};
113117
// Check that we can talk to journald, by sending empty payload which journald discards.
114118
// However if the socket didn't exist or if none listened we'd get an error here.
@@ -129,6 +133,41 @@ impl Layer {
129133
self
130134
}
131135

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

237290
/// Construct a journald layer
@@ -286,7 +339,7 @@ where
286339
}
287340

288341
// Record event fields
289-
put_priority(&mut buf, event.metadata());
342+
self.put_priority(&mut buf, event.metadata());
290343
put_metadata(&mut buf, event.metadata(), None);
291344
put_field_length_encoded(&mut buf, "SYSLOG_IDENTIFIER", |buf| {
292345
write!(buf, "{}", self.syslog_identifier).unwrap()
@@ -374,18 +427,114 @@ impl Visit for EventVisitor<'_> {
374427
}
375428
}
376429

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

391540
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
@@ -6,8 +6,8 @@ use std::time::Duration;
66

77
use serde::Deserialize;
88

9-
use tracing::{debug, error, info, info_span, warn};
10-
use tracing_journald::Layer;
9+
use tracing::{debug, error, info, info_span, trace, warn};
10+
use tracing_journald::{Layer, Priority, PriorityMappings};
1111
use tracing_subscriber::layer::SubscriberExt;
1212
use tracing_subscriber::Registry;
1313

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

1919
fn with_journald(f: impl FnOnce()) {
20-
with_journald_layer(Layer::new().unwrap().with_field_prefix(None), f)
20+
with_journald_layer(
21+
Layer::new()
22+
.unwrap()
23+
.with_field_prefix(None)
24+
.with_priority_mappings(PriorityMappings {
25+
trace: Priority::Informational,
26+
..PriorityMappings::new()
27+
}),
28+
f,
29+
)
2130
}
2231

2332
fn with_journald_layer(layer: Layer, f: impl FnOnce()) {
@@ -168,6 +177,41 @@ fn simple_message() {
168177
});
169178
}
170179

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

0 commit comments

Comments
 (0)