#![doc(html_logo_url = "https://cdn.floofy.dev/images/trans.png")]
#![doc(html_favicon_url = "https://cdn.floofy.dev/images/trans.png")]
#![cfg_attr(any(noeldoc, docsrs), feature(doc_cfg))]
#[cfg(feature = "writers")]
#[cfg_attr(any(docsrs, noeldoc), doc(cfg(feature = "writers")))]
pub mod writers;
#[cfg(not(feature = "writers"))]
mod writers;
#[cfg(not(feature = "writers"))]
pub use writers::JsonVisitor;
use std::{io::Write, sync::RwLock};
use tracing::{span, Event, Metadata, Subscriber};
use tracing_subscriber::{
registry::{LookupSpan, SpanRef},
Layer,
};
pub trait WriteFn<S: for<'l> LookupSpan<'l>>: Send {
fn buffer(&self, event: &Event, metadata: &Metadata, spans: Vec<SpanRef<'_, S>>) -> String;
}
impl<S: for<'l> LookupSpan<'l>, F> WriteFn<S> for F
where
F: Fn(&Event, &Metadata, Vec<SpanRef<'_, S>>) -> String + Send,
{
fn buffer(&self, event: &Event, metadata: &Metadata, spans: Vec<SpanRef<'_, S>>) -> String {
(self)(event, metadata, spans)
}
}
pub struct WriteLayer<S: for<'l> LookupSpan<'l>> {
writer: RwLock<Box<dyn Write + Send + Sync>>,
write_fn: Option<Box<dyn WriteFn<S> + Send + Sync>>,
}
impl<S: for<'l> LookupSpan<'l>> WriteLayer<S> {
pub fn new<W: Write + Send + Sync + 'static>(writer: W) -> WriteLayer<S> {
WriteLayer {
writer: RwLock::new(Box::new(writer)),
write_fn: None,
}
}
pub fn new_with<W: Write + Send + Sync + 'static, F: WriteFn<S> + Send + Sync + 'static>(
writer: W,
fn_: F,
) -> WriteLayer<S> {
WriteLayer {
writer: RwLock::new(Box::new(writer)),
write_fn: Some(Box::new(fn_)),
}
}
}
#[derive(Debug)]
pub(crate) struct JsonExtension(pub(crate) std::collections::BTreeMap<String, serde_json::Value>);
impl<S: Subscriber + for<'l> LookupSpan<'l>> Layer<S> for WriteLayer<S> {
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
let span = ctx.span(id).unwrap();
let mut data = std::collections::BTreeMap::new();
let mut visitor = crate::writers::JsonVisitor(&mut data);
attrs.record(&mut visitor);
span.extensions_mut().insert(JsonExtension(data));
}
fn on_record(&self, span: &span::Id, values: &span::Record<'_>, ctx: tracing_subscriber::layer::Context<'_, S>) {
let span = ctx.span(span).unwrap();
let mut exts = span.extensions_mut();
let data: &mut JsonExtension = exts.get_mut::<JsonExtension>().unwrap();
let mut visitor = crate::writers::JsonVisitor(&mut data.0);
values.record(&mut visitor);
}
fn on_event(&self, event: &Event<'_>, ctx: tracing_subscriber::layer::Context<'_, S>) {
let mut writer = self.writer.write().unwrap();
if let Some(ref fn_) = self.write_fn {
cfg_if::cfg_if! {
if #[cfg(feature = "tracing-log")] {
use tracing_log::NormalizeEvent;
let metadata = event.normalized_metadata();
let metadata = metadata.as_ref().unwrap_or_else(|| event.metadata());
} else {
let metadata = event.metadata();
}
};
let mut spans: Vec<SpanRef<'_, S>> = vec![];
if let Some(scope) = ctx.event_scope(event) {
for span in scope.from_root() {
spans.push(span);
}
}
let buf = fn_.buffer(event, metadata, spans);
let _ = write!(writer, "{buf}");
let _ = writeln!(writer);
}
}
}
#[cfg(test)]
mod tests {
use crate::WriteLayer;
use std::io;
use tracing::Dispatch;
use tracing_subscriber::{layer::SubscriberExt, registry, Layer, Registry};
fn __assert_is_layer<S>(_: &dyn Layer<S>) {
}
fn __assert_is_dispatchable(_: impl Into<Dispatch>) {
}
#[test]
fn assertions() {
__assert_is_layer::<Registry>(&WriteLayer::new(io::stdout()));
__assert_is_dispatchable(registry().with(WriteLayer::new(io::stdout())));
#[cfg(feature = "writers")]
__assert_is_dispatchable(registry().with(WriteLayer::new_with(
io::stdout(),
crate::writers::default::Writer::default(),
)));
#[cfg(feature = "writers")]
__assert_is_dispatchable(registry().with(WriteLayer::new_with(io::stdout(), crate::writers::json)));
}
}