journald: allow custom journal fields (#2708)

It's currently not possible to customize how messages will get send to journald.

This became apparent in #2425, where first a specific API got designed, but then
it was decided that users should not get restricted in only a subset of fields,
but should be able to simply choose by themselves what fields get set with what
values.

So in a sense, this is the successor/rework of #2425.

Allow custom fields to be set in tracing-journald.

- [x] How should we deal with fields that also get supplied by other options?
  For example, setting `SYSLOG_IDENTIFIER` here and also setting
  `.with_syslog_identifier()` will send said field twice, potentially with
  differing values. Is that a problem?
    - Answer: No, this is not a problem.

Closes #2425
This commit is contained in:
Finomnis 2023-09-05 22:13:43 +02:00 committed by Eliza Weisman
parent b8180dd886
commit 7666f6c453
2 changed files with 59 additions and 0 deletions

View File

@ -83,6 +83,7 @@ pub struct Layer {
socket: UnixDatagram,
field_prefix: Option<String>,
syslog_identifier: String,
additional_fields: Vec<u8>,
}
#[cfg(unix)]
@ -107,6 +108,7 @@ impl Layer {
.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),
additional_fields: Vec::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.
@ -148,6 +150,40 @@ impl Layer {
self
}
/// Adds fields that will get be passed to journald with every log entry.
///
/// The input values of this function are interpreted as `(field, value)` pairs.
///
/// This can for example be used to configure the syslog facility.
/// See [Journal Fields](https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html)
/// and [journalctl](https://www.freedesktop.org/software/systemd/man/journalctl.html)
/// for more information.
///
/// Fields specified using this method will be added to the journald
/// message alongside fields generated from the event's fields, its
/// metadata, and the span context. If the name of a field provided using
/// this method is the same as the name of a field generated by the
/// layer, both fields will be sent to journald.
///
/// ```no_run
/// # use tracing_journald::Layer;
/// let layer = Layer::new()
/// .unwrap()
/// .with_custom_fields([("SYSLOG_FACILITY", "17")]);
/// ```
///
pub fn with_custom_fields<T: AsRef<str>, U: AsRef<[u8]>>(
mut self,
fields: impl IntoIterator<Item = (T, U)>,
) -> Self {
for (name, value) in fields {
put_field_length_encoded(&mut self.additional_fields, name.as_ref(), |buf| {
buf.extend_from_slice(value.as_ref())
})
}
self
}
/// Returns the syslog identifier in use.
pub fn syslog_identifier(&self) -> &str {
&self.syslog_identifier
@ -255,6 +291,7 @@ where
put_field_length_encoded(&mut buf, "SYSLOG_IDENTIFIER", |buf| {
write!(buf, "{}", self.syslog_identifier).unwrap()
});
buf.extend_from_slice(&self.additional_fields);
event.record(&mut EventVisitor::new(
&mut buf,

View File

@ -238,6 +238,28 @@ fn simple_metadata() {
});
}
#[test]
fn journal_fields() {
let sub = Layer::new()
.unwrap()
.with_field_prefix(None)
.with_custom_fields([("SYSLOG_FACILITY", "17")])
.with_custom_fields([("ABC", "dEf"), ("XYZ", "123")]);
with_journald_layer(sub, || {
info!(test.name = "journal_fields", "Hello World");
let message = retry_read_one_line_from_journal("journal_fields");
assert_eq!(message["MESSAGE"], "Hello World");
assert_eq!(message["PRIORITY"], "5");
assert_eq!(message["TARGET"], "journal");
assert_eq!(message["SYSLOG_FACILITY"], "17");
assert_eq!(message["ABC"], "dEf");
assert_eq!(message["XYZ"], "123");
assert!(message["CODE_FILE"].as_text().is_some());
assert!(message["CODE_LINE"].as_text().is_some());
});
}
#[test]
fn span_metadata() {
with_journald(|| {