feat(log): support customizing default log formatting (#17722)

The LogPlugin now allows overriding the default
`tracing_subscriber::fmt::Layer` through a new `fmt_layer` option. This
enables customization of the default log output format without having to
replace the entire logging system.

For example, to disable timestamps in the log output:

```rust
fn fmt_layer(_app: &mut App) -> Option<bevy::log::BoxedFmtLayer> {
    Some(Box::new(
        bevy::log::tracing_subscriber::fmt::Layer::default()
            .without_time()
            .with_writer(std::io::stderr),
    ))
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins.set(bevy::log::LogPlugin {
            fmt_layer,
            ..default()
        }))
        .run();
}
```

This is different from the existing `custom_layer` option, because that
option _adds_ additional layers to the subscriber, but can't modify the
default formatter layer (at least, not to my knowledge).

I almost always disable timestamps in my Bevy logs, and usually also
tweak other default log formatting (such as `with_span_events`), which
made it so that I always had to disable the default logger. This allows
me to use everything the Bevy logger supports (including tracy support),
while still formatting the default logs the way I like them.

---------

Signed-off-by: Jean Mertz <git@jeanmertz.com>
This commit is contained in:
Jean Mertz 2025-05-06 01:01:06 +02:00 committed by GitHub
parent bf42cb3532
commit 3b24f520b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 53 additions and 6 deletions

View File

@ -56,6 +56,7 @@ use bevy_app::{App, Plugin};
use tracing_log::LogTracer;
use tracing_subscriber::{
filter::{FromEnvError, ParseError},
layer::Layered,
prelude::*,
registry::Registry,
EnvFilter, Layer,
@ -97,6 +98,7 @@ pub(crate) struct FlushGuard(SyncCell<tracing_chrome::FlushGuard>);
/// level: Level::DEBUG,
/// filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(),
/// custom_layer: |_| None,
/// fmt_layer: |_| None,
/// }))
/// .run();
/// }
@ -240,11 +242,38 @@ pub struct LogPlugin {
///
/// Please see the `examples/log_layers.rs` for a complete example.
pub custom_layer: fn(app: &mut App) -> Option<BoxedLayer>,
/// Override the default [`tracing_subscriber::fmt::Layer`] with a custom one.
///
/// This differs from [`custom_layer`](Self::custom_layer) in that
/// [`fmt_layer`](Self::fmt_layer) allows you to overwrite the default formatter layer, while
/// `custom_layer` only allows you to add additional layers (which are unable to modify the
/// default formatter).
///
/// For example, you can use [`tracing_subscriber::fmt::Layer::without_time`] to remove the
/// timestamp from the log output.
///
/// Please see the `examples/log_layers.rs` for a complete example.
pub fmt_layer: fn(app: &mut App) -> Option<BoxedFmtLayer>,
}
/// A boxed [`Layer`] that can be used with [`LogPlugin`].
/// A boxed [`Layer`] that can be used with [`LogPlugin::custom_layer`].
pub type BoxedLayer = Box<dyn Layer<Registry> + Send + Sync + 'static>;
#[cfg(feature = "trace")]
type BaseSubscriber =
Layered<EnvFilter, Layered<Option<Box<dyn Layer<Registry> + Send + Sync>>, Registry>>;
#[cfg(feature = "trace")]
type PreFmtSubscriber = Layered<tracing_error::ErrorLayer<BaseSubscriber>, BaseSubscriber>;
#[cfg(not(feature = "trace"))]
type PreFmtSubscriber =
Layered<EnvFilter, Layered<Option<Box<dyn Layer<Registry> + Send + Sync>>, Registry>>;
/// A boxed [`Layer`] that can be used with [`LogPlugin::fmt_layer`].
pub type BoxedFmtLayer = Box<dyn Layer<PreFmtSubscriber> + Send + Sync + 'static>;
/// The default [`LogPlugin`] [`EnvFilter`].
pub const DEFAULT_FILTER: &str = "wgpu=error,naga=warn";
@ -254,6 +283,7 @@ impl Default for LogPlugin {
filter: DEFAULT_FILTER.to_string(),
level: Level::INFO,
custom_layer: |_| None,
fmt_layer: |_| None,
}
}
}
@ -328,10 +358,12 @@ impl Plugin for LogPlugin {
#[cfg(feature = "tracing-tracy")]
let tracy_layer = tracing_tracy::TracyLayer::default();
// note: the implementation of `Default` reads from the env var NO_COLOR
// to decide whether to use ANSI color codes, which is common convention
// https://no-color.org/
let fmt_layer = tracing_subscriber::fmt::Layer::default().with_writer(std::io::stderr);
let fmt_layer = (self.fmt_layer)(app).unwrap_or_else(|| {
// note: the implementation of `Default` reads from the env var NO_COLOR
// to decide whether to use ANSI color codes, which is common convention
// https://no-color.org/
Box::new(tracing_subscriber::fmt::Layer::default().with_writer(std::io::stderr))
});
// bevy_render::renderer logs a `tracy.frame_mark` event every frame
// at Level::INFO. Formatted logs should omit it.

View File

@ -4,7 +4,7 @@ use bevy::{
log::{
tracing::{self, Subscriber},
tracing_subscriber::Layer,
BoxedLayer,
BoxedFmtLayer, BoxedLayer,
},
prelude::*,
};
@ -36,10 +36,24 @@ fn custom_layer(_app: &mut App) -> Option<BoxedLayer> {
]))
}
// While `custom_layer` allows you to add _additional_ layers, it won't allow you to override the
// default `tracing_subscriber::fmt::Layer` added by `LogPlugin`. To do that, you can use the
// `fmt_layer` option.
//
// In this example, we're disabling the timestamp in the log output.
fn fmt_layer(_app: &mut App) -> Option<BoxedFmtLayer> {
Some(Box::new(
bevy::log::tracing_subscriber::fmt::Layer::default()
.without_time()
.with_writer(std::io::stderr),
))
}
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(bevy::log::LogPlugin {
custom_layer,
fmt_layer,
..default()
}))

View File

@ -30,6 +30,7 @@ fn main() {
level: Level::TRACE,
filter: "warn,log_layers_ecs=trace".to_string(),
custom_layer,
..default()
}))
.add_systems(Startup, (log_system, setup))
.add_systems(Update, print_logs)