Skip to content

Commit

Permalink
subscriber: add minimal #![no_std] support (#1648)
Browse files Browse the repository at this point in the history
Backports #1648 from `master`.

Depends on #1649

## Motivation

Presently, the `tracing-subscriber` crate requires the Rust standard
library and doesn't build with `#![no_std]` targets. For the most part,
this is fine, as much of `tracing-subscriber` inherently depends on
`std` APIs.

However, `tracing-subscriber` also contains some key abstractions that
are necessary for interoperability: the `Layer` and `LookupSpan`
traits. Since these traits are in `tracing-subscriber`, `no-std` users
cannot currently access them.

Some of the other utilities in this crate, such as the field visitor
combinators, may also be useful for `#![no_std]` projects.

## Solution

This branch adds "std" and "alloc" feature flags to
`tracing-subscriber`, for conditionally enabling `libstd` and
`liballoc`, respectively. The `registry`, `fmt`, `EnvFilter`, and
`reload` APIs all require libstd, and cannot be implemented without it,
but the core `Layer` and `LookupSpan` traits are now available with
`#![no_std]`.

Fixes #999
  • Loading branch information
hawkw committed Mar 24, 2022
1 parent e3ca2ce commit cc28878
Show file tree
Hide file tree
Showing 13 changed files with 279 additions and 193 deletions.
57 changes: 48 additions & 9 deletions tracing-subscriber/src/filter/directive.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::filter::level::{self, LevelFilter};
use std::{cmp::Ordering, error::Error, fmt, iter::FromIterator, str::FromStr};
use tracing_core::Metadata;
use alloc::{string::String, vec::Vec};
use core::{cmp::Ordering, fmt, iter::FromIterator, slice, str::FromStr};
use tracing_core::{Level, Metadata};
/// Indicates that a string could not be parsed as a filtering directive.
#[derive(Debug)]
pub struct ParseError {
Expand Down Expand Up @@ -35,19 +36,21 @@ pub(in crate::filter) trait Match {

#[derive(Debug)]
enum ParseErrorKind {
Field(Box<dyn Error + Send + Sync>),
#[cfg(feature = "std")]
Field(Box<dyn std::error::Error + Send + Sync>),
Level(level::ParseError),
Other(Option<&'static str>),
}

// === impl DirectiveSet ===

impl<T> DirectiveSet<T> {
#[cfg(feature = "std")]
pub(crate) fn is_empty(&self) -> bool {
self.directives.is_empty()
}

pub(crate) fn iter(&self) -> std::slice::Iter<'_, T> {
pub(crate) fn iter(&self) -> slice::Iter<'_, T> {
self.directives.iter()
}
}
Expand Down Expand Up @@ -118,7 +121,7 @@ impl<T> IntoIterator for DirectiveSet<T> {
#[cfg(feature = "smallvec")]
type IntoIter = smallvec::IntoIter<[T; 8]>;
#[cfg(not(feature = "smallvec"))]
type IntoIter = std::vec::IntoIter<T>;
type IntoIter = alloc::vec::IntoIter<T>;

fn into_iter(self) -> Self::IntoIter {
self.directives.into_iter()
Expand All @@ -135,6 +138,22 @@ impl DirectiveSet<StaticDirective> {
None => false,
}
}

/// Same as `enabled` above, but skips `Directive`'s with fields.
pub(crate) fn target_enabled(&self, target: &str, level: &Level) -> bool {
match self.directives_for_target(target).next() {
Some(d) => d.level >= *level,
None => false,
}
}

pub(crate) fn directives_for_target<'a>(
&'a self,
target: &'a str,
) -> impl Iterator<Item = &'a StaticDirective> + 'a {
self.directives()
.filter(move |d| d.cares_about_target(target))
}
}

// === impl StaticDirective ===
Expand All @@ -151,6 +170,22 @@ impl StaticDirective {
level,
}
}

pub(in crate::filter) fn cares_about_target(&self, to_check: &str) -> bool {
// Does this directive have a target filter, and does it match the
// metadata's target?
if let Some(ref target) = self.target {
if !to_check.starts_with(&target[..]) {
return false;
}
}

if !self.field_names.is_empty() {
return false;
}

true
}
}

