1pub(crate) mod builder;
2
3use std::fmt::{self, Write};
4
5use tracing::field::Visit;
6use tracing_core::{Event, Field, Subscriber};
7use tracing_subscriber::field::RecordFields;
8use tracing_subscriber::fmt::format::{self, FormatEvent, FormatFields};
9use tracing_subscriber::fmt::{FmtContext, FormattedFields};
10use tracing_subscriber::registry::LookupSpan;
11use tracing_subscriber::Layer;
12
13use crate::serializer::{Serializer, SerializerError};
14
15pub fn layer<S>() -> impl Layer<S>
30where
31 S: Subscriber + for<'a> LookupSpan<'a>,
32{
33 crate::builder().layer()
34}
35
36pub struct EventsFormatter {
38 pub(crate) with_level: bool,
39 pub(crate) with_target: bool,
40 pub(crate) with_span_name: bool,
41 pub(crate) with_span_path: bool,
42 pub(crate) with_location: bool,
43 pub(crate) with_module_path: bool,
44 pub(crate) with_timestamp: bool,
45 pub(crate) with_thread_names: bool,
46 pub(crate) with_thread_ids: bool,
47 #[cfg(feature = "ansi_logs")]
48 pub(crate) with_ansi_color: bool,
49}
50
51impl Default for EventsFormatter {
52 fn default() -> Self {
53 Self {
54 with_level: true,
55 with_target: true,
56 with_span_name: true,
57 with_span_path: true,
58 with_location: false,
59 with_module_path: false,
60 with_timestamp: true,
61 with_thread_names: false,
62 with_thread_ids: false,
63 #[cfg(feature = "ansi_logs")]
64 with_ansi_color: default_enable_ansi_color(),
65 }
66 }
67}
68
69#[cfg(feature = "ansi_logs")]
70fn default_enable_ansi_color() -> bool {
71 use std::io::IsTerminal;
72 std::io::stdout().is_terminal()
73}
74
75#[inline]
85#[allow(unsafe_code)]
86fn current_thread_id() -> u64 {
87 #[cfg(any(target_os = "linux", target_os = "android"))]
88 {
89 #[link(name = "c")]
90 unsafe extern "C" {
91 safe fn gettid() -> i32;
92 }
93
94 gettid() as _
95 }
96
97 #[cfg(target_os = "windows")]
98 {
99 #[link(name = "kernel32", kind = "raw-dylib")]
100 unsafe extern "system" {
101 safe fn GetCurrentThreadId() -> u32;
102 }
103
104 GetCurrentThreadId() as _
105 }
106
107 #[cfg(target_vendor = "apple")]
108 {
109 #[link(name = "c")]
110 unsafe extern "C" {
111 safe fn pthread_threadid_np(thread: usize, id: *mut u64) -> i32;
114 }
115
116 let mut id = 0;
117 if pthread_threadid_np(0, &mut id) != 0 {
118 id = 0;
119 }
120
121 id
122 }
123
124 #[cfg(not(any(
125 any(target_os = "linux", target_os = "android"),
126 target_os = "windows",
127 target_vendor = "apple"
128 )))]
129 {
130 0
131 }
132}
133
134impl<S, N> FormatEvent<S, N> for EventsFormatter
135where
136 S: Subscriber + for<'a> LookupSpan<'a>,
137 N: for<'a> FormatFields<'a> + 'static,
138{
139 fn format_event(
140 &self,
141 ctx: &FmtContext<'_, S, N>,
142 mut writer: format::Writer<'_>,
143 event: &Event<'_>,
144 ) -> fmt::Result {
145 let mut serializer = Serializer::new(
146 &mut writer,
147 #[cfg(feature = "ansi_logs")]
148 self.with_ansi_color,
149 );
150
151 let mut visit = || {
152 let metadata = event.metadata();
153
154 if self.with_timestamp {
155 serializer.serialize_key("ts")?;
156 serializer.writer.write_char('=')?;
157 time::OffsetDateTime::now_utc()
158 .format_into(
159 &mut serializer,
160 &time::format_description::well_known::Rfc3339,
161 )
162 .map_err(|_e| fmt::Error)?;
163 }
164
165 if self.with_level {
166 let level = match *metadata.level() {
167 tracing::Level::ERROR => "error",
168 tracing::Level::WARN => "warn",
169 tracing::Level::INFO => "info",
170 tracing::Level::DEBUG => "debug",
171 tracing::Level::TRACE => "trace",
172 };
173
174 #[cfg(feature = "ansi_logs")]
175 {
176 if self.with_ansi_color {
177 let level_str = match *metadata.level() {
178 tracing::Level::ERROR => nu_ansi_term::Color::Red,
179 tracing::Level::WARN => nu_ansi_term::Color::Yellow,
180 tracing::Level::INFO => nu_ansi_term::Color::Green,
181 tracing::Level::DEBUG => nu_ansi_term::Color::Blue,
182 tracing::Level::TRACE => nu_ansi_term::Color::Purple,
183 }
184 .bold()
185 .paint(level);
186
187 serializer.serialize_entry("level", &level_str.to_string())?;
188 } else {
189 serializer.serialize_entry("level", level)?;
190 }
191 }
192
193 #[cfg(not(feature = "ansi_logs"))]
194 serializer.serialize_entry("level", level)?;
195 }
196
197 if self.with_target {
198 serializer.serialize_entry("target", metadata.target())?;
199 }
200
201 if self.with_thread_names {
204 if let Some(name) = std::thread::current().name() {
205 serializer.serialize_entry("thread_name", name)?;
206 } else if !self.with_thread_ids {
207 serializer
209 .serialize_entry("thread_name", &format!("{}", current_thread_id()))?;
210 }
211 }
212
213 if self.with_thread_ids {
214 serializer.serialize_entry_no_quote("thread_id", current_thread_id())?;
215 }
216
217 let span = if self.with_span_name || self.with_span_path {
218 event
219 .parent()
220 .and_then(|id| ctx.span(id))
221 .or_else(|| ctx.lookup_current())
222 } else {
223 None
224 };
225
226 if self.with_location {
227 if let (Some(file), Some(line)) = (metadata.file(), metadata.line()) {
228 serializer.serialize_entry("location", &format!("{}:{}", file, line))?;
229 }
230 }
231 if self.with_module_path {
232 if let Some(module) = metadata.module_path() {
233 serializer.serialize_entry("module_path", module)?;
234 }
235 }
236
237 if let Some(span) = span {
238 if self.with_span_name {
239 serializer.serialize_entry("span", span.name())?;
240 }
241
242 if self.with_span_path {
243 serializer.serialize_key("span_path")?;
244 serializer.writer.write_char('=')?;
245
246 let needs_quote = span
247 .scope()
248 .from_root()
249 .any(|span| span.name().chars().any(crate::serializer::need_quote));
250
251 if needs_quote {
253 let mut required_capacity = 0;
254 let mut insert_sep = false;
255 for span in span.scope().from_root() {
256 if insert_sep {
257 required_capacity += 1;
258 }
259 required_capacity += span.name().len();
260 insert_sep = true;
261 }
262
263 let mut span_path = String::with_capacity(required_capacity);
264 let s = Serializer::new(
265 &mut span_path,
266 #[cfg(feature = "ansi_logs")]
267 self.with_ansi_color,
268 );
269 let mut insert_sep = false;
270 for span in span.scope().from_root() {
271 if insert_sep {
272 s.writer.write_char('>')?;
273 }
274 s.writer.write_str(span.name())?;
275 insert_sep = true;
276 }
277 serializer.serialize_value(&span_path)?;
278 } else {
279 let mut insert_sep = false;
280 for span in span.scope().from_root() {
281 if insert_sep {
282 serializer.writer.write_char('>')?;
283 }
284 serializer.writer.write_str(span.name())?;
285 insert_sep = true;
286 }
287 }
288 }
289 }
290
291 let mut visitor = Visitor::new(&mut serializer);
292 event.record(&mut visitor);
293 visitor.state?;
294
295 Ok(())
296 };
297
298 visit().map_err(|_e: SerializerError| fmt::Error)?;
299
300 if let Some(leaf_span) = ctx.lookup_current() {
302 for span in leaf_span.scope().from_root() {
303 let ext = span.extensions();
304 let data = ext
305 .get::<FormattedFields<N>>()
306 .expect("Unable to find FormattedFields in extensions; this is a bug");
307
308 if !data.is_empty() {
309 write!(writer, " ")?;
310 write!(writer, "{}", data)?;
311 }
312 }
313 }
314
315 writeln!(writer)
316 }
317}
318
319#[derive(Default)]
321#[non_exhaustive]
322pub struct FieldsFormatter {}
323
324impl<'writer> FormatFields<'writer> for FieldsFormatter {
325 fn format_fields<R: RecordFields>(
326 &self,
327 mut writer: format::Writer<'writer>,
328 fields: R,
329 ) -> fmt::Result {
330 let mut serializer = Serializer::new(
331 &mut writer,
332 #[cfg(feature = "ansi_logs")]
333 false,
334 );
335 let mut visitor = Visitor::new(&mut serializer);
336 fields.record(&mut visitor);
337 Ok(())
338 }
339}
340
341struct Visitor<'a, W> {
342 serializer: &'a mut Serializer<W>,
343 state: Result<(), SerializerError>,
344 debug_fmt_buffer: String,
345}
346
347impl<'a, W> Visitor<'a, W> {
348 fn new(serializer: &'a mut Serializer<W>) -> Self {
349 Self {
350 serializer,
351 state: Ok(()),
352 debug_fmt_buffer: String::new(),
353 }
354 }
355}
356
357impl<'a, W> Visit for Visitor<'a, W>
358where
359 W: fmt::Write,
360{
361 fn record_f64(&mut self, field: &Field, value: f64) {
362 if self.state.is_ok() {
363 self.record_debug_no_quote(field, value);
364 }
365 }
366
367 fn record_i64(&mut self, field: &Field, value: i64) {
368 if self.state.is_ok() {
369 self.record_debug_no_quote(field, value);
370 }
371 }
372
373 fn record_u64(&mut self, field: &Field, value: u64) {
374 if self.state.is_ok() {
375 self.record_debug_no_quote(field, value);
376 }
377 }
378
379 fn record_bool(&mut self, field: &Field, value: bool) {
380 if self.state.is_ok() {
381 self.record_debug_no_quote(field, value);
382 }
383 }
384
385 fn record_str(&mut self, field: &Field, value: &str) {
386 if self.state.is_ok() {
387 self.state = self.serializer.serialize_entry(field.name(), value);
388 }
389 }
390
391 fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) {
392 if self.state.is_ok() {
393 self.record_debug(field, &format_args!("{}", value));
394 }
395 }
396
397 fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
398 if self.state.is_ok() {
399 self.debug_fmt_buffer.clear();
400 let _ = write!(self.debug_fmt_buffer, "{:?}", value);
401 self.state = self
402 .serializer
403 .serialize_entry(field.name(), &self.debug_fmt_buffer);
404 }
405 }
406}
407
408impl<'a, W> Visitor<'a, W>
409where
410 W: fmt::Write,
411{
412 fn record_debug_no_quote(&mut self, field: &Field, value: impl fmt::Debug) {
413 if self.state.is_ok() {
414 self.state = self
415 .serializer
416 .serialize_entry_no_quote(field.name(), value);
417 }
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use std::{
424 io,
425 sync::{Arc, Mutex},
426 };
427
428 use tracing::info_span;
429 #[allow(unused_imports)]
430 use tracing_subscriber::fmt::{MakeWriter, SubscriberBuilder};
431
432 use super::*;
433
434 #[derive(Clone, Debug)]
435 struct MockWriter {
436 buf: Arc<Mutex<Vec<u8>>>,
437 }
438
439 #[derive(Clone, Debug)]
440 struct MockMakeWriter {
441 buf: Arc<Mutex<Vec<u8>>>,
442 }
443
444 impl MockMakeWriter {
445 fn new() -> Self {
446 Self {
447 buf: Arc::new(Mutex::new(Vec::new())),
448 }
449 }
450 fn get_content(&self) -> String {
451 let buf = self.buf.lock().unwrap();
452 std::str::from_utf8(&buf[..]).unwrap().to_owned()
453 }
454 }
455
456 impl MockWriter {
457 fn new(buf: Arc<Mutex<Vec<u8>>>) -> Self {
458 Self { buf }
459 }
460 }
461
462 impl io::Write for MockWriter {
463 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
464 self.buf.lock().unwrap().write(buf)
465 }
466
467 fn flush(&mut self) -> io::Result<()> {
468 self.buf.lock().unwrap().flush()
469 }
470 }
471
472 impl<'a> MakeWriter<'a> for MockMakeWriter {
473 type Writer = MockWriter;
474
475 fn make_writer(&'a self) -> Self::Writer {
476 MockWriter::new(self.buf.clone())
477 }
478 }
479
480 macro_rules! contains {
481 ($content:expr, [$($item:expr),+$(,)?], [$($neg:expr),*$(,)?]) => {
482 eprintln!("{}", $content);
483
484 $(
485 assert!($content.contains($item), "failed to find expected string '{}'", $item);
486 )+
487
488 $(
489 assert!(!$content.contains($neg), "found unexpected string '{}'", $neg);
490 )*
491 }
492 }
493
494 #[cfg(not(feature = "ansi_logs"))]
495 mod no_ansi {
496 use super::*;
497
498 fn subscriber() -> SubscriberBuilder<FieldsFormatter, EventsFormatter> {
499 builder::builder().subscriber_builder()
500 }
501
502 #[test]
503 fn enable_thread_name_and_id() {
504 use tracing::subscriber;
505
506 let mock_writer = MockMakeWriter::new();
507 let subscriber = builder::builder()
508 .with_thread_names(true)
509 .with_thread_ids(true)
510 .subscriber_builder()
511 .with_writer(mock_writer.clone())
512 .finish();
513
514 std::thread::Builder::new()
515 .name("worker-1".to_string())
516 .spawn(move || {
517 subscriber::with_default(subscriber, || {
518 tracing::info!("message");
519 });
520 })
521 .unwrap()
522 .join()
523 .unwrap();
524
525 let content = mock_writer.get_content();
526 contains!(content, ["thread_name=worker-1", "thread_id="], []);
527 }
528
529 #[test]
530 fn span_and_span_path_with_quoting() {
531 use tracing::subscriber;
532
533 let mock_writer = MockMakeWriter::new();
534 let subscriber = subscriber().with_writer(mock_writer.clone()).finish();
535
536 subscriber::with_default(subscriber, || {
537 let _top = info_span!("top").entered();
538 let _middle = info_span!("mid dle").entered();
540 let _bottom = info_span!("bottom").entered();
541
542 tracing::info!("message");
543 });
544
545 let content = mock_writer.get_content();
546
547 contains!(
548 content,
549 [
550 "span=bottom",
551 "span_path=\"top>mid dle>bottom\"",
552 "info",
553 "ts=20"
554 ],
555 []
556 );
557 }
558
559 #[test]
560 fn span_and_span_path_without_quoting() {
561 use tracing::subscriber;
562
563 let mock_writer = MockMakeWriter::new();
564 let subscriber = subscriber().with_writer(mock_writer.clone()).finish();
565
566 subscriber::with_default(subscriber, || {
567 let _top = info_span!("top").entered();
568 let _middle = info_span!("middle").entered();
569 let _bottom = info_span!("bottom").entered();
570
571 tracing::info!("message");
572 });
573
574 let content = mock_writer.get_content();
575
576 contains!(
577 content,
578 [
579 "span=bottom",
580 "span_path=top>middle>bottom",
581 "info",
582 "ts=20"
583 ],
584 []
585 );
586 }
587
588 #[test]
589 fn disable_span_and_span_path() {
590 use tracing::subscriber;
591
592 let mock_writer = MockMakeWriter::new();
593 let subscriber = builder::builder()
594 .with_span_name(false)
595 .with_span_path(false)
596 .subscriber_builder()
597 .with_writer(mock_writer.clone())
598 .finish();
599
600 subscriber::with_default(subscriber, || {
601 let _top = info_span!("top").entered();
602 let _middle = info_span!("middle").entered();
603 let _bottom = info_span!("bottom").entered();
604
605 tracing::info!("message");
606 });
607
608 let content = mock_writer.get_content();
609
610 contains!(content, ["level=info", "ts=20"], ["span=", "span_path="]);
611 }
612
613 #[test]
614 fn enable_module_path() {
615 use tracing::subscriber;
616
617 let mock_writer = MockMakeWriter::new();
618 let subscriber = builder::builder()
619 .with_module_path(true)
620 .subscriber_builder()
621 .with_writer(mock_writer.clone())
622 .finish();
623
624 subscriber::with_default(subscriber, || {
625 let _top = info_span!("top").entered();
626 let _middle = info_span!("middle").entered();
627 let _bottom = info_span!("bottom").entered();
628
629 tracing::info!("message");
630 });
631
632 let content = mock_writer.get_content();
633
634 contains!(
635 content,
636 ["module_path=tracing_logfmt::formatter::tests", "info"],
637 []
638 );
639 }
640
641 #[test]
642 #[cfg(not(windows))]
643 fn enable_location() {
644 use tracing::subscriber;
645
646 let mock_writer = MockMakeWriter::new();
647 let subscriber = builder::builder()
648 .with_location(true)
649 .subscriber_builder()
650 .with_writer(mock_writer.clone())
651 .finish();
652
653 subscriber::with_default(subscriber, || {
654 let _top = info_span!("top").entered();
655 let _middle = info_span!("middle").entered();
656 let _bottom = info_span!("bottom").entered();
657
658 tracing::info!("message");
659 });
660
661 let content = mock_writer.get_content();
662 let split = content.split("location=src/formatter.rs:").last().unwrap();
663 let line = &split[..3];
664 assert!(line.parse::<u32>().is_ok());
665
666 contains!(content, ["location=src/formatter.rs:", "info"], []);
667 }
668
669 #[test]
670 #[cfg(windows)]
671 fn enable_location() {
672 use tracing::subscriber;
673
674 let mock_writer = MockMakeWriter::new();
675 let subscriber = builder::builder()
676 .with_location(true)
677 .subscriber_builder()
678 .with_writer(mock_writer.clone())
679 .finish();
680
681 subscriber::with_default(subscriber, || {
682 let _top = info_span!("top").entered();
683 let _middle = info_span!("middle").entered();
684 let _bottom = info_span!("bottom").entered();
685
686 tracing::info!("message");
687 });
688
689 let content = mock_writer.get_content();
690 let split = content.split(r"location=src\formatter.rs:").last().unwrap();
691 let line = &split[..3];
692 assert!(line.parse::<u32>().is_ok());
693
694 contains!(content, [r"location=src\formatter.rs:", "info"], []);
695 }
696 }
697
698 #[cfg(feature = "ansi_logs")]
699 mod ansi {
700 use super::*;
701
702 fn make_ansi_key_value(key: &str, value: &str) -> String {
703 use nu_ansi_term::Color;
704 let mut key = Color::Rgb(109, 139, 140).bold().paint(key).to_string();
705 key.push_str(value);
706 key
707 }
708
709 fn builder() -> builder::Builder {
712 builder::builder().with_ansi_color(true)
713 }
714
715 #[test]
716 fn disable_span_and_span_path() {
717 use nu_ansi_term::Color;
718 use tracing::subscriber;
719
720 let mock_writer = MockMakeWriter::new();
721 let subscriber = builder()
722 .with_span_name(false)
723 .with_span_path(false)
724 .subscriber_builder()
725 .with_writer(mock_writer.clone())
726 .finish();
727
728 subscriber::with_default(subscriber, || {
729 let _top = info_span!("top").entered();
730 let _middle = info_span!("middle").entered();
731 let _bottom = info_span!("bottom").entered();
732
733 tracing::info!("message");
734 });
735
736 let content = mock_writer.get_content();
737 let message = make_ansi_key_value("message", "=message");
738 let target = make_ansi_key_value("target", "=tracing_logfmt::formatter::tests");
739 let ts = make_ansi_key_value("ts", "=");
740
741 contains!(
742 content,
743 [
744 &Color::Green.bold().paint("info").to_string(),
745 &ts,
746 &target,
747 &message
748 ],
749 ["span=", "span_path="]
750 );
751 }
752
753 #[test]
754 fn span_and_span_path_without_quoting() {
755 use tracing::subscriber;
756
757 let mock_writer = MockMakeWriter::new();
758 let subscriber = builder()
759 .subscriber_builder()
760 .with_writer(mock_writer.clone())
761 .finish();
762
763 subscriber::with_default(subscriber, || {
764 let _top = info_span!("top").entered();
765 let _middle = info_span!("middle").entered();
766 let _bottom = info_span!("bottom").entered();
767
768 tracing::info!("message");
769 });
770
771 let content = mock_writer.get_content();
772
773 let span = make_ansi_key_value("span", "=bottom");
774 let span_path = make_ansi_key_value("span_path", "=top>middle>bottom");
775 let ts = make_ansi_key_value("ts", "=20");
776
777 contains!(content, [&span, &span_path, "info", &ts], []);
778 }
779
780 #[test]
781 fn span_and_span_path_with_quoting() {
782 use tracing::subscriber;
783
784 let mock_writer = MockMakeWriter::new();
785 let subscriber = builder()
786 .subscriber_builder()
787 .with_writer(mock_writer.clone())
788 .finish();
789
790 subscriber::with_default(subscriber, || {
791 let _top = info_span!("top").entered();
792 let _middle = info_span!("mid dle").entered();
794 let _bottom = info_span!("bottom").entered();
795
796 tracing::info!("message");
797 });
798
799 let content = mock_writer.get_content();
800
801 let span = make_ansi_key_value("span", "=bottom");
802 let span_path = make_ansi_key_value("span_path", "=\"top>mid dle>bottom\"");
803 let ts = make_ansi_key_value("ts", "=20");
804
805 contains!(content, [&span, &span_path, "info", &ts], []);
806 }
807
808 #[test]
809 fn enable_thread_name_and_id() {
810 use tracing::subscriber;
811
812 let mock_writer = MockMakeWriter::new();
813 let subscriber = builder()
814 .with_thread_names(true)
815 .with_thread_ids(true)
816 .subscriber_builder()
817 .with_writer(mock_writer.clone())
818 .finish();
819
820 std::thread::Builder::new()
821 .name("worker-1".to_string())
822 .spawn(move || {
823 subscriber::with_default(subscriber, || {
824 tracing::info!("message");
825 });
826 })
827 .unwrap()
828 .join()
829 .unwrap();
830
831 let content = mock_writer.get_content();
832
833 let thread_name_prefix = make_ansi_key_value("thread_name", "=");
834 let thread_id_prefix = make_ansi_key_value("thread_id", "=");
835
836 contains!(
837 content,
838 [&format!("{thread_name_prefix}worker-1"), &thread_id_prefix],
839 []
840 );
841 }
842
843 #[test]
844 fn disable_ansi_color() {
845 use tracing::subscriber;
846
847 let mock_writer = MockMakeWriter::new();
848 let subscriber = builder()
849 .with_timestamp(false)
851 .with_ansi_color(false)
852 .subscriber_builder()
853 .with_writer(mock_writer.clone())
854 .finish();
855
856 subscriber::with_default(subscriber, || {
857 tracing::info!("message");
858 });
859
860 let content = mock_writer.get_content();
861
862 assert_eq!(
864 content,
865 "level=info target=tracing_logfmt::formatter::tests::ansi message=message\n"
866 );
867 }
868 }
869}