log_layers_ecs/
log_layers_ecs.rs1use std::sync::mpsc;
15
16use bevy::{
17 log::{
18 tracing::{self, Subscriber},
19 tracing_subscriber::{self, Layer},
20 BoxedLayer, Level,
21 },
22 prelude::*,
23};
24
25fn main() {
26 App::new()
27 .add_plugins(DefaultPlugins.set(bevy::log::LogPlugin {
28 level: Level::TRACE,
31 filter: "warn,log_layers_ecs=trace".to_string(),
32 custom_layer,
33 }))
34 .add_systems(Startup, (log_system, setup))
35 .add_systems(Update, print_logs)
36 .run();
37}
38
39#[derive(Debug, Event)]
41struct LogEvent {
42 message: String,
43 level: Level,
44}
45
46#[derive(Deref, DerefMut)]
49struct CapturedLogEvents(mpsc::Receiver<LogEvent>);
50
51fn transfer_log_events(
53 receiver: NonSend<CapturedLogEvents>,
54 mut log_events: EventWriter<LogEvent>,
55) {
56 log_events.write_batch(receiver.try_iter());
58}
59
60struct CaptureLayer {
63 sender: mpsc::Sender<LogEvent>,
64}
65impl<S: Subscriber> Layer<S> for CaptureLayer {
66 fn on_event(
67 &self,
68 event: &tracing::Event<'_>,
69 _ctx: tracing_subscriber::layer::Context<'_, S>,
70 ) {
71 let mut message = None;
75 event.record(&mut CaptureLayerVisitor(&mut message));
76 if let Some(message) = message {
77 let metadata = event.metadata();
78
79 self.sender
80 .send(LogEvent {
81 message,
82 level: *metadata.level(),
83 })
84 .expect("LogEvents resource no longer exists!");
85 }
86 }
87}
88
89struct CaptureLayerVisitor<'a>(&'a mut Option<String>);
91impl tracing::field::Visit for CaptureLayerVisitor<'_> {
92 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
93 if field.name() == "message" {
95 *self.0 = Some(format!("{value:?}"));
96 }
97 }
98}
99fn custom_layer(app: &mut App) -> Option<BoxedLayer> {
100 let (sender, receiver) = mpsc::channel();
101
102 let layer = CaptureLayer { sender };
103 let resource = CapturedLogEvents(receiver);
104
105 app.insert_non_send_resource(resource);
106 app.add_event::<LogEvent>();
107 app.add_systems(Update, transfer_log_events);
108
109 Some(layer.boxed())
110}
111
112fn log_system() {
113 error!("Something failed");
116 warn!("Something bad happened that isn't a failure, but thats worth calling out");
117 info!("Helpful information that is worth printing by default");
118 debug!("Helpful for debugging");
119 trace!("Very noisy");
120}
121
122#[derive(Component)]
123struct LogViewerRoot;
124
125fn setup(mut commands: Commands) {
126 commands.spawn(Camera2d);
127
128 commands.spawn((
129 Node {
130 width: Val::Vw(100.0),
131 height: Val::Vh(100.0),
132 flex_direction: FlexDirection::Column,
133 padding: UiRect::all(Val::Px(12.)),
134 ..default()
135 },
136 LogViewerRoot,
137 ));
138}
139
140fn print_logs(
143 mut events: EventReader<LogEvent>,
144 mut commands: Commands,
145 log_viewer_root: Single<Entity, With<LogViewerRoot>>,
146) {
147 let root_entity = *log_viewer_root;
148
149 commands.entity(root_entity).with_children(|child| {
150 for event in events.read() {
151 child.spawn(Text::default()).with_children(|child| {
152 child.spawn((
153 TextSpan::new(format!("{:5} ", event.level)),
154 TextColor(level_color(&event.level)),
155 ));
156 child.spawn(TextSpan::new(&event.message));
157 });
158 }
159 });
160}
161
162fn level_color(level: &Level) -> Color {
163 use bevy::color::palettes::tailwind::*;
164 Color::from(match *level {
165 Level::WARN => ORANGE_400,
166 Level::ERROR => RED_400,
167 Level::INFO => GREEN_400,
168 Level::TRACE => PURPLE_400,
169 Level::DEBUG => BLUE_400,
170 })
171}