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

Signed-off-by: Eliza Weisman <[email protected]>
  • Loading branch information
hawkw committed Oct 21, 2021
1 parent f0214e1 commit 485fd48
Show file tree
Hide file tree
Showing 28 changed files with 724 additions and 736 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,15 @@ jobs:
working-directory: tracing
# this skips running doctests under the `--no-default-features` flag,
# as rustdoc isn't aware of cargo's feature flags.
- name: "Test tracing-subscriber with all features disabled"
- name: "Test tracing-subscriber no-std support"
run: cargo test --lib --tests --no-default-features
working-directory: tracing-subscriber
- name: "Test tracing-subscriber with liballoc only"
run: cargo test --lib --tests --no-default-features --features "alloc"
working-directory: tracing-subscriber
- name: "Test tracing-subscriber with no default features"
run: cargo test --lib --tests --no-default-features --features "std"
working-directory: tracing-subscriber

style:
# Check style.
Expand Down
2 changes: 1 addition & 1 deletion tracing-opentelemetry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ default = ["tracing-log"]
opentelemetry = { version = "0.16", default-features = false, features = ["trace"] }
tracing = { path = "../tracing", version = "0.1", default-features = false, features = ["std"] }
tracing-core = { path = "../tracing-core", version = "0.1" }
tracing-subscriber = { path = "../tracing-subscriber", version = "0.2", default-features = false, features = ["registry"] }
tracing-subscriber = { path = "../tracing-subscriber", version = "0.2", default-features = false, features = ["registry", "std"] }
tracing-log = { path = "../tracing-log", version = "0.1", default-features = false, optional = true }

[dev-dependencies]
Expand Down
6 changes: 4 additions & 2 deletions tracing-subscriber/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ keywords = ["logging", "tracing", "metrics", "subscriber"]

[features]

default = ["smallvec", "fmt", "ansi", "tracing-log"]
default = ["smallvec", "fmt", "ansi", "tracing-log", "std"]
alloc = []
std = ["alloc", "tracing-core/std"]
env-filter = ["matchers", "regex", "lazy_static", "tracing"]
fmt = ["registry"]
ansi = ["fmt", "ansi_term"]
Expand All @@ -37,7 +39,7 @@ local-time = ["time/local-offset"]
tracing-core = { path = "../tracing-core", version = "0.1.20" }

# only required by the filter feature
tracing = { optional = true, path = "../tracing", version = "0.1", default-features = false, features = ["std"] }
tracing = { optional = true, path = "../tracing", version = "0.1", default-features = false }
matchers = { optional = true, version = "0.1.0" }
regex = { optional = true, version = "1", default-features = false, features = ["std"] }
smallvec = { optional = true, version = "1" }
Expand Down
24 changes: 15 additions & 9 deletions tracing-subscriber/src/field/debug.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! `MakeVisitor` wrappers for working with `fmt::Debug` fields.
use super::{MakeVisitor, VisitFmt, VisitOutput, VisitWrite};
use super::{MakeVisitor, VisitFmt, VisitOutput};
use tracing_core::field::{Field, Visit};

use std::{fmt, io};
use core::fmt;

/// A visitor wrapper that ensures any `fmt::Debug` fields are formatted using
/// the alternate (`:#`) formatter.
Expand Down Expand Up @@ -84,13 +84,19 @@ where
}
}

