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
21#[cfg(feature = "json")]
22use crate::format::RenderMode;
23#[cfg(feature = "json")]
24use crate::format::json::render_event_json;
25
26pub struct SystemdLayer {
40 config: FormatConfig,
41 output: Output,
42 #[cfg(feature = "colors")]
43 color_mode: ColorMode,
44 #[cfg(feature = "colors")]
45 color_theme: ColorTheme,
46}
47
48impl fmt::Debug for SystemdLayer {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 let mut d = f.debug_struct("SystemdLayer");
51 d.field("config", &self.config).field("output", &self.output);
52 #[cfg(feature = "colors")]
53 d.field("color_mode", &self.color_mode)
54 .field("color_theme", &self.color_theme);
55 d.finish()
56 }
57}
58
59impl Default for SystemdLayer {
60 fn default() -> Self {
61 Self::stdout()
62 }
63}
64
65impl SystemdLayer {
68 #[must_use]
72 pub fn stdout() -> Self {
73 Self {
74 config: FormatConfig::default(),
75 output: Output::stdout(),
76 #[cfg(feature = "colors")]
77 color_mode: ColorMode::default(),
78 #[cfg(feature = "colors")]
79 color_theme: ColorTheme::default(),
80 }
81 }
82
83 #[must_use]
85 pub fn stderr() -> Self {
86 Self {
87 config: FormatConfig::default(),
88 output: Output::stderr(),
89 #[cfg(feature = "colors")]
90 color_mode: ColorMode::default(),
91 #[cfg(feature = "colors")]
92 color_theme: ColorTheme::default(),
93 }
94 }
95
96 #[must_use]
105 pub fn unit_stdout() -> Self {
106 Self {
107 config: FormatConfig {
108 use_level_prefix: true,
109 ..FormatConfig::default()
110 },
111 output: Output::stdout(),
112 #[cfg(feature = "colors")]
113 color_mode: ColorMode::Never,
114 #[cfg(feature = "colors")]
115 color_theme: ColorTheme::monochrome(),
116 }
117 }
118
119 #[cfg(feature = "json")]
148 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
149 #[must_use]
150 pub fn json() -> Self {
151 Self {
152 config: FormatConfig {
153 mode: RenderMode::Json,
154 show_target: true,
155 show_timestamp: true,
156 timestamp_format: TimestampFormat::Rfc3339,
157 use_level_prefix: false,
158 ..FormatConfig::default()
159 },
160 output: Output::stdout(),
161 #[cfg(feature = "colors")]
162 color_mode: ColorMode::Never,
163 #[cfg(feature = "colors")]
164 color_theme: ColorTheme::monochrome(),
165 }
166 }
167}
168
169impl SystemdLayer {
172 #[must_use]
175 pub fn with_output(mut self, output: Output) -> Self {
176 self.output = output;
177 self
178 }
179
180 #[must_use]
183 pub fn with_target(mut self, show: bool) -> Self {
184 self.config.show_target = show;
185 self
186 }
187
188 #[must_use]
190 pub fn with_thread_ids(mut self, show: bool) -> Self {
191 self.config.show_thread_id = show;
192 self
193 }
194
195 #[must_use]
198 pub fn with_timestamps(mut self, show: bool) -> Self {
199 self.config.show_timestamp = show;
200 self
201 }
202
203 #[must_use]
206 pub fn with_timestamp_format(mut self, format: TimestampFormat) -> Self {
207 self.config.timestamp_format = format;
208 if format != TimestampFormat::None {
209 self.config.show_timestamp = true;
210 }
211 self
212 }
213
214 #[must_use]
218 pub fn with_level_prefix(mut self, use_prefix: bool) -> Self {
219 self.config.use_level_prefix = use_prefix;
220 self
221 }
222
223 #[must_use]
225 pub fn with_span_separator(mut self, sep: impl Into<Cow<'static, str>>) -> Self {
226 self.config.span_separator = sep.into();
227 self
228 }
229
230 #[must_use]
232 pub fn with_message_separator(mut self, sep: impl Into<Cow<'static, str>>) -> Self {
233 self.config.message_separator = sep.into();
234 self
235 }
236
237 #[must_use]
239 pub fn with_level_separator(mut self, sep: impl Into<Cow<'static, str>>) -> Self {
240 self.config.level_separator = sep.into();
241 self
242 }
243
244 #[must_use]
246 pub fn with_function_bracket_left(mut self, s: impl Into<Cow<'static, str>>) -> Self {
247 self.config.function_bracket_left = s.into();
248 self
249 }
250
251 #[must_use]
253 pub fn with_function_bracket_right(mut self, s: impl Into<Cow<'static, str>>) -> Self {
254 self.config.function_bracket_right = s.into();
255 self
256 }
257
258 #[must_use]
260 pub fn with_arguments_equality(mut self, s: impl Into<Cow<'static, str>>) -> Self {
261 self.config.arguments_equality = s.into();
262 self
263 }
264
265 #[must_use]
267 pub fn with_arguments_separator(mut self, s: impl Into<Cow<'static, str>>) -> Self {
268 self.config.arguments_separator = s.into();
269 self
270 }
271
272 #[must_use]
274 pub fn with_thread_id_prefix(mut self, s: impl Into<Cow<'static, str>>) -> Self {
275 self.config.thread_id_prefix = s.into();
276 self
277 }
278
279 #[must_use]
281 pub fn with_thread_id_suffix(mut self, s: impl Into<Cow<'static, str>>) -> Self {
282 self.config.thread_id_suffix = s.into();
283 self
284 }
285
286 #[cfg(feature = "colors")]
289 #[cfg_attr(docsrs, doc(cfg(feature = "colors")))]
290 #[must_use]
291 pub fn with_color_mode(mut self, mode: ColorMode) -> Self {
292 self.color_mode = mode;
293 self
294 }
295
296 #[cfg(feature = "colors")]
298 #[cfg_attr(docsrs, doc(cfg(feature = "colors")))]
299 #[must_use]
300 pub fn with_color_theme(mut self, theme: ColorTheme) -> Self {
301 self.color_theme = theme;
302 self
303 }
304}
305
306impl<S> Layer<S> for SystemdLayer
309where
310 S: Subscriber + for<'a> LookupSpan<'a>,
311{
312 fn on_new_span(
313 &self,
314 attrs: &tracing::span::Attributes<'_>,
315 id: &tracing::span::Id,
316 ctx: Context<'_, S>,
317 ) {
318 let mut fields = FieldMap::new();
319 attrs.record(&mut FieldVisitor::new(&mut fields));
320
321 if let Some(span) = ctx.span(id) {
322 span.extensions_mut().insert(FieldStorage(fields));
323 }
324 }
325
326 fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
327 let mut chain: Vec<SpanLink> = Vec::new();
331 if let Some(scope) = ctx.event_scope(event) {
332 for span in scope.from_root() {
333 let exts = span.extensions();
334 let fields = exts
335 .get::<FieldStorage>()
336 .map(|s| s.0.clone())
337 .unwrap_or_default();
338 chain.push(SpanLink {
339 name: span.name(),
340 fields,
341 });
342 }
343 }
344 let leaf = chain.last().cloned();
345 let parents: &[SpanLink] = if chain.is_empty() {
346 &[]
347 } else {
348 &chain[..chain.len() - 1]
349 };
350
351 let mut event_fields = FieldMap::new();
353 event.record(&mut FieldVisitor::new(&mut event_fields));
354
355 let metadata = event.metadata();
356 let level = *metadata.level();
357
358 let input = EventInput {
359 level,
360 target: metadata.target(),
361 parents,
362 leaf: leaf.as_ref(),
363 fields: &event_fields,
364 };
365
366 let line = self.render(&input);
367
368 if self.config.use_level_prefix {
369 self.output.write_line(&format!("{}{}", syslog_prefix(level), line));
370 } else {
371 self.output.write_line(&line);
372 }
373 }
374}
375
376impl SystemdLayer {
377 fn render(&self, input: &EventInput<'_>) -> String {
378 #[cfg(feature = "json")]
379 if matches!(self.config.mode, RenderMode::Json) {
380 return render_event_json(&self.config, input);
381 }
382
383 #[cfg(feature = "colors")]
384 {
385 let use_color = self.color_mode.resolve_now(self.output.is_terminal());
386 let theme = if use_color { Some(&self.color_theme) } else { None };
387 render_event(&self.config, input, theme)
388 }
389 #[cfg(not(feature = "colors"))]
390 {
391 render_event(&self.config, input)
392 }
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399 use std::io::Write;
400 use std::sync::{Arc, Mutex};
401 use tracing::{Level, info, info_span, warn};
402 use tracing_subscriber::prelude::*;
403
404 #[derive(Clone, Default)]
405 struct Buf(Arc<Mutex<Vec<u8>>>);
406 impl Write for Buf {
407 fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
408 self.0.lock().unwrap().extend_from_slice(b);
409 Ok(b.len())
410 }
411 fn flush(&mut self) -> std::io::Result<()> {
412 Ok(())
413 }
414 }
415
416 fn capture<F: FnOnce()>(layer: SystemdLayer, body: F) -> String {
417 let buf = Buf::default();
418 let captured = buf.0.clone();
419 let layer = layer.with_output(Output::writer(buf));
420 tracing::subscriber::with_default(tracing_subscriber::registry().with(layer), body);
421 let bytes = captured.lock().unwrap().clone();
422 String::from_utf8(bytes).expect("utf-8 output")
423 }
424
425 #[test]
426 fn bare_info_event() {
427 let layer = SystemdLayer::stdout().with_level_prefix(false);
428 let out = capture(layer, || {
429 info!("hello");
430 });
431 assert!(out.ends_with(": hello\n"), "got {out:?}");
433 assert!(out.starts_with("INFO "), "got {out:?}");
434 let _ = Level::INFO; }
436
437 #[test]
438 fn span_arguments_appear_in_output() {
439 let layer = SystemdLayer::stdout().with_level_prefix(false);
440 let out = capture(layer, || {
441 let span = info_span!("worker", id = 7u64);
442 let _g = span.enter();
443 warn!("done");
444 });
445 assert!(out.contains("worker(id: 7)"), "got {out:?}");
446 assert!(out.contains("done"), "got {out:?}");
447 }
448
449 #[test]
450 fn level_prefix_emits_syslog_marker() {
451 let layer = SystemdLayer::stdout().with_level_prefix(true);
452 let out = capture(layer, || {
453 info!("p");
454 });
455 assert!(out.starts_with("<5>INFO"), "got {out:?}");
456 }
457}