subscriber: allow subscribers to expose stored metadata to layers (#382)

## Motivation

Currently, when using `tracing-subscriber`'s `Layer` API, it is
necessary for each layer to manage its own storage for per-span data.
Since subscribers are shared concurrently, this means that `Layer`s must
also manage synchronization on this storage. This is easy to get wrong,
and even when it's implemented correctly, having every layer synchronize
separately adds a lot of overhead. Ideally, it should be possible for
state stored by the subscriber to be exposed to `Layer`s.

## Solution:

This branch *starts* on the project of a general-purpose span registry
by adding a new trait, `LookupMetadata`. Subscribers may implement this
trait to allow looking up a span's `Metadata` by ID. If a subscriber
implements this trait, the `layer::Context` type will then allow any
layers wrapping that subscriber to look up metadata by ID. The
`FmtSubscriber` implements this trait.

Future work will be able to an interface for `Subscriber`s to expose
more span data, such as parents, children, and arbitrarily-typed
extensions, to `Layer`s as well.

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
This commit is contained in:
Eliza Weisman 2019-10-17 15:12:40 -07:00 committed by GitHub
parent f19f63f83d
commit 97a8681fb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 131 additions and 1 deletions

View File

@ -24,6 +24,7 @@ default = ["env-filter", "smallvec", "fmt", "ansi", "chrono", "tracing-log"]
env-filter = ["matchers", "regex", "lazy_static"]
fmt = ["owning_ref"]
ansi = ["fmt", "ansi_term"]
registry_unstable = []
# Alias for `env-filter`; renamed in version 0.1.2, and will be removed in 0.2.
filter = ["env-filter"]
@ -56,6 +57,9 @@ criterion = { version = "0.3", default_features = false }
azure-devops = { project = "tracing/tracing", pipeline = "tokio-rs.tracing", build = "1" }
maintenance = { status = "experimental" }
[package.metadata.docs.rs]
all-features = true
[[bench]]
name = "filter"
harness = false

View File

@ -271,7 +271,19 @@ where
}
}
#[cfg(feature = "registry_unstable")]
impl<N, E, F, W> crate::registry::LookupMetadata for Subscriber<N, E, F, W>
where
layer::Layered<F, Formatter<N, E, W>>: crate::registry::LookupMetadata,
{
#[inline]
fn metadata(&self, id: &span::Id) -> Option<&'static Metadata<'static>> {
self.inner.metadata(id)
}
}
// === impl Formatter ===
impl<N, E, W> Formatter<N, E, W>
where
N: for<'writer> FormatFields<'writer>,
@ -378,6 +390,13 @@ where
}
}
#[cfg(feature = "registry_unstable")]
impl<N, E, W> crate::registry::LookupMetadata for Formatter<N, E, W> {
fn metadata(&self, id: &span::Id) -> Option<&'static Metadata<'static>> {
self.spans.get(&id).map(|span| span.metadata())
}
}
// ===== impl Builder =====
impl Default for Builder {
@ -933,4 +952,11 @@ mod test {
assert!(dispatch.downcast_ref::<LevelFilter>().is_some());
assert!(dispatch.downcast_ref::<format::Format>().is_some())
}
#[test]
#[cfg(feature = "registry_unstable")]
fn is_lookup_meta() {
fn assert_lookup_meta<T: crate::registry::LookupMetadata>(_: T) {}
assert_lookup_meta(Subscriber::builder().finish())
}
}

View File

