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