use std::sync::mpsc;
use bevy::{
asset::uuid::{NoContext, Timestamp},
log::{
BoxedLayer, Level,
tracing::{self, Subscriber},
tracing_subscriber::{self, Layer},
},
prelude::*,
};
#[derive(Debug, Message)]
pub struct LogMessage {
pub timestamp: Timestamp,
pub message: String,
pub level: Level,
}
#[derive(Deref, DerefMut)]
struct CapturedLogMessages(mpsc::Receiver<LogMessage>);
fn transfer_log_messages(
receiver: NonSend<CapturedLogMessages>,
mut message_writer: MessageWriter<LogMessage>,
) {
message_writer.write_batch(receiver.try_iter());
}
struct CaptureLayer {
sender: mpsc::Sender<LogMessage>,
}
impl<S: Subscriber> Layer<S> for CaptureLayer {
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
let mut message = None;
event.record(&mut CaptureLayerVisitor(&mut message));
if let Some(message) = message {
let metadata = event.metadata();
self.sender
.send(LogMessage {
timestamp: Timestamp::now(NoContext),
message,
level: *metadata.level(),
})
.expect("LogEvents resource no longer exists!");
}
}
}
struct CaptureLayerVisitor<'a>(&'a mut Option<String>);
impl tracing::field::Visit for CaptureLayerVisitor<'_> {
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
if field.name() == "message" {
*self.0 = Some(format!("{value:?}"));
}
}
}
pub fn custom_layer(app: &mut App) -> Option<BoxedLayer> {
let (sender, receiver) = mpsc::channel();
let layer = CaptureLayer { sender };
let resource = CapturedLogMessages(receiver);
app.insert_non_send_resource(resource);
app.add_message::<LogMessage>();
app.add_systems(Update, transfer_log_messages);
Some(layer.boxed())
}