@ -6,6 +6,8 @@ use tracing_core::{
Event,
};
#[cfg(feature = "registry_unstable")]
use crate::registry::LookupMetadata;
use std::{any::TypeId, marker::PhantomData};
/// A composable handler for `tracing` events.
@ -605,6 +607,16 @@ where
}
}
#[cfg(feature = "registry_unstable")]
impl<L, S> LookupMetadata for Layered<L, S>
where
S: Subscriber + LookupMetadata,
{
fn metadata(&self, span: &span::Id) -> Option<&'static Metadata<'static>> {
self.inner.metadata(span)
}
}
impl<L, S> Layered<L, S>
where
S: Subscriber,
@ -678,6 +690,42 @@ impl<'a, S: Subscriber> Context<'a, S> {
subscriber.event(event);
}
}
/// Returns metadata for tne span with the given `id`, if it exists.
///
/// If this returns `None`, then no span exists for that ID (either it has
/// closed or the ID is invalid).
///
/// **Note**: This requires the wrapped subscriber to implement the
/// [`LookupMetadata`] trait. `Layer` implementations that wish to use this
/// function can bound their `Subscriber` type parameter with
/// ```rust,ignore
/// where S: Subscriber + LookupMetadata,
/// ```
/// or similar.
///
/// [`LookupMetadata`]: ../registry/trait.LookupMetadata.html
#[inline]
#[cfg(feature = "registry_unstable")]
pub fn metadata(&self, id: &span::Id) -> Option<&'static Metadata<'static>>
where
S: LookupMetadata,
{
self.subscriber.as_ref()?.metadata(id)
}
/// Returns `true` if an active span exists for the given `Id`.
#[inline]
#[cfg(feature = "registry_unstable")]
pub fn exists(&self, id: &span::Id) -> bool
where
S: LookupMetadata,
{
self.subscriber
.as_ref()
.map(|s| s.exists(id))
.unwrap_or(false)
}
}
impl<'a, S> Context<'a, S> {

View File

@ -25,7 +25,9 @@
//! - `fmt`: Enables the [`fmt`] module, which provides a subscriber
//! implementation for printing formatted representations of trace events.
//! Enabled by default.
//! - `ansi`: Enables `fmt` support for ANSI terminal colors. Enabled by default.
//! - `ansi`: Enables `fmt` support for ANSI terminal colors. Enabled by
//! default.
//! - `registry_unstable`: enables the experimental [`registry`] module.
//!
//! ### Optional Dependencies
//!
@ -47,6 +49,7 @@
//! [`chrono`]: https://crates.io/crates/chrono
//! [`env_logger` crate]: https://crates.io/crates/env_logger
//! [`parking_lot`]: https://crates.io/crates/parking_lot
//! [`registry`]: registry/index.html
#![doc(html_root_url = "https://docs.rs/tracing-subscriber/0.1.5")]
#![warn(
missing_debug_implementations,
@ -105,6 +108,8 @@ pub mod filter;
pub mod fmt;
pub mod layer;
pub mod prelude;
#[cfg(feature = "registry_unstable")]
pub mod registry;
pub mod reload;
pub(crate) mod sync;
pub(crate) mod thread;

View File

@ -0,0 +1,47 @@
//! **EXPERIMENTAL**: Storage for span data shared by multiple [`Layer`]s.
//!
//! This module is experimental. Although potential breaking changes will be
//! avoided when possible, we reserve the right to make breaking changes to this
//! module until it is no longer experimental.
//!
//! Add the `registry_unstable` feature to your `Cargo.toml` to enable
//! this module:
//!
//! ```toml
//! [dependencies.tracing-subscriber]
//! features = ["registry_unstable"]
//! ```
//!
//! [`Layer`]: ../layer/struct.Layer.html
use tracing_core::{span::Id, Metadata};
/// Provides access to stored span metadata.
///
/// Subscribers which store span metadata and associate it with span IDs should
/// implement this trait; if they do, any [`Layer`]s wrapping them can look up
/// metadata via the [`Context`] type's [`metadata()`] method.
///
/// [`Layer`]: ../layer/struct.Layer.html
/// [`Context`]: ../layer/struct.Context.html
/// [`metadata()`]: ../layer/struct.Context.html#method.metadata
pub trait LookupMetadata {
/// Returns metadata for tne span with the given `id`, if it exists.
///
/// If no span exists for the provided ID (e.g. the span has closed and been
/// removed from the registry, or the ID is invalid), this should return `None`.
fn metadata(&self, id: &Id) -> Option<&'static Metadata<'static>>;
/// Returns `true` if a span with the given `id` exists, false otherwise.
///
/// **Note**: The default implementation of this method is simply:
///```rust,ignore
/// fn exists(&self, id: &span::Id) -> bool {
/// self.metadata(id).is_some()
/// }
///```
/// If the subscriber has a faster way of determining whether a span exists
/// for a given ID (e.g., if the ID is greater than the current value of an
/// increasing ID counter, etc), this method may be overridden as an optimization.
fn exists(&self, id: &Id) -> bool {
self.metadata(id).is_some()
}
}