Repository: bevyengine/bevy
Pull request #18782: Allow partial support for `bevy_log` in `no_std`
Author: login: bushrat011899
Labels: name: C-Feature
description: A new feature, making something new possible
name: S-Ready-For-Final-Review
description: This PR has been approved by the community. It's ready for a maintainer to consider merging it
name: M-Migration-Guide
description: A breaking change to Bevy's public API that needs to be noted in a migration guide
name: A-Utils
description: Utility functions and types
name: X-Contentious
description: There are nontrivial implications that should be thought through
name: D-Straightforward
description: Simple bug fixes and API improvements, docs, test and examples
name: O-Embedded
description: Weird hardware and no_std platforms
Created: 2025-04-10T00:02:01Z | Merged: 2025-04-11T02:01:57Z

# Objective

- Fixes #18781

## Solution

- Moved `LogPlugin` into its own file gated behind a new `tracing` feature.
- Used `log` instead of `tracing` where possible.
- Exposed a new `tracing` feature in `bevy` which enables `bevy_log/tracing`.
- Gated `LogPlugin` from `DefaultPlugins` on `tracing` feature.

## Testing

- CI

---

## Migration Guide

- If you were previously using `bevy_log` with default features disabled, enable the new `std` and `tracing` features.
- If you were using `bevy` with the default features disabled, enable the new `tracing` feature.

## Notes

Almost all of the diffs in this PR come from moving `LogPlugin` into its own file. This just makes the PR less noisy, since the alternative is excessive `#[cfg(feature = "tracing")]` directives all over the plugin.

Linked issue #18781: Add `no_std` support to `bevy_log`
## What problem does this solve or what need does it fill?

Many crates use logging and in order to support `no_std`, it's required to manually depend on `log` because `tracing` doesn't work on `no_std` yet.

## What solution would you like?

It would be convenient to cleave the `bevy_log` crate with a `tracing` feature.
Linked issue comments: user: login: hukasu
created_at: 2025-04-10T00:07:27Z
body: this makes it seem that it does work on `no_std` https://docs.rs/tracing/latest/tracing/#crate-feature-flags , unless depending on `liballoc` is a blocker
author_association: CONTRIBUTOR

Diff:
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<std::alloc::System> =
 /// 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<tracing_chrome::FlushGuard>);