impl<V> VisitWrite for Alt<V>
where
V: VisitWrite,
{
#[inline]
fn writer(&mut self) -> &mut dyn io::Write {
self.0.writer()
feature! {
#![feature = "std"]
use super::VisitWrite;
use std::io;

impl<V> VisitWrite for Alt<V>
where
V: VisitWrite,
{
#[inline]
fn writer(&mut self) -> &mut dyn io::Write {
self.0.writer()
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion tracing-subscriber/src/field/delimited.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! A `MakeVisitor` wrapper that separates formatted fields with a delimiter.
use super::{MakeVisitor, VisitFmt, VisitOutput};

use std::fmt;
use core::fmt;
use tracing_core::field::{Field, Visit};

/// A `MakeVisitor` wrapper that wraps a visitor that writes formatted output so
Expand Down Expand Up @@ -133,6 +133,7 @@ where
}

#[cfg(test)]
#[cfg(all(test, feature = "alloc"))]
mod test {
use super::*;
use crate::field::test_util::*;
Expand Down
24 changes: 15 additions & 9 deletions tracing-subscriber/src/field/display.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! `MakeVisitor` wrappers for working with `fmt::Display` fields.
use super::{MakeVisitor, VisitFmt, VisitOutput, VisitWrite};
use super::{MakeVisitor, VisitFmt, VisitOutput};
use tracing_core::field::{Field, Visit};

use std::{fmt, io};
use core::fmt;

/// A visitor wrapper that ensures any strings named "message" are formatted
/// using `fmt::Display`
Expand Down Expand Up @@ -90,13 +90,19 @@ where
}
}

impl<V> VisitWrite for Messages<V>
where
V: VisitWrite,
{
#[inline]
fn writer(&mut self) -> &mut dyn io::Write {
self.0.writer()
feature! {
#![feature = "std"]
use super::VisitWrite;
use std::io;

impl<V> VisitWrite for Messages<V>
where
V: VisitWrite,
{
#[inline]
fn writer(&mut self) -> &mut dyn io::Write {
self.0.writer()
}
}
}

Expand Down
26 changes: 16 additions & 10 deletions tracing-subscriber/src/field/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Utilities for working with [fields] and [field visitors].
//!
//! [fields]: https://docs.rs/tracing-core/latest/tracing_core/field/index.html
//! [field visitors]: https://docs.rs/tracing-core/latest/tracing_core/field/trait.Visit.html
use std::{fmt, io};
//! [fields]: tracing_core::field
//! [field visitors]: tracing_core::field::Visit
use core::{fmt, marker::PhantomData};
pub use tracing_core::field::Visit;
use tracing_core::{
span::{Attributes, Record},
Expand Down Expand Up @@ -108,11 +108,16 @@ where
}
}

/// Extension trait implemented by visitors to indicate that they write to an
/// `io::Write` instance, and allow access to that writer.
pub trait VisitWrite: VisitOutput<Result<(), io::Error>> {
/// Returns the writer that this visitor writes to.
fn writer(&mut self) -> &mut dyn io::Write;
feature! {
#![feature = "std"]
use std::io;

/// Extension trait implemented by visitors to indicate that they write to an
/// `io::Write` instance, and allow access to that writer.
pub trait VisitWrite: VisitOutput<Result<(), io::Error>> {
/// Returns the writer that this visitor writes to.
fn writer(&mut self) -> &mut dyn io::Write;
}
}

/// Extension trait implemented by visitors to indicate that they write to a
Expand Down Expand Up @@ -223,7 +228,7 @@ where
#[derive(Debug)]
#[doc(hidden)]
pub struct MakeExtMarker<T> {
_p: std::marker::PhantomData<T>,
_p: PhantomData<T>,
}

#[derive(Debug)]
Expand All @@ -232,10 +237,11 @@ pub struct RecordFieldsMarker {
_p: (),
}

#[cfg(test)]
#[cfg(all(test, feature = "alloc"))]
#[macro_use]
pub(in crate::field) mod test_util {
use super::*;
pub(in crate::field) use alloc::string::String;
use tracing_core::{
callsite::Callsite,
field::{Field, Value},
Expand Down
27 changes: 19 additions & 8 deletions tracing-subscriber/src/filter/directive.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use crate::filter::level::{self, LevelFilter};
use std::{cmp::Ordering, error::Error, fmt, iter::FromIterator, str::FromStr};
#[cfg(not(feature = "std"))]
use alloc::{
string::String,
vec::{self, Vec},
};
use core::{cmp::Ordering, fmt, iter::FromIterator, slice, str::FromStr};
use tracing_core::Metadata;
/// Indicates that a string could not be parsed as a filtering directive.
#[derive(Debug)]
Expand Down Expand Up @@ -35,19 +40,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 +125,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 = vec::IntoIter<T>;

fn into_iter(self) -> Self::IntoIter {
self.directives.into_iter()
Expand Down Expand Up @@ -358,6 +365,7 @@ impl FromStr for StaticDirective {
// === impl ParseError ===

impl ParseError {
#[cfg(feature = "std")]
pub(crate) fn new() -> Self {
ParseError {
kind: ParseErrorKind::Other(None),
Expand All @@ -377,17 +385,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 @@ -396,8 +406,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
17 changes: 7 additions & 10 deletions tracing-subscriber/src/filter/env/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,13 @@ use tracing_core::{
/// without filtering on field values. When these features are not required,
/// [`Targets`] provides a lighter-weight alternative to [`EnvFilter`].
///
/// [`Layer`]: ../layer/trait.Layer.html
/// [`env_logger`]: https://docs.rs/env_logger/0.7.1/env_logger/#enabling-logging
/// [`Span`]: https://docs.rs/tracing-core/latest/tracing_core/span/index.html
/// [fields]: https://docs.rs/tracing-core/latest/tracing_core/struct.Field.html
/// [`Event`]: https://docs.rs/tracing-core/latest/tracing_core/struct.Event.html
/// [`level`]: https://docs.rs/tracing-core/latest/tracing_core/struct.Level.html
/// [`Metadata`]: https://docs.rs/tracing-core/latest/tracing_core/struct.Metadata.html
/// [`Span`]: tracing_core::span
/// [fields]: tracing_core::Field
/// [`Event`]: tracing_core::Event
/// [`level`]: tracing_core::Level
/// [`Metadata`]: tracing_core::Metadata
/// [`Targets`]: crate::filter::Targets
#[cfg(feature = "env-filter")]
#[cfg_attr(docsrs, doc(cfg(feature = "env-filter")))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "env-filter", feature = "std"))))]
#[derive(Debug)]
pub struct EnvFilter {
statics: directive::Statics,
Expand All @@ -121,7 +118,7 @@ type FieldMap<T> = HashMap<Field, T>;

/// Indicates that an error occurred while parsing a `EnvFilter` from an
/// environment variable.
#[cfg_attr(docsrs, doc(cfg(feature = "env-filter")))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "env-filter", feature = "std"))))]
#[derive(Debug)]
pub struct FromEnvError {
kind: ErrorKind,
Expand Down
Loading

0 comments on commit 485fd48

Please sign in to comment.