impl Ord for StaticDirective {
Expand Down Expand Up @@ -353,6 +388,7 @@ impl FromStr for StaticDirective {
// === impl ParseError ===

impl ParseError {
#[cfg(feature = "std")]
pub(crate) fn new() -> Self {
ParseError {
kind: ParseErrorKind::Other(None),
Expand All @@ -372,17 +408,19 @@ impl fmt::Display for ParseError {
ParseErrorKind::Other(None) => f.pad("invalid filter directive"),
ParseErrorKind::Other(Some(msg)) => write!(f, "invalid filter directive: {}", msg),
ParseErrorKind::Level(ref l) => l.fmt(f),
#[cfg(feature = "std")]
ParseErrorKind::Field(ref e) => write!(f, "invalid field filter: {}", e),
}
}
}

impl Error for ParseError {
#[cfg(feature = "std")]
impl std::error::Error for ParseError {
fn description(&self) -> &str {
"invalid filter directive"
}

fn source(&self) -> Option<&(dyn Error + 'static)> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self.kind {
ParseErrorKind::Other(_) => None,
ParseErrorKind::Level(ref l) => Some(l),
Expand All @@ -391,8 +429,9 @@ impl Error for ParseError {
}
}

impl From<Box<dyn Error + Send + Sync>> for ParseError {
fn from(e: Box<dyn Error + Send + Sync>) -> Self {
#[cfg(feature = "std")]
impl From<Box<dyn std::error::Error + Send + Sync>> for ParseError {
fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Self {
Self {
kind: ParseErrorKind::Field(e),
}
Expand Down
1 change: 0 additions & 1 deletion tracing-subscriber/src/filter/env/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ use tracing_core::{
/// [`Metadata`]: tracing_core::Metadata
/// [`Targets`]: crate::filter::Targets
#[cfg_attr(docsrs, doc(cfg(all(feature = "env-filter", feature = "std"))))]
#[cfg(feature = "env-filter")]
#[derive(Debug)]
pub struct EnvFilter {
statics: directive::Statics,
Expand Down
83 changes: 42 additions & 41 deletions tracing-subscriber/src/filter/filter_fn.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#[cfg(feature = "registry")]
use crate::subscribe::Filter;
use crate::{
filter::LevelFilter,
subscribe::{Context, Subscribe},
Expand Down Expand Up @@ -323,25 +321,6 @@ where
}
}

#[cfg(feature = "registry")]
#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
impl<C, F> Filter<C> for FilterFn<F>
where
F: Fn(&Metadata<'_>) -> bool,
{
fn enabled(&self, metadata: &Metadata<'_>, _: &Context<'_, C>) -> bool {
self.is_enabled(metadata)
}

fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest {
self.is_callsite_enabled(metadata)
}

fn max_level_hint(&self) -> Option<LevelFilter> {
self.max_level_hint
}
}

impl<C, F> Subscribe<C> for FilterFn<F>
where
F: Fn(&Metadata<'_>) -> bool + 'static,
Expand Down Expand Up @@ -661,26 +640,6 @@ where
}
}

#[cfg(feature = "registry")]
#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
impl<C, F, R> Filter<C> for DynFilterFn<C, F, R>
where
F: Fn(&Metadata<'_>, &Context<'_, C>) -> bool,
R: Fn(&'static Metadata<'static>) -> Interest,
{
fn enabled(&self, metadata: &Metadata<'_>, cx: &Context<'_, C>) -> bool {
self.is_enabled(metadata, cx)
}

fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest {
self.is_callsite_enabled(metadata)
}

fn max_level_hint(&self) -> Option<LevelFilter> {
self.max_level_hint
}
}

impl<C, F, R> Subscribe<C> for DynFilterFn<C, F, R>
where
F: Fn(&Metadata<'_>, &Context<'_, C>) -> bool + 'static,
Expand Down Expand Up @@ -741,6 +700,48 @@ where
}
}

// === PLF impls ===

feature! {
#![all(feature = "registry", feature = "std")]
use crate::subscribe::Filter;

impl<C, F> Filter<C> for FilterFn<F>
where
F: Fn(&Metadata<'_>) -> bool,
{
fn enabled(&self, metadata: &Metadata<'_>, _: &Context<'_, C>) -> bool {
self.is_enabled(metadata)
}

fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest {
self.is_callsite_enabled(metadata)
}

fn max_level_hint(&self) -> Option<LevelFilter> {
self.max_level_hint
}
}

impl<C, F, R> Filter<C> for DynFilterFn<C, F, R>
where
F: Fn(&Metadata<'_>, &Context<'_, C>) -> bool,
R: Fn(&'static Metadata<'static>) -> Interest,
{
fn enabled(&self, metadata: &Metadata<'_>, cx: &Context<'_, C>) -> bool {
self.is_enabled(metadata, cx)
}

fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest {
self.is_callsite_enabled(metadata)
}

fn max_level_hint(&self) -> Option<LevelFilter> {
self.max_level_hint
}
}
}

fn is_below_max_level(hint: &Option<LevelFilter>, metadata: &Metadata<'_>) -> bool {
hint.as_ref()
.map(|hint| metadata.level() <= hint)
Expand Down
38 changes: 23 additions & 15 deletions tracing-subscriber/src/filter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,42 @@
//!
//! [`subscribe` module documentation]: crate::subscribe#filtering-with-subscribers
//! [`Subscribe`]: crate::subscribe
mod directive;
#[cfg(feature = "env-filter")]
mod env;
mod filter_fn;
mod level;
#[cfg(feature = "registry")]
mod subscriber_filters;
pub mod targets;

pub use self::directive::ParseError;
feature! {
#![all(feature = "env-filter", feature = "std")]
mod env;
pub use self::env::*;
}

feature! {
#![all(feature = "registry", feature = "std")]
mod subscriber_filters;
pub use self::subscriber_filters::*;
}

pub use self::filter_fn::*;
#[cfg(not(feature = "registry"))]
pub(crate) use self::has_psf_stubs::*;
#[cfg(feature = "registry")]
#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
pub use self::subscriber_filters::*;

pub use self::level::{LevelFilter, ParseError as LevelParseError};

#[cfg(feature = "env-filter")]
#[cfg_attr(docsrs, doc(cfg(feature = "env-filter")))]
pub use self::env::*;
#[cfg(not(all(feature = "registry", feature = "std")))]
pub(crate) use self::has_plf_stubs::*;

pub use self::targets::Targets;
feature! {
#![any(feature = "std", feature = "alloc")]
pub mod targets;
pub use self::targets::Targets;

mod directive;
pub use self::directive::ParseError;
}

/// Stub implementations of the per-subscriber-fitler detection functions for
/// when the `registry` feature is disabled.
#[cfg(not(feature = "registry"))]
#[cfg(not(all(feature = "registry", feature = "std")))]
mod has_psf_stubs {
pub(crate) fn is_psf_downcast_marker(_: std::any::TypeId) -> bool {
false
Expand Down
20 changes: 18 additions & 2 deletions tracing-subscriber/src/filter/targets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ use crate::{
},
subscribe,
};
use std::{
#[cfg(not(feature = "std"))]
use alloc::string::String;
use core::{
iter::{Extend, FilterMap, FromIterator},
slice,
str::FromStr,
};
use tracing_core::{Collect, Interest, Metadata};
Expand Down Expand Up @@ -462,7 +465,7 @@ impl Iterator for IntoIter {
#[derive(Debug)]
pub struct Iter<'a>(
FilterMap<
std::slice::Iter<'a, StaticDirective>,
slice::Iter<'a, StaticDirective>,
fn(&'a StaticDirective) -> Option<(&'a str, LevelFilter)>,
>,
);
Expand Down Expand Up @@ -494,6 +497,17 @@ impl<'a> Iterator for Iter<'a> {
mod tests {
use super::*;

feature! {
#![not(feature = "std")]
use alloc::{vec, vec::Vec, string::ToString};

// `dbg!` is only available with `libstd`; just nop it out when testing
// with alloc only.
macro_rules! dbg {
($x:expr) => { $x }
}
}

fn expect_parse(s: &str) -> Targets {
match dbg!(s).parse::<Targets>() {
Err(e) => panic!("string {:?} did not parse successfully: {}", s, e),
Expand Down Expand Up @@ -643,6 +657,8 @@ mod tests {
}

#[test]
// `println!` is only available with `libstd`.
#[cfg(feature = "std")]
fn size_of_filters() {
fn print_sz(s: &str) {
let filter = s.parse::<Targets>().expect("filter should parse");
Expand Down
2 changes: 1 addition & 1 deletion tracing-subscriber/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
//! [`time` crate]: https://crates.io/crates/time
//! [`liballoc`]: https://doc.rust-lang.org/alloc/index.html
//! [`libstd`]: https://doc.rust-lang.org/std/index.html
#![doc(html_root_url = "https://docs.rs/tracing-subscriber/0.2.12")]
#![doc(html_root_url = "https://docs.rs/tracing-subscriber/0.2.25")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png",
html_favicon_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/favicon.ico",
Expand Down

0 comments on commit cc28878

Please sign in to comment.