use std::borrow::Cow;
use std::fmt;
use tracing::{Event, Subscriber};
use tracing_subscriber::Layer;
use tracing_subscriber::layer::Context;
use tracing_subscriber::registry::LookupSpan;
use crate::format::event::{EventInput, render_event};
use crate::format::span_chain::SpanLink;
use crate::format::{FormatConfig, TimestampFormat, syslog_prefix};
use crate::output::Output;
use crate::visit::{FieldMap, FieldStorage, FieldVisitor};
#[cfg(feature = "colors")]
use crate::format::color::{ColorMode, ColorTheme};
pub struct SystemdLayer {
config: FormatConfig,
output: Output,
#[cfg(feature = "colors")]
color_mode: ColorMode,
#[cfg(feature = "colors")]
color_theme: ColorTheme,
}
impl fmt::Debug for SystemdLayer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("SystemdLayer");
d.field("config", &self.config).field("output", &self.output);
#[cfg(feature = "colors")]
d.field("color_mode", &self.color_mode)
.field("color_theme", &self.color_theme);
d.finish()
}
}
impl Default for SystemdLayer {
fn default() -> Self {
Self::stdout()
}
}
impl SystemdLayer {
#[must_use]
pub fn stdout() -> Self {
Self {
config: FormatConfig::default(),
output: Output::stdout(),
#[cfg(feature = "colors")]
color_mode: ColorMode::default(),
#[cfg(feature = "colors")]
color_theme: ColorTheme::default(),
}
}
#[must_use]
pub fn stderr() -> Self {
Self {
config: FormatConfig::default(),
output: Output::stderr(),
#[cfg(feature = "colors")]
color_mode: ColorMode::default(),
#[cfg(feature = "colors")]
color_theme: ColorTheme::default(),
}
}
#[must_use]
pub fn unit_stdout() -> Self {
Self {
config: FormatConfig {
use_level_prefix: true,
..FormatConfig::default()
},
output: Output::stdout(),
#[cfg(feature = "colors")]
color_mode: ColorMode::Never,
#[cfg(feature = "colors")]
color_theme: ColorTheme::monochrome(),
}
}
}
impl SystemdLayer {
#[must_use]
pub fn with_output(mut self, output: Output) -> Self {
self.output = output;
self
}
#[must_use]
pub fn with_target(mut self, show: bool) -> Self {
self.config.show_target = show;
self
}
#[must_use]
pub fn with_thread_ids(mut self, show: bool) -> Self {
self.config.show_thread_id = show;
self
}
#[must_use]
pub fn with_timestamps(mut self, show: bool) -> Self {
self.config.show_timestamp = show;
self
}
#[must_use]
pub fn with_timestamp_format(mut self, format: TimestampFormat) -> Self {
self.config.timestamp_format = format;
if format != TimestampFormat::None {
self.config.show_timestamp = true;
}
self
}
#[must_use]
pub fn with_level_prefix(mut self, use_prefix: bool) -> Self {
self.config.use_level_prefix = use_prefix;
self
}
#[must_use]
pub fn with_span_separator(mut self, sep: impl Into<Cow<'static, str>>) -> Self {
self.config.span_separator = sep.into();
self
}
#[must_use]
pub fn with_message_separator(mut self, sep: impl Into<Cow<'static, str>>) -> Self {
self.config.message_separator = sep.into();
self
}
#[must_use]
pub fn with_level_separator(mut self, sep: impl Into<Cow<'static, str>>) -> Self {
self.config.level_separator = sep.into();
self
}
#[must_use]
pub fn with_function_bracket_left(mut self, s: impl Into<Cow<'static, str>>) -> Self {
self.config.function_bracket_left = s.into();
self
}
#[must_use]
pub fn with_function_bracket_right(mut self, s: impl Into<Cow<'static, str>>) -> Self {
self.config.function_bracket_right = s.into();
self
}
#[must_use]
pub fn with_arguments_equality(mut self, s: impl Into<Cow<'static, str>>) -> Self {
self.config.arguments_equality = s.into();
self
}
#[must_use]
pub fn with_arguments_separator(mut self, s: impl Into<Cow<'static, str>>) -> Self {
self.config.arguments_separator = s.into();
self
}
#[must_use]
pub fn with_thread_id_prefix(mut self, s: impl Into<Cow<'static, str>>) -> Self {
self.config.thread_id_prefix = s.into();
self
}
#[must_use]
pub fn with_thread_id_suffix(mut self, s: impl Into<Cow<'static, str>>) -> Self {
self.config.thread_id_suffix = s.into();
self
}
#[cfg(feature = "colors")]
#[cfg_attr(docsrs, doc(cfg(feature = "colors")))]
#[must_use]
pub fn with_color_mode(mut self, mode: ColorMode) -> Self {
self.color_mode = mode;
self
}
#[cfg(feature = "colors")]
#[cfg_attr(docsrs, doc(cfg(feature = "colors")))]
#[must_use]
pub fn with_color_theme(mut self, theme: ColorTheme) -> Self {
self.color_theme = theme;
self
}
}
impl<S> Layer<S> for SystemdLayer
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
fn on_new_span(
&self,
attrs: &tracing::span::Attributes<'_>,
id: &tracing::span::Id,
ctx: Context<'_, S>,
) {
let mut fields = FieldMap::new();
attrs.record(&mut FieldVisitor::new(&mut fields));
if let Some(span) = ctx.span(id) {
span.extensions_mut().insert(FieldStorage(fields));
}
}
fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
let mut chain: Vec<SpanLink> = Vec::new();
if let Some(scope) = ctx.event_scope(event) {
for span in scope.from_root() {
let exts = span.extensions();
let fields = exts
.get::<FieldStorage>()
.map(|s| s.0.clone())
.unwrap_or_default();
chain.push(SpanLink {
name: span.name(),
fields,
});
}
}
let leaf = chain.last().cloned();
let parents: &[SpanLink] = if chain.is_empty() {
&[]
} else {
&chain[..chain.len() - 1]
};
let mut event_fields = FieldMap::new();
event.record(&mut FieldVisitor::new(&mut event_fields));
let metadata = event.metadata();
let level = *metadata.level();
let input = EventInput {
level,
target: metadata.target(),
parents,
leaf: leaf.as_ref(),
fields: &event_fields,
};
#[cfg(feature = "colors")]
let line = {
let use_color = self.color_mode.resolve_now(self.output.is_terminal());
let theme = if use_color { Some(&self.color_theme) } else { None };
render_event(&self.config, &input, theme)
};
#[cfg(not(feature = "colors"))]
let line = render_event(&self.config, &input);
if self.config.use_level_prefix {
self.output.write_line(&format!("{}{}", syslog_prefix(level), line));
} else {
self.output.write_line(&line);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use std::sync::{Arc, Mutex};
use tracing::{Level, info, info_span, warn};
use tracing_subscriber::prelude::*;
#[derive(Clone, Default)]
struct Buf(Arc<Mutex<Vec<u8>>>);
impl Write for Buf {
fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
self.0.lock().unwrap().extend_from_slice(b);
Ok(b.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
fn capture<F: FnOnce()>(layer: SystemdLayer, body: F) -> String {
let buf = Buf::default();
let captured = buf.0.clone();
let layer = layer.with_output(Output::writer(buf));
tracing::subscriber::with_default(tracing_subscriber::registry().with(layer), body);
let bytes = captured.lock().unwrap().clone();
String::from_utf8(bytes).expect("utf-8 output")
}
#[test]
fn bare_info_event() {
let layer = SystemdLayer::stdout().with_level_prefix(false);
let out = capture(layer, || {
info!("hello");
});
assert!(out.ends_with(": hello\n"), "got {out:?}");
assert!(out.starts_with("INFO "), "got {out:?}");
let _ = Level::INFO; }
#[test]
fn span_arguments_appear_in_output() {
let layer = SystemdLayer::stdout().with_level_prefix(false);
let out = capture(layer, || {
let span = info_span!("worker", id = 7u64);
let _g = span.enter();
warn!("done");
});
assert!(out.contains("worker(id: 7)"), "got {out:?}");
assert!(out.contains("done"), "got {out:?}");
}
#[test]
fn level_prefix_emits_syslog_marker() {
let layer = SystemdLayer::stdout().with_level_prefix(true);
let out = capture(layer, || {
info!("p");
});
assert!(out.starts_with("<5>INFO"), "got {out:?}");
}
}