diff --git a/Cargo.toml b/Cargo.toml index 8bb16b741db86..04db26208b3dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -138,7 +138,6 @@ default = [ "bevy_gizmos", "bevy_gltf", "bevy_input_focus", - "bevy_log", "bevy_mesh_picking_backend", "bevy_pbr", "bevy_picking", @@ -160,6 +159,7 @@ default = [ "smaa_luts", "sysinfo_plugin", "tonemapping_luts", + "tracing", "vorbis", "webgl2", "x11", @@ -285,9 +285,6 @@ bevy_dev_tools = ["bevy_internal/bevy_dev_tools"] # Enable the Bevy Remote Protocol bevy_remote = ["bevy_internal/bevy_remote"] -# Enable integration with `tracing` and `log` -bevy_log = ["bevy_internal/bevy_log"] - # Enable input focus subsystem bevy_input_focus = ["bevy_internal/bevy_input_focus"] @@ -314,7 +311,10 @@ trace_tracy_memory = [ ] # Tracing support -trace = ["bevy_internal/trace", "dep:tracing"] +tracing = ["bevy_internal/tracing", "dep:tracing"] + +# Enables traces within Bevy using tracing +trace = ["bevy_internal/trace", "tracing"] # Basis Universal compressed texture support basis-universal = ["bevy_internal/basis-universal"] @@ -1592,7 +1592,7 @@ wasm = true name = "headless" path = "examples/app/headless.rs" doc-scrape-examples = true -required-features = ["bevy_log"] +required-features = [] [package.metadata.example.headless] name = "Headless" diff --git a/crates/bevy_animation/Cargo.toml b/crates/bevy_animation/Cargo.toml index 37bc05aaffa0f..882904f4296ea 100644 --- a/crates/bevy_animation/Cargo.toml +++ b/crates/bevy_animation/Cargo.toml @@ -47,6 +47,18 @@ tracing = { version = "0.1", default-features = false, features = ["std"] } [target.'cfg(target_arch = "wasm32")'.dependencies] # TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption. uuid = { version = "1.13.1", default-features = false, features = ["js"] } +bevy_log = { path = "../bevy_log", version = "0.16.0-dev", default-features = false, features = [ + "web", +] } +bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [ + "web", +] } +bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ + "web", +] } +bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [ + "web", +] } [lints] workspace = true diff --git a/crates/bevy_dylib/src/lib.rs b/crates/bevy_dylib/src/lib.rs index 1ff40ce3e8bb8..982ef0207d7e1 100644 --- a/crates/bevy_dylib/src/lib.rs +++ b/crates/bevy_dylib/src/lib.rs @@ -53,6 +53,8 @@ //! use bevy_dylib; //! ``` +#![no_std] + // Force linking of the main bevy crate #[expect( unused_imports, diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index 48adba2fd29ec..95b8b46d35275 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -65,6 +65,24 @@ serde_json = "1" smallvec = "1.11" tracing = { version = "0.1", default-features = false, features = ["std"] } +[target.'cfg(target_arch = "wasm32")'.dependencies] +# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption. +bevy_log = { path = "../bevy_log", version = "0.16.0-dev", default-features = false, features = [ + "web", +] } +bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [ + "web", +] } +bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ + "web", +] } +bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [ + "web", +] } +bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [ + "web", +] } + [dev-dependencies] bevy_log = { path = "../bevy_log", version = "0.16.0-dev" } diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index c9e3978a38a8e..40d83084d3d80 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -10,7 +10,9 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"] categories = ["game-engines", "graphics", "gui", "rendering"] [features] +tracing = ["bevy_log/tracing"] trace = [ + "tracing", "bevy_app/trace", "bevy_asset?/trace", "bevy_core_pipeline?/trace", @@ -21,10 +23,18 @@ trace = [ "bevy_render?/trace", "bevy_winit?/trace", ] -trace_chrome = ["bevy_log/tracing-chrome"] -trace_tracy = ["bevy_render?/tracing-tracy", "bevy_log/tracing-tracy"] -trace_tracy_memory = ["bevy_log/trace_tracy_memory"] -detailed_trace = ["bevy_ecs/detailed_trace", "bevy_render?/detailed_trace"] +trace_chrome = ["tracing", "bevy_log/tracing-chrome"] +trace_tracy = [ + "tracing", + "bevy_render?/tracing-tracy", + "bevy_log/tracing-tracy", +] +trace_tracy_memory = ["tracing", "bevy_log/trace_tracy_memory"] +detailed_trace = [ + "tracing", + "bevy_ecs/detailed_trace", + "bevy_render?/detailed_trace", +] sysinfo_plugin = ["bevy_diagnostic/sysinfo_plugin"] @@ -293,6 +303,7 @@ std = [ "bevy_ecs/std", "bevy_input/std", "bevy_input_focus?/std", + "bevy_log/std", "bevy_math/std", "bevy_platform_support/std", "bevy_reflect/std", @@ -344,6 +355,7 @@ async_executor = [ # Note this is currently only applicable on `wasm32` architectures. web = [ "bevy_app/web", + "bevy_log/web", "bevy_platform_support/web", "bevy_reflect/web", "bevy_tasks/web", @@ -385,8 +397,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features ] } bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false } -# bevy (std required) -bevy_log = { path = "../bevy_log", version = "0.16.0-dev", optional = true } +bevy_log = { path = "../bevy_log", version = "0.16.0-dev", default-features = false } # bevy (optional) bevy_a11y = { path = "../bevy_a11y", optional = true, version = "0.16.0-dev", features = [ diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index db1152a362e31..7539b56bc461e 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -4,7 +4,7 @@ plugin_group! { /// This plugin group will add all the default plugins for a *Bevy* application: pub struct DefaultPlugins { bevy_app:::PanicHandlerPlugin, - #[cfg(feature = "bevy_log")] + #[cfg(feature = "tracing")] bevy_log:::LogPlugin, bevy_app:::TaskPoolPlugin, bevy_diagnostic:::FrameCountPlugin, diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 84795bc6bb6ea..3a7e13d1a2dcc 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -44,7 +44,6 @@ pub use bevy_image as image; pub use bevy_input as input; #[cfg(feature = "bevy_input_focus")] pub use bevy_input_focus as input_focus; -#[cfg(feature = "bevy_log")] pub use bevy_log as log; pub use bevy_math as math; #[cfg(feature = "bevy_pbr")] diff --git a/crates/bevy_internal/src/prelude.rs b/crates/bevy_internal/src/prelude.rs index 40a896b78e9b6..fbceef7a8979f 100644 --- a/crates/bevy_internal/src/prelude.rs +++ b/crates/bevy_internal/src/prelude.rs @@ -1,14 +1,10 @@ #[doc(hidden)] pub use crate::{ - app::prelude::*, ecs::prelude::*, input::prelude::*, math::prelude::*, + app::prelude::*, ecs::prelude::*, input::prelude::*, log::prelude::*, math::prelude::*, platform_support::prelude::*, reflect::prelude::*, time::prelude::*, transform::prelude::*, utils::prelude::*, DefaultPlugins, MinimalPlugins, }; -#[doc(hidden)] -#[cfg(feature = "bevy_log")] -pub use crate::log::prelude::*; - #[doc(hidden)] #[cfg(feature = "bevy_window")] pub use crate::window::prelude::*; diff --git a/crates/bevy_log/Cargo.toml b/crates/bevy_log/Cargo.toml index cc7c53e676826..bf940c912c9b9 100644 --- a/crates/bevy_log/Cargo.toml +++ b/crates/bevy_log/Cargo.toml @@ -9,24 +9,44 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [features] -trace = ["tracing-error"] -trace_tracy_memory = ["dep:tracy-client"] +default = ["std", "tracing"] + +tracing = ["dep:tracing", "dep:tracing-subscriber", "dep:tracing-log"] +trace = ["tracing", "tracing-error"] +trace_tracy_memory = ["tracing", "dep:tracy-client"] +tracing-chrome = ["tracing", "dep:tracing-chrome"] +tracing-error = ["tracing", "dep:tracing-error"] +tracing-tracy = ["tracing", "dep:tracing-tracy"] + +# Allows access to the `std` crate. Enabling this feature will prevent compilation +# on `no_std` targets, but provides access to certain additional features on +# supported platforms. +std = ["bevy_app/std", "bevy_utils/std", "bevy_ecs/std"] + +# Enables use of browser APIs. +# Note this is currently only applicable on `wasm32` architectures. +web = ["bevy_app/web", "dep:tracing-wasm"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.16.0-dev" } -bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } -bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } +bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false } +bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false, features = [ + "alloc", +] } +bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } # other -tracing-subscriber = { version = "0.3.1", features = [ +log = { version = "0.4", default-features = false } +tracing-subscriber = { version = "0.3.1", optional = true, features = [ "registry", "env-filter", ] } tracing-chrome = { version = "0.7.0", optional = true } -tracing-log = "0.2.0" +tracing-log = { version = "0.2.0", optional = true } tracing-error = { version = "0.2.0", optional = true } -tracing = { version = "0.1", default-features = false, features = ["std"] } +tracing = { version = "0.1", default-features = false, optional = true, features = [ + "std", +] } # Tracy dependency compatibility table: # https://github.com/nagisa/rust_tracy_client @@ -37,11 +57,7 @@ tracy-client = { version = "0.18.0", optional = true } android_log-sys = "0.3.0" [target.'cfg(target_arch = "wasm32")'.dependencies] -tracing-wasm = "0.2.1" -# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption. -bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [ - "web", -] } +tracing-wasm = { version = "0.2.1", optional = true } [target.'cfg(target_os = "ios")'.dependencies] tracing-oslog = "0.2" diff --git a/crates/bevy_log/src/android_tracing.rs b/crates/bevy_log/src/android_tracing.rs index ba0b3b7a27a38..fc9931607d401 100644 --- a/crates/bevy_log/src/android_tracing.rs +++ b/crates/bevy_log/src/android_tracing.rs @@ -1,4 +1,4 @@ -use alloc::ffi::CString; +use alloc::{ffi::CString, format, string::String, vec::Vec}; use core::fmt::{Debug, Write}; use tracing::{ field::Field, diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index 055395bad7206..1bcf20709f65b 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -15,14 +15,22 @@ //! //! For more fine-tuned control over logging behavior, set up the [`LogPlugin`] or //! `DefaultPlugins` during app initialization. +#![cfg_attr( + not(feature = "tracing"), + doc = "\n\n[`LogPlugin`]: https://docs.rs/bevy_log" +)] +#![no_std] -extern crate alloc; +#[cfg(feature = "std")] +extern crate std; -use core::error::Error; +extern crate alloc; -#[cfg(target_os = "android")] +#[cfg(all(target_os = "android", feature = "std"))] mod android_tracing; mod once; +#[cfg(feature = "tracing")] +mod plugin; #[cfg(feature = "trace_tracy_memory")] #[global_allocator] @@ -34,350 +42,24 @@ static GLOBAL: tracy_client::ProfiledAllocator = /// This includes the most common types in this crate, re-exported for your convenience. pub mod prelude { #[doc(hidden)] - pub use tracing::{ - debug, debug_span, error, error_span, info, info_span, trace, trace_span, warn, warn_span, + pub use crate::{ + debug, debug_once, error, error_once, info, info_once, trace, trace_once, warn, warn_once, }; #[doc(hidden)] - pub use crate::{debug_once, error_once, info_once, trace_once, warn_once}; + pub use bevy_utils::once; + #[cfg(feature = "tracing")] #[doc(hidden)] - pub use bevy_utils::once; + pub use crate::{debug_span, error_span, info_span, trace_span, warn_span}; } pub use bevy_utils::once; -pub use tracing::{ - self, debug, debug_span, error, error_span, info, info_span, trace, trace_span, warn, - warn_span, Level, -}; -pub use tracing_subscriber; +pub use log::{debug, error, info, trace, warn}; -use bevy_app::{App, Plugin}; -use tracing_log::LogTracer; -use tracing_subscriber::{ - filter::{FromEnvError, ParseError}, - prelude::*, - registry::Registry, - EnvFilter, Layer, -}; -#[cfg(feature = "tracing-chrome")] -use { - bevy_ecs::resource::Resource, - bevy_utils::synccell::SyncCell, - tracing_subscriber::fmt::{format::DefaultFields, FormattedFields}, +#[cfg(feature = "tracing")] +pub use { + crate::plugin::{BoxedLayer, LogPlugin, DEFAULT_FILTER}, + tracing::{self, debug_span, error_span, info_span, trace_span, warn_span, Level}, + tracing_subscriber, }; - -/// Wrapper resource for `tracing-chrome`'s flush guard. -/// When the guard is dropped the chrome log is written to file. -#[cfg(feature = "tracing-chrome")] -#[expect( - dead_code, - reason = "`FlushGuard` never needs to be read, it just needs to be kept alive for the `App`'s lifetime." -)] -#[derive(Resource)] -pub(crate) struct FlushGuard(SyncCell); - -/// Adds logging to Apps. This plugin is part of the `DefaultPlugins`. Adding -/// this plugin will setup a collector appropriate to your target platform: -/// * Using [`tracing-subscriber`](https://crates.io/crates/tracing-subscriber) by default, -/// logging to `stdout`. -/// * Using [`android_log-sys`](https://crates.io/crates/android_log-sys) on Android, -/// logging to Android logs. -/// * Using [`tracing-wasm`](https://crates.io/crates/tracing-wasm) in Wasm, logging -/// to the browser console. -/// -/// You can configure this plugin. -/// ```no_run -/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup}; -/// # use bevy_log::LogPlugin; -/// # use tracing::Level; -/// fn main() { -/// App::new() -/// .add_plugins(DefaultPlugins.set(LogPlugin { -/// level: Level::DEBUG, -/// filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), -/// custom_layer: |_| None, -/// })) -/// .run(); -/// } -/// ``` -/// -/// Log level can also be changed using the `RUST_LOG` environment variable. -/// For example, using `RUST_LOG=wgpu=error,bevy_render=info,bevy_ecs=trace cargo run ..` -/// -/// It has the same syntax as the field [`LogPlugin::filter`], see [`EnvFilter`]. -/// If you define the `RUST_LOG` environment variable, the [`LogPlugin`] settings -/// will be ignored. -/// -/// Also, to disable color terminal output (ANSI escape codes), you can -/// set the environment variable `NO_COLOR` to any value. This common -/// convention is documented at [no-color.org](https://no-color.org/). -/// For example: -/// ```no_run -/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup}; -/// # use bevy_log::LogPlugin; -/// fn main() { -/// # // SAFETY: Single-threaded -/// # unsafe { -/// std::env::set_var("NO_COLOR", "1"); -/// # } -/// App::new() -/// .add_plugins(DefaultPlugins) -/// .run(); -/// } -/// ``` -/// -/// If you want to setup your own tracing collector, you should disable this -/// plugin from `DefaultPlugins`: -/// ```no_run -/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup}; -/// # use bevy_log::LogPlugin; -/// fn main() { -/// App::new() -/// .add_plugins(DefaultPlugins.build().disable::()) -/// .run(); -/// } -/// ``` -/// # Example Setup -/// -/// For a quick setup that enables all first-party logging while not showing any of your dependencies' -/// log data, you can configure the plugin as shown below. -/// -/// ```no_run -/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup}; -/// # use bevy_log::*; -/// App::new() -/// .add_plugins(DefaultPlugins.set(LogPlugin { -/// filter: "warn,my_crate=trace".to_string(), //specific filters -/// level: Level::TRACE,//Change this to be globally change levels -/// ..Default::default() -/// })) -/// .run(); -/// ``` -/// The filter (in this case an `EnvFilter`) chooses whether to print the log. The most specific filters apply with higher priority. -/// Let's start with an example: `filter: "warn".to_string()` will only print logs with level `warn` level or greater. -/// From here, we can change to `filter: "warn,my_crate=trace".to_string()`. Logs will print at level `warn` unless it's in `mycrate`, -/// which will instead print at `trace` level because `my_crate=trace` is more specific. -/// -/// -/// ## Log levels -/// Events can be logged at various levels of importance. -/// Only events at your configured log level and higher will be shown. -/// ```no_run -/// # use bevy_log::*; -/// // here is how you write new logs at each "log level" (in "most important" to -/// // "least important" order) -/// error!("something failed"); -/// warn!("something bad happened that isn't a failure, but that's worth calling out"); -/// info!("helpful information that is worth printing by default"); -/// debug!("helpful for debugging"); -/// trace!("very noisy"); -/// ``` -/// In addition to `format!` style arguments, you can print a variable's debug -/// value by using syntax like: `trace(?my_value)`. -/// -/// ## Per module logging levels -/// Modules can have different logging levels using syntax like `crate_name::module_name=debug`. -/// -/// -/// ```no_run -/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup}; -/// # use bevy_log::*; -/// App::new() -/// .add_plugins(DefaultPlugins.set(LogPlugin { -/// filter: "warn,my_crate=trace,my_crate::my_module=debug".to_string(), // Specific filters -/// level: Level::TRACE, // Change this to be globally change levels -/// ..Default::default() -/// })) -/// .run(); -/// ``` -/// The idea is that instead of deleting logs when they are no longer immediately applicable, -/// 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. -/// -/// ## Further reading -/// -/// The `tracing` crate has much more functionality than these examples can show. -/// Much of this configuration can be done with "layers" in the `log` crate. -/// Check out: -/// - Using spans to add more fine grained filters to logs -/// - Adding instruments to capture more function information -/// - Creating layers to add additional context such as line numbers -/// # Panics -/// -/// This plugin should not be added multiple times in the same process. This plugin -/// sets up global logging configuration for **all** Apps in a given process, and -/// rerunning the same initialization multiple times will lead to a panic. -/// -/// # Performance -/// -/// Filters applied through this plugin are computed at _runtime_, which will -/// have a non-zero impact on performance. -/// To achieve maximum performance, consider using -/// [_compile time_ filters](https://docs.rs/log/#compile-time-filters) -/// provided by the [`log`](https://crates.io/crates/log) crate. -/// -/// ```toml -/// # cargo.toml -/// [dependencies] -/// log = { version = "0.4", features = ["max_level_debug", "release_max_level_warn"] } -/// ``` -pub struct LogPlugin { - /// Filters logs using the [`EnvFilter`] format - pub filter: String, - - /// Filters out logs that are "less than" the given level. - /// This can be further filtered using the `filter` setting. - pub level: Level, - - /// 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` is also an acceptable return value. - /// - /// Access to [`App`] is also provided to allow for communication between the - /// [`Subscriber`](tracing::Subscriber) and the [`App`]. - /// - /// Please see the `examples/log_layers.rs` for a complete example. - pub custom_layer: fn(app: &mut App) -> Option, -} - -/// A boxed [`Layer`] that can be used with [`LogPlugin`]. -pub type BoxedLayer = Box + Send + Sync + 'static>; - -/// The default [`LogPlugin`] [`EnvFilter`]. -pub const DEFAULT_FILTER: &str = "wgpu=error,naga=warn"; - -impl Default for LogPlugin { - fn default() -> Self { - Self { - filter: DEFAULT_FILTER.to_string(), - level: Level::INFO, - custom_layer: |_| None, - } - } -} - -impl Plugin for LogPlugin { - #[expect(clippy::print_stderr, reason = "Allowed during logger setup")] - fn build(&self, app: &mut App) { - #[cfg(feature = "trace")] - { - let old_handler = std::panic::take_hook(); - std::panic::set_hook(Box::new(move |infos| { - eprintln!("{}", tracing_error::SpanTrace::capture()); - old_handler(infos); - })); - } - - 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(|from_env_error| { - _ = from_env_error - .source() - .and_then(|source| source.downcast_ref::()) - .map(|parse_err| { - // we cannot use the `error!` macro here because the logger is not ready yet. - eprintln!("LogPlugin failed to parse filter from env: {}", parse_err); - }); - - Ok::(EnvFilter::builder().parse_lossy(&default_filter)) - }) - .unwrap(); - let subscriber = subscriber.with(filter_layer); - - #[cfg(feature = "trace")] - let subscriber = subscriber.with(tracing_error::ErrorLayer::default()); - - #[cfg(all( - not(target_arch = "wasm32"), - not(target_os = "android"), - not(target_os = "ios") - ))] - { - #[cfg(feature = "tracing-chrome")] - let chrome_layer = { - let mut layer = tracing_chrome::ChromeLayerBuilder::new(); - if let Ok(path) = std::env::var("TRACE_CHROME") { - layer = layer.file(path); - } - let (chrome_layer, guard) = layer - .name_fn(Box::new(|event_or_span| match event_or_span { - tracing_chrome::EventOrSpan::Event(event) => event.metadata().name().into(), - tracing_chrome::EventOrSpan::Span(span) => { - if let Some(fields) = - span.extensions().get::>() - { - format!("{}: {}", span.metadata().name(), fields.fields.as_str()) - } else { - span.metadata().name().into() - } - } - })) - .build(); - app.insert_resource(FlushGuard(SyncCell::new(guard))); - chrome_layer - }; - - #[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); - - // bevy_render::renderer logs a `tracy.frame_mark` event every frame - // at Level::INFO. Formatted logs should omit it. - #[cfg(feature = "tracing-tracy")] - let fmt_layer = - fmt_layer.with_filter(tracing_subscriber::filter::FilterFn::new(|meta| { - meta.fields().field("tracy.frame_mark").is_none() - })); - - let subscriber = subscriber.with(fmt_layer); - - #[cfg(feature = "tracing-chrome")] - let subscriber = subscriber.with(chrome_layer); - #[cfg(feature = "tracing-tracy")] - let subscriber = subscriber.with(tracy_layer); - finished_subscriber = subscriber; - } - - #[cfg(target_arch = "wasm32")] - { - finished_subscriber = subscriber.with(tracing_wasm::WASMLayer::new( - tracing_wasm::WASMLayerConfig::default(), - )); - } - - #[cfg(target_os = "android")] - { - finished_subscriber = subscriber.with(android_tracing::AndroidLayer::default()); - } - - #[cfg(target_os = "ios")] - { - finished_subscriber = subscriber.with(tracing_oslog::OsLogger::default()); - } - - let logger_already_set = LogTracer::init().is_err(); - let subscriber_already_set = - tracing::subscriber::set_global_default(finished_subscriber).is_err(); - - match (logger_already_set, subscriber_already_set) { - (true, true) => error!( - "Could not set global logger and tracing subscriber as they are already set. Consider disabling LogPlugin." - ), - (true, false) => error!("Could not set global logger as it is already set. Consider disabling LogPlugin."), - (false, true) => error!("Could not set global tracing subscriber as it is already set. Consider disabling LogPlugin."), - (false, false) => (), - } - } -} diff --git a/crates/bevy_log/src/plugin.rs b/crates/bevy_log/src/plugin.rs new file mode 100644 index 0000000000000..10745dc03ca81 --- /dev/null +++ b/crates/bevy_log/src/plugin.rs @@ -0,0 +1,344 @@ +use alloc::{ + boxed::Box, + format, + string::{String, ToString}, +}; +use core::error::Error; +use std::eprintln; + +use bevy_app::{App, Plugin}; +use tracing::Level; +use tracing_log::LogTracer; +use tracing_subscriber::{ + filter::{FromEnvError, ParseError}, + prelude::*, + registry::Registry, + EnvFilter, Layer, +}; + +use crate::error; + +#[cfg(all(target_os = "android", feature = "std"))] +use crate::android_tracing; + +#[cfg(feature = "tracing-chrome")] +use { + bevy_ecs::resource::Resource, + bevy_utils::synccell::SyncCell, + tracing_subscriber::fmt::{format::DefaultFields, FormattedFields}, +}; + +/// Wrapper resource for `tracing-chrome`'s flush guard. +/// When the guard is dropped the chrome log is written to file. +#[cfg(feature = "tracing-chrome")] +#[expect( + dead_code, + reason = "`FlushGuard` never needs to be read, it just needs to be kept alive for the `App`'s lifetime." +)] +#[derive(Resource)] +pub(crate) struct FlushGuard(SyncCell); + +/// Adds logging to Apps. This plugin is part of the `DefaultPlugins`. Adding +/// this plugin will setup a collector appropriate to your target platform: +/// * Using [`tracing-subscriber`](https://crates.io/crates/tracing-subscriber) by default, +/// logging to `stdout`. +/// * Using [`android_log-sys`](https://crates.io/crates/android_log-sys) on Android, +/// logging to Android logs. +/// * Using [`tracing-wasm`](https://crates.io/crates/tracing-wasm) in Wasm, logging +/// to the browser console. +/// +/// You can configure this plugin. +/// ```no_run +/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup}; +/// # use bevy_log::LogPlugin; +/// # use tracing::Level; +/// fn main() { +/// App::new() +/// .add_plugins(DefaultPlugins.set(LogPlugin { +/// level: Level::DEBUG, +/// filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), +/// custom_layer: |_| None, +/// })) +/// .run(); +/// } +/// ``` +/// +/// Log level can also be changed using the `RUST_LOG` environment variable. +/// For example, using `RUST_LOG=wgpu=error,bevy_render=info,bevy_ecs=trace cargo run ..` +/// +/// It has the same syntax as the field [`LogPlugin::filter`], see [`EnvFilter`]. +/// If you define the `RUST_LOG` environment variable, the [`LogPlugin`] settings +/// will be ignored. +/// +/// Also, to disable color terminal output (ANSI escape codes), you can +/// set the environment variable `NO_COLOR` to any value. This common +/// convention is documented at [no-color.org](https://no-color.org/). +/// For example: +/// ```no_run +/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup}; +/// # use bevy_log::LogPlugin; +/// fn main() { +/// # // SAFETY: Single-threaded +/// # unsafe { +/// std::env::set_var("NO_COLOR", "1"); +/// # } +/// App::new() +/// .add_plugins(DefaultPlugins) +/// .run(); +/// } +/// ``` +/// +/// If you want to setup your own tracing collector, you should disable this +/// plugin from `DefaultPlugins`: +/// ```no_run +/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup}; +/// # use bevy_log::LogPlugin; +/// fn main() { +/// App::new() +/// .add_plugins(DefaultPlugins.build().disable::()) +/// .run(); +/// } +/// ``` +/// # Example Setup +/// +/// For a quick setup that enables all first-party logging while not showing any of your dependencies' +/// log data, you can configure the plugin as shown below. +/// +/// ```no_run +/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup}; +/// # use bevy_log::*; +/// App::new() +/// .add_plugins(DefaultPlugins.set(LogPlugin { +/// filter: "warn,my_crate=trace".to_string(), //specific filters +/// level: Level::TRACE,//Change this to be globally change levels +/// ..Default::default() +/// })) +/// .run(); +/// ``` +/// The filter (in this case an `EnvFilter`) chooses whether to print the log. The most specific filters apply with higher priority. +/// Let's start with an example: `filter: "warn".to_string()` will only print logs with level `warn` level or greater. +/// From here, we can change to `filter: "warn,my_crate=trace".to_string()`. Logs will print at level `warn` unless it's in `mycrate`, +/// which will instead print at `trace` level because `my_crate=trace` is more specific. +/// +/// +/// ## Log levels +/// Events can be logged at various levels of importance. +/// Only events at your configured log level and higher will be shown. +/// ```no_run +/// # use bevy_log::*; +/// // here is how you write new logs at each "log level" (in "most important" to +/// // "least important" order) +/// error!("something failed"); +/// warn!("something bad happened that isn't a failure, but that's worth calling out"); +/// info!("helpful information that is worth printing by default"); +/// debug!("helpful for debugging"); +/// trace!("very noisy"); +/// ``` +/// In addition to `format!` style arguments, you can print a variable's debug +/// value by using syntax like: `trace(?my_value)`. +/// +/// ## Per module logging levels +/// Modules can have different logging levels using syntax like `crate_name::module_name=debug`. +/// +/// +/// ```no_run +/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup}; +/// # use bevy_log::*; +/// App::new() +/// .add_plugins(DefaultPlugins.set(LogPlugin { +/// filter: "warn,my_crate=trace,my_crate::my_module=debug".to_string(), // Specific filters +/// level: Level::TRACE, // Change this to be globally change levels +/// ..Default::default() +/// })) +/// .run(); +/// ``` +/// The idea is that instead of deleting logs when they are no longer immediately applicable, +/// 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. +/// +/// ## Further reading +/// +/// The `tracing` crate has much more functionality than these examples can show. +/// Much of this configuration can be done with "layers" in the `log` crate. +/// Check out: +/// - Using spans to add more fine grained filters to logs +/// - Adding instruments to capture more function information +/// - Creating layers to add additional context such as line numbers +/// # Panics +/// +/// This plugin should not be added multiple times in the same process. This plugin +/// sets up global logging configuration for **all** Apps in a given process, and +/// rerunning the same initialization multiple times will lead to a panic. +/// +/// # Performance +/// +/// Filters applied through this plugin are computed at _runtime_, which will +/// have a non-zero impact on performance. +/// To achieve maximum performance, consider using +/// [_compile time_ filters](https://docs.rs/log/#compile-time-filters) +/// provided by the [`log`](https://crates.io/crates/log) crate. +/// +/// ```toml +/// # cargo.toml +/// [dependencies] +/// log = { version = "0.4", features = ["max_level_debug", "release_max_level_warn"] } +/// ``` +pub struct LogPlugin { + /// Filters logs using the [`EnvFilter`] format + pub filter: String, + + /// Filters out logs that are "less than" the given level. + /// This can be further filtered using the `filter` setting. + pub level: Level, + + /// 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` is also an acceptable return value. + /// + /// Access to [`App`] is also provided to allow for communication between the + /// [`Subscriber`](tracing::Subscriber) and the [`App`]. + /// + /// Please see the `examples/log_layers.rs` for a complete example. + pub custom_layer: fn(app: &mut App) -> Option, +} + +/// A boxed [`Layer`] that can be used with [`LogPlugin`]. +pub type BoxedLayer = Box + Send + Sync + 'static>; + +/// The default [`LogPlugin`] [`EnvFilter`]. +pub const DEFAULT_FILTER: &str = "wgpu=error,naga=warn"; + +impl Default for LogPlugin { + fn default() -> Self { + Self { + filter: DEFAULT_FILTER.to_string(), + level: Level::INFO, + custom_layer: |_| None, + } + } +} + +impl Plugin for LogPlugin { + #[expect(clippy::print_stderr, reason = "Allowed during logger setup")] + fn build(&self, app: &mut App) { + #[cfg(feature = "trace")] + { + let old_handler = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |infos| { + eprintln!("{}", tracing_error::SpanTrace::capture()); + old_handler(infos); + })); + } + + 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(|from_env_error| { + _ = from_env_error + .source() + .and_then(|source| source.downcast_ref::()) + .map(|parse_err| { + // we cannot use the `error!` macro here because the logger is not ready yet. + eprintln!("LogPlugin failed to parse filter from env: {}", parse_err); + }); + + Ok::(EnvFilter::builder().parse_lossy(&default_filter)) + }) + .unwrap(); + let subscriber = subscriber.with(filter_layer); + + #[cfg(feature = "trace")] + let subscriber = subscriber.with(tracing_error::ErrorLayer::default()); + + #[cfg(all( + not(target_arch = "wasm32"), + not(target_os = "android"), + not(target_os = "ios") + ))] + { + #[cfg(feature = "tracing-chrome")] + let chrome_layer = { + let mut layer = tracing_chrome::ChromeLayerBuilder::new(); + if let Ok(path) = std::env::var("TRACE_CHROME") { + layer = layer.file(path); + } + let (chrome_layer, guard) = layer + .name_fn(Box::new(|event_or_span| match event_or_span { + tracing_chrome::EventOrSpan::Event(event) => event.metadata().name().into(), + tracing_chrome::EventOrSpan::Span(span) => { + if let Some(fields) = + span.extensions().get::>() + { + format!("{}: {}", span.metadata().name(), fields.fields.as_str()) + } else { + span.metadata().name().into() + } + } + })) + .build(); + app.insert_resource(FlushGuard(SyncCell::new(guard))); + chrome_layer + }; + + #[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); + + // bevy_render::renderer logs a `tracy.frame_mark` event every frame + // at Level::INFO. Formatted logs should omit it. + #[cfg(feature = "tracing-tracy")] + let fmt_layer = + fmt_layer.with_filter(tracing_subscriber::filter::FilterFn::new(|meta| { + meta.fields().field("tracy.frame_mark").is_none() + })); + + let subscriber = subscriber.with(fmt_layer); + + #[cfg(feature = "tracing-chrome")] + let subscriber = subscriber.with(chrome_layer); + #[cfg(feature = "tracing-tracy")] + let subscriber = subscriber.with(tracy_layer); + finished_subscriber = subscriber; + } + + #[cfg(target_arch = "wasm32")] + { + finished_subscriber = subscriber.with(tracing_wasm::WASMLayer::new( + tracing_wasm::WASMLayerConfig::default(), + )); + } + + #[cfg(all(target_os = "android", feature = "std"))] + { + finished_subscriber = subscriber.with(android_tracing::AndroidLayer::default()); + } + + #[cfg(target_os = "ios")] + { + finished_subscriber = subscriber.with(tracing_oslog::OsLogger::default()); + } + + let logger_already_set = LogTracer::init().is_err(); + let subscriber_already_set = + tracing::subscriber::set_global_default(finished_subscriber).is_err(); + + match (logger_already_set, subscriber_already_set) { + (true, true) => error!( + "Could not set global logger and tracing subscriber as they are already set. Consider disabling LogPlugin." + ), + (true, false) => error!("Could not set global logger as it is already set. Consider disabling LogPlugin."), + (false, true) => error!("Could not set global tracing subscriber as it is already set. Consider disabling LogPlugin."), + (false, false) => (), + } + } +} diff --git a/crates/bevy_text/Cargo.toml b/crates/bevy_text/Cargo.toml index bc939f2dafd8f..8a09ac48ae5f6 100644 --- a/crates/bevy_text/Cargo.toml +++ b/crates/bevy_text/Cargo.toml @@ -41,6 +41,21 @@ unicode-bidi = "0.3.13" sys-locale = "0.3.0" tracing = { version = "0.1", default-features = false, features = ["std"] } +[target.'cfg(target_arch = "wasm32")'.dependencies] +# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption. +bevy_log = { path = "../bevy_log", version = "0.16.0-dev", default-features = false, features = [ + "web", +] } +bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [ + "web", +] } +bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ + "web", +] } +bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [ + "web", +] } + [dev-dependencies] approx = "0.5.1" diff --git a/crates/bevy_transform/Cargo.toml b/crates/bevy_transform/Cargo.toml index 348db148ce66a..a70f3b6c8a673 100644 --- a/crates/bevy_transform/Cargo.toml +++ b/crates/bevy_transform/Cargo.toml @@ -70,6 +70,8 @@ std = [ "alloc", "bevy_app?/std", "bevy_log", + "bevy_log?/std", + "bevy_log?/tracing", "bevy_ecs?/std", "bevy_math/std", "bevy_reflect?/std", diff --git a/crates/bevy_winit/Cargo.toml b/crates/bevy_winit/Cargo.toml index 61a44e7810358..32e2bb3120247 100644 --- a/crates/bevy_winit/Cargo.toml +++ b/crates/bevy_winit/Cargo.toml @@ -80,6 +80,9 @@ bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-d bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [ "web", ] } +bevy_log = { path = "../bevy_log", version = "0.16.0-dev", default-features = false, features = [ + "web", +] } [lints] workspace = true diff --git a/docs/cargo_features.md b/docs/cargo_features.md index f15fa1c4c6683..774dafe952889 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -25,7 +25,6 @@ The default feature set enables most of the expected features of a game engine, |bevy_gizmos|Adds support for rendering gizmos| |bevy_gltf|[glTF](https://www.khronos.org/gltf/) support| |bevy_input_focus|Enable input focus subsystem| -|bevy_log|Enable integration with `tracing` and `log`| |bevy_mesh_picking_backend|Provides an implementation for picking meshes| |bevy_pbr|Adds PBR rendering| |bevy_picking|Provides picking functionality| @@ -49,6 +48,7 @@ The default feature set enables most of the expected features of a game engine, |std|Allows access to the `std` crate.| |sysinfo_plugin|Enables system information diagnostic plugin| |tonemapping_luts|Include tonemapping Look Up Tables KTX2 files. If everything is pink, you need to enable this feature or change the `Tonemapping` method for your `Camera2d` or `Camera3d`.| +|tracing|Tracing support| |vorbis|OGG/VORBIS audio format support| |webgl2|Enable some limitations to be able to use WebGL2. Please refer to the [WebGL2 and WebGPU](https://github.com/bevyengine/bevy/tree/latest/examples#webgl2-and-webgpu) section of the examples README for more information on how to run Wasm builds with WebGPU.| |x11|X11 display server support| @@ -115,7 +115,7 @@ The default feature set enables most of the expected features of a game engine, |symphonia-wav|WAV audio format support (through symphonia)| |tga|TGA image format support| |tiff|TIFF image format support| -|trace|Tracing support| +|trace|Enables traces within Bevy using tracing| |trace_chrome|Tracing support, saving a file in Chrome Tracing format| |trace_tracy|Tracing support, exposing a port for Tracy| |trace_tracy_memory|Tracing support, with memory profiling, exposing a port for Tracy|