Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_log/src/lib.rs
6595 views
1
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2
#![doc(
3
html_logo_url = "https://bevy.org/assets/icon.png",
4
html_favicon_url = "https://bevy.org/assets/icon.png"
5
)]
6
7
//! This crate provides logging functions and configuration for [Bevy](https://bevy.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
19
extern crate alloc;
20
21
use core::error::Error;
22
23
#[cfg(target_os = "android")]
24
mod android_tracing;
25
mod once;
26
27
#[cfg(feature = "trace_tracy_memory")]
28
#[global_allocator]
29
static GLOBAL: tracy_client::ProfiledAllocator<std::alloc::System> =
30
tracy_client::ProfiledAllocator::new(std::alloc::System, 100);
31
32
/// The log prelude.
33
///
34
/// This includes the most common types in this crate, re-exported for your convenience.
35
pub mod prelude {
36
#[doc(hidden)]
37
pub use tracing::{
38
debug, debug_span, error, error_span, info, info_span, trace, trace_span, warn, warn_span,
39
};
40
41
#[doc(hidden)]
42
pub use crate::{debug_once, error_once, info_once, trace_once, warn_once};
43
44
#[doc(hidden)]
45
pub use bevy_utils::once;
46
}
47
48
pub use bevy_utils::once;
49
pub use tracing::{
50
self, debug, debug_span, error, error_span, info, info_span, trace, trace_span, warn,
51
warn_span, Level,
52
};
53
pub use tracing_subscriber;
54
55
use bevy_app::{App, Plugin};
56
use tracing_log::LogTracer;
57
use tracing_subscriber::{
58
filter::{FromEnvError, ParseError},
59
layer::Layered,
60
prelude::*,
61
registry::Registry,
62
EnvFilter, Layer,
63
};
64
#[cfg(feature = "tracing-chrome")]
65
use {
66
bevy_ecs::resource::Resource,
67
bevy_platform::cell::SyncCell,
68
tracing_subscriber::fmt::{format::DefaultFields, FormattedFields},
69
};
70
71
/// Wrapper resource for `tracing-chrome`'s flush guard.
72
/// When the guard is dropped the chrome log is written to file.
73
#[cfg(feature = "tracing-chrome")]
74
#[expect(
75
dead_code,
76
reason = "`FlushGuard` never needs to be read, it just needs to be kept alive for the `App`'s lifetime."
77
)]
78
#[derive(Resource)]
79
pub(crate) struct FlushGuard(SyncCell<tracing_chrome::FlushGuard>);
80
81
/// Adds logging to Apps. This plugin is part of the `DefaultPlugins`. Adding
82
/// this plugin will setup a collector appropriate to your target platform:
83
/// * Using [`tracing-subscriber`](https://crates.io/crates/tracing-subscriber) by default,
84
/// logging to `stdout`.
85
/// * Using [`android_log-sys`](https://crates.io/crates/android_log-sys) on Android,
86
/// logging to Android logs.
87
/// * Using [`tracing-wasm`](https://crates.io/crates/tracing-wasm) in Wasm, logging
88
/// to the browser console.
89
///
90
/// You can configure this plugin.
91
/// ```no_run
92
/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup};
93
/// # use bevy_log::LogPlugin;
94
/// # use tracing::Level;
95
/// fn main() {
96
/// App::new()
97
/// .add_plugins(DefaultPlugins.set(LogPlugin {
98
/// level: Level::DEBUG,
99
/// filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(),
100
/// custom_layer: |_| None,
101
/// fmt_layer: |_| None,
102
/// }))
103
/// .run();
104
/// }
105
/// ```
106
///
107
/// Log level can also be changed using the `RUST_LOG` environment variable.
108
/// For example, using `RUST_LOG=wgpu=error,bevy_render=info,bevy_ecs=trace cargo run ..`
109
///
110
/// It has the same syntax as the field [`LogPlugin::filter`], see [`EnvFilter`].
111
/// If you define the `RUST_LOG` environment variable, the [`LogPlugin`] settings
112
/// will be ignored.
113
///
114
/// Also, to disable color terminal output (ANSI escape codes), you can
115
/// set the environment variable `NO_COLOR` to any value. This common
116
/// convention is documented at [no-color.org](https://no-color.org/).
117
/// For example:
118
/// ```no_run
119
/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup};
120
/// # use bevy_log::LogPlugin;
121
/// fn main() {
122
/// # // SAFETY: Single-threaded
123
/// # unsafe {
124
/// std::env::set_var("NO_COLOR", "1");
125
/// # }
126
/// App::new()
127
/// .add_plugins(DefaultPlugins)
128
/// .run();
129
/// }
130
/// ```
131
///
132
/// If you want to setup your own tracing collector, you should disable this
133
/// plugin from `DefaultPlugins`:
134
/// ```no_run
135
/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup};
136
/// # use bevy_log::LogPlugin;
137
/// fn main() {
138
/// App::new()
139
/// .add_plugins(DefaultPlugins.build().disable::<LogPlugin>())
140
/// .run();
141
/// }
142
/// ```
143
/// # Example Setup
144
///
145
/// For a quick setup that enables all first-party logging while not showing any of your dependencies'
146
/// log data, you can configure the plugin as shown below.
147
///
148
/// ```no_run
149
/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup};
150
/// # use bevy_log::*;
151
/// App::new()
152
/// .add_plugins(DefaultPlugins.set(LogPlugin {
153
/// filter: "warn,my_crate=trace".to_string(), //specific filters
154
/// level: Level::TRACE,//Change this to be globally change levels
155
/// ..Default::default()
156
/// }))
157
/// .run();
158
/// ```
159
/// The filter (in this case an `EnvFilter`) chooses whether to print the log. The most specific filters apply with higher priority.
160
/// Let's start with an example: `filter: "warn".to_string()` will only print logs with level `warn` level or greater.
161
/// From here, we can change to `filter: "warn,my_crate=trace".to_string()`. Logs will print at level `warn` unless it's in `mycrate`,
162
/// which will instead print at `trace` level because `my_crate=trace` is more specific.
163
///
164
///
165
/// ## Log levels
166
/// Events can be logged at various levels of importance.
167
/// Only events at your configured log level and higher will be shown.
168
/// ```no_run
169
/// # use bevy_log::*;
170
/// // here is how you write new logs at each "log level" (in "most important" to
171
/// // "least important" order)
172
/// error!("something failed");
173
/// warn!("something bad happened that isn't a failure, but that's worth calling out");
174
/// info!("helpful information that is worth printing by default");
175
/// debug!("helpful for debugging");
176
/// trace!("very noisy");
177
/// ```
178
/// In addition to `format!` style arguments, you can print a variable's debug
179
/// value by using syntax like: `trace(?my_value)`.
180
///
181
/// ## Per module logging levels
182
/// Modules can have different logging levels using syntax like `crate_name::module_name=debug`.
183
///
184
///
185
/// ```no_run
186
/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup};
187
/// # use bevy_log::*;
188
/// App::new()
189
/// .add_plugins(DefaultPlugins.set(LogPlugin {
190
/// filter: "warn,my_crate=trace,my_crate::my_module=debug".to_string(), // Specific filters
191
/// level: Level::TRACE, // Change this to be globally change levels
192
/// ..Default::default()
193
/// }))
194
/// .run();
195
/// ```
196
/// The idea is that instead of deleting logs when they are no longer immediately applicable,
197
/// 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.
198
///
199
/// ## Further reading
200
///
201
/// The `tracing` crate has much more functionality than these examples can show.
202
/// Much of this configuration can be done with "layers" in the `log` crate.
203
/// Check out:
204
/// - Using spans to add more fine grained filters to logs
205
/// - Adding instruments to capture more function information
206
/// - Creating layers to add additional context such as line numbers
207
/// # Panics
208
///
209
/// This plugin should not be added multiple times in the same process. This plugin
210
/// sets up global logging configuration for **all** Apps in a given process, and
211
/// rerunning the same initialization multiple times will lead to a panic.
212
///
213
/// # Performance
214
///
215
/// Filters applied through this plugin are computed at _runtime_, which will
216
/// have a non-zero impact on performance.
217
/// To achieve maximum performance, consider using
218
/// [_compile time_ filters](https://docs.rs/log/#compile-time-filters)
219
/// provided by the [`log`](https://crates.io/crates/log) crate.
220
///
221
/// ```toml
222
/// # cargo.toml
223
/// [dependencies]
224
/// log = { version = "0.4", features = ["max_level_debug", "release_max_level_warn"] }
225
/// ```
226
pub struct LogPlugin {
227
/// Filters logs using the [`EnvFilter`] format
228
pub filter: String,
229
230
/// Filters out logs that are "less than" the given level.
231
/// This can be further filtered using the `filter` setting.
232
pub level: Level,
233
234
/// Optionally add an extra [`Layer`] to the tracing subscriber
235
///
236
/// This function is only called once, when the plugin is built.
237
///
238
/// Because [`BoxedLayer`] takes a `dyn Layer`, `Vec<Layer>` is also an acceptable return value.
239
///
240
/// Access to [`App`] is also provided to allow for communication between the
241
/// [`Subscriber`](tracing::Subscriber) and the [`App`].
242
///
243
/// Please see the `examples/log_layers.rs` for a complete example.
244
pub custom_layer: fn(app: &mut App) -> Option<BoxedLayer>,
245
246
/// Override the default [`tracing_subscriber::fmt::Layer`] with a custom one.
247
///
248
/// This differs from [`custom_layer`](Self::custom_layer) in that
249
/// [`fmt_layer`](Self::fmt_layer) allows you to overwrite the default formatter layer, while
250
/// `custom_layer` only allows you to add additional layers (which are unable to modify the
251
/// default formatter).
252
///
253
/// For example, you can use [`tracing_subscriber::fmt::Layer::without_time`] to remove the
254
/// timestamp from the log output.
255
///
256
/// Please see the `examples/log_layers.rs` for a complete example.
257
pub fmt_layer: fn(app: &mut App) -> Option<BoxedFmtLayer>,
258
}
259
260
/// A boxed [`Layer`] that can be used with [`LogPlugin::custom_layer`].
261
pub type BoxedLayer = Box<dyn Layer<Registry> + Send + Sync + 'static>;
262
263
#[cfg(feature = "trace")]
264
type BaseSubscriber =
265
Layered<EnvFilter, Layered<Option<Box<dyn Layer<Registry> + Send + Sync>>, Registry>>;
266
267
#[cfg(feature = "trace")]
268
type PreFmtSubscriber = Layered<tracing_error::ErrorLayer<BaseSubscriber>, BaseSubscriber>;
269
270
#[cfg(not(feature = "trace"))]
271
type PreFmtSubscriber =
272
Layered<EnvFilter, Layered<Option<Box<dyn Layer<Registry> + Send + Sync>>, Registry>>;
273
274
/// A boxed [`Layer`] that can be used with [`LogPlugin::fmt_layer`].
275
pub type BoxedFmtLayer = Box<dyn Layer<PreFmtSubscriber> + Send + Sync + 'static>;
276
277
/// The default [`LogPlugin`] [`EnvFilter`].
278
pub const DEFAULT_FILTER: &str = "wgpu=error,naga=warn";
279
280
impl Default for LogPlugin {
281
fn default() -> Self {
282
Self {
283
filter: DEFAULT_FILTER.to_string(),
284
level: Level::INFO,
285
custom_layer: |_| None,
286
fmt_layer: |_| None,
287
}
288
}
289
}
290
291
impl Plugin for LogPlugin {
292
#[expect(clippy::print_stderr, reason = "Allowed during logger setup")]
293
fn build(&self, app: &mut App) {
294
#[cfg(feature = "trace")]
295
{
296
let old_handler = std::panic::take_hook();
297
std::panic::set_hook(Box::new(move |infos| {
298
eprintln!("{}", tracing_error::SpanTrace::capture());
299
old_handler(infos);
300
}));
301
}
302
303
let finished_subscriber;
304
let subscriber = Registry::default();
305
306
// add optional layer provided by user
307
let subscriber = subscriber.with((self.custom_layer)(app));
308
309
let default_filter = { format!("{},{}", self.level, self.filter) };
310
let filter_layer = EnvFilter::try_from_default_env()
311
.or_else(|from_env_error| {
312
_ = from_env_error
313
.source()
314
.and_then(|source| source.downcast_ref::<ParseError>())
315
.map(|parse_err| {
316
// we cannot use the `error!` macro here because the logger is not ready yet.
317
eprintln!("LogPlugin failed to parse filter from env: {parse_err}");
318
});
319
320
Ok::<EnvFilter, FromEnvError>(EnvFilter::builder().parse_lossy(&default_filter))
321
})
322
.unwrap();
323
let subscriber = subscriber.with(filter_layer);
324
325
#[cfg(feature = "trace")]
326
let subscriber = subscriber.with(tracing_error::ErrorLayer::default());
327
328
#[cfg(all(
329
not(target_arch = "wasm32"),
330
not(target_os = "android"),
331
not(target_os = "ios")
332
))]
333
{
334
#[cfg(feature = "tracing-chrome")]
335
let chrome_layer = {
336
let mut layer = tracing_chrome::ChromeLayerBuilder::new();
337
if let Ok(path) = std::env::var("TRACE_CHROME") {
338
layer = layer.file(path);
339
}
340
let (chrome_layer, guard) = layer
341
.name_fn(Box::new(|event_or_span| match event_or_span {
342
tracing_chrome::EventOrSpan::Event(event) => event.metadata().name().into(),
343
tracing_chrome::EventOrSpan::Span(span) => {
344
if let Some(fields) =
345
span.extensions().get::<FormattedFields<DefaultFields>>()
346
{
347
format!("{}: {}", span.metadata().name(), fields.fields.as_str())
348
} else {
349
span.metadata().name().into()
350
}
351
}
352
}))
353
.build();
354
app.insert_resource(FlushGuard(SyncCell::new(guard)));
355
chrome_layer
356
};
357
358
#[cfg(feature = "tracing-tracy")]
359
let tracy_layer = tracing_tracy::TracyLayer::default();
360
361
let fmt_layer = (self.fmt_layer)(app).unwrap_or_else(|| {
362
// note: the implementation of `Default` reads from the env var NO_COLOR
363
// to decide whether to use ANSI color codes, which is common convention
364
// https://no-color.org/
365
Box::new(tracing_subscriber::fmt::Layer::default().with_writer(std::io::stderr))
366
});
367
368
// bevy_render::renderer logs a `tracy.frame_mark` event every frame
369
// at Level::INFO. Formatted logs should omit it.
370
#[cfg(feature = "tracing-tracy")]
371
let fmt_layer =
372
fmt_layer.with_filter(tracing_subscriber::filter::FilterFn::new(|meta| {
373
meta.fields().field("tracy.frame_mark").is_none()
374
}));
375
376
let subscriber = subscriber.with(fmt_layer);
377
378
#[cfg(feature = "tracing-chrome")]
379
let subscriber = subscriber.with(chrome_layer);
380
#[cfg(feature = "tracing-tracy")]
381
let subscriber = subscriber.with(tracy_layer);
382
finished_subscriber = subscriber;
383
}
384
385
#[cfg(target_arch = "wasm32")]
386
{
387
finished_subscriber = subscriber.with(tracing_wasm::WASMLayer::new(
388
tracing_wasm::WASMLayerConfig::default(),
389
));
390
}
391
392
#[cfg(target_os = "android")]
393
{
394
finished_subscriber = subscriber.with(android_tracing::AndroidLayer::default());
395
}
396
397
#[cfg(target_os = "ios")]
398
{
399
finished_subscriber = subscriber.with(tracing_oslog::OsLogger::default());
400
}
401
402
let logger_already_set = LogTracer::init().is_err();
403
let subscriber_already_set =
404
tracing::subscriber::set_global_default(finished_subscriber).is_err();
405
406
match (logger_already_set, subscriber_already_set) {
407
(true, true) => error!(
408
"Could not set global logger and tracing subscriber as they are already set. Consider disabling LogPlugin."
409
),
410
(true, false) => error!("Could not set global logger as it is already set. Consider disabling LogPlugin."),
411
(false, true) => error!("Could not set global tracing subscriber as it is already set. Consider disabling LogPlugin."),
412
(false, false) => (),
413
}
414
}
415
}
416
417