1use std::borrow::Cow;
5use std::fmt;
6
7use tracing::{Event, Subscriber};
8use tracing_subscriber::Layer;
9use tracing_subscriber::layer::Context;
10use tracing_subscriber::registry::LookupSpan;
11
12use crate::format::event::{EventInput, render_event};
13use crate::format::span_chain::SpanLink;
14use crate::format::{FormatConfig, TimestampFormat, syslog_prefix};
15use crate::output::Output;
16use crate::visit::{FieldMap, FieldStorage, FieldVisitor};
17
18#[cfg(feature = "colors")]
19use crate::format::color::{ColorMode, ColorTheme};
20
21pub struct SystemdLayer {
35 config: FormatConfig,
36 output: Output,
37 #[cfg(feature = "colors")]
38 color_mode: ColorMode,
39 #[cfg(feature = "colors")]
40 color_theme: ColorTheme,
41}
42
43impl fmt::Debug for SystemdLayer {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 let mut d = f.debug_struct("SystemdLayer");
46 d.field("config", &self.config).field("output", &self.output);
47 #[cfg(feature = "colors")]
48 d.field("color_mode", &self.color_mode)
49 .field("color_theme", &self.color_theme);
50 d.finish()
51 }
52}
53
54impl Default for SystemdLayer {
55 fn default() -> Self {
56 Self::stdout()
57 }
58}
59
60impl SystemdLayer {
63 #[must_use]
67 pub fn stdout() -> Self {
68 Self {
69 config: FormatConfig::default(),
70 output: Output::stdout(),
71 #[cfg(feature = "colors")]
72 color_mode: ColorMode::default(),
73 #[cfg(feature = "colors")]
74 color_theme: ColorTheme::default(),
75 }
76 }
77
78 #[must_use]
80 pub fn stderr() -> Self {
81 Self {
82 config: FormatConfig::default(),
83 output: Output::stderr(),
84 #[cfg(feature = "colors")]
85 color_mode: ColorMode::default(),
86 #[cfg(feature = "colors")]
87 color_theme: ColorTheme::default(),
88 }
89 }
90
91 #[must_use]
100 pub fn unit_stdout() -> Self {
101 Self {
102 config: FormatConfig {
103 use_level_prefix: true,
104 ..FormatConfig::default()
105 },
106 output: Output::stdout(),
107 #[cfg(feature = "colors")]
108 color_mode: ColorMode::Never,
109 #[cfg(feature = "colors")]
110 color_theme: ColorTheme::monochrome(),
111 }
112 }
113}
114
115impl SystemdLayer {
118 #[must_use]
121 pub fn with_output(mut self, output: Output) -> Self {
122 self.output = output;
123 self
124 }
125
126 #[must_use]
129 pub fn with_target(mut self, show: bool) -> Self {
130 self.config.show_target = show;
131 self
132 }
133
134 #[must_use]
136 pub fn with_thread_ids(mut self, show: bool) -> Self {
137 self.config.show_thread_id = show;
138 self
139 }
140
141 #[must_use]
144 pub fn with_timestamps(mut self, show: bool) -> Self {
145 self.config.show_timestamp = show;
146 self
147 }
148
149 #[must_use]
152 pub fn with_timestamp_format(mut self, format: TimestampFormat) -> Self {
153 self.config.timestamp_format = format;
154 if format != TimestampFormat::None {
155 self.config.show_timestamp = true;
156 }
157 self
158 }
159
160 #[must_use]
164 pub fn with_level_prefix(mut self, use_prefix: bool) -> Self {
165 self.config.use_level_prefix = use_prefix;
166 self
167 }
168
169 #[must_use]
171 pub fn with_span_separator(mut self, sep: impl Into<Cow<'static, str>>) -> Self {
172 self.config.span_separator = sep.into();
173 self
174 }
175
176 #[must_use]
178 pub fn with_message_separator(mut self, sep: impl Into<Cow<'static, str>>) -> Self {
179 self.config.message_separator = sep.into();
180 self
181 }
182
183 #[must_use]
185 pub fn with_level_separator(mut self, sep: impl Into<Cow<'static, str>>) -> Self {
186 self.config.level_separator = sep.into();
187 self
188 }
189
190 #[must_use]
192 pub fn with_function_bracket_left(mut self, s: impl Into<Cow<'static, str>>) -> Self {
193 self.config.function_bracket_left = s.into();
194 self
195 }
196
197 #[must_use]
199 pub fn with_function_bracket_right(mut self, s: impl Into<Cow<'static, str>>) -> Self {
200 self.config.function_bracket_right = s.into();
201 self
202 }
203
204 #[must_use]
206 pub fn with_arguments_equality(mut self, s: impl Into<Cow<'static, str>>) -> Self {
207 self.config.arguments_equality = s.into();
208 self
209 }
210
211 #[must_use]
213 pub fn with_arguments_separator(mut self, s: impl Into<Cow<'static, str>>) -> Self {
214 self.config.arguments_separator = s.into();
215 self
216 }
217
218 #[must_use]
220 pub fn with_thread_id_prefix(mut self, s: impl Into<Cow<'static, str>>) -> Self {
221 self.config.thread_id_prefix = s.into();
222 self
223 }
224
225 #[must_use]
227 pub fn with_thread_id_suffix(mut self, s: impl Into<Cow<'static, str>>) -> Self {
228 self.config.thread_id_suffix = s.into();
229 self
230 }
231
232 #[cfg(feature = "colors")]
235 #[cfg_attr(docsrs, doc(cfg(feature = "colors")))]
236 #[must_use]
237 pub fn with_color_mode(mut self, mode: ColorMode) -> Self {
238 self.color_mode = mode;
239 self
240 }
241
242 #[cfg(feature = "colors")]
244 #[cfg_attr(docsrs, doc(cfg(feature = "colors")))]
245 #[must_use]
246 pub fn with_color_theme(mut self, theme: ColorTheme) -> Self {
247 self.color_theme = theme;
248 self
249 }
250}
251
252impl<S> Layer<S> for SystemdLayer
255where
256 S: Subscriber + for<'a> LookupSpan<'a>,
257{
258 fn on_new_span(
259 &self,
260 attrs: &tracing::span::Attributes<'_>,
261 id: &tracing::span::Id,
262 ctx: Context<'_, S>,
263 ) {
264 let mut fields = FieldMap::new();
265 attrs.record(&mut FieldVisitor::new(&mut fields));
266
267 if let Some(span) = ctx.span(id) {
268 span.extensions_mut().insert(FieldStorage(fields));
269 }
270 }
271
272 fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
273 let mut chain: Vec<SpanLink> = Vec::new();
277 if let Some(scope) = ctx.event_scope(event) {
278 for span in scope.from_root() {
279 let exts = span.extensions();
280 let fields = exts
281 .get::<FieldStorage>()
282 .map(|s| s.0.clone())
283 .unwrap_or_default();
284 chain.push(SpanLink {
285 name: span.name(),
286 fields,
287 });
288 }
289 }
290 let leaf = chain.last().cloned();
291 let parents: &[SpanLink] = if chain.is_empty() {
292 &[]
293 } else {
294 &chain[..chain.len() - 1]
295 };
296
297 let mut event_fields = FieldMap::new();
299 event.record(&mut FieldVisitor::new(&mut event_fields));
300
301 let metadata = event.metadata();
302 let level = *metadata.level();
303
304 let input = EventInput {
305 level,
306 target: metadata.target(),
307 parents,
308 leaf: leaf.as_ref(),
309 fields: &event_fields,
310 };
311
312 #[cfg(feature = "colors")]
314 let line = {
315 let use_color = self.color_mode.resolve_now(self.output.is_terminal());
316 let theme = if use_color { Some(&self.color_theme) } else { None };
317 render_event(&self.config, &input, theme)
318 };
319 #[cfg(not(feature = "colors"))]
320 let line = render_event(&self.config, &input);
321
322 if self.config.use_level_prefix {
323 self.output.write_line(&format!("{}{}", syslog_prefix(level), line));
324 } else {
325 self.output.write_line(&line);
326 }
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333 use std::io::Write;
334 use std::sync::{Arc, Mutex};
335 use tracing::{Level, info, info_span, warn};
336 use tracing_subscriber::prelude::*;
337
338 #[derive(Clone, Default)]
339 struct Buf(Arc<Mutex<Vec<u8>>>);
340 impl Write for Buf {
341 fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
342 self.0.lock().unwrap().extend_from_slice(b);
343 Ok(b.len())
344 }
345 fn flush(&mut self) -> std::io::Result<()> {
346 Ok(())
347 }
348 }
349
350 fn capture<F: FnOnce()>(layer: SystemdLayer, body: F) -> String {
351 let buf = Buf::default();
352 let captured = buf.0.clone();
353 let layer = layer.with_output(Output::writer(buf));
354 tracing::subscriber::with_default(tracing_subscriber::registry().with(layer), body);
355 let bytes = captured.lock().unwrap().clone();
356 String::from_utf8(bytes).expect("utf-8 output")
357 }
358
359 #[test]
360 fn bare_info_event() {
361 let layer = SystemdLayer::stdout().with_level_prefix(false);
362 let out = capture(layer, || {
363 info!("hello");
364 });
365 assert!(out.ends_with(": hello\n"), "got {out:?}");
367 assert!(out.starts_with("INFO "), "got {out:?}");
368 let _ = Level::INFO; }
370
371 #[test]
372 fn span_arguments_appear_in_output() {
373 let layer = SystemdLayer::stdout().with_level_prefix(false);
374 let out = capture(layer, || {
375 let span = info_span!("worker", id = 7u64);
376 let _g = span.enter();
377 warn!("done");
378 });
379 assert!(out.contains("worker(id: 7)"), "got {out:?}");
380 assert!(out.contains("done"), "got {out:?}");
381 }
382
383 #[test]
384 fn level_prefix_emits_syslog_marker() {
385 let layer = SystemdLayer::stdout().with_level_prefix(true);
386 let out = capture(layer, || {
387 info!("p");
388 });
389 assert!(out.starts_with("<5>INFO"), "got {out:?}");
390 }
391}