#[allow(unreachable_pub)]
pub use self::{
directive::{Directive, ParseError},
field::BadName as BadFieldName,
};
mod directive;
mod field;
use crate::{
filter::LevelFilter,
layer::{Context, Layer},
sync::RwLock,
};
use std::{cell::RefCell, collections::HashMap, env, error::Error, fmt, str::FromStr};
use tracing_core::{
callsite,
field::Field,
span,
subscriber::{Interest, Subscriber},
Metadata,
};
#[cfg(feature = "env-filter")]
#[cfg_attr(docsrs, doc(cfg(feature = "env-filter")))]
#[derive(Debug)]
pub struct EnvFilter {
statics: directive::Statics,
dynamics: directive::Dynamics,
has_dynamics: bool,
by_id: RwLock<HashMap<span::Id, directive::SpanMatcher>>,
by_cs: RwLock<HashMap<callsite::Identifier, directive::CallsiteMatcher>>,
}
thread_local! {
static SCOPE: RefCell<Vec<LevelFilter>> = RefCell::new(Vec::new());
}
type FieldMap<T> = HashMap<Field, T>;
#[cfg(feature = "smallvec")]
type FilterVec<T> = smallvec::SmallVec<[T; 8]>;
#[cfg(not(feature = "smallvec"))]
type FilterVec<T> = Vec<T>;
#[derive(Debug)]
pub struct FromEnvError {
kind: ErrorKind,
}
#[derive(Debug)]
enum ErrorKind {
Parse(ParseError),
Env(env::VarError),
}
impl EnvFilter {
pub const DEFAULT_ENV: &'static str = "RUST_LOG";
pub fn from_default_env() -> Self {
Self::from_env(Self::DEFAULT_ENV)
}
pub fn from_env<A: AsRef<str>>(env: A) -> Self {
env::var(env.as_ref()).map(Self::new).unwrap_or_default()
}
pub fn new<S: AsRef<str>>(dirs: S) -> Self {
let directives = dirs.as_ref().split(',').filter_map(|s| match s.parse() {
Ok(d) => Some(d),
Err(err) => {
eprintln!("ignoring `{}`: {}", s, err);
None
}
});
Self::from_directives(directives)
}
pub fn try_new<S: AsRef<str>>(dirs: S) -> Result<Self, ParseError> {
let directives = dirs
.as_ref()
.split(',')
.map(|s| s.parse())
.collect::<Result<Vec<_>, _>>()?;
Ok(Self::from_directives(directives))
}
pub fn try_from_default_env() -> Result<Self, FromEnvError> {
Self::try_from_env(Self::DEFAULT_ENV)
}
pub fn try_from_env<A: AsRef<str>>(env: A) -> Result<Self, FromEnvError> {
env::var(env.as_ref())?.parse().map_err(Into::into)
}
pub fn add_directive(mut self, directive: Directive) -> Self {
if let Some(stat) = directive.to_static() {
self.statics.add(stat)
} else {
self.has_dynamics = true;
self.dynamics.add(directive);
}
self
}
fn from_directives(directives: impl IntoIterator<Item = Directive>) -> Self {
use tracing::level_filters::STATIC_MAX_LEVEL;
use tracing::Level;
let directives: Vec<_> = directives.into_iter().collect();
let disabled: Vec<_> = directives
.iter()
.filter(|directive| directive.level > STATIC_MAX_LEVEL)
.collect();
if !disabled.is_empty() {
#[cfg(feature = "ansi_term")]
use ansi_term::{Color, Style};
let warn = |msg: &str| {
#[cfg(not(feature = "ansi_term"))]
let msg = format!("warning: {}", msg);
#[cfg(feature = "ansi_term")]
let msg = {
let bold = Style::new().bold();
let mut warning = Color::Yellow.paint("warning");
warning.style_ref_mut().is_bold = true;
format!("{}{} {}", warning, bold.clone().paint(":"), bold.paint(msg))
};
eprintln!("{}", msg);
};
let ctx_prefixed = |prefix: &str, msg: &str| {
#[cfg(not(feature = "ansi_term"))]
let msg = format!("note: {}", msg);
#[cfg(feature = "ansi_term")]
let msg = {
let mut equal = Color::Fixed(21).paint("=");
equal.style_ref_mut().is_bold = true;
format!(" {} {} {}", equal, Style::new().bold().paint(prefix), msg)
};
eprintln!("{}", msg);
};
let ctx_help = |msg| ctx_prefixed("help:", msg);
let ctx_note = |msg| ctx_prefixed("note:", msg);
let ctx = |msg: &str| {
#[cfg(not(feature = "ansi_term"))]
let msg = format!("note: {}", msg);
#[cfg(feature = "ansi_term")]
let msg = {
let mut pipe = Color::Fixed(21).paint("|");
pipe.style_ref_mut().is_bold = true;
format!(" {} {}", pipe, msg)
};
eprintln!("{}", msg);
};
warn("some trace filter directives would enable traces that are disabled statically");
for directive in disabled {
let target = if let Some(target) = &directive.target {
format!("the `{}` target", target)
} else {
"all targets".into()
};
let level = directive
.level
.clone()
.into_level()
.expect("=off would not have enabled any filters");
ctx(&format!(
"`{}` would enable the {} level for {}",
directive, level, target
));
}
ctx_note(&format!("the static max level is `{}`", STATIC_MAX_LEVEL));
let help_msg = || {
let (feature, filter) = match STATIC_MAX_LEVEL.into_level() {
Some(Level::TRACE) => unreachable!(
"if the max level is trace, no static filtering features are enabled"
),
Some(Level::DEBUG) => ("max_level_debug", Level::TRACE),
Some(Level::INFO) => ("max_level_info", Level::DEBUG),
Some(Level::WARN) => ("max_level_warn", Level::INFO),
Some(Level::ERROR) => ("max_level_error", Level::WARN),
None => return ("max_level_off", String::new()),
};
(feature, format!("{} ", filter))
};
let (feature, earlier_level) = help_msg();
ctx_help(&format!(
"to enable {}logging, remove the `{}` feature",
earlier_level, feature
));
}
let (dynamics, mut statics) = Directive::make_tables(directives);
let has_dynamics = !dynamics.is_empty();
if statics.is_empty() && !has_dynamics {
statics.add(directive::StaticDirective::default());
}
Self {
statics,
dynamics,
has_dynamics,
by_id: RwLock::new(HashMap::new()),
by_cs: RwLock::new(HashMap::new()),
}
}
fn cares_about_span(&self, span: &span::Id) -> bool {
let spans = try_lock!(self.by_id.read(), else return false);
spans.contains_key(span)
}
fn base_interest(&self) -> Interest {
if self.has_dynamics {
Interest::sometimes()
} else {
Interest::never()
}
}
}
impl<S: Subscriber> Layer<S> for EnvFilter {
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
if self.has_dynamics && metadata.is_span() {
if let Some(matcher) = self.dynamics.matcher(metadata) {
let mut by_cs = try_lock!(self.by_cs.write(), else return self.base_interest());
by_cs.insert(metadata.callsite(), matcher);
return Interest::always();
}
}
if self.statics.enabled(metadata) {
Interest::always()
} else {
self.base_interest()
}
}
fn max_level_hint(&self) -> Option<LevelFilter> {
if self.dynamics.has_value_filters() {
return Some(LevelFilter::TRACE);
}
std::cmp::max(
self.statics.max_level.clone().into(),
self.dynamics.max_level.clone().into(),
)
}
fn enabled(&self, metadata: &Metadata<'_>, _: Context<'_, S>) -> bool {
let level = metadata.level();
if self.has_dynamics && self.dynamics.max_level >= *level {
if metadata.is_span() {
let enabled_by_cs = self
.by_cs
.read()
.ok()
.map(|by_cs| by_cs.contains_key(&metadata.callsite()))
.unwrap_or(false);
if enabled_by_cs {
return true;
}
}
let enabled_by_scope = SCOPE.with(|scope| {
for filter in scope.borrow().iter() {
if filter >= level {
return true;
}
}
false
});
if enabled_by_scope {
return true;
}
}
if self.statics.max_level >= *level {
return self.statics.enabled(metadata);
}
false
}
fn new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, _: Context<'_, S>) {
let by_cs = try_lock!(self.by_cs.read());
if let Some(cs) = by_cs.get(&attrs.metadata().callsite()) {
let span = cs.to_span_match(attrs);
try_lock!(self.by_id.write()).insert(id.clone(), span);
}
}
fn on_record(&self, id: &span::Id, values: &span::Record<'_>, _: Context<'_, S>) {
if let Some(span) = try_lock!(self.by_id.read()).get(id) {
span.record_update(values);
}
}
fn on_enter(&self, id: &span::Id, _: Context<'_, S>) {
if let Some(span) = try_lock!(self.by_id.read()).get(id) {
SCOPE.with(|scope| scope.borrow_mut().push(span.level()));
}
}
fn on_exit(&self, id: &span::Id, _: Context<'_, S>) {
if self.cares_about_span(id) {
SCOPE.with(|scope| scope.borrow_mut().pop());
}
}
fn on_close(&self, id: span::Id, _: Context<'_, S>) {
if !self.cares_about_span(&id) {
return;
}
let mut spans = try_lock!(self.by_id.write());
spans.remove(&id);
}
}
impl FromStr for EnvFilter {
type Err = ParseError;
fn from_str(spec: &str) -> Result<Self, Self::Err> {
Self::try_new(spec)
}
}
impl<S> From<S> for EnvFilter
where
S: AsRef<str>,
{
fn from(s: S) -> Self {
Self::new(s)
}
}
impl Default for EnvFilter {
fn default() -> Self {
Self::from_directives(std::iter::empty())
}
}
impl fmt::Display for EnvFilter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut statics = self.statics.iter();
let wrote_statics = if let Some(next) = statics.next() {
fmt::Display::fmt(next, f)?;
for directive in statics {
write!(f, ",{}", directive)?;
}
true
} else {
false
};
let mut dynamics = self.dynamics.iter();
if let Some(next) = dynamics.next() {
if wrote_statics {
f.write_str(",")?;
}
fmt::Display::fmt(next, f)?;
for directive in dynamics {
write!(f, ",{}", directive)?;
}
}
Ok(())
}
}
impl From<ParseError> for FromEnvError {
fn from(p: ParseError) -> Self {
Self {
kind: ErrorKind::Parse(p),
}
}
}
impl From<env::VarError> for FromEnvError {
fn from(v: env::VarError) -> Self {
Self {
kind: ErrorKind::Env(v),
}
}
}
impl fmt::Display for FromEnvError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
ErrorKind::Parse(ref p) => p.fmt(f),
ErrorKind::Env(ref e) => e.fmt(f),
}
}
}
impl Error for FromEnvError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self.kind {
ErrorKind::Parse(ref p) => Some(p),
ErrorKind::Env(ref e) => Some(e),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tracing_core::field::FieldSet;
use tracing_core::*;
struct NoSubscriber;
impl Subscriber for NoSubscriber {
#[inline]
fn register_callsite(&self, _: &'static Metadata<'static>) -> subscriber::Interest {
subscriber::Interest::always()
}
fn new_span(&self, _: &span::Attributes<'_>) -> span::Id {
span::Id::from_u64(0xDEAD)
}
fn event(&self, _event: &Event<'_>) {}
fn record(&self, _span: &span::Id, _values: &span::Record<'_>) {}
fn record_follows_from(&self, _span: &span::Id, _follows: &span::Id) {}
#[inline]
fn enabled(&self, _metadata: &Metadata<'_>) -> bool {
true
}
fn enter(&self, _span: &span::Id) {}
fn exit(&self, _span: &span::Id) {}
}
struct Cs;
impl Callsite for Cs {
fn set_interest(&self, _interest: Interest) {}
fn metadata(&self) -> &Metadata<'_> {
unimplemented!()
}
}
#[test]
fn callsite_enabled_no_span_directive() {
let filter = EnvFilter::new("app=debug").with_subscriber(NoSubscriber);
static META: &Metadata<'static> = &Metadata::new(
"mySpan",
"app",
Level::TRACE,
None,
None,
None,
FieldSet::new(&[], identify_callsite!(&Cs)),
Kind::SPAN,
);
let interest = filter.register_callsite(META);
assert!(interest.is_never());
}
#[test]
fn callsite_off() {
let filter = EnvFilter::new("app=off").with_subscriber(NoSubscriber);
static META: &Metadata<'static> = &Metadata::new(
"mySpan",
"app",
Level::ERROR,
None,
None,
None,
FieldSet::new(&[], identify_callsite!(&Cs)),
Kind::SPAN,
);
let interest = filter.register_callsite(&META);
assert!(interest.is_never());
}
#[test]
fn callsite_enabled_includes_span_directive() {
let filter = EnvFilter::new("app[mySpan]=debug").with_subscriber(NoSubscriber);
static META: &Metadata<'static> = &Metadata::new(
"mySpan",
"app",
Level::TRACE,
None,
None,
None,
FieldSet::new(&[], identify_callsite!(&Cs)),
Kind::SPAN,
);
let interest = filter.register_callsite(&META);
assert!(interest.is_always());
}
#[test]
fn callsite_enabled_includes_span_directive_field() {
let filter =
EnvFilter::new("app[mySpan{field=\"value\"}]=debug").with_subscriber(NoSubscriber);
static META: &Metadata<'static> = &Metadata::new(
"mySpan",
"app",
Level::TRACE,
None,
None,
None,
FieldSet::new(&["field"], identify_callsite!(&Cs)),
Kind::SPAN,
);
let interest = filter.register_callsite(&META);
assert!(interest.is_always());
}
#[test]
fn callsite_enabled_includes_span_directive_multiple_fields() {
let filter = EnvFilter::new("app[mySpan{field=\"value\",field2=2}]=debug")
.with_subscriber(NoSubscriber);
static META: &Metadata<'static> = &Metadata::new(
"mySpan",
"app",
Level::TRACE,
None,
None,
None,
FieldSet::new(&["field"], identify_callsite!(&Cs)),
Kind::SPAN,
);
let interest = filter.register_callsite(&META);
assert!(interest.is_never());
}
#[test]
fn roundtrip() {
let f1: EnvFilter =
"[span1{foo=1}]=error,[span2{bar=2 baz=false}],crate2[{quux=\"quuux\"}]=debug"
.parse()
.unwrap();
let f2: EnvFilter = format!("{}", f1).parse().unwrap();
assert_eq!(f1.statics, f2.statics);
assert_eq!(f1.dynamics, f2.dynamics);
}
}