Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
143 changes: 141 additions & 2 deletions src/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ use std::io::prelude::Write;
use std::rc::Rc;
use std::{fmt, io, mem};

#[cfg(feature = "color")]
use log::Level;
use log::Record;

Expand All @@ -84,6 +83,18 @@ pub use crate::writer::WriteStyle;

use crate::writer::{Buffer, Writer};

/// Controls whether systemd journal priority prefixes are prepended to log lines.
#[derive(Copy, Clone, Debug, Default)]
pub enum JournalPrefix {
/// Prepend `<N>` prefix based on log level when `JOURNAL_STREAM` is set.
#[default]
Auto,
/// Always prepend `<N>` prefix.
Enable,
/// Never prepend prefix.
Disable,
}

/// Formatting precision of timestamps.
///
/// Seconds give precision of full seconds, milliseconds give thousands of a
Expand Down Expand Up @@ -218,6 +229,7 @@ pub(crate) type FormatFn = Box<dyn RecordFormat + Sync + Send>;
pub(crate) struct Builder {
pub(crate) default_format: ConfigurableFormat,
pub(crate) custom_format: Option<FormatFn>,
pub(crate) journal_prefix: JournalPrefix,
built: bool,
}

Expand All @@ -241,7 +253,13 @@ impl Builder {
if let Some(fmt) = built.custom_format {
fmt
} else {
Box::new(built.default_format)
let mut format = built.default_format;
format.journal_prefix = match built.journal_prefix {
JournalPrefix::Enable => true,
JournalPrefix::Disable => false,
JournalPrefix::Auto => std::env::var_os("JOURNAL_STREAM").is_some(),
};
Box::new(format)
}
}
}
Expand Down Expand Up @@ -286,6 +304,7 @@ pub struct ConfigurableFormat {
pub(crate) source_line_number: bool,
pub(crate) indent: Option<usize>,
pub(crate) suffix: &'static str,
pub(crate) journal_prefix: bool,
#[cfg(feature = "kv")]
pub(crate) kv_format: Option<Box<KvFormatFn>>,
}
Expand Down Expand Up @@ -355,6 +374,12 @@ impl ConfigurableFormat {
self
}

/// Whether or not to prepend systemd journal priority prefixes (e.g. `<3>` for error).
pub fn journal_prefix(&mut self, write: bool) -> &mut Self {
self.journal_prefix = write;
self
}

/// Set the format for structured key/value pairs in the log record
///
/// With the default format, this function is called for each record and should format
Expand Down Expand Up @@ -386,6 +411,7 @@ impl Default for ConfigurableFormat {
source_line_number: false,
indent: Some(4),
suffix: "\n",
journal_prefix: false,
#[cfg(feature = "kv")]
kv_format: None,
}
Expand All @@ -409,6 +435,7 @@ struct ConfigurableFormatWriter<'a> {

impl ConfigurableFormatWriter<'_> {
fn write(mut self, record: &Record<'_>) -> io::Result<()> {
self.write_journal_prefix(record)?;
self.write_timestamp()?;
self.write_level(record)?;
self.write_module_path(record)?;
Expand All @@ -422,6 +449,21 @@ impl ConfigurableFormatWriter<'_> {
write!(self.buf, "{}", self.format.suffix)
}

fn write_journal_prefix(&mut self, record: &Record<'_>) -> io::Result<()> {
if !self.format.journal_prefix {
return Ok(());
}

let priority = match record.level() {
Level::Error => 3,
Level::Warn => 4,
Level::Info => 6,
Level::Debug | Level::Trace => 7,
};

write!(self.buf, "<{priority}>")
}

