rust_analyzer/tracing/
config.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! Simple logger that logs either to stderr or to a file, using `tracing_subscriber`
//! filter syntax and `tracing_appender` for non blocking output.

use std::io::{self};

use anyhow::Context;
use tracing::level_filters::LevelFilter;
use tracing_subscriber::{
    filter::{filter_fn, Targets},
    fmt::{time, MakeWriter},
    layer::SubscriberExt,
    Layer, Registry,
};
use tracing_tree::HierarchicalLayer;

use crate::tracing::hprof;
use crate::tracing::json;

#[derive(Debug)]
pub struct Config<T> {
    pub writer: T,
    pub filter: String,
    /// The meaning of CHALK_DEBUG is to tell chalk crates
    /// (i.e. chalk-solve, chalk-ir, chalk-recursive) how to filter tracing
    /// logs. But now we can only have just one filter, which means we have to
    /// merge chalk filter to our main filter (from RA_LOG env).
    ///
    /// The acceptable syntax of CHALK_DEBUG is `target[span{field=value}]=level`.
    /// As the value should only affect chalk crates, we'd better manually
    /// specify the target. And for simplicity, CHALK_DEBUG only accept the value
    /// that specify level.
    pub chalk_filter: Option<String>,
    /// Filtering syntax, set in a shell:
    /// ```
    /// env RA_PROFILE=*             // dump everything
    /// env RA_PROFILE=foo|bar|baz   // enabled only selected entries
    /// env RA_PROFILE=*@3>10        // dump everything, up to depth 3, if it takes more than 10
    /// ```
    pub profile_filter: Option<String>,

    /// Filtering syntax, set in a shell:
    /// ```
    /// env RA_PROFILE_JSON=foo|bar|baz
    /// ```
    pub json_profile_filter: Option<String>,
}

impl<T> Config<T>
where
    T: for<'writer> MakeWriter<'writer> + Send + Sync + 'static,
{
    pub fn init(self) -> anyhow::Result<()> {
        let targets_filter: Targets = self
            .filter
            .parse()
            .with_context(|| format!("invalid log filter: `{}`", self.filter))?;

        let writer = self.writer;

        let ra_fmt_layer = tracing_subscriber::fmt::layer()
            .with_target(false)
            .with_ansi(false)
            .with_writer(writer);

        let ra_fmt_layer = match time::OffsetTime::local_rfc_3339() {
            Ok(timer) => {
                // If we can get the time offset, format logs with the timezone.
                ra_fmt_layer.with_timer(timer).boxed()
            }
            Err(_) => {
                // Use system time if we can't get the time offset. This should
                // never happen on Linux, but can happen on e.g. OpenBSD.
                ra_fmt_layer.boxed()
            }
        }
        .with_filter(targets_filter);

        let chalk_layer = match self.chalk_filter {
            Some(chalk_filter) => {
                let level: LevelFilter =
                    chalk_filter.parse().with_context(|| "invalid chalk log filter")?;

                let chalk_filter = Targets::new()
                    .with_target("chalk_solve", level)
                    .with_target("chalk_ir", level)
                    .with_target("chalk_recursive", level);
                // TODO: remove `.with_filter(LevelFilter::OFF)` on the `None` branch.
                HierarchicalLayer::default()
                    .with_indent_lines(true)
                    .with_ansi(false)
                    .with_indent_amount(2)
                    .with_writer(io::stderr)
                    .with_filter(chalk_filter)
                    .boxed()
            }
            None => None::<HierarchicalLayer>.with_filter(LevelFilter::OFF).boxed(),
        };

        // TODO: remove `.with_filter(LevelFilter::OFF)` on the `None` branch.
        let profiler_layer = match self.profile_filter {
            Some(spec) => Some(hprof::SpanTree::new(&spec)).with_filter(LevelFilter::INFO),
            None => None.with_filter(LevelFilter::OFF),
        };

        let json_profiler_layer = match self.json_profile_filter {
            Some(spec) => {
                let filter = json::JsonFilter::from_spec(&spec);
                let filter = filter_fn(move |metadata| {
                    let allowed = match &filter.allowed_names {
                        Some(names) => names.contains(metadata.name()),
                        None => true,
                    };

                    allowed && metadata.is_span()
                });
                Some(json::TimingLayer::new(std::io::stderr).with_filter(filter))
            }
            None => None,
        };

        let subscriber = Registry::default()
            .with(ra_fmt_layer)
            .with(json_profiler_layer)
            .with(profiler_layer)
            .with(chalk_layer);

        tracing::subscriber::set_global_default(subscriber)?;

        Ok(())
    }
}