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(())
}
}