bevy_log/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc(
3    html_logo_url = "https://bevyengine.org/assets/icon.png",
4    html_favicon_url = "https://bevyengine.org/assets/icon.png"
5)]
6
7//! This crate provides logging functions and configuration for [Bevy](https://bevyengine.org)
8//! apps, and automatically configures platform specific log handlers (i.e. Wasm or Android).
9//!
10//! The macros provided for logging are reexported from [`tracing`](https://docs.rs/tracing),
11//! and behave identically to it.
12//!
13//! By default, the [`LogPlugin`] from this crate is included in Bevy's `DefaultPlugins`
14//! and the logging macros can be used out of the box, if used.
15//!
16//! For more fine-tuned control over logging behavior, set up the [`LogPlugin`] or
17//! `DefaultPlugins` during app initialization.
18
19extern crate alloc;
20
21use core::error::Error;
22
23#[cfg(target_os = "android")]
24mod android_tracing;
25
26#[cfg(feature = "trace_tracy_memory")]
27#[global_allocator]
28static GLOBAL: tracy_client::ProfiledAllocator<std::alloc::System> =
29    tracy_client::ProfiledAllocator::new(std::alloc::System, 100);
30
31/// The log prelude.
32///
33/// This includes the most common types in this crate, re-exported for your convenience.
34pub mod prelude {
35    #[doc(hidden)]
36    pub use bevy_utils::tracing::{
37        debug, debug_span, error, error_span, info, info_span, trace, trace_span, warn, warn_span,
38    };
39
40    #[doc(hidden)]
41    pub use bevy_utils::{debug_once, error_once, info_once, once, trace_once, warn_once};
42}
43
44pub use bevy_utils::{
45    debug_once, error_once, info_once, once, trace_once,
46    tracing::{
47        debug, debug_span, error, error_span, info, info_span, trace, trace_span, warn, warn_span,
48        Level,
49    },
50    warn_once,
51};
52pub use tracing_subscriber;
53
54use bevy_app::{App, Plugin};
55use tracing_log::LogTracer;
56use tracing_subscriber::{
57    filter::{FromEnvError, ParseError},
58    prelude::*,
59    registry::Registry,
60    EnvFilter, Layer,
61};
62#[cfg(feature = "tracing-chrome")]
63use {
64    bevy_ecs::system::Resource,
65    bevy_utils::synccell::SyncCell,
66    tracing_subscriber::fmt::{format::DefaultFields, FormattedFields},
67};
68
69/// Wrapper resource for `tracing-chrome`'s flush guard.
70/// When the guard is dropped the chrome log is written to file.
71#[cfg(feature = "tracing-chrome")]
72#[expect(
73    dead_code,
74    reason = "`FlushGuard` never needs to be read, it just needs to be kept alive for the `App`'s lifetime."
75)]
76#[derive(Resource)]
77pub(crate) struct FlushGuard(SyncCell<tracing_chrome::FlushGuard>);
78
79/// Adds logging to Apps. This plugin is part of the `DefaultPlugins`. Adding
80/// this plugin will setup a collector appropriate to your target platform:
81/// * Using [`tracing-subscriber`](https://crates.io/crates/tracing-subscriber) by default,
82///     logging to `stdout`.
83/// * Using [`android_log-sys`](https://crates.io/crates/android_log-sys) on Android,
84///     logging to Android logs.
85/// * Using [`tracing-wasm`](https://crates.io/crates/tracing-wasm) in Wasm, logging
86///     to the browser console.
87///
88/// You can configure this plugin.
89/// ```no_run
90/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup};
91/// # use bevy_log::LogPlugin;
92/// # use bevy_utils::tracing::Level;
93/// fn main() {
94///     App::new()
95///         .add_plugins(DefaultPlugins.set(LogPlugin {
96///             level: Level::DEBUG,
97///             filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(),
98///             custom_layer: |_| None,
99///         }))
100///         .run();
101/// }
102/// ```
103///
104/// Log level can also be changed using the `RUST_LOG` environment variable.
105/// For example, using `RUST_LOG=wgpu=error,bevy_render=info,bevy_ecs=trace cargo run ..`
106///
107/// It has the same syntax as the field [`LogPlugin::filter`], see [`EnvFilter`].
108/// If you define the `RUST_LOG` environment variable, the [`LogPlugin`] settings
109/// will be ignored.
110///
111/// Also, to disable color terminal output (ANSI escape codes), you can
112/// set the environment variable `NO_COLOR` to any value. This common
113/// convention is documented at [no-color.org](https://no-color.org/).
114/// For example:
115/// ```no_run
116/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup};
117/// # use bevy_log::LogPlugin;
118/// fn main() {
119///     std::env::set_var("NO_COLOR", "1");
120///     App::new()
121///        .add_plugins(DefaultPlugins)
122///        .run();
123/// }
124/// ```
125///
126/// If you want to setup your own tracing collector, you should disable this
127/// plugin from `DefaultPlugins`:
128/// ```no_run
129/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup};
130/// # use bevy_log::LogPlugin;
131/// fn main() {
132///     App::new()
133///         .add_plugins(DefaultPlugins.build().disable::<LogPlugin>())
134///         .run();
135/// }
136/// ```
137///
138/// # Panics
139///
140/// This plugin should not be added multiple times in the same process. This plugin
141/// sets up global logging configuration for **all** Apps in a given process, and
142/// rerunning the same initialization multiple times will lead to a panic.
143///
144/// # Performance
145///
146/// Filters applied through this plugin are computed at _runtime_, which will
147/// have a non-zero impact on performance.
148/// To achieve maximum performance, consider using
149/// [_compile time_ filters](https://docs.rs/log/#compile-time-filters)
150/// provided by the [`log`](https://crates.io/crates/log) crate.
151///
152/// ```toml
153/// # cargo.toml
154/// [dependencies]
155/// log = { version = "0.4", features = ["max_level_debug", "release_max_level_warn"] }
156/// ```
157pub struct LogPlugin {
158    /// Filters logs using the [`EnvFilter`] format
159    pub filter: String,
160
161    /// Filters out logs that are "less than" the given level.
162    /// This can be further filtered using the `filter` setting.
163    pub level: Level,
164
165    /// Optionally add an extra [`Layer`] to the tracing subscriber
166    ///
167    /// This function is only called once, when the plugin is built.
168    ///
169    /// Because [`BoxedLayer`] takes a `dyn Layer`, `Vec<Layer>` is also an acceptable return value.
170    ///
171    /// Access to [`App`] is also provided to allow for communication between the
172    /// [`Subscriber`](bevy_utils::tracing::Subscriber) and the [`App`].
173    ///
174    /// Please see the `examples/log_layers.rs` for a complete example.
175    pub custom_layer: fn(app: &mut App) -> Option<BoxedLayer>,
176}
177
178/// A boxed [`Layer`] that can be used with [`LogPlugin`].
179pub type BoxedLayer = Box<dyn Layer<Registry> + Send + Sync + 'static>;
180
181/// The default [`LogPlugin`] [`EnvFilter`].
182pub const DEFAULT_FILTER: &str = "wgpu=error,naga=warn";
183
184impl Default for LogPlugin {
185    fn default() -> Self {
186        Self {
187            filter: DEFAULT_FILTER.to_string(),
188            level: Level::INFO,
189            custom_layer: |_| None,
190        }
191    }
192}
193
194impl Plugin for LogPlugin {
195    fn build(&self, app: &mut App) {
196        #[cfg(feature = "trace")]
197        {
198            let old_handler = std::panic::take_hook();
199            std::panic::set_hook(Box::new(move |infos| {
200                eprintln!("{}", tracing_error::SpanTrace::capture());
201                old_handler(infos);
202            }));
203        }
204
205        let finished_subscriber;
206        let subscriber = Registry::default();
207
208        // add optional layer provided by user
209        let subscriber = subscriber.with((self.custom_layer)(app));
210
211        let default_filter = { format!("{},{}", self.level, self.filter) };
212        let filter_layer = EnvFilter::try_from_default_env()
213            .or_else(|from_env_error| {
214                _ = from_env_error
215                    .source()
216                    .and_then(|source| source.downcast_ref::<ParseError>())
217                    .map(|parse_err| {
218                        // we cannot use the `error!` macro here because the logger is not ready yet.
219                        eprintln!("LogPlugin failed to parse filter from env: {}", parse_err);
220                    });
221
222                Ok::<EnvFilter, FromEnvError>(EnvFilter::builder().parse_lossy(&default_filter))
223            })
224            .unwrap();
225        let subscriber = subscriber.with(filter_layer);
226
227        #[cfg(feature = "trace")]
228        let subscriber = subscriber.with(tracing_error::ErrorLayer::default());
229
230        #[cfg(all(
231            not(target_arch = "wasm32"),
232            not(target_os = "android"),
233            not(target_os = "ios")
234        ))]
235        {
236            #[cfg(feature = "tracing-chrome")]
237            let chrome_layer = {
238                let mut layer = tracing_chrome::ChromeLayerBuilder::new();
239                if let Ok(path) = std::env::var("TRACE_CHROME") {
240                    layer = layer.file(path);
241                }
242                let (chrome_layer, guard) = layer
243                    .name_fn(Box::new(|event_or_span| match event_or_span {
244                        tracing_chrome::EventOrSpan::Event(event) => event.metadata().name().into(),
245                        tracing_chrome::EventOrSpan::Span(span) => {
246                            if let Some(fields) =
247                                span.extensions().get::<FormattedFields<DefaultFields>>()
248                            {
249                                format!("{}: {}", span.metadata().name(), fields.fields.as_str())
250                            } else {
251                                span.metadata().name().into()
252                            }
253                        }
254                    }))
255                    .build();
256                app.insert_resource(FlushGuard(SyncCell::new(guard)));
257                chrome_layer
258            };
259
260            #[cfg(feature = "tracing-tracy")]
261            let tracy_layer = tracing_tracy::TracyLayer::default();
262
263            // note: the implementation of `Default` reads from the env var NO_COLOR
264            // to decide whether to use ANSI color codes, which is common convention
265            // https://no-color.org/
266            let fmt_layer = tracing_subscriber::fmt::Layer::default().with_writer(std::io::stderr);
267
268            // bevy_render::renderer logs a `tracy.frame_mark` event every frame
269            // at Level::INFO. Formatted logs should omit it.
270            #[cfg(feature = "tracing-tracy")]
271            let fmt_layer =
272                fmt_layer.with_filter(tracing_subscriber::filter::FilterFn::new(|meta| {
273                    meta.fields().field("tracy.frame_mark").is_none()
274                }));
275
276            let subscriber = subscriber.with(fmt_layer);
277
278            #[cfg(feature = "tracing-chrome")]
279            let subscriber = subscriber.with(chrome_layer);
280            #[cfg(feature = "tracing-tracy")]
281            let subscriber = subscriber.with(tracy_layer);
282            finished_subscriber = subscriber;
283        }
284
285        #[cfg(target_arch = "wasm32")]
286        {
287            finished_subscriber = subscriber.with(tracing_wasm::WASMLayer::new(
288                tracing_wasm::WASMLayerConfig::default(),
289            ));
290        }
291
292        #[cfg(target_os = "android")]
293        {
294            finished_subscriber = subscriber.with(android_tracing::AndroidLayer::default());
295        }
296
297        #[cfg(target_os = "ios")]
298        {
299            finished_subscriber = subscriber.with(tracing_oslog::OsLogger::default());
300        }
301
302        let logger_already_set = LogTracer::init().is_err();
303        let subscriber_already_set =
304            bevy_utils::tracing::subscriber::set_global_default(finished_subscriber).is_err();
305
306        match (logger_already_set, subscriber_already_set) {
307            (true, true) => error!(
308                "Could not set global logger and tracing subscriber as they are already set. Consider disabling LogPlugin."
309            ),
310            (true, false) => error!("Could not set global logger as it is already set. Consider disabling LogPlugin."),
311            (false, true) => error!("Could not set global tracing subscriber as it is already set. Consider disabling LogPlugin."),
312            (false, false) => (),
313        }
314    }
315}