Expand description
The Subscribe trait, a composable abstraction for building collectors.
The Collect trait in tracing-core represents the complete set of
functionality required to consume tracing instrumentation. This means that
a single Collect instance is a self-contained implementation of a
complete strategy for collecting traces; but it also means that the
Collect trait cannot easily be composed with other collectors.
In particular, collectors are responsible for generating span IDs and assigning them to spans. Since these IDs must uniquely identify a span within the context of the current trace, this means that there may only be a single collector for a given thread at any point in time — otherwise, there would be no authoritative source of span IDs.
On the other hand, the majority of the Collect trait’s functionality
is composable: any number of subscribers may observe events, span entry
and exit, and so on, provided that there is a single authoritative source of
span IDs. The Subscribe trait represents this composable subset of the
Collect behavior; it can observe events and spans, but does not
assign IDs.
§Composing Subscribers
Since a subscriber does not implement a complete strategy for collecting
traces, it must be composed with a collector in order to be used. The
Subscribe trait is generic over a type parameter (called C in the trait
definition), representing the types of Collect they can be composed
with. Thus, a subscriber may be implemented that will only compose with a
particular Collect implementation, or additional trait bounds may be
added to constrain what types implementing Collect a subscriber can wrap.
Subscribers may be added to a collector by using the CollectExt::with
method, which is provided by tracing-subscriber’s prelude. This method
returns a Layered struct that implements Collect by composing the
Subscribe with the collector.
For example:
use tracing_subscriber::Subscribe;
use tracing_subscriber::prelude::*;
use tracing::Collect;
pub struct MySubscriber {
// ...
}
impl<C: Collect> Subscribe<C> for MySubscriber {
// ...
}
pub struct MyCollector {
// ...
}
impl Collect for MyCollector {
// ...
}
let collector = MyCollector::new()
.with(MySubscriber::new());
tracing::collect::set_global_default(collector);Multiple subscriber may be composed in the same manner:
pub struct MyOtherSubscriber {
// ...
}
impl<C: Collect> Subscribe<C> for MyOtherSubscriber {
// ...
}
pub struct MyThirdSubscriber {
// ...
}
impl<C: Collect> Subscribe<C> for MyThirdSubscriber {
// ...
}
}
let collect = MyCollector::new()
.with(MySubscriber::new())
.with(MyOtherSubscriber::new())
.with(MyThirdSubscriber::new());
tracing::collect::set_global_default(collect);The Subscribe::with_collector constructs the Layered type from a
Subscribe and Collect, and is called by CollectExt::with. In
general, it is more idiomatic to use CollectExt::with, and treat
Subscribe::with_collector as an implementation detail, as with_collector
calls must be nested, leading to less clear code for the reader.
§Runtime Configuration With Subscribers
In some cases, a particular subscriber may be enabled or disabled based on
runtime configuration. This can introduce challenges, because the type of a
layered collector depends on which subscribers are added to it: if an if
or match expression adds some Subscribe implementation in one branch,
and other subscribers in another, the collector values returned by those
branches will have different types. For example, the following will not
work:
use std::fs::File;
use tracing_subscriber::{Registry, prelude::*};
let stdout_log = tracing_subscriber::fmt::subscriber().pretty();
let collector = Registry::default().with(stdout_log);
// The compile error will occur here because the if and else
// branches have different (and therefore incompatible) types.
let collector = if cfg.is_prod {
let file = File::create(cfg.path)?;
let collector = tracing_subscriber::fmt::subscriber()
.json()
.with_writer(Arc::new(file));
collector.with(subscriber)
} else {
collector
};
tracing::collect::set_global_default(collector)
.expect("Unable to set global collector");However, a Subscribe wrapped in an Option also implements the Subscribe
trait. This allows individual layers to be enabled or disabled at
runtime while always producing a Collect of the same type. For
example:
use std::fs::File;
use tracing_subscriber::{Registry, prelude::*};
let stdout_log = tracing_subscriber::fmt::subscriber().pretty();
let collector = Registry::default().with(stdout_log);
// if `cfg.is_prod` is true, also log JSON-formatted logs to a file.
let json_log = if cfg.is_prod {
let file = File::create(cfg.path)?;
let json_log = tracing_subscriber::fmt::subscriber()
.json()
.with_writer(file);
Some(json_log)
} else {
None
};
// If `cfg.is_prod` is false, then `json` will be `None`, and this subscriber
// will do nothing. However, the collector will still have the same type
// regardless of whether the `Option`'s value is `None` or `Some`.
let collector = collector.with(json_log);
tracing::collect::set_global_default(collector)
.expect("Unable to set global collector");If a subscriber may be one of several different types, note that Box<dyn Subscribe<C> + Send + Sync + 'static> implements Subscribe.
This may be used to erase the type of a subscriber.
For example, a function that configures a subscriber to log to one of
several outputs might return a Box<dyn Subscribe<C> + Send + Sync + 'static>:
use tracing_subscriber::{
Subscribe,
registry::LookupSpan,
prelude::*,
};
use std::{path::PathBuf, fs::File, io};
/// Configures whether logs are emitted to a file, to stdout, or to stderr.
pub enum LogConfig {
File(PathBuf),
Stdout,
Stderr,
}
impl LogConfig {
pub fn subscriber<C>(self) -> Box<dyn Subscribe<C> + Send + Sync + 'static>
where
C: tracing_core::Collect,
for<'a> C: LookupSpan<'a>,
{
// Shared configuration regardless of where logs are output to.
let fmt = tracing_subscriber::fmt::subscriber()
.with_target(true)
.with_thread_names(true);
// Configure the writer based on the desired log target:
match self {
LogConfig::File(path) => {
let file = File::create(path).expect("failed to create log file");
Box::new(fmt.with_writer(file))
},
LogConfig::Stdout => Box::new(fmt.with_writer(io::stdout)),
LogConfig::Stderr => Box::new(fmt.with_writer(io::stderr)),
}
}
}
let config = LogConfig::Stdout;
tracing_subscriber::registry()
.with(config.subscriber())
.init();The Subscribe::boxed method is provided to make boxing a subscriber
more convenient, but Box::new may be used as well.
When the number of subscribers varies at runtime, note that a
Vec<S> where S: Subscribe also implements Subscribe. This
can be used to add a variable number of subscribers to a collector:
use tracing_subscriber::{Subscribe, prelude::*};
struct MySubscriber {
// ...
}
impl<C: tracing_core::Collect> Subscribe<C> for MySubscriber {
// ...
}
/// Returns how many subscribers we need
fn how_many_subscribers() -> usize {
// ...
}
// Create a variable-length `Vec` of subscribers
let mut subscribers = Vec::new();
for _ in 0..how_many_subscribers() {
subscribers.push(MySubscriber::new());
}
tracing_subscriber::registry()
.with(subscribers)
.init();If a variable number of subscribers is needed and those subscribers have
different types, a Vec of boxed subscriber trait objects may
be used. For example:
use tracing_subscriber::{filter::LevelFilter, Subscribe, prelude::*};
use std::fs::File;
struct Config {
enable_log_file: bool,
enable_stdout: bool,
enable_stderr: bool,
// ...
}
let cfg = Config::from_config_file()?;
// Based on our dynamically loaded config file, create any number of subscribers:
let mut subscribers = Vec::new();
if cfg.enable_log_file {
let file = File::create("myapp.log")?;
let subscriber = tracing_subscriber::fmt::subscriber()
.with_thread_names(true)
.with_target(true)
.json()
.with_writer(file)
// Box the subscriber as a type-erased trait object, so that it can
// be pushed to the `Vec`.
.boxed();
subscribers.push(subscriber);
}
if cfg.enable_stdout {
let subscriber = tracing_subscriber::fmt::subscriber()
.pretty()
.with_filter(LevelFilter::INFO)
// Box the subscriber as a type-erased trait object, so that it can
// be pushed to the `Vec`.
.boxed();
subscribers.push(subscriber);
}
if cfg.enable_stdout {
let subscriber = tracing_subscriber::fmt::subscriber()
.with_target(false)
.with_filter(LevelFilter::WARN)
// Box the subscriber as a type-erased trait object, so that it can
// be pushed to the `Vec`.
.boxed();
subscribers.push(subscriber);
}
tracing_subscriber::registry()
.with(subscribers)
.init();Finally, if the number of subscribers changes at runtime, a Vec of
subscribers can be used alongside the reload module to
add or remove subscribers dynamically at runtime.
§Recording Traces
The Subscribe trait defines a set of methods for consuming notifications from
tracing instrumentation, which are generally equivalent to the similarly
named methods on Collect. Unlike Collect, the methods on
Subscribe are additionally passed a Context type, which exposes additional
information provided by the wrapped subscriber (such as the current span)
to the subscriber.
§Filtering with Subscribers
As well as strategies for handling trace events, the Subscribe trait may also
be used to represent composable filters. This allows the determination of
what spans and events should be recorded to be decoupled from how they are
recorded: a filtering subscriber can be applied to other subscribers or
subscribers. Subscribes can be used to implement global filtering, where a
Subscribe provides a filtering strategy for the entire subscriber.
Additionally, individual recording Subscribes or sets of Subscribes may be
combined with per-subscriber filters that control what spans and events are
recorded by those subscribers.
§Global Filtering
A Subscribe that implements a filtering strategy should override the
register_callsite and/or enabled methods. It may also choose to implement
methods such as on_enter, if it wishes to filter trace events based on
the current span context.
Note that the Subscribe::register_callsite and Subscribe::enabled methods
determine whether a span or event is enabled globally. Thus, they should
not be used to indicate whether an individual subscriber wishes to record a
particular span or event. Instead, if a subscriber is only interested in a subset
of trace data, but does not wish to disable other spans and events for the
rest of the subscriber stack should ignore those spans and events in its
notification methods.
The filtering methods on a stack of Subscribes are evaluated in a top-down
order, starting with the outermost Subscribe and ending with the wrapped
Collect. If any subscriber returns false from its enabled method, or
Interest::never() from its register_callsite method, filter
evaluation will short-circuit and the span or event will be disabled.
§Enabling Interest
Whenever an tracing event (or span) is emitted, it goes through a number of steps to determine how and how much it should be processed. The earlier an event is disabled, the less work has to be done to process the event, so subscribers that implement filtering should attempt to disable unwanted events as early as possible. In order, each event checks:
register_callsite, once per callsite (roughly: once per time thatevent!orspan!is written in the source code; this is cached at the callsite). See [Collect::register_callsite] and [tracing_core::callsite] for a summary of how this behaves.enabled, once per emitted event (roughly: once per time thatevent!orspan!is executed), and only ifregister_callsiteregisters an [Interest::sometimes]. This is the main customization point to globally filter events based on their [Metadata]. If an event can be disabled based only on [Metadata], it should be, as this allows the construction of the actualEvent/Spanto be skipped.- For events only (and not spans),
event_enabledis called just before processing the event. This gives subscribers one last chance to say that an event should be filtered out, now that the event’s fields are known.
§Per-Subscriber Filtering
Note: per-subscriber filtering APIs currently require the "registry" crate
feature flag to be enabled.
Sometimes, it may be desirable for one Subscribe to record a particular subset
of spans and events, while a different subset of spans and events are
recorded by other Subscribes. For example:
- A subscriber that records metrics may wish to observe only events including particular tracked values, while a logging subscriber ignores those events.
- If recording a distributed trace is expensive, it might be desirable to
only send spans with
INFOand lower verbosity to the distributed tracing system, while logging more verbose spans to a file. - Spans and events with a particular target might be recorded differently from others, such as by generating an HTTP access log from a span that tracks the lifetime of an HTTP request.
The Filter trait is used to control what spans and events are
observed by an individual Subscribe, while still allowing other Subscribes to
potentially record them. The Subscribe::with_filter method combines a
Subscribe with a Filter, returning a Filtered subscriber.
This crate’s filter module provides a number of types which implement
the Filter trait, such as LevelFilter, Targets, and
FilterFn. These Filters provide ready-made implementations of common
forms of filtering. For custom filtering policies, the FilterFn and
DynFilterFn types allow implementing a Filter with a closure or
function pointer. In addition, when more control is required, the Filter
trait may also be implemented for user-defined types.
Option<Filter> also implements Filter, which allows for an optional
filter. None filters out nothing (that is, allows
everything through). For example:
fn setup_tracing<C: Collect>(filter_config: Option<&str>) {
let layer = MySubscriber::<C>::new()
.with_filter(filter_config.map(|config| filter_fn(my_filter(config))));
//...
}Warning: Currently, theRegistrytype defined in this crate is the only rootCollectcapable of supporting subscriberss with per-subscriber filters. In the future, new APIs will be added to allow other rootCollects to support per-subscriber filters.
For example, to generate an HTTP access log based on spans with
the http_access target, while logging other spans and events to
standard out, a Filter can be added to the access log subscriber:
use tracing_subscriber::{filter, prelude::*};
// Generates an HTTP access log.
let access_log = // ...
// Add a filter to the access log subscriber so that it only observes
// spans and events with the `http_access` target.
let access_log = access_log.with_filter(filter::filter_fn(|metadata| {
// Returns `true` if and only if the span or event's target is
// "http_access".
metadata.target() == "http_access"
}));
// A general-purpose logging subscriber.
let fmt_subscriber = tracing_subscriber::fmt::subscriber();
// Build a subscriber that combines the access log and stdout log
// subscribers.
tracing_subscriber::registry()
.with(fmt_subscriber)
.with(access_log)
.init();Multiple subscribers can have their own, separate per-subscriber filters. A span or event will be recorded if it is enabled by any per-subscriber filter, but it will be skipped by the subscribers whose filters did not enable it. Building on the previous example:
use tracing_subscriber::{filter::{filter_fn, LevelFilter}, prelude::*};
let access_log = // ...
let fmt_subscriber = tracing_subscriber::fmt::subscriber();
tracing_subscriber::registry()
// Add the filter for the "http_access" target to the access
// log subscriber, like before.
.with(access_log.with_filter(filter_fn(|metadata| {
metadata.target() == "http_access"
})))
// Add a filter for spans and events with the INFO level
// and below to the logging subscriber.
.with(fmt_subscriber.with_filter(LevelFilter::INFO))
.init();
// Neither subscriber will observe this event
tracing::debug!(does_anyone_care = false, "a tree fell in the forest");
// This event will be observed by the logging subscriber, but not
// by the access log subscriber.
tracing::warn!(dose_roentgen = %3.8, "not great, but not terrible");
// This event will be observed only by the access log subscriber.
tracing::trace!(target: "http_access", "HTTP request started");
// Both subscribers will observe this event.
tracing::error!(target: "http_access", "HTTP request failed with a very bad error!");A per-subscriber filter can be applied to multiple Subscribes at a time, by
combining them into a Layered subscriber using Subscribe::and_then, and then
calling Subscribe::with_filter on the resulting Layered subscriber.
Consider the following:
subscriber_aandsubscriber_b, which should only receive spans and events at theINFOlevel and above.- A third subscriber,
subscriber_c, which should receive spans and events at theDEBUGlevel as well.
The subscribers and filters would be composed thusly:
use tracing_subscriber::{filter::LevelFilter, prelude::*};
let subscriber_a = // ...
let subscriber_b = // ...
let subscriber_c = // ...
let info_subscribers = subscriber_a
// Combine `subscriber_a` and `subscriber_b` into a `Layered` subscriber:
.and_then(subscriber_b)
// ...and then add an `INFO` `LevelFilter` to that subscriber:
.with_filter(LevelFilter::INFO);
tracing_subscriber::registry()
// Add `subscriber_c` with a `DEBUG` filter.
.with(subscriber_c.with_filter(LevelFilter::DEBUG))
.with(info_subscribers)
.init();If a Filtered Subscribe is combined with another Subscribe
Subscribe::and_then, and a filter is added to the Layered subscriber, that
subscriber will be filtered by both the inner filter and the outer filter.
Only spans and events that are enabled by both filters will be
observed by that subscriber. This can be used to implement complex filtering
trees.
As an example, consider the following constraints:
- Suppose that a particular target is used to indicate events that should be counted as part of a metrics system, which should be only observed by a subscriber that collects metrics.
- A log of high-priority events (
INFOand above) should be logged to stdout, while more verbose events should be logged to a debugging log file. - Metrics-focused events should not be included in either log output.
In that case, it is possible to apply a filter to both logging subscribers to
exclude the metrics events, while additionally adding a LevelFilter
to the stdout log:
use tracing_subscriber::{filter, prelude::*};
use std::{fs::File, sync::Arc};
// A subscriber that logs events to stdout using the human-readable "pretty"
// format.
let stdout_log = tracing_subscriber::fmt::subscriber()
.pretty();
// A subscriber that logs events to a file.
let file = File::create("debug.log")?;
let debug_log = tracing_subscriber::fmt::subscriber()
.with_writer(file);
// A subscriber that collects metrics using specific events.
let metrics_subscriber = /* ... */ filter::LevelFilter::INFO;
tracing_subscriber::registry()
.with(
stdout_log
// Add an `INFO` filter to the stdout logging subscriber
.with_filter(filter::LevelFilter::INFO)
// Combine the filtered `stdout_log` subscriber with the
// `debug_log` subscriber, producing a new `Layered` subscriber.
.and_then(debug_log)
// Add a filter to *both* subscribers that rejects spans and
// events whose targets start with `metrics`.
.with_filter(filter::filter_fn(|metadata| {
!metadata.target().starts_with("metrics")
}))
)
.with(
// Add a filter to the metrics label that *only* enables
// events whose targets start with `metrics`.
metrics_subscriber.with_filter(filter::filter_fn(|metadata| {
metadata.target().starts_with("metrics")
}))
)
.init();
// This event will *only* be recorded by the metrics subscriber.
tracing::info!(target: "metrics::cool_stuff_count", value = 42);
// This event will only be seen by the debug log file subscriber:
tracing::debug!("this is a message, and part of a system of messages");
// This event will be seen by both the stdout log subscriber *and*
// the debug log file subscriber, but not by the metrics subscriber.
tracing::warn!("the message is a warning about danger!");Structs§
- Context
- Represents information about the current context provided to subscribers by the wrapped collector.
- Identity
- A subscriber that does nothing.
- Layered
- A collector composed of a collector wrapped by one or more subscribers.
Traits§
- Collect
Ext - Extension trait adding a
with(Subscribe)combinator to types implementing [Collect]. - Filter
registryandstd - A per-
Subscribefilter that determines whether a span or event is enabled for an individual subscriber. - Subscribe
- A composable handler for
tracingevents.