#![cfg_attr(docsrs, feature(doc_cfg))]1#![doc(2html_logo_url = "https://bevy.org/assets/icon.png",3html_favicon_url = "https://bevy.org/assets/icon.png"4)]56//! This crate provides logging functions and configuration for [Bevy](https://bevy.org)7//! apps, and automatically configures platform specific log handlers (i.e. Wasm or Android).8//!9//! The macros provided for logging are reexported from [`tracing`](https://docs.rs/tracing),10//! and behave identically to it.11//!12//! By default, the [`LogPlugin`] from this crate is included in Bevy's `DefaultPlugins`13//! and the logging macros can be used out of the box, if used.14//!15//! For more fine-tuned control over logging behavior, set up the [`LogPlugin`] or16//! `DefaultPlugins` during app initialization.1718extern crate alloc;1920use core::error::Error;2122#[cfg(target_os = "android")]23mod android_tracing;24mod once;2526#[cfg(feature = "trace_tracy_memory")]27#[global_allocator]28static GLOBAL: tracy_client::ProfiledAllocator<std::alloc::System> =29tracy_client::ProfiledAllocator::new(std::alloc::System, 100);3031/// The log prelude.32///33/// This includes the most common types in this crate, re-exported for your convenience.34pub mod prelude {35#[doc(hidden)]36pub use tracing::{37debug, debug_span, error, error_span, info, info_span, trace, trace_span, warn, warn_span,38};3940#[doc(hidden)]41pub use crate::{debug_once, error_once, info_once, trace_once, warn_once};4243#[doc(hidden)]44pub use bevy_utils::once;45}4647pub use bevy_utils::once;48pub use tracing::{49self, debug, debug_span, error, error_span, event, info, info_span, trace, trace_span, warn,50warn_span, Level,51};52pub use tracing_subscriber;5354use bevy_app::{App, Plugin};55use tracing_log::LogTracer;56use tracing_subscriber::{57filter::{FromEnvError, ParseError},58layer::Layered,59prelude::*,60registry::Registry,61EnvFilter, Layer,62};63#[cfg(feature = "tracing-chrome")]64use {65bevy_ecs::resource::Resource,66bevy_platform::cell::SyncCell,67tracing_subscriber::fmt::{format::DefaultFields, FormattedFields},68};6970/// Wrapper resource for `tracing-chrome`'s flush guard.71/// When the guard is dropped the chrome log is written to file.72#[cfg(feature = "tracing-chrome")]73#[expect(74dead_code,75reason = "`FlushGuard` never needs to be read, it just needs to be kept alive for the `App`'s lifetime."76)]77#[derive(Resource)]78pub(crate) struct FlushGuard(SyncCell<tracing_chrome::FlushGuard>);7980/// Adds logging to Apps. This plugin is part of the `DefaultPlugins`. Adding81/// this plugin will setup a collector appropriate to your target platform:82/// * Using [`tracing-subscriber`](https://crates.io/crates/tracing-subscriber) by default,83/// logging to `stdout`.84/// * Using [`android_log-sys`](https://crates.io/crates/android_log-sys) on Android,85/// logging to Android logs.86/// * Using [`tracing-wasm`](https://crates.io/crates/tracing-wasm) in Wasm, logging87/// to the browser console.88///89/// You can configure this plugin.90/// ```no_run91/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup};92/// # use bevy_log::LogPlugin;93/// # use tracing::Level;94/// fn main() {95/// App::new()96/// .add_plugins(DefaultPlugins.set(LogPlugin {97/// level: Level::DEBUG,98/// filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(),99/// custom_layer: |_| None,100/// fmt_layer: |_| None,101/// }))102/// .run();103/// }104/// ```105///106/// Log level can also be changed using the `RUST_LOG` environment variable.107/// For example, using `RUST_LOG=wgpu=error,bevy_render=info,bevy_ecs=trace cargo run ..`108///109/// It has the same syntax as the field [`LogPlugin::filter`], see [`EnvFilter`].110/// If you define the `RUST_LOG` environment variable, the [`LogPlugin`] settings111/// will be ignored.112///113/// Also, to disable color terminal output (ANSI escape codes), you can114/// set the environment variable `NO_COLOR` to any value. This common115/// convention is documented at [no-color.org](https://no-color.org/).116/// For example:117/// ```no_run118/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup};119/// # use bevy_log::LogPlugin;120/// fn main() {121/// # // SAFETY: Single-threaded122/// # unsafe {123/// std::env::set_var("NO_COLOR", "1");124/// # }125/// App::new()126/// .add_plugins(DefaultPlugins)127/// .run();128/// }129/// ```130///131/// If you want to setup your own tracing collector, you should disable this132/// plugin from `DefaultPlugins`:133/// ```no_run134/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup};135/// # use bevy_log::LogPlugin;136/// fn main() {137/// App::new()138/// .add_plugins(DefaultPlugins.build().disable::<LogPlugin>())139/// .run();140/// }141/// ```142/// # Example Setup143///144/// For a quick setup that enables all first-party logging while not showing any of your dependencies'145/// log data, you can configure the plugin as shown below.146///147/// ```no_run148/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup};149/// # use bevy_log::*;150/// App::new()151/// .add_plugins(DefaultPlugins.set(LogPlugin {152/// filter: "warn,my_crate=trace".to_string(), //specific filters153/// level: Level::TRACE,//Change this to be globally change levels154/// ..Default::default()155/// }))156/// .run();157/// ```158/// The filter (in this case an `EnvFilter`) chooses whether to print the log. The most specific filters apply with higher priority.159/// Let's start with an example: `filter: "warn".to_string()` will only print logs with level `warn` level or greater.160/// From here, we can change to `filter: "warn,my_crate=trace".to_string()`. Logs will print at level `warn` unless it's in `mycrate`,161/// which will instead print at `trace` level because `my_crate=trace` is more specific.162///163///164/// ## Log levels165/// Events can be logged at various levels of importance.166/// Only events at your configured log level and higher will be shown.167/// ```no_run168/// # use bevy_log::*;169/// // here is how you write new logs at each "log level" (in "most important" to170/// // "least important" order)171/// error!("something failed");172/// warn!("something bad happened that isn't a failure, but that's worth calling out");173/// info!("helpful information that is worth printing by default");174/// debug!("helpful for debugging");175/// trace!("very noisy");176/// ```177/// In addition to `format!` style arguments, you can print a variable's debug178/// value by using syntax like: `trace(?my_value)`.179///180/// ## Per module logging levels181/// Modules can have different logging levels using syntax like `crate_name::module_name=debug`.182///183///184/// ```no_run185/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup};186/// # use bevy_log::*;187/// App::new()188/// .add_plugins(DefaultPlugins.set(LogPlugin {189/// filter: "warn,my_crate=trace,my_crate::my_module=debug".to_string(), // Specific filters190/// level: Level::TRACE, // Change this to be globally change levels191/// ..Default::default()192/// }))193/// .run();194/// ```195/// The idea is that instead of deleting logs when they are no longer immediately applicable,196/// you just disable them. If you do need to log in the future, then you can enable the logs instead of having to rewrite them.197///198/// ## Further reading199///200/// The `tracing` crate has much more functionality than these examples can show.201/// Much of this configuration can be done with "layers" in the `log` crate.202/// Check out:203/// - Using spans to add more fine grained filters to logs204/// - Adding instruments to capture more function information205/// - Creating layers to add additional context such as line numbers206/// # Panics207///208/// This plugin should not be added multiple times in the same process. This plugin209/// sets up global logging configuration for **all** Apps in a given process, and210/// rerunning the same initialization multiple times will lead to a panic.211///212/// # Performance213///214/// Filters applied through this plugin are computed at _runtime_, which will215/// have a non-zero impact on performance.216/// To achieve maximum performance, consider using217/// [_compile time_ filters](https://docs.rs/log/#compile-time-filters)218/// provided by the [`log`](https://crates.io/crates/log) crate.219///220/// ```toml221/// # cargo.toml222/// [dependencies]223/// log = { version = "0.4", features = ["max_level_debug", "release_max_level_warn"] }224/// ```225pub struct LogPlugin {226/// Filters logs using the [`EnvFilter`] format227pub filter: String,228229/// Filters out logs that are "less than" the given level.230/// This can be further filtered using the `filter` setting.231pub level: Level,232233/// Optionally add an extra [`Layer`] to the tracing subscriber234///235/// This function is only called once, when the plugin is built.236///237/// Because [`BoxedLayer`] takes a `dyn Layer`, `Vec<Layer>` is also an acceptable return value.238///239/// Access to [`App`] is also provided to allow for communication between the240/// [`Subscriber`](tracing::Subscriber) and the [`App`].241///242/// Please see the `examples/app/log_layers.rs` for a complete example.243pub custom_layer: fn(app: &mut App) -> Option<BoxedLayer>,244245/// Override the default [`tracing_subscriber::fmt::Layer`] with a custom one.246///247/// This differs from [`custom_layer`](Self::custom_layer) in that248/// [`fmt_layer`](Self::fmt_layer) allows you to overwrite the default formatter layer, while249/// `custom_layer` only allows you to add additional layers (which are unable to modify the250/// default formatter).251///252/// For example, you can use [`tracing_subscriber::fmt::Layer::without_time`] to remove the253/// timestamp from the log output.254///255/// Please see the `examples/app/log_layers.rs` for a complete example.256pub fmt_layer: fn(app: &mut App) -> Option<BoxedFmtLayer>,257}258259/// A boxed [`Layer`] that can be used with [`LogPlugin::custom_layer`].260pub type BoxedLayer = Box<dyn Layer<Registry> + Send + Sync + 'static>;261262#[cfg(feature = "trace")]263type BaseSubscriber =264Layered<EnvFilter, Layered<Option<Box<dyn Layer<Registry> + Send + Sync>>, Registry>>;265266#[cfg(feature = "trace")]267type PreFmtSubscriber = Layered<tracing_error::ErrorLayer<BaseSubscriber>, BaseSubscriber>;268269#[cfg(not(feature = "trace"))]270type PreFmtSubscriber =271Layered<EnvFilter, Layered<Option<Box<dyn Layer<Registry> + Send + Sync>>, Registry>>;272273/// A boxed [`Layer`] that can be used with [`LogPlugin::fmt_layer`].274pub type BoxedFmtLayer = Box<dyn Layer<PreFmtSubscriber> + Send + Sync + 'static>;275276/// The default [`LogPlugin`] [`EnvFilter`].277pub const DEFAULT_FILTER: &str = concat!(278"wgpu=error,",279"naga=warn,",280"symphonia_bundle_mp3::demuxer=warn,",281"symphonia_format_caf::demuxer=warn,",282"symphonia_format_isompf4::demuxer=warn,",283"symphonia_format_mkv::demuxer=warn,",284"symphonia_format_ogg::demuxer=warn,",285"symphonia_format_riff::demuxer=warn,",286"symphonia_format_wav::demuxer=warn,",287"calloop::loop_logic=error,",288);289290impl Default for LogPlugin {291fn default() -> Self {292Self {293filter: DEFAULT_FILTER.to_string(),294level: Level::INFO,295custom_layer: |_| None,296fmt_layer: |_| None,297}298}299}300301impl Plugin for LogPlugin {302#[expect(clippy::print_stderr, reason = "Allowed during logger setup")]303fn build(&self, app: &mut App) {304#[cfg(feature = "trace")]305{306let old_handler = std::panic::take_hook();307std::panic::set_hook(Box::new(move |infos| {308eprintln!("{}", tracing_error::SpanTrace::capture());309old_handler(infos);310}));311}312313let finished_subscriber;314let subscriber = Registry::default();315316// add optional layer provided by user317let subscriber = subscriber.with((self.custom_layer)(app));318319let default_filter = { format!("{},{}", self.level, self.filter) };320let filter_layer = EnvFilter::try_from_default_env()321.or_else(|from_env_error| {322_ = from_env_error323.source()324.and_then(|source| source.downcast_ref::<ParseError>())325.map(|parse_err| {326// we cannot use the `error!` macro here because the logger is not ready yet.327eprintln!("LogPlugin failed to parse filter from env: {parse_err}");328});329330Ok::<EnvFilter, FromEnvError>(EnvFilter::builder().parse_lossy(&default_filter))331})332.unwrap();333let subscriber = subscriber.with(filter_layer);334335#[cfg(feature = "trace")]336let subscriber = subscriber.with(tracing_error::ErrorLayer::default());337338#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))]339{340#[cfg(all(feature = "tracing-chrome", not(target_os = "android")))]341let chrome_layer = {342let mut layer = tracing_chrome::ChromeLayerBuilder::new();343if let Ok(path) = std::env::var("TRACE_CHROME") {344layer = layer.file(path);345}346let (chrome_layer, guard) = layer347.name_fn(Box::new(|event_or_span| match event_or_span {348tracing_chrome::EventOrSpan::Event(event) => event.metadata().name().into(),349tracing_chrome::EventOrSpan::Span(span) => {350if let Some(fields) =351span.extensions().get::<FormattedFields<DefaultFields>>()352{353format!("{}: {}", span.metadata().name(), fields.fields.as_str())354} else {355span.metadata().name().into()356}357}358}))359.build();360app.insert_resource(FlushGuard(SyncCell::new(guard)));361chrome_layer362};363364#[cfg(feature = "tracing-tracy")]365let tracy_layer = tracing_tracy::TracyLayer::default();366367let fmt_layer = (self.fmt_layer)(app).unwrap_or_else(|| {368// note: the implementation of `Default` reads from the env var NO_COLOR369// to decide whether to use ANSI color codes, which is common convention370// https://no-color.org/371Box::new(tracing_subscriber::fmt::Layer::default().with_writer(std::io::stderr))372});373374// bevy_render::renderer logs a `tracy.frame_mark` event every frame375// at Level::INFO. Formatted logs should omit it.376#[cfg(feature = "tracing-tracy")]377let fmt_layer =378fmt_layer.with_filter(tracing_subscriber::filter::FilterFn::new(|meta| {379meta.fields().field("tracy.frame_mark").is_none()380}));381382let subscriber = subscriber.with(fmt_layer);383384#[cfg(all(feature = "tracing-chrome", not(target_os = "android")))]385let subscriber = subscriber.with(chrome_layer);386#[cfg(feature = "tracing-tracy")]387let subscriber = subscriber.with(tracy_layer);388#[cfg(target_os = "android")]389let subscriber = subscriber.with(android_tracing::AndroidLayer::default());390finished_subscriber = subscriber;391}392393#[cfg(target_arch = "wasm32")]394{395finished_subscriber = subscriber.with(tracing_wasm::WASMLayer::new(396tracing_wasm::WASMLayerConfig::default(),397));398}399400#[cfg(target_os = "ios")]401{402finished_subscriber = subscriber.with(tracing_oslog::OsLogger::default());403}404405let logger_already_set = LogTracer::init().is_err();406let subscriber_already_set =407tracing::subscriber::set_global_default(finished_subscriber).is_err();408409#[cfg(feature = "tracing-tracy")]410warn!("Tracing with Tracy is active, memory consumption will grow until a client is connected");411412match (logger_already_set, subscriber_already_set) {413(true, true) => error!(414"Could not set global logger and tracing subscriber as they are already set. Consider disabling LogPlugin."415),416(true, false) => error!("Could not set global logger as it is already set. Consider disabling LogPlugin."),417(false, true) => error!("Could not set global tracing subscriber as it is already set. Consider disabling LogPlugin."),418(false, false) => (),419}420}421}422423424