#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(
html_logo_url = "https://bevy.org/assets/icon.png",
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
extern crate alloc;
use core::error::Error;
#[cfg(target_os = "android")]
mod android_tracing;
mod once;
#[cfg(feature = "trace_tracy_memory")]
#[global_allocator]
static GLOBAL: tracy_client::ProfiledAllocator<std::alloc::System> =
tracy_client::ProfiledAllocator::new(std::alloc::System, 100);
pub mod prelude {
#[doc(hidden)]
pub use tracing::{
debug, debug_span, error, error_span, info, info_span, trace, trace_span, warn, warn_span,
};
#[doc(hidden)]
pub use crate::{debug_once, error_once, info_once, trace_once, warn_once};
#[doc(hidden)]
pub use bevy_utils::once;
}
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;
use bevy_app::{App, Plugin};
use tracing_log::LogTracer;
use tracing_subscriber::{
filter::{FromEnvError, ParseError},
layer::Layered,
prelude::*,
registry::Registry,
EnvFilter, Layer,
};
#[cfg(feature = "tracing-chrome")]
use {
bevy_ecs::resource::Resource,
bevy_platform::cell::SyncCell,
tracing_subscriber::fmt::{format::DefaultFields, FormattedFields},
};
#[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>);
pub struct LogPlugin {
pub filter: String,
pub level: Level,
pub custom_layer: fn(app: &mut App) -> Option<BoxedLayer>,
pub fmt_layer: fn(app: &mut App) -> Option<BoxedFmtLayer>,
}
pub type BoxedLayer = Box<dyn Layer<Registry> + Send + Sync + 'static>;
#[cfg(feature = "trace")]
type BaseSubscriber =
Layered<EnvFilter, Layered<Option<Box<dyn Layer<Registry> + Send + Sync>>, Registry>>;
#[cfg(feature = "trace")]
type PreFmtSubscriber = Layered<tracing_error::ErrorLayer<BaseSubscriber>, BaseSubscriber>;
#[cfg(not(feature = "trace"))]
type PreFmtSubscriber =
Layered<EnvFilter, Layered<Option<Box<dyn Layer<Registry> + Send + Sync>>, Registry>>;
pub type BoxedFmtLayer = Box<dyn Layer<PreFmtSubscriber> + Send + Sync + 'static>;
pub const DEFAULT_FILTER: &str = concat!(
"wgpu=error,",
"naga=warn,",
"symphonia_bundle_mp3::demuxer=warn,",
"symphonia_format_caf::demuxer=warn,",
"symphonia_format_isompf4::demuxer=warn,",
"symphonia_format_mkv::demuxer=warn,",
"symphonia_format_ogg::demuxer=warn,",
"symphonia_format_riff::demuxer=warn,",
"symphonia_format_wav::demuxer=warn,",
"calloop::loop_logic=error,",
);
impl Default for LogPlugin {
fn default() -> Self {
Self {
filter: DEFAULT_FILTER.to_string(),
level: Level::INFO,
custom_layer: |_| None,
fmt_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();
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| {
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 = "ios")))]
{
#[cfg(all(feature = "tracing-chrome", not(target_os = "android")))]
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();
let fmt_layer = (self.fmt_layer)(app).unwrap_or_else(|| {
Box::new(tracing_subscriber::fmt::Layer::default().with_writer(std::io::stderr))
});
#[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(all(feature = "tracing-chrome", not(target_os = "android")))]
let subscriber = subscriber.with(chrome_layer);
#[cfg(feature = "tracing-tracy")]
let subscriber = subscriber.with(tracy_layer);
#[cfg(target_os = "android")]
let subscriber = subscriber.with(android_tracing::AndroidLayer::default());
finished_subscriber = subscriber;
}
#[cfg(target_arch = "wasm32")]
{
finished_subscriber = subscriber.with(tracing_wasm::WASMLayer::new(
tracing_wasm::WASMLayerConfig::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();
#[cfg(feature = "tracing-tracy")]
warn!("Tracing with Tracy is active, memory consumption will grow until a client is connected");
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) => (),
}
}
}