use std::{
io::{self, Write},
sync::mpsc::{self, Receiver, Sender},
};
use ::tracing::{
Event, Metadata, Subscriber,
field::{Field, Visit},
};
use parking_lot::Mutex;
use tracing_subscriber::{
Layer,
fmt::{
self, FmtContext, MakeWriter,
format::{FormatEvent, FormatFields, Writer},
},
registry::LookupSpan,
};
use crate::Context;
pub const MBOX_TARGET: &str = "mbox";
#[must_use]
#[inline]
pub fn is_mbox_target(meta: &Metadata<'_>) -> bool {
meta.target() == MBOX_TARGET
}
static SINK: Mutex<Option<Sender<String>>> = Mutex::new(None);
static DRAIN: Mutex<Option<Receiver<String>>> = Mutex::new(None);
pub(crate) fn drain(ctx: &Context, handler: &str) {
let lines: Vec<String> = {
let guard = DRAIN.lock();
match guard.as_ref() {
Some(rx) => rx.try_iter().collect(),
None => return,
}
};
for line in lines {
ctx.display_message(handler, "", &line);
}
}
pub(crate) fn cleanup() {
let mut sink = SINK.lock();
*sink = None;
*DRAIN.lock() = None;
drop(sink);
}
#[derive(Debug, Clone, Copy, Default)]
pub struct MboxFormat;
#[derive(Default)]
struct MboxVisitor {
message: String,
fields: String,
}
impl MboxVisitor {
fn separator(&mut self) {
if !self.fields.is_empty() {
self.fields.push(' ');
}
}
}
#[expect(
clippy::missing_trait_methods,
reason = "the other record_* defaults forward to record_debug, which we implement"
)]
impl Visit for MboxVisitor {
fn record_str(&mut self, field: &Field, value: &str) {
use std::fmt::Write as _;
if field.name() == "message" {
value.clone_into(&mut self.message);
} else {
self.separator();
let _ = write!(self.fields, "{}={value}", field.name());
}
}
fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
use std::fmt::Write as _;
if field.name() == "message" {
self.message.clear();
let _ = write!(self.message, "{value:?}");
} else {
self.separator();
let _ = write!(self.fields, "{}={value:?}", field.name());
}
}
}
impl<S, N> FormatEvent<S, N> for MboxFormat
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(
&self,
_ctx: &FmtContext<'_, S, N>,
mut writer: Writer<'_>,
event: &Event<'_>,
) -> std::fmt::Result {
let mut visitor = MboxVisitor::default();
event.record(&mut visitor);
write!(writer, "{}", visitor.message)?;
if !visitor.fields.is_empty() {
write!(writer, " [{}]", visitor.fields)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub struct MboxWriter;
impl MboxWriter {
#[must_use]
pub fn new() -> Self {
let mut sink = SINK.lock();
if sink.is_none() {
let (tx, rx) = mpsc::channel();
*sink = Some(tx);
*DRAIN.lock() = Some(rx);
drop(sink);
}
Self
}
}
impl Default for MboxWriter {
fn default() -> Self {
Self::new()
}
}
#[expect(
clippy::missing_trait_methods,
reason = "make_writer_for's default (delegating to make_writer) is correct here"
)]
impl<'a> MakeWriter<'a> for MboxWriter {
type Writer = MboxLine;
fn make_writer(&'a self) -> Self::Writer {
MboxLine { buf: Vec::new() }
}
}
#[doc(hidden)]
pub struct MboxLine {
buf: Vec<u8>,
}
#[expect(
clippy::missing_trait_methods,
reason = "the write_* defaults over our write/flush are correct"
)]
impl Write for MboxLine {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buf.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[expect(clippy::missing_trait_methods, reason = "we do not need pin_drop")]
impl Drop for MboxLine {
fn drop(&mut self) {
if self.buf.is_empty() {
return;
}
let line = String::from_utf8_lossy(&self.buf).into_owned();
if let Some(tx) = SINK.lock().as_ref() {
let _ = tx.send(line);
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct MboxLayer;
impl MboxLayer {
#[must_use]
#[expect(
clippy::new_ret_no_self,
reason = "MboxLayer namespaces the constructor; the layer is an opaque fmt::Layer"
)]
pub fn new<S>() -> impl Layer<S>
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
fmt::layer()
.event_format(MboxFormat)
.with_ansi(false)
.with_writer(MboxWriter::new())
}
}