From 1aa6311bd682f147c2777aea03bc87f24e442e12 Mon Sep 17 00:00:00 2001 From: IceSentry Date: Thu, 31 Aug 2023 23:05:21 -0400 Subject: [PATCH 1/7] add log to file --- crates/bevy_log/Cargo.toml | 1 + crates/bevy_log/src/lib.rs | 176 ++++++++++++++++++++++++++-------- examples/app/logs.rs | 2 + examples/ecs/system_piping.rs | 1 + 4 files changed, 142 insertions(+), 38 deletions(-) diff --git a/crates/bevy_log/Cargo.toml b/crates/bevy_log/Cargo.toml index de5807b96ed9e..07e58880a1b5c 100644 --- a/crates/bevy_log/Cargo.toml +++ b/crates/bevy_log/Cargo.toml @@ -22,6 +22,7 @@ tracing-chrome = { version = "0.7.0", optional = true } tracing-tracy = { version = "0.10.0", optional = true } tracing-log = "0.1.2" tracing-error = { version = "0.2.0", optional = true } +tracing-appender = "0.2.2" tracy-client = { version = "0.16", optional = true } [target.'cfg(target_os = "android")'.dependencies] diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index a912d7d984b5c..811d5dabe923e 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -15,6 +15,8 @@ #[cfg(feature = "trace")] use std::panic; +use std::path::PathBuf; + #[cfg(target_os = "android")] mod android_tracing; @@ -31,6 +33,7 @@ pub mod prelude { }; } +use bevy_ecs::system::Resource; pub use bevy_utils::tracing::{ debug, debug_span, error, error_span, info, info_span, trace, trace_span, warn, warn_span, Level, @@ -61,6 +64,7 @@ use tracing_subscriber::{prelude::*, registry::Registry, EnvFilter}; /// .add_plugins(DefaultPlugins.set(LogPlugin { /// level: Level::DEBUG, /// filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), +/// file_appender_settings: None /// })) /// .run(); /// } @@ -97,6 +101,13 @@ pub struct LogPlugin { /// Filters out logs that are "less than" the given level. /// This can be further filtered using the `filter` setting. pub level: Level, + + /// Configure file logging + /// + /// ## Platform-specific + /// + /// **`WASM`** does not support logging to a file. + pub file_appender_settings: Option, } impl Default for LogPlugin { @@ -104,6 +115,60 @@ impl Default for LogPlugin { Self { filter: "wgpu=error,naga=warn".to_string(), level: Level::INFO, + file_appender_settings: None, + } + } +} + +/// Enum to control how often a new log file will be created +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Rolling { + /// Creates a new file every minute and appends the date to the file name + /// Date format: YYYY-MM-DD-HH-mm + Minutely, + /// Creates a new file every hour and appends the date to the file name + /// Date format: YYYY-MM-DD-HH + Hourly, + /// Creates a new file every day and appends the date to the file name + /// Date format: YYYY-MM-DD + Daily, + /// Never creates a new file + Never, +} + +impl From for tracing_appender::rolling::Rotation { + fn from(val: Rolling) -> Self { + match val { + Rolling::Minutely => tracing_appender::rolling::Rotation::MINUTELY, + Rolling::Hourly => tracing_appender::rolling::Rotation::HOURLY, + Rolling::Daily => tracing_appender::rolling::Rotation::DAILY, + Rolling::Never => tracing_appender::rolling::Rotation::NEVER, + } + } +} + +#[derive(Resource)] +struct FileAppenderWorkerGuard(tracing_appender::non_blocking::WorkerGuard); + +/// Settings to control how to log to a file +#[derive(Debug, Clone)] +pub struct FileAppenderSettings { + /// Controls how often a new file will be created + pub rolling: Rolling, + /// The path of the directory where the log files will be added + /// + /// Defaults to the local directory + pub path: PathBuf, + /// The prefix added when creating a file + pub prefix: String, +} + +impl Default for FileAppenderSettings { + fn default() -> Self { + Self { + rolling: Rolling::Never, + path: PathBuf::from("."), + prefix: String::from("log"), } } } @@ -130,51 +195,86 @@ impl Plugin for LogPlugin { #[cfg(feature = "trace")] let subscriber = subscriber.with(tracing_error::ErrorLayer::default()); - #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))] + #[cfg(not(target_arch = "wasm32"))] { - #[cfg(feature = "tracing-chrome")] - let chrome_layer = { - let mut layer = tracing_chrome::ChromeLayerBuilder::new(); - if let Ok(path) = std::env::var("TRACE_CHROME") { - layer = layer.file(path); - } - let (chrome_layer, guard) = layer - .name_fn(Box::new(|event_or_span| match event_or_span { - tracing_chrome::EventOrSpan::Event(event) => event.metadata().name().into(), - tracing_chrome::EventOrSpan::Span(span) => { - if let Some(fields) = - span.extensions().get::>() - { - format!("{}: {}", span.metadata().name(), fields.fields.as_str()) - } else { - span.metadata().name().into() + #[cfg(not(target_os = "android"))] + let subscriber = { + #[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() } - } - })) - .build(); - app.world.insert_non_send_resource(guard); - chrome_layer - }; + tracing_chrome::EventOrSpan::Span(span) => { + if let Some(fields) = + span.extensions().get::>() + { + format!( + "{}: {}", + span.metadata().name(), + fields.fields.as_str() + ) + } else { + span.metadata().name().into() + } + } + })) + .build(); + app.world.insert_non_send_resource(guard); + chrome_layer + }; - #[cfg(feature = "tracing-tracy")] - let tracy_layer = tracing_tracy::TracyLayer::new(); + #[cfg(feature = "tracing-tracy")] + let tracy_layer = tracing_tracy::TracyLayer::new(); - let fmt_layer = tracing_subscriber::fmt::Layer::default().with_writer(std::io::stderr); + 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() - })); + // 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); + 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); + #[cfg(feature = "tracing-chrome")] + let subscriber = subscriber.with(chrome_layer); + #[cfg(feature = "tracing-tracy")] + let subscriber = subscriber.with(tracy_layer); + subscriber + }; + + let file_appender_layer = if let Some(settings) = &self.file_appender_settings { + if settings.rolling == Rolling::Never && settings.prefix.is_empty() { + panic!("Using the Rolling::Never variant with no prefix will result in an empty filename which is invalid"); + } + let file_appender = tracing_appender::rolling::RollingFileAppender::new( + settings.rolling.into(), + &settings.path, + &settings.prefix, + ); + + let (non_blocking, worker_guard) = tracing_appender::non_blocking(file_appender); + // WARN We need to keep this somewhere so it doesn't get dropped. + // If it gets dropped then it will silently stop writing to the file + app.insert_resource(FileAppenderWorkerGuard(worker_guard)); + + let file_fmt_layer = tracing_subscriber::fmt::Layer::default() + .with_ansi(false) + .with_writer(non_blocking); + Some(file_fmt_layer) + } else { + None + }; + let subscriber = subscriber.with(file_appender_layer); finished_subscriber = subscriber; } diff --git a/examples/app/logs.rs b/examples/app/logs.rs index 17fd51dfda46e..614b4654643ed 100644 --- a/examples/app/logs.rs +++ b/examples/app/logs.rs @@ -8,6 +8,8 @@ fn main() { // Uncomment this to override the default log settings: // level: bevy::log::Level::TRACE, // filter: "wgpu=warn,bevy_ecs=info".to_string(), + // This will let you configure file logging + // file_appender_settings: Some(FileAppenderSettings::default()), ..default() })) .add_systems(Update, log_system) diff --git a/examples/ecs/system_piping.rs b/examples/ecs/system_piping.rs index 0863daa700797..e4028658e7861 100644 --- a/examples/ecs/system_piping.rs +++ b/examples/ecs/system_piping.rs @@ -14,6 +14,7 @@ fn main() { .add_plugins(LogPlugin { level: Level::TRACE, filter: "".to_string(), + ..default() }) .add_systems( Update, From 94d686f65970edc9fb0df92bff9e11c0aef9e57c Mon Sep 17 00:00:00 2001 From: IceSentry Date: Thu, 31 Aug 2023 23:30:39 -0400 Subject: [PATCH 2/7] add panic hook --- crates/bevy_log/src/lib.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index 811d5dabe923e..395d88106bc6f 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -161,6 +161,8 @@ pub struct FileAppenderSettings { pub path: PathBuf, /// The prefix added when creating a file pub prefix: String, + /// When this is enabled, a panic hook will be used and any panic will be logged as an error + pub use_panic_hook: bool, } impl Default for FileAppenderSettings { @@ -169,6 +171,7 @@ impl Default for FileAppenderSettings { rolling: Rolling::Never, path: PathBuf::from("."), prefix: String::from("log"), + use_panic_hook: true, } } } @@ -253,6 +256,16 @@ impl Plugin for LogPlugin { }; let file_appender_layer = if let Some(settings) = &self.file_appender_settings { + if settings.use_panic_hook { + std::panic::set_hook(Box::new(|panic_info| { + if let Some(s) = panic_info.payload().downcast_ref::<&str>() { + error!("panic occurred: {s:?}"); + } else { + error!("panic occurred"); + } + })); + } + if settings.rolling == Rolling::Never && settings.prefix.is_empty() { panic!("Using the Rolling::Never variant with no prefix will result in an empty filename which is invalid"); } From ba78be96a9d3963078d1f3ceb1f0e6aed60a5364 Mon Sep 17 00:00:00 2001 From: IceSentry Date: Tue, 5 Sep 2023 02:09:22 -0400 Subject: [PATCH 3/7] Update crates/bevy_log/src/lib.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: François --- crates/bevy_log/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index 395d88106bc6f..f70f6d73e7293 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -257,12 +257,14 @@ impl Plugin for LogPlugin { let file_appender_layer = if let Some(settings) = &self.file_appender_settings { if settings.use_panic_hook { + let old_handler = panic::take_hook(); std::panic::set_hook(Box::new(|panic_info| { if let Some(s) = panic_info.payload().downcast_ref::<&str>() { error!("panic occurred: {s:?}"); } else { error!("panic occurred"); } + old_handler(panic_info); })); } From d634146892a86d26e626a74756ac932a676c82b7 Mon Sep 17 00:00:00 2001 From: IceSentry Date: Tue, 5 Sep 2023 02:12:37 -0400 Subject: [PATCH 4/7] fix for ci --- crates/bevy_log/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index f70f6d73e7293..7efd74b6c4794 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -257,7 +257,7 @@ impl Plugin for LogPlugin { let file_appender_layer = if let Some(settings) = &self.file_appender_settings { if settings.use_panic_hook { - let old_handler = panic::take_hook(); + let old_handler = std::panic::take_hook(); std::panic::set_hook(Box::new(|panic_info| { if let Some(s) = panic_info.payload().downcast_ref::<&str>() { error!("panic occurred: {s:?}"); From 88fc5ec647c60e6ae19865b59f06e30aa4a37013 Mon Sep 17 00:00:00 2001 From: IceSentry Date: Tue, 5 Sep 2023 11:15:18 -0400 Subject: [PATCH 5/7] Update crates/bevy_log/src/lib.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: François --- crates/bevy_log/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index 7efd74b6c4794..a5cd6bc88e265 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -258,7 +258,7 @@ impl Plugin for LogPlugin { let file_appender_layer = if let Some(settings) = &self.file_appender_settings { if settings.use_panic_hook { let old_handler = std::panic::take_hook(); - std::panic::set_hook(Box::new(|panic_info| { + std::panic::set_hook(Box::new(move |panic_info| { if let Some(s) = panic_info.payload().downcast_ref::<&str>() { error!("panic occurred: {s:?}"); } else { From f13630f5339260c5084f75613219d74af2f9353c Mon Sep 17 00:00:00 2001 From: IceSentry Date: Fri, 26 Jan 2024 17:29:05 -0500 Subject: [PATCH 6/7] fix merge --- crates/bevy_log/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index 03fd38eb84e53..5c75f1ca13246 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -35,6 +35,7 @@ pub mod prelude { pub use bevy_utils::{debug_once, error_once, info_once, once, trace_once, warn_once}; } +use bevy_ecs::system::Resource; pub use bevy_utils::{ debug_once, error_once, info_once, once, trace_once, tracing::{ From 1e3a5790231fceacbf8d98274bda4df078350539 Mon Sep 17 00:00:00 2001 From: IceSentry Date: Fri, 26 Jan 2024 17:47:33 -0500 Subject: [PATCH 7/7] address come comments --- crates/bevy_log/src/lib.rs | 24 +++++++++++++----------- examples/app/logs.rs | 1 + 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index 5c75f1ca13246..ce15e19f06e72 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -120,6 +120,7 @@ pub struct LogPlugin { /// ## Platform-specific /// /// **`WASM`** does not support logging to a file. + #[cfg(not(target_arch = "wasm32"))] pub file_appender_settings: Option, } @@ -171,14 +172,20 @@ struct FileAppenderWorkerGuard(tracing_appender::non_blocking::WorkerGuard); #[derive(Debug, Clone)] pub struct FileAppenderSettings { /// Controls how often a new file will be created + /// + /// Defaults to [`Rolling::Never`] pub rolling: Rolling, /// The path of the directory where the log files will be added /// /// Defaults to the local directory pub path: PathBuf, /// The prefix added when creating a file + /// + /// Defaults to "log" pub prefix: String, /// When this is enabled, a panic hook will be used and any panic will be logged as an error + /// + /// Defaults to true pub use_panic_hook: bool, } @@ -263,16 +270,14 @@ impl Plugin for LogPlugin { 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); - subscriber + subscriber.with(fmt_layer) }; - let file_appender_layer = if let Some(settings) = &self.file_appender_settings { + let file_appender_layer = self.file_appender_settings.as_ref().map(|settings| { if settings.use_panic_hook { let old_handler = std::panic::take_hook(); std::panic::set_hook(Box::new(move |panic_info| { @@ -286,7 +291,7 @@ impl Plugin for LogPlugin { } if settings.rolling == Rolling::Never && settings.prefix.is_empty() { - panic!("Using the Rolling::Never variant with no prefix will result in an empty filename which is invalid"); + panic!("Using the Rolling::Never variant with no prefix will result in an empty filename, which is invalid"); } let file_appender = tracing_appender::rolling::RollingFileAppender::new( settings.rolling.into(), @@ -299,13 +304,10 @@ impl Plugin for LogPlugin { // If it gets dropped then it will silently stop writing to the file app.insert_resource(FileAppenderWorkerGuard(worker_guard)); - let file_fmt_layer = tracing_subscriber::fmt::Layer::default() + tracing_subscriber::fmt::Layer::default() .with_ansi(false) - .with_writer(non_blocking); - Some(file_fmt_layer) - } else { - None - }; + .with_writer(non_blocking) + }); let subscriber = subscriber.with(file_appender_layer); #[cfg(feature = "tracing-chrome")] diff --git a/examples/app/logs.rs b/examples/app/logs.rs index 6eae3a809bfe4..84569c703e2e3 100644 --- a/examples/app/logs.rs +++ b/examples/app/logs.rs @@ -1,6 +1,7 @@ //! This example illustrates how to use logs in bevy. use bevy::log::once; +use bevy::log::FileAppenderSettings; use bevy::prelude::*; fn main() {