#[allow(unreachable_pub)]
pub use self::{builder::Builder, directive::Directive, field::BadName as BadFieldName};
mod builder;
mod directive;
mod field;
use crate::{
filter::LevelFilter,
layer::{Context, Layer},
sync::RwLock,
};
use alloc::{fmt, str::FromStr, vec::Vec};
use core::cell::RefCell;
use directive::ParseError;
use std::{collections::HashMap, env, error::Error};
use thread_local::ThreadLocal;
use tracing_core::{
callsite,
field::Field,
span,
subscriber::{Interest, Subscriber},
Metadata,
};
#[cfg_attr(docsrs, doc(cfg(all(feature = "env-filter", feature = "std"))))]
#[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>>,
scope: ThreadLocal<RefCell<Vec<LevelFilter>>>,
regex: bool,
}
impl Clone for EnvFilter {
fn clone(&self) -> EnvFilter {
EnvFilter {
statics: self.statics.clone(),
dynamics: self.dynamics.clone(),
has_dynamics: self.has_dynamics,
by_id: RwLock::default(),
by_cs: RwLock::default(),
scope: ThreadLocal::new(),
regex: self.regex,
}
}
}
type FieldMap<T> = HashMap<Field, T>;
#[cfg_attr(docsrs, doc(cfg(all(feature = "env-filter", feature = "std"))))]
#[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 builder() -> Builder {
Builder::default()
}
pub fn from_default_env() -> Self {
Self::builder()
.with_default_directive(LevelFilter::ERROR.into())
.from_env_lossy()
}
pub fn from_env<A: AsRef<str>>(env: A) -> Self {
Self::builder()
.with_default_directive(LevelFilter::ERROR.into())
.with_env_var(env.as_ref())
.from_env_lossy()
}
pub fn new<S: AsRef<str>>(directives: S) -> Self {
Self::builder()
.with_default_directive(LevelFilter::ERROR.into())
.parse_lossy(directives)
}
pub fn try_new<S: AsRef<str>>(dirs: S) -> Result<Self, directive::ParseError> {
Self::builder().parse(dirs)
}
pub fn try_from_default_env() -> Result<Self, FromEnvError> {
Self::builder().try_from_env()
}
pub fn try_from_env<A: AsRef<str>>(env: A) -> Result<Self, FromEnvError> {
Self::builder().with_env_var(env.as_ref()).try_from_env()
}
pub fn add_directive(mut self, mut directive: Directive) -> Self {
if !self.regex {
directive.deregexify();
}
if let Some(stat) = directive.to_static() {
self.statics.add(stat)
} else {
self.has_dynamics = true;
self.dynamics.add(directive);
}
self
}
pub fn enabled<S>(&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 = {
let scope = self.scope.get_or_default().borrow();
for filter in &*scope {
if filter >= level {
return true;
}
}
false
};
if enabled_by_scope {
return true;
}
}
if self.statics.max_level >= *level {
return self.statics.enabled(metadata);
}
false
}
pub fn max_level_hint(&self) -> Option<LevelFilter> {
if self.dynamics.has_value_filters() {
return Some(LevelFilter::TRACE);
}
std::cmp::max(
self.statics.max_level.into(),
self.dynamics.max_level.into(),
)
}
pub fn on_new_span<S>(&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);
}
}
pub fn on_enter<S>(&self, id: &span::Id, _: Context<'_, S>) {
if let Some(span) = try_lock!(self.by_id.read()).get(id) {
self.scope.get_or_default().borrow_mut().push(span.level());
}
}
pub fn on_exit<S>(&self, id: &span::Id, _: Context<'_, S>) {
if self.cares_about_span(id) {
self.scope.get_or_default().borrow_mut().pop();
}
}
pub fn on_close<S>(&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);
}
pub fn on_record<S>(&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 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()
}
}
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()
}
}
}
impl<S: Subscriber> Layer<S> for EnvFilter {
#[inline]
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
EnvFilter::register_callsite(self, metadata)
}
#[inline]
fn max_level_hint(&self) -> Option<LevelFilter> {
EnvFilter::max_level_hint(self)
}
#[inline]
fn enabled(&self, metadata: &Metadata<'_>, ctx: Context<'_, S>) -> bool {
self.enabled(metadata, ctx)
}
#[inline]
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
self.on_new_span(attrs, id, ctx)
}
#[inline]
fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
self.on_record(id, values, ctx);
}
#[inline]
fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
self.on_enter(id, ctx);
}
#[inline]
fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
self.on_exit(id, ctx);
}
#[inline]
fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
self.on_close(id, ctx);
}
}
feature! {
#![all(feature = "registry", feature = "std")]
use crate::layer::Filter;
impl<S> Filter<S> for EnvFilter {
#[inline]
fn enabled(&self, meta: &Metadata<'_>, ctx: &Context<'_, S>) -> bool {
self.enabled(meta, ctx.clone())
}
#[inline]
fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
self.register_callsite(meta)
}
#[inline]
fn max_level_hint(&self) -> Option<LevelFilter> {
EnvFilter::max_level_hint(self)
}
#[inline]
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
self.on_new_span(attrs, id, ctx)
}
#[inline]
fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
self.on_record(id, values, ctx);
}
#[inline]
fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
self.on_enter(id, ctx);
}
#[inline]
fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
self.on_exit(id, ctx);
}
#[inline]
fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
self.on_close(id, ctx);
}
}
}
impl FromStr for EnvFilter {
type Err = directive::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 {
Builder::default().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<directive::ParseError> for FromEnvError {
fn from(p: directive::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 alloc::format;
use std::println;
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);
}
#[test]
fn size_of_filters() {
fn print_sz(s: &str) {
let filter = s.parse::<EnvFilter>().expect("filter should parse");
println!(
"size_of_val({:?})\n -> {}B",
s,
std::mem::size_of_val(&filter)
);
}
print_sz("info");
print_sz("foo=debug");
print_sz(
"crate1::mod1=error,crate1::mod2=warn,crate1::mod2::mod3=info,\
crate2=debug,crate3=trace,crate3::mod2::mod1=off",
);
print_sz("[span1{foo=1}]=error,[span2{bar=2 baz=false}],crate2[{quux=\"quuux\"}]=debug");
print_sz(
"crate1::mod1=error,crate1::mod2=warn,crate1::mod2::mod3=info,\
crate2=debug,crate3=trace,crate3::mod2::mod1=off,[span1{foo=1}]=error,\
[span2{bar=2 baz=false}],crate2[{quux=\"quuux\"}]=debug",
);
}
#[test]
fn parse_empty_string() {
assert!(EnvFilter::builder().parse("").is_ok());
}
}