-
-/// 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::<LogPlugin>())
-///         .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<Layer>` 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<BoxedLayer>,
-}
-
-/// A boxed [`Layer`] that can be used with [`LogPlugin`].
-pub type BoxedLayer = Box<dyn Layer<Registry> + 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::<ParseError>())
-                    .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, FromEnvError>(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::<FormattedFields<DefaultFields>>()
-                            {
-                                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<tracing_chrome::FlushGuard>);
+
+/// 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::<LogPlugin>())
+///         .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<Layer>` 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<BoxedLayer>,
+}
+
+/// A boxed [`Layer`] that can be used with [`LogPlugin`].
+pub type BoxedLayer = Box<dyn Layer<Registry> + 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::<ParseError>())
+                    .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, FromEnvError>(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::<FormattedFields<DefaultFields>>()
+                            {
+                                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|


Reviews:
user: login: Shatur
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T00:12:32Z
user: login: cart
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T01:25:44Z
user: login: cart
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T01:30:54Z
user: login: bushrat011899
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T01:43:32Z
user: login: bushrat011899
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T01:52:05Z
user: login: cart
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T04:17:17Z
user: login: cart
body: Ok I think this is the move for now, given the current approach. I think the "virality" of both `std` and `web` is a maintenance liability and an "ecosystem UX" liability. Needing to know what dependencies are "std" or "web" is something we should avoid thrusting on users (and ourselves) whenever possible.

I've written out a potential path forward here: https://github.com/bevyengine/bevy/discussions/18799
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-10T22:03:57Z
user: login: mockersf
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T23:21:13Z
user: login: mockersf
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T23:22:13Z
user: login: bushrat011899
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T23:29:38Z
user: login: mockersf
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T23:31:42Z
user: login: mockersf
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-11T01:28:59Z

Review comments:
diff_hunk: @@ -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 = [
path: crates/bevy_text/Cargo.toml
user: login: cart
body: This "needing to anticipate that a dependency has a 'web' feature and enabling it manually at every consumption site" approach feels brittle and toilsome. Any way around this?

Presumably any 3rd party crate that wants to use bevy_log on wasm would also need to anticipate this and enable it? Not ideal!
created_at: 2025-04-10T01:25:43Z
author_association: MEMBER
diff_hunk: @@ -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 = [
path: crates/bevy_text/Cargo.toml
user: login: cart
body: Every crate author needing to do this (and needing to know which crates need it and which don't) feels very blah:

```toml
[target.'cfg(target_arch = "wasm32")'.dependencies]
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",
] }
```
created_at: 2025-04-10T01:30:53Z
author_association: MEMBER
diff_hunk: @@ -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 = [
path: crates/bevy_text/Cargo.toml
user: login: bushrat011899
body: Yeah it's not ideal. The issue is a lot of the Rust ecosystem (reasonably) assumed WASM was synonymous with browser targets. Now that there are web assembly runtimes that _don't_ have access to a JS runtime/browser APIs, there's a mismatch in how dependencies handle WASM in _practice_ and how they _should_.

In #4906 this was originally raised, and in #17499 I added a feature flag, `web`, to be Bevy's canonical way of indicating that a crate can assume access to browser/JS APIs. An alternative floated was adding a environment variable, similar to `getrandom`, but that requires configuration in `.cargo/config.toml` like the below:

```toml
[target.wasm32-unknown-unknown]
rustflags = ['--cfg', 'getrandom_backend="wasm_js"']
```

This would be pervasive tho, and is harder to explain to new users, which is why we settled on a `web` feature. Spiritually, it's very similar to `std` as a feature, with a similar virality.

IMO, this is a consequence paid by users who are already expected to be more knowledgeable in Rust and Bevy, since if they weren't they'd be using `bevy` directly, where they only have to enable `web` on it directly.
created_at: 2025-04-10T01:43:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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 = [
path: crates/bevy_text/Cargo.toml
user: login: bushrat011899
body: I would also like to clarify if this concern is a blocker on _this_ PR, or a more general concern for a future follow-up?
created_at: 2025-04-10T01:52:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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 = [
path: crates/bevy_text/Cargo.toml
user: login: cart
body: Not _necessarily_ a blocker for this PR, as it is taking a pattern that already exists and making it more prominent. But before merging I would like to spend a little bit of time tomorrow to consider other options.
created_at: 2025-04-10T04:17:17Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,341 @@
+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;
path: crates/bevy_log/src/plugin.rs
user: login: mockersf
body: ```suggestion
use crate::error;
 #[cfg(target_os = "android")]
 use android_tracing;
```
created_at: 2025-04-10T23:21:12Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,341 @@
+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;
path: crates/bevy_log/src/plugin.rs
user: login: mockersf
body: and also a bunch of `std` usage in this mod needs to be fixed, or android should be gated to work only with `std`
created_at: 2025-04-10T23:22:13Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,341 @@
+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;
path: crates/bevy_log/src/plugin.rs
user: login: bushrat011899
body: Good catch! I hate that Android issues keep slipping past me.
created_at: 2025-04-10T23:29:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,341 @@
+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;
path: crates/bevy_log/src/plugin.rs
user: login: mockersf
body: that why we have CI!
created_at: 2025-04-10T23:31:42Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-10T00:03:03Z
body: It looks like your PR is a breaking change, but **you didn't provide a migration guide**.

  Please review the [instructions for writing migration guides](https://github.com/bevyengine/bevy/tree/main/release-content/migration_guides.md), then expand or revise the content in the [migration guides directory](https://github.com/bevyengine/bevy/tree/main/release-content/migration-guides) to reflect your changes.
author_association: CONTRIBUTOR
user: login: Shatur
created_at: 2025-04-10T00:08:07Z
body: Maybe depend on `bevy_log` for other internal Bevy crates? :thinking: 
author_association: CONTRIBUTOR
user: login: bushrat011899
created_at: 2025-04-10T00:09:59Z
body: > Maybe depend on `bevy_log` for other internal Bevy crates? 🤔

Definitely could do that, but I'd save that for a follow-up, this is already pretty lengthy for a last-minute RC update.
author_association: CONTRIBUTOR
user: login: hukasu
created_at: 2025-04-10T00:13:26Z
body: i commented on the issue, but `tracing` seems to work on `no_std`, unless `liballoc` is a blocker
https://docs.rs/tracing/latest/tracing/#crate-feature-flags

Edit: `liballoc` seems to be just `extern crate alloc;` which is already use in many places
author_association: CONTRIBUTOR
user: login: bushrat011899
created_at: 2025-04-10T00:22:18Z
body: > i commented on the issue, but `tracing` seems to work on `no_std`, unless `liballoc` is a blocker https://docs.rs/tracing/latest/tracing/#crate-feature-flags
> 
> Edit: `liballoc` seems to be just `extern crate alloc;` which is already use in many places

The issue is actually to do with `no_atomic` rather than `no_std`, but right now Bevy considers them synonymous. Trying to consider them as separate features further fragments the codebase for little gain, since getting `no_atomic` support in a `no_std` context is comparatively trivial.

I already have `no_atomic` PRs for `tracing` opened which would close the gap and allow `tracing` on all platforms (e.g., `thumbv6m-none-eabi`), just waiting for further review before they're merged.
author_association: CONTRIBUTOR
user: login: cBournhonesque
created_at: 2025-04-14T13:34:56Z
body: The move from `log` to `tracing` is a pretty big breaking change, right?
I was using
```
use bevy::prelude::*;
use tracing::*;
```
everywhere in my library for convenience, and now this causes conflicts because `bevy`'s prelude switched from using `tracing::debug!` to `log::debug!`

Even if I enable the `tracing` feature, it only seems to add `debug_span!`, `error_span!`, etc. to the bevy prelude, not `debug!`, `error!` from `tracing` crate.

If `tracing` feature is enabled, should the `bevy::log::debug!` macro be using the macro from `tracing` instead of `log`?

> Used log instead of tracing where possible.

That's a pretty change as the macros from `tracing` and `log` have different capabilities/syntax. I wouldn't expect a PR to enable `no_std` for `bevy_log` to have such a big side-effect
author_association: CONTRIBUTOR