fn subtle_style(&self, text: &'static str) -> SubtleStyle {
#[cfg(feature = "color")]
{
Expand Down Expand Up @@ -672,6 +714,7 @@ mod tests {
kv_format: Some(Box::new(hidden_kv_format)),
indent: None,
suffix: "\n",
journal_prefix: false,
},
written_header_value: false,
buf: &mut f,
Expand All @@ -696,6 +739,7 @@ mod tests {
kv_format: Some(Box::new(hidden_kv_format)),
indent: None,
suffix: "\n",
journal_prefix: false,
},
written_header_value: false,
buf: &mut f,
Expand All @@ -720,6 +764,7 @@ mod tests {
kv_format: Some(Box::new(hidden_kv_format)),
indent: Some(4),
suffix: "\n",
journal_prefix: false,
},
written_header_value: false,
buf: &mut f,
Expand All @@ -744,6 +789,7 @@ mod tests {
kv_format: Some(Box::new(hidden_kv_format)),
indent: Some(0),
suffix: "\n",
journal_prefix: false,
},
written_header_value: false,
buf: &mut f,
Expand All @@ -768,6 +814,7 @@ mod tests {
kv_format: Some(Box::new(hidden_kv_format)),
indent: Some(4),
suffix: "\n",
journal_prefix: false,
},
written_header_value: false,
buf: &mut f,
Expand All @@ -792,6 +839,7 @@ mod tests {
kv_format: Some(Box::new(hidden_kv_format)),
indent: None,
suffix: "\n\n",
journal_prefix: false,
},
written_header_value: false,
buf: &mut f,
Expand All @@ -816,6 +864,7 @@ mod tests {
kv_format: Some(Box::new(hidden_kv_format)),
indent: Some(4),
suffix: "\n\n",
journal_prefix: false,
},
written_header_value: false,
buf: &mut f,
Expand All @@ -842,6 +891,7 @@ mod tests {
kv_format: Some(Box::new(hidden_kv_format)),
indent: None,
suffix: "\n",
journal_prefix: false,
},
written_header_value: false,
buf: &mut f,
Expand All @@ -867,6 +917,7 @@ mod tests {
kv_format: Some(Box::new(hidden_kv_format)),
indent: None,
suffix: "\n",
journal_prefix: false,
},
written_header_value: false,
buf: &mut f,
Expand All @@ -893,6 +944,7 @@ mod tests {
kv_format: Some(Box::new(hidden_kv_format)),
indent: None,
suffix: "\n",
journal_prefix: false,
},
written_header_value: false,
buf: &mut f,
Expand All @@ -918,6 +970,7 @@ mod tests {
kv_format: Some(Box::new(hidden_kv_format)),
indent: None,
suffix: "\n",
journal_prefix: false,
},
written_header_value: false,
buf: &mut f,
Expand Down Expand Up @@ -999,4 +1052,90 @@ mod tests {
written
);
}

#[test]
fn format_journal_prefix_enabled() {
let mut f = formatter();

let written = write(ConfigurableFormatWriter {
format: &ConfigurableFormat {
timestamp: None,
module_path: false,
target: false,
level: true,
source_file: false,
source_line_number: false,
#[cfg(feature = "kv")]
kv_format: Some(Box::new(hidden_kv_format)),
indent: None,
suffix: "\n",
journal_prefix: true,
},
written_header_value: false,
buf: &mut f,
});

assert_eq!("<6>[INFO ] log\nmessage\n", written);
}

#[test]
fn format_journal_prefix_error_level() {
let mut f = formatter();

let record = Record::builder()
.args(format_args!("oops"))
.level(Level::Error)
.build();

let buf = f.buf.clone();

let fmt = ConfigurableFormatWriter {
format: &ConfigurableFormat {
timestamp: None,
module_path: false,
target: false,
level: false,
source_file: false,
source_line_number: false,
#[cfg(feature = "kv")]
kv_format: Some(Box::new(hidden_kv_format)),
indent: None,
suffix: "\n",
journal_prefix: true,
},
written_header_value: false,
buf: &mut f,
};

fmt.write(&record).unwrap();
let buf = buf.borrow();
let written = String::from_utf8(buf.as_bytes().to_vec()).unwrap();

assert_eq!("<3>oops\n", written);
}

#[test]
fn format_journal_prefix_disabled() {
let mut f = formatter();

let written = write(ConfigurableFormatWriter {
format: &ConfigurableFormat {
timestamp: None,
module_path: false,
target: false,
level: true,
source_file: false,
source_line_number: false,
#[cfg(feature = "kv")]
kv_format: Some(Box::new(hidden_kv_format)),
indent: None,
suffix: "\n",
journal_prefix: false,
},
written_header_value: false,
buf: &mut f,
});

assert_eq!("[INFO ] log\nmessage\n", written);
}
}
85 changes: 85 additions & 0 deletions src/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,22 @@ impl Builder {
self
}

/// Whether or not to prepend systemd journal priority prefixes.
///
/// Defaults to [`Auto`](fmt::JournalPrefix::Auto), which enables prefixes
/// when `JOURNAL_STREAM` is set.
///
/// ```
/// use env_logger::{Builder, fmt::JournalPrefix};
///
/// let mut builder = Builder::new();
/// builder.format_journal_prefix(JournalPrefix::Disable);
/// ```
pub fn format_journal_prefix(&mut self, value: fmt::JournalPrefix) -> &mut Self {
self.format.journal_prefix = value;
self
}

/// Set the format for structured key/value pairs in the log record
///
/// With the default format, this function is called for each record and should format
Expand Down Expand Up @@ -1055,4 +1071,73 @@ mod tests {

assert_eq!(builder.filter.build().filter(), LevelFilter::Debug);
}

#[test]
fn journal_prefix_auto_enabled_when_journal_stream_set() {
env::set_var("JOURNAL_STREAM", "8:12345");

let buf = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let buf2 = buf.clone();

let mut builder = Builder::new();
builder
.target(fmt::Target::Pipe(Box::new(SharedBuf(buf2))))
.format_timestamp(None)
.format_journal_prefix(fmt::JournalPrefix::Auto)
.filter_level(LevelFilter::Info);
let logger = builder.build();

logger.log(
&Record::builder()
.args(format_args!("hello"))
.level(log::Level::Warn)
.target("")
.build(),
);

let output = String::from_utf8(buf.lock().unwrap().clone()).unwrap();
assert!(output.starts_with("<4>"), "expected journal prefix, got: {output}");

env::remove_var("JOURNAL_STREAM");
}

#[test]
fn journal_prefix_auto_disabled_when_journal_stream_unset() {
env::remove_var("JOURNAL_STREAM");

let buf = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let buf2 = buf.clone();

let mut builder = Builder::new();
builder
.target(fmt::Target::Pipe(Box::new(SharedBuf(buf2))))
.format_timestamp(None)
.format_journal_prefix(fmt::JournalPrefix::Auto)
.filter_level(LevelFilter::Info);
let logger = builder.build();

logger.log(
&Record::builder()
.args(format_args!("hello"))
.level(log::Level::Warn)
.target("")
.build(),
);

let output = String::from_utf8(buf.lock().unwrap().clone()).unwrap();
assert!(!output.starts_with("<"), "unexpected journal prefix, got: {output}");
}

#[derive(Clone)]
struct SharedBuf(std::sync::Arc<std::sync::Mutex<Vec<u8>>>);

impl io::Write for SharedBuf {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.lock().unwrap().extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
}