rust_analyzer/tracing/
config.rs

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
4use std::io::{self};
5
6use anyhow::Context;
7use tracing::level_filters::LevelFilter;
8use tracing_subscriber::{
9    Layer, Registry,
10    filter::{Targets, filter_fn},
11    fmt::{MakeWriter, time},
12    layer::SubscriberExt,
13};
14use tracing_tree::HierarchicalLayer;
15
16use crate::tracing::hprof;
17use crate::tracing::json;
18
19#[derive(Debug)]
20pub struct Config<T> {
21    pub writer: T,
22    pub filter: String,
23    /// The meaning of CHALK_DEBUG is to tell chalk crates
24    /// (i.e. chalk-solve, chalk-ir, chalk-recursive) how to filter tracing
25    /// logs. But now we can only have just one filter, which means we have to
26    /// merge chalk filter to our main filter (from RA_LOG env).
27    ///
28    /// The acceptable syntax of CHALK_DEBUG is `target[span{field=value}]=level`.
29    /// As the value should only affect chalk crates, we'd better manually
30    /// specify the target. And for simplicity, CHALK_DEBUG only accept the value
31    /// that specify level.
32    pub chalk_filter: Option<String>,
33    /// Filtering syntax, set in a shell:
34    /// ```text
35    /// env RA_PROFILE=*             // dump everything
36    /// env RA_PROFILE=foo|bar|baz   // enabled only selected entries
37    /// env RA_PROFILE=*@3>10        // dump everything, up to depth 3, if it takes more than 10
38    /// ```
39    pub profile_filter: Option<String>,
40
41    /// Filtering syntax, set in a shell:
42    /// ```text
43    /// env RA_PROFILE_JSON=foo|bar|baz
44    /// ```
45    pub json_profile_filter: Option<String>,
46}
47
48impl<T> Config<T>
49where
50    T: for<'writer> MakeWriter<'writer> + Send + Sync + 'static,
51{
52    pub fn init(self) -> anyhow::Result<()> {
53        let targets_filter: Targets = self
54            .filter
55            .parse()
56            .with_context(|| format!("invalid log filter: `{}`", self.filter))?;
57
58        let writer = self.writer;
59
60        let ra_fmt_layer = tracing_subscriber::fmt::layer()
61            .with_target(false)
62            .with_ansi(false)
63            .with_writer(writer);
64
65        let ra_fmt_layer = match time::OffsetTime::local_rfc_3339() {
66            Ok(timer) => {
67                // If we can get the time offset, format logs with the timezone.
68                ra_fmt_layer.with_timer(timer).boxed()
69            }
70            Err(_) => {
71                // Use system time if we can't get the time offset. This should
72                // never happen on Linux, but can happen on e.g. OpenBSD.
73                ra_fmt_layer.boxed()
74            }
75        }
76        .with_filter(targets_filter);
77
78        let chalk_layer = match self.chalk_filter {
79            Some(chalk_filter) => {
80                let level: LevelFilter =
81                    chalk_filter.parse().with_context(|| "invalid chalk log filter")?;
82
83                let chalk_filter = Targets::new()
84                    .with_target("chalk_solve", level)
85                    .with_target("chalk_ir", level)
86                    .with_target("chalk_recursive", level);
87                // TODO: remove `.with_filter(LevelFilter::OFF)` on the `None` branch.
88                HierarchicalLayer::default()
89                    .with_indent_lines(true)
90                    .with_ansi(false)
91                    .with_indent_amount(2)
92                    .with_writer(io::stderr)
93                    .with_filter(chalk_filter)
94                    .boxed()
95            }
96            None => None::<HierarchicalLayer>.with_filter(LevelFilter::OFF).boxed(),
97        };
98
99        // TODO: remove `.with_filter(LevelFilter::OFF)` on the `None` branch.
100        let profiler_layer = match self.profile_filter {
101            Some(spec) => Some(hprof::SpanTree::new(&spec)).with_filter(LevelFilter::INFO),
102            None => None.with_filter(LevelFilter::OFF),
103        };
104
105        let json_profiler_layer = match self.json_profile_filter {
106            Some(spec) => {
107                let filter = json::JsonFilter::from_spec(&spec);
108                let filter = filter_fn(move |metadata| {
109                    let allowed = match &filter.allowed_names {
110                        Some(names) => names.contains(metadata.name()),
111                        None => true,
112                    };
113
114                    allowed && metadata.is_span()
115                });
116                Some(json::TimingLayer::new(std::io::stderr).with_filter(filter))
117            }
118            None => None,
119        };
120
121        let subscriber = Registry::default()
122            .with(ra_fmt_layer)
123            .with(json_profiler_layer)
124            .with(profiler_layer)
125            .with(chalk_layer);
126
127        tracing::subscriber::set_global_default(subscriber)?;
128
129        Ok(())
130    }
131}