1 //! Simple logger that logs either to stderr or to a file, using `tracing_subscriber`
2 //! filter syntax and `tracing_appender` for non blocking output.
3 
4 use std::{
5     fmt,
6     fs::File,
7     io::{self, Stderr},
8     sync::Arc,
9 };
10 
11 use rust_analyzer::Result;
12 use tracing::{level_filters::LevelFilter, Event, Subscriber};
13 use tracing_log::NormalizeEvent;
14 use tracing_subscriber::{
15     fmt::{
16         format::Writer, writer::BoxMakeWriter, FmtContext, FormatEvent, FormatFields,
17         FormattedFields, MakeWriter,
18     },
19     layer::SubscriberExt,
20     registry::LookupSpan,
21     util::SubscriberInitExt,
22     EnvFilter, Registry,
23 };
24 use tracing_tree::HierarchicalLayer;
25 
26 pub(crate) struct Logger {
27     filter: EnvFilter,
28     file: Option<File>,
29 }
30 
31 struct MakeWriterStderr;
32 
33 impl<'a> MakeWriter<'a> for MakeWriterStderr {
34     type Writer = Stderr;
35 
make_writer(&'a self) -> Self::Writer36     fn make_writer(&'a self) -> Self::Writer {
37         io::stderr()
38     }
39 }
40 
41 impl Logger {
new(file: Option<File>, filter: Option<&str>) -> Logger42     pub(crate) fn new(file: Option<File>, filter: Option<&str>) -> Logger {
43         let filter = filter.map_or(EnvFilter::default(), EnvFilter::new);
44 
45         Logger { filter, file }
46     }
47 
install(self) -> Result<()>48     pub(crate) fn install(self) -> Result<()> {
49         // The meaning of CHALK_DEBUG I suspected is to tell chalk crates
50         // (i.e. chalk-solve, chalk-ir, chalk-recursive) how to filter tracing
51         // logs. But now we can only have just one filter, which means we have to
52         // merge chalk filter to our main filter (from RA_LOG env).
53         //
54         // The acceptable syntax of CHALK_DEBUG is `target[span{field=value}]=level`.
55         // As the value should only affect chalk crates, we'd better mannually
56         // specify the target. And for simplicity, CHALK_DEBUG only accept the value
57         // that specify level.
58         let chalk_level_dir = std::env::var("CHALK_DEBUG")
59             .map(|val| {
60                 val.parse::<LevelFilter>().expect(
61                     "invalid CHALK_DEBUG value, expect right log level (like debug or trace)",
62                 )
63             })
64             .ok();
65 
66         let chalk_layer = HierarchicalLayer::default()
67             .with_indent_lines(true)
68             .with_ansi(false)
69             .with_indent_amount(2)
70             .with_writer(io::stderr);
71 
72         let writer = match self.file {
73             Some(file) => BoxMakeWriter::new(Arc::new(file)),
74             None => BoxMakeWriter::new(io::stderr),
75         };
76         let ra_fmt_layer =
77             tracing_subscriber::fmt::layer().event_format(LoggerFormatter).with_writer(writer);
78 
79         match chalk_level_dir {
80             Some(val) => {
81                 Registry::default()
82                     .with(
83                         self.filter
84                             .add_directive(format!("chalk_solve={}", val).parse()?)
85                             .add_directive(format!("chalk_ir={}", val).parse()?)
86                             .add_directive(format!("chalk_recursive={}", val).parse()?),
87                     )
88                     .with(ra_fmt_layer)
89                     .with(chalk_layer)
90                     .init();
91             }
92             None => {
93                 Registry::default().with(self.filter).with(ra_fmt_layer).init();
94             }
95         };
96 
97         Ok(())
98     }
99 }
100 
101 #[derive(Debug)]
102 struct LoggerFormatter;
103 
104 impl<S, N> FormatEvent<S, N> for LoggerFormatter
105 where
106     S: Subscriber + for<'a> LookupSpan<'a>,
107     N: for<'a> FormatFields<'a> + 'static,
108 {
format_event( &self, ctx: &FmtContext<'_, S, N>, mut writer: Writer, event: &Event<'_>, ) -> fmt::Result109     fn format_event(
110         &self,
111         ctx: &FmtContext<'_, S, N>,
112         mut writer: Writer,
113         event: &Event<'_>,
114     ) -> fmt::Result {
115         // Write level and target
116         let level = *event.metadata().level();
117 
118         // If this event is issued from `log` crate, then the value of target is
119         // always "log". `tracing-log` has hard coded it for some reason, so we
120         // need to extract it using `normalized_metadata` method which is part of
121         // `tracing_log::NormalizeEvent`.
122         let target = match event.normalized_metadata() {
123             // This event is issued from `log` crate
124             Some(log) => log.target(),
125             None => event.metadata().target(),
126         };
127         write!(writer, "[{} {}] ", level, target)?;
128 
129         // Write spans and fields of each span
130         ctx.visit_spans(|span| {
131             write!(writer, "{}", span.name())?;
132 
133             let ext = span.extensions();
134 
135             // `FormattedFields` is a a formatted representation of the span's
136             // fields, which is stored in its extensions by the `fmt` layer's
137             // `new_span` method. The fields will have been formatted
138             // by the same field formatter that's provided to the event
139             // formatter in the `FmtContext`.
140             let fields = &ext.get::<FormattedFields<N>>().expect("will never be `None`");
141 
142             if !fields.is_empty() {
143                 write!(writer, "{{{}}}", fields)?;
144             }
145             write!(writer, ": ")?;
146 
147             Ok(())
148         })?;
149 
150         // Write fields on the event
151         ctx.field_format().format_fields(writer.by_ref(), event)?;
152 
153         writeln!(writer)
154     }
155 }
156