Module tracing_subscriber::layer
source · [−]Expand description
The Layer trait, a composable abstraction for building Subscribers.
The Subscriber trait in tracing-core represents the complete set of
functionality required to consume tracing instrumentation. This means that
a single Subscriber instance is a self-contained implementation of a
complete strategy for collecting traces; but it also means that the
Subscriber trait cannot easily be composed with other Subscribers.
In particular, Subscribers 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 Subscriber 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 Subscriber 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 Layer trait represents this composable subset of the
Subscriber behavior; it can observe events and spans, but does not
assign IDs.
Composing Layers
Since a Layer does not implement a complete strategy for collecting
traces, it must be composed with a Subscriber in order to be used. The
Layer trait is generic over a type parameter (called S in the trait
definition), representing the types of Subscriber they can be composed
with. Thus, a Layer may be implemented that will only compose with a
particular Subscriber implementation, or additional trait bounds may be
added to constrain what types implementing Subscriber a Layer can wrap.
Layers may be added to a Subscriber by using the SubscriberExt::with
method, which is provided by tracing-subscriber’s prelude. This method
returns a Layered struct that implements Subscriber by composing the
Layer with the Subscriber.
For example:
use tracing_subscriber::Layer;
use tracing_subscriber::prelude::*;
use tracing::Subscriber;
pub struct MyLayer {
// ...
}
impl<S: Subscriber> Layer<S> for MyLayer {
// ...
}
pub struct MySubscriber {
// ...
}
impl Subscriber for MySubscriber {
// ...
}
let subscriber = MySubscriber::new()
.with(MyLayer::new());
tracing::subscriber::set_global_default(subscriber);Multiple Layers may be composed in the same manner:
pub struct MyOtherLayer {
// ...
}
impl<S: Subscriber> Layer<S> for MyOtherLayer {
// ...
}
pub struct MyThirdLayer {
// ...
}
impl<S: Subscriber> Layer<S> for MyThirdLayer {
// ...
}
}
let subscriber = MySubscriber::new()
.with(MyLayer::new())
.with(MyOtherLayer::new())
.with(MyThirdLayer::new());
tracing::subscriber::set_global_default(subscriber);The Layer::with_subscriber constructs the Layered type from a
Layer and Subscriber, and is called by SubscriberExt::with. In
general, it is more idiomatic to use SubscriberExt::with, and treat
Layer::with_subscriber as an implementation detail, as with_subscriber
calls must be nested, leading to less clear code for the reader.
Runtime Configuration With Layers
In some cases, a particular Layer may be enabled or disabled based on
runtime configuration. This can introduce challenges, because the type of a
layered Subscriber depends on which layers are added to it: if an if
or match expression adds some Layer implementation in one branch,
and other layers in another, the Subscriber 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::layer().pretty();
let subscriber = Registry::default().with(stdout_log);
// The compile error will occur here because the if and else
// branches have different (and therefore incompatible) types.
let subscriber = if cfg.is_prod {
let file = File::create(cfg.path)?;
let layer = tracing_subscriber::fmt::layer()
.json()
.with_writer(Arc::new(file));
layer.with(subscriber)
} else {
layer
};
tracing::subscriber::set_global_default(subscriber)
.expect("Unable to set global subscriber");However, a Layer wrapped in an Option also implements the Layer
trait. This allows individual layers to be enabled or disabled at
runtime while always producing a Subscriber of the same type. For
example:
use std::fs::File;
use tracing_subscriber::{Registry, prelude::*};
let stdout_log = tracing_subscriber::fmt::layer().pretty();
let subscriber = 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::layer()
.json()
.with_writer(file);
Some(json_log)
} else {
None
};
// If `cfg.is_prod` is false, then `json` will be `None`, and this layer
// will do nothing. However, the subscriber will still have the same type
// regardless of whether the `Option`'s value is `None` or `Some`.
let subscriber = subscriber.with(json_log);
tracing::subscriber::set_global_default(subscriber)
.expect("Unable to set global subscriber");If a Layer may be one of several different types, note that Box<dyn Layer<S> + Send + Sync> implements Layer.
This may be used to erase the type of a Layer.
For example, a function that configures a Layer to log to one of
several outputs might return a Box<dyn Layer<S> + Send + Sync + 'static>:
use tracing_subscriber::{
Layer,
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 layer<S>(self) -> Box<dyn Layer<S> + Send + Sync + 'static>
where
S: tracing_core::Subscriber,
for<'a> S: LookupSpan<'a>,
{
// Shared configuration regardless of where logs are output to.
let fmt = tracing_subscriber::fmt::layer()
.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.layer())
.init();The Layer::boxed method is provided to make boxing a Layer
more convenient, but Box::new may be used as well.
When the number of Layers varies at runtime, note that a
[Vec<L> where L: Layer`` also implements Layer][vec-impl]. This
can be used to add a variable number of Layers to a Subscriber:
use tracing_subscriber::{Layer, prelude::*};
struct MyLayer {
// ...
}
impl<S: tracing_core::Subscriber> Layer<S> for MyLayer {
// ...
}
/// Returns how many layers we need
fn how_many_layers() -> usize {
// ...
}
// Create a variable-length `Vec` of layers
let mut layers = Vec::new();
for _ in 0..how_many_layers() {
layers.push(MyLayer::new());
}
tracing_subscriber::registry()
.with(layers)
.init();If a variable number of Layer is needed and those Layers have
different types, a Vec of boxed Layer trait objects may
be used. For example:
use tracing_subscriber::{filter::LevelFilter, Layer, 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 layers:
let mut layers = Vec::new();
if cfg.enable_log_file {
let file = File::create("myapp.log")?;
let layer = tracing_subscriber::fmt::layer()
.with_thread_names(true)
.with_target(true)
.json()
.with_writer(file)
// Box the layer as a type-erased trait object, so that it can
// be pushed to the `Vec`.
.boxed();
layers.push(layer);
}
if cfg.enable_stdout {
let layer = tracing_subscriber::fmt::layer()
.pretty()
.with_filter(LevelFilter::INFO)
// Box the layer as a type-erased trait object, so that it can
// be pushed to the `Vec`.
.boxed();
layers.push(layer);
}
if cfg.enable_stdout {
let layer = tracing_subscriber::fmt::layer()
.with_target(false)
.with_filter(LevelFilter::WARN)
// Box the layer as a type-erased trait object, so that it can
// be pushed to the `Vec`.
.boxed();
layers.push(layer);
}
tracing_subscriber::registry()
.with(layers)
.init();Finally, if the number of layers 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 Layer trait defines a set of methods for consuming notifications from
tracing instrumentation, which are generally equivalent to the similarly
named methods on Subscriber. Unlike Subscriber, the methods on
Layer are additionally passed a Context type, which exposes additional
information provided by the wrapped subscriber (such as the current span)
to the layer.
Filtering with Layers
As well as strategies for handling trace events, the Layer 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 layer can be applied to other layers or
subscribers. Layers can be used to implement global filtering, where a
Layer provides a filtering strategy for the entire subscriber.
Additionally, individual recording Layers or sets of Layers may be
combined with per-layer filters that control what spans and events are
recorded by those layers.
Global Filtering
A Layer 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 Layer::register_callsite and Layer::enabled methods
determine whether a span or event is enabled globally. Thus, they should
not be used to indicate whether an individual layer wishes to record a
particular span or event. Instead, if a layer is only interested in a subset
of trace data, but does not wish to disable other spans and events for the
rest of the layer stack should ignore those spans and events in its
notification methods.
The filtering methods on a stack of Layers are evaluated in a top-down
order, starting with the outermost Layer and ending with the wrapped
Subscriber. If any layer 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.
Per-Layer Filtering
Note: per-layer filtering APIs currently require the "registry" crate
feature flag to be enabled.
Sometimes, it may be desirable for one Layer to record a particular subset
of spans and events, while a different subset of spans and events are
recorded by other Layers. For example:
- A layer that records metrics may wish to observe only events including particular tracked values, while a logging layer 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 Layer, while still allowing other Layers to
potentially record them. The Layer::with_filter method combines a
Layer with a Filter, returning a Filtered layer.
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.
Warning: Currently, the
Registry type defined in this crate is the only root
Subscriber capable of supporting Layers with
per-layer filters. In the future, new APIs will be added to allow other
root Subscribers to support per-layer 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 layer:
use tracing_subscriber::{filter, prelude::*};
// Generates an HTTP access log.
let access_log = // ...
// Add a filter to the access log layer 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 layer.
let fmt_layer = tracing_subscriber::fmt::layer();
// Build a subscriber that combines the access log and stdout log
// layers.
tracing_subscriber::registry()
.with(fmt_layer)
.with(access_log)
.init();Multiple layers can have their own, separate per-layer filters. A span or event will be recorded if it is enabled by any per-layer filter, but it will be skipped by the layers whose filters did not enable it. Building on the previous example:
use tracing_subscriber::{filter::{filter_fn, LevelFilter}, prelude::*};
let access_log = // ...
let fmt_layer = tracing_subscriber::fmt::layer();
tracing_subscriber::registry()
// Add the filter for the "http_access" target to the access
// log layer, 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 layer.
.with(fmt_layer.with_filter(LevelFilter::INFO))
.init();
// Neither layer will observe this event
tracing::debug!(does_anyone_care = false, "a tree fell in the forest");
// This event will be observed by the logging layer, but not
// by the access log layer.
tracing::warn!(dose_roentgen = %3.8, "not great, but not terrible");
// This event will be observed only by the access log layer.
tracing::trace!(target: "http_access", "HTTP request started");
// Both layers will observe this event.
tracing::error!(target: "http_access", "HTTP request failed with a very bad error!");A per-layer filter can be applied to multiple Layers at a time, by
combining them into a Layered layer using Layer::and_then, and then
calling Layer::with_filter on the resulting Layered layer.
Consider the following:
layer_aandlayer_b, which should only receive spans and events at theINFOlevel and above.- A third layer,
layer_c, which should receive spans and events at theDEBUGlevel as well. The layers and filters would be composed thusly:
use tracing_subscriber::{filter::LevelFilter, prelude::*};
let layer_a = // ...
let layer_b = // ...
let layer_c = // ...
let info_layers = layer_a
// Combine `layer_a` and `layer_b` into a `Layered` layer:
.and_then(layer_b)
// ...and then add an `INFO` `LevelFilter` to that layer:
.with_filter(LevelFilter::INFO);
tracing_subscriber::registry()
// Add `layer_c` with a `DEBUG` filter.
.with(layer_c.with_filter(LevelFilter::DEBUG))
.with(info_layers)
.init();If a Filtered Layer is combined with another Layer
Layer::and_then, and a filter is added to the Layered layer, that
layer 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 layer. 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 layer 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 layers 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 layer that logs events to stdout using the human-readable "pretty"
// format.
let stdout_log = tracing_subscriber::fmt::layer()
.pretty();
// A layer that logs events to a file.
let file = File::create("debug.log")?;
let debug_log = tracing_subscriber::fmt::layer()
.with_writer(Arc::new(file));
// A layer that collects metrics using specific events.
let metrics_layer = /* ... */ filter::LevelFilter::INFO;
tracing_subscriber::registry()
.with(
stdout_log
// Add an `INFO` filter to the stdout logging layer
.with_filter(filter::LevelFilter::INFO)
// Combine the filtered `stdout_log` layer with the
// `debug_log` layer, producing a new `Layered` layer.
.and_then(debug_log)
// Add a filter to *both* layers 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_layer.with_filter(filter::filter_fn(|metadata| {
metadata.target().starts_with("metrics")
}))
)
.init();
// This event will *only* be recorded by the metrics layer.
tracing::info!(target: "metrics::cool_stuff_count", value = 42);
// This event will only be seen by the debug log file layer:
tracing::debug!("this is a message, and part of a system of messages");
// This event will be seen by both the stdout log layer *and*
// the debug log file layer, but not by the metrics layer.
tracing::warn!("the message is a warning about danger!");Structs
Represents information about the current context provided to Layers by the
wrapped Subscriber.
A layer that does nothing.
A Subscriber composed of a Subscriber wrapped by one or more
Layers.
Traits
A per-Layer filter that determines whether a span or event is enabled
for an individual layer.
A composable handler for tracing events.
Extension trait adding a with(Layer) combinator to Subscribers.
