@@ -56,6 +56,7 @@ use bevy_app::{App, Plugin};
56
56
use tracing_log:: LogTracer ;
57
57
use tracing_subscriber:: {
58
58
filter:: { FromEnvError , ParseError } ,
59
+ layer:: Layered ,
59
60
prelude:: * ,
60
61
registry:: Registry ,
61
62
EnvFilter , Layer ,
@@ -83,8 +84,6 @@ pub(crate) struct FlushGuard(SyncCell<tracing_chrome::FlushGuard>);
83
84
/// logging to `stdout`.
84
85
/// * Using [`android_log-sys`](https://crates.io/crates/android_log-sys) on Android,
85
86
/// logging to Android logs.
86
- /// * Using [`tracing_oslog`](https://crates.io/crates/tracing_oslog) on iOS,
87
- /// logging to iOS logs.
88
87
/// * Using [`tracing-wasm`](https://crates.io/crates/tracing-wasm) in Wasm, logging
89
88
/// to the browser console.
90
89
///
@@ -255,15 +254,26 @@ pub struct LogPlugin {
255
254
/// timestamp from the log output.
256
255
///
257
256
/// Please see the `examples/log_layers.rs` for a complete example.
258
- ///
259
- /// Note that this field has no effect when `os_target` is `android`, `ios` or `wasm`, as on those
260
- /// platforms we don't use [`tracing_subscriber::fmt::Layer`] but rather the platform default.
261
- pub fmt_layer : fn ( app : & mut App ) -> Option < BoxedLayer > ,
257
+ pub fmt_layer : fn ( app : & mut App ) -> Option < BoxedFmtLayer > ,
262
258
}
263
259
264
260
/// A boxed [`Layer`] that can be used with [`LogPlugin::custom_layer`].
265
261
pub type BoxedLayer = Box < dyn Layer < Registry > + Send + Sync + ' static > ;
266
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
+
267
277
/// The default [`LogPlugin`] [`EnvFilter`].
268
278
pub const DEFAULT_FILTER : & str = "wgpu=error,naga=warn" ;
269
279
@@ -290,25 +300,30 @@ impl Plugin for LogPlugin {
290
300
} ) ) ;
291
301
}
292
302
293
- // We use a Vec of BoxedLayer instead of adding each layer individually using the
294
- // `layer.with(next_layer)`.
295
- // Otherwise, the types of each successive layer becomes unwieldy,
296
- // as the type of each new layer would depend on the types of the previous layers.
297
- let mut layers: Vec < BoxedLayer > = Vec :: new ( ) ;
298
-
299
- // Add optional layer provided by user
300
- // As they are added first, any of the following layers won't be applied.
301
- // In particular, it won't be affected by the filtering we put in place next.
302
- if let Some ( layer) = ( self . custom_layer ) ( app) {
303
- layers. push ( layer) ;
304
- }
303
+ let finished_subscriber;
304
+ let subscriber = Registry :: default ( ) ;
305
305
306
- layers. push ( Self :: build_filter_layer ( self . level , & self . filter ) ) ;
306
+ // add optional layer provided by user
307
+ let subscriber = subscriber. with ( ( self . custom_layer ) ( app) ) ;
307
308
308
- #[ cfg( feature = "trace" ) ]
309
- layers. push ( tracing_error:: ErrorLayer :: default ( ) . boxed ( ) ) ;
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) ;
310
324
311
- layers. push ( Self :: build_system_output_layer ( ( self . fmt_layer ) ( app) ) ) ;
325
+ #[ cfg( feature = "trace" ) ]
326
+ let subscriber = subscriber. with ( tracing_error:: ErrorLayer :: default ( ) ) ;
312
327
313
328
#[ cfg( all(
314
329
not( target_arch = "wasm32" ) ,
@@ -317,136 +332,84 @@ impl Plugin for LogPlugin {
317
332
) ) ]
318
333
{
319
334
#[ cfg( feature = "tracing-chrome" ) ]
320
- {
321
- let ( chrome_layer, guard) = Self :: build_chrome_layer ( ) ;
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 ( ) ;
322
354
app. insert_resource ( FlushGuard ( SyncCell :: new ( guard) ) ) ;
323
- layers. push ( chrome_layer) ;
324
- }
325
- #[ cfg( feature = "tracing-tracy" ) ]
326
- layers. push ( tracing_tracy:: TracyLayer :: default ( ) . boxed ( ) ) ;
327
- }
355
+ chrome_layer
356
+ } ;
328
357
329
- let subscriber = Registry :: default ( ) . with ( layers) ;
330
-
331
- let logger_already_set = LogTracer :: init ( ) . is_err ( ) ;
332
- let subscriber_already_set = tracing:: subscriber:: set_global_default ( subscriber) . is_err ( ) ;
333
-
334
- match ( logger_already_set, subscriber_already_set) {
335
- ( true , true ) => error ! (
336
- "Could not set global logger and tracing subscriber as they are already set. Consider disabling LogPlugin."
337
- ) ,
338
- ( true , false ) => error ! ( "Could not set global logger as it is already set. Consider disabling LogPlugin." ) ,
339
- ( false , true ) => error ! ( "Could not set global tracing subscriber as it is already set. Consider disabling LogPlugin." ) ,
340
- ( false , false ) => ( ) ,
341
- }
342
- }
343
- }
358
+ #[ cfg( feature = "tracing-tracy" ) ]
359
+ let tracy_layer = tracing_tracy:: TracyLayer :: default ( ) ;
344
360
345
- impl LogPlugin {
346
- /// Build a [`BoxedLayer`] that will filter which logs are outputted.
347
- /// It will read the `RUST_LOG` env variable to override the settings
348
- /// on a given run, the default will fallback to the provided `level` and `filter`
349
- fn build_filter_layer ( level : Level , filter : & str ) -> BoxedLayer {
350
- let default_filter = { format ! ( "{},{}" , level , filter ) } ;
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
+ } ) ;
351
367
352
- EnvFilter :: try_from_default_env ( )
353
- . or_else ( |from_env_error| {
354
- _ = from_env_error
355
- . source ( )
356
- . and_then ( |source| source. downcast_ref :: < ParseError > ( ) )
357
- . map ( |parse_err| {
358
- #[ expect(
359
- clippy:: print_stderr,
360
- reason = "We cannot use the `error!` macro here because the logger is not ready yet."
361
- ) ]
362
- {
363
- eprintln ! ( "LogPlugin failed to parse filter from env: {}" , parse_err) ;
364
- }
365
- } ) ;
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
+ } ) ) ;
366
375
367
- Ok :: < EnvFilter , FromEnvError > ( EnvFilter :: builder ( ) . parse_lossy ( & default_filter) )
368
- } )
369
- . unwrap ( ) . boxed ( )
370
- }
376
+ let subscriber = subscriber. with ( fmt_layer) ;
371
377
372
- #[ cfg( feature = "tracing-chrome" ) ]
373
- /// [`BoxedLayer`] to build the necessary output when the `tracing-chrome` feature is enabled.
374
- /// The [`tracing_chrome::FlushGuard`] must be kept around till we don't need to output logs
375
- /// any more
376
- fn build_chrome_layer ( ) -> ( BoxedLayer , tracing_chrome:: FlushGuard ) {
377
- let mut layer = tracing_chrome:: ChromeLayerBuilder :: new ( ) ;
378
- if let Ok ( path) = std:: env:: var ( "TRACE_CHROME" ) {
379
- layer = layer. file ( path) ;
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;
380
383
}
381
- let ( chrome_layer, guard) = layer
382
- . name_fn ( Box :: new ( |event_or_span| match event_or_span {
383
- tracing_chrome:: EventOrSpan :: Event ( event) => event. metadata ( ) . name ( ) . into ( ) ,
384
- tracing_chrome:: EventOrSpan :: Span ( span) => {
385
- if let Some ( fields) = span. extensions ( ) . get :: < FormattedFields < DefaultFields > > ( )
386
- {
387
- format ! ( "{}: {}" , span. metadata( ) . name( ) , fields. fields. as_str( ) )
388
- } else {
389
- span. metadata ( ) . name ( ) . into ( )
390
- }
391
- }
392
- } ) )
393
- . build ( ) ;
394
- ( chrome_layer. boxed ( ) , guard)
395
- }
396
384
397
- #[ expect(
398
- clippy:: allow_attributes,
399
- reason = "We can't switch to `expect` for allow(unused_variables) as we use it if not on those platforms"
400
- ) ]
401
- #[ allow( unused_variables, reason = "Not used on `wasm32`, `android` or `ios" ) ]
402
- /// Build a [`BoxedLayer`] that outputs logs to the system default.
403
- /// On most platforms, it will be `stderr` with [`tracing_subscriber::fmt::Layer`], expect on `android`, `ios` and` wasm32` where it
404
- /// uses those system default log infrastructure.
405
- /// It is possible to override how you output those logs by providing a `custom_format_layer`.
406
- /// Note that won't have an effect on platform that don't use [`tracing_subscriber::fmt::Layer`]
407
- fn build_system_output_layer ( custom_format_layer : Option < BoxedLayer > ) -> BoxedLayer {
408
- let layer: BoxedLayer ;
409
385
#[ cfg( target_arch = "wasm32" ) ]
410
386
{
411
- layer = tracing_wasm:: WASMLayer :: new ( tracing_wasm:: WASMLayerConfig :: default ( ) ) . boxed ( ) ;
387
+ finished_subscriber = subscriber. with ( tracing_wasm:: WASMLayer :: new (
388
+ tracing_wasm:: WASMLayerConfig :: default ( ) ,
389
+ ) ) ;
412
390
}
413
391
414
392
#[ cfg( target_os = "android" ) ]
415
393
{
416
- layer = android_tracing:: AndroidLayer :: default ( ) . boxed ( ) ;
394
+ finished_subscriber = subscriber . with ( android_tracing:: AndroidLayer :: default ( ) ) ;
417
395
}
418
396
419
397
#[ cfg( target_os = "ios" ) ]
420
398
{
421
- layer = tracing_oslog:: OsLogger :: default ( ) . boxed ( ) ;
399
+ finished_subscriber = subscriber . with ( tracing_oslog:: OsLogger :: default ( ) ) ;
422
400
}
423
401
424
- #[ cfg( all(
425
- not( target_arch = "wasm32" ) ,
426
- not( target_os = "android" ) ,
427
- not( target_os = "ios" )
428
- ) ) ]
429
- {
430
- layer = {
431
- let fmt_layer = custom_format_layer. unwrap_or_else ( || {
432
- tracing_subscriber:: fmt:: Layer :: default ( )
433
- // note: the implementation of `Default` reads from the env var NO_COLOR
434
- // to decide whether to use ANSI color codes, which is common convention
435
- // https://no-color.org/
436
- . with_writer ( std:: io:: stderr)
437
- . boxed ( )
438
- } ) ;
439
-
440
- // bevy_render::renderer logs a `tracy.frame_mark` event every frame
441
- // at Level::INFO. Formatted logs should omit it.
442
- #[ cfg( feature = "tracing-tracy" ) ]
443
- let fmt_layer =
444
- fmt_layer. with_filter ( tracing_subscriber:: filter:: FilterFn :: new ( |meta| {
445
- meta. fields ( ) . field ( "tracy.frame_mark" ) . is_none ( )
446
- } ) ) ;
447
- fmt_layer. boxed ( )
448
- }
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 ) => ( ) ,
449
413
}
450
- layer
451
414
}
452
415
}
0 commit comments