Improve tracing layer customization (#13159)

# Objective

- Fixes https://github.com/bevyengine/bevy/issues/12597

The current tracing customization option (the `update_subscriber` field)
was basically unusable because it provides a `dyn Subscriber` and most
layers require a `Subscriber` that also implements `for<'a>
LookupSpan<'a, Data=Data<'a>>`, so it was impossible to add a layer on
top of the `dyn Subscriber`.

This PR provides an alternative way of adding additional tracing layers
to the LogPlugin by instead creating an `Option<Layer>`.

This is enough for most situations because `Option<Layer>` and
`Vec<Layer>` both implement `Layer`.

## Solution

- Replace the `update_subscriber` field of `LogPlugin` with a
`custom_layer` field which is function pointer returning an
`Option<BoxedLayer>`
- Update the examples to showcase that this works:
  - with multiple additional layers
- with Layers that were previously problematic, such as
`bevy::log::tracing_subscriber::fmt::layer().with_file(true)` (mentioned
in the issue)
  
Note that in the example this results in duplicate logs, since we have
our own layer on top of the default `fmt_layer` added in the LogPlugin;
maybe in the future we might want to provide a single one? Or to let the
user customize the default `fmt_layer` ? I still think this change is an
improvement upon the previous solution, which was basically broken.

---

## Changelog

> This section is optional. If this was a trivial fix, or has no
externally-visible impact, you can delete this section.

- The `LogPlugin`'s `update_subscriber` field has been replaced with
`custom_layer` to allow the user to flexibly add a `tracing::Layer` to
the layer stack

## Migration Guide

- The `LogPlugin`'s `update_subscriber` field has been replaced with
`custom_layer`

---------

Co-authored-by: BD103 <59022059+BD103@users.noreply.github.com>
This commit is contained in:
Periwink 2024-05-12 17:16:56 -04:00 committed by GitHub
parent 2fd432c463
commit ded5d523bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 38 additions and 39 deletions

View file

@ -49,11 +49,10 @@ pub use bevy_utils::{
pub use tracing_subscriber;
use bevy_app::{App, Plugin};
use bevy_utils::tracing::Subscriber;
use tracing_log::LogTracer;
#[cfg(feature = "tracing-chrome")]
use tracing_subscriber::fmt::{format::DefaultFields, FormattedFields};
use tracing_subscriber::{prelude::*, registry::Registry, EnvFilter};
use tracing_subscriber::{prelude::*, registry::Registry, EnvFilter, Layer};
#[cfg(feature = "tracing-chrome")]
use {bevy_ecs::system::Resource, bevy_utils::synccell::SyncCell};
@ -83,7 +82,7 @@ pub(crate) struct FlushGuard(SyncCell<tracing_chrome::FlushGuard>);
/// .add_plugins(DefaultPlugins.set(LogPlugin {
/// level: Level::DEBUG,
/// filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(),
/// update_subscriber: None,
/// custom_layer: |_| None,
/// }))
/// .run();
/// }
@ -121,23 +120,28 @@ pub struct LogPlugin {
/// This can be further filtered using the `filter` setting.
pub level: Level,
/// Optionally apply extra transformations to the tracing subscriber,
/// such as adding [`Layer`](tracing_subscriber::layer::Layer)s.
/// Optionally add an extra [`Layer`] to the tracing subscriber
///
/// This function is only called once, when the plugin is built.
///
/// Because [`BoxedLayer`] takes a `dyn Layer`, `Vec<Layer>` is also an acceptable return value.
///
/// Access to [`App`] is also provided to allow for communication between the [`Subscriber`]
/// and the [`App`].
pub update_subscriber: Option<fn(&mut App, BoxedSubscriber) -> BoxedSubscriber>,
///
/// Please see the `examples/log_layers.rs` for a complete example.
pub custom_layer: fn(app: &mut App) -> Option<BoxedLayer>,
}
/// Alias for a boxed [`Subscriber`].
pub type BoxedSubscriber = Box<dyn Subscriber + Send + Sync + 'static>;
/// A boxed [`Layer`] that can be used with [`LogPlugin`].
pub type BoxedLayer = Box<dyn Layer<Registry> + Send + Sync + 'static>;
impl Default for LogPlugin {
fn default() -> Self {
Self {
filter: "wgpu=error,naga=warn".to_string(),
level: Level::INFO,
update_subscriber: None,
custom_layer: |_| None,
}
}
}
@ -155,11 +159,16 @@ impl Plugin for LogPlugin {
}
let finished_subscriber;
let subscriber = Registry::default();
// add optional layer provided by user
let subscriber = subscriber.with((self.custom_layer)(app));
let default_filter = { format!("{},{}", self.level, self.filter) };
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new(&default_filter))
.unwrap();
let subscriber = Registry::default().with(filter_layer);
let subscriber = subscriber.with(filter_layer);
#[cfg(feature = "trace")]
let subscriber = subscriber.with(tracing_error::ErrorLayer::default());
@ -209,12 +218,7 @@ impl Plugin for LogPlugin {
let subscriber = subscriber.with(chrome_layer);
#[cfg(feature = "tracing-tracy")]
let subscriber = subscriber.with(tracy_layer);
if let Some(update_subscriber) = self.update_subscriber {
finished_subscriber = update_subscriber(app, Box::new(subscriber));
} else {
finished_subscriber = Box::new(subscriber);
}
finished_subscriber = subscriber;
}
#[cfg(target_arch = "wasm32")]

View file

@ -1,11 +1,7 @@
//! This example illustrates how to add custom log layers in bevy.
use bevy::{
log::tracing_subscriber::{layer::SubscriberExt, Layer},
log::BoxedSubscriber,
prelude::*,
utils::tracing::Subscriber,
};
use bevy::log::BoxedLayer;
use bevy::{log::tracing_subscriber::Layer, prelude::*, utils::tracing::Subscriber};
struct CustomLayer;
@ -24,22 +20,21 @@ impl<S: Subscriber> Layer<S> for CustomLayer {
// We don't need App for this example, as we are just printing log information.
// For an example that uses App, see log_layers_ecs.
fn update_subscriber(_: &mut App, subscriber: BoxedSubscriber) -> BoxedSubscriber {
Box::new(subscriber.with(CustomLayer))
fn custom_layer(_app: &mut App) -> Option<BoxedLayer> {
// You can provide multiple layers like this, since Vec<Layer> is also a layer:
Some(Box::new(vec![
bevy::log::tracing_subscriber::fmt::layer()
.with_file(true)
.boxed(),
CustomLayer.boxed(),
]))
}
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(bevy::log::LogPlugin {
update_subscriber: Some(update_subscriber),
// You can chain multiple subscriber updates like this:
//
// update_subscriber: Some(|app, subscriber| {
// let subscriber = update_subscriber_a(app, subscriber);
// let subscriber = update_subscriber_b(app, subscriber);
//
// update_subscriber_c(app, subscriber)
// }),
custom_layer,
..default()
}))
.add_systems(Update, log_system)

View file

@ -3,7 +3,7 @@
//! The way we will do this is via a [`mpsc`] channel. [`mpsc`] channels allow 2 unrelated
//! parts of the program to communicate (in this case, [`Layer`]s and Bevy's ECS).
//!
//! Inside the `update_subscriber` function we will create a [`mpsc::Sender`] and a [`mpsc::Receiver`] from a
//! Inside the `custom_layer` function we will create a [`mpsc::Sender`] and a [`mpsc::Receiver`] from a
//! [`mpsc::channel`]. The [`Sender`](mpsc::Sender) will go into the `AdvancedLayer` and the [`Receiver`](mpsc::Receiver) will
//! go into a non-send resource called `LogEvents` (It has to be non-send because [`Receiver`](mpsc::Receiver) is [`!Sync`](Sync)).
//! From there we will use `transfer_log_events` to transfer log events from `LogEvents` to an ECS event called `LogEvent`.
@ -13,9 +13,9 @@
use std::sync::mpsc;
use bevy::log::BoxedLayer;
use bevy::{
log::tracing_subscriber::{self, layer::SubscriberExt, Layer},
log::BoxedSubscriber,
log::tracing_subscriber::{self, Layer},
prelude::*,
utils::tracing,
utils::tracing::Subscriber,
@ -77,7 +77,7 @@ impl tracing::field::Visit for CaptureLayerVisitor<'_> {
}
}
}
fn update_subscriber(app: &mut App, subscriber: BoxedSubscriber) -> BoxedSubscriber {
fn custom_layer(app: &mut App) -> Option<BoxedLayer> {
let (sender, receiver) = mpsc::channel();
let layer = CaptureLayer { sender };
@ -87,13 +87,13 @@ fn update_subscriber(app: &mut App, subscriber: BoxedSubscriber) -> BoxedSubscri
app.add_event::<LogEvent>();
app.add_systems(Update, transfer_log_events);
Box::new(subscriber.with(layer))
Some(layer.boxed())
}
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(bevy::log::LogPlugin {
update_subscriber: Some(update_subscriber),
custom_layer,
..default()
}))
.add_systems(Startup, (log_system, setup))