#![forbid(unsafe_code)]
#![warn(missing_docs)]
mod filter;
mod json;
mod time;
mod writer;
use std::io;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;
use tracing_core::span::{Attributes, Id, Record};
use tracing_core::{Event, Metadata, Subscriber};
pub use filter::Filter;
pub use writer::{MakeWriter, Stderr, Stdout};
pub fn json() -> Builder<Stdout> {
Builder {
writer: Stdout,
filter: None,
}
}
#[derive(Debug)]
pub struct Builder<W> {
writer: W,
filter: Option<Filter>,
}
impl<W: MakeWriter> Builder<W> {
pub fn filter(mut self, directives: &str) -> Self {
self.filter = Some(Filter::parse(directives));
self
}
pub fn with_writer<T: MakeWriter>(self, writer: T) -> Builder<T> {
Builder {
writer,
filter: self.filter,
}
}
pub fn finish(self) -> Penna<W> {
Penna {
writer: self.writer,
filter: self.filter.unwrap_or_else(Filter::from_env),
spans: Mutex::new(Vec::new()),
next_id: AtomicU64::new(1),
}
}
pub fn try_install(self) -> Result<(), tracing_core::dispatcher::SetGlobalDefaultError> {
tracing_core::dispatcher::set_global_default(tracing_core::Dispatch::new(self.finish()))
}
pub fn install(self) {
let _ = self.try_install();
}
}
#[derive(Debug)]
pub struct Penna<W> {
writer: W,
filter: Filter,
spans: Mutex<Vec<Option<SpanData>>>,
next_id: AtomicU64,
}
#[derive(Debug)]
struct SpanData {
name: &'static str,
fields: String,
refs: usize,
}
thread_local! {
static STACK: std::cell::RefCell<Vec<u64>> = const { std::cell::RefCell::new(Vec::new()) };
}
impl<W: MakeWriter> Subscriber for Penna<W> {
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
self.filter.enabled(metadata.target(), *metadata.level())
}
fn max_level_hint(&self) -> Option<tracing_core::LevelFilter> {
Some(self.filter.max_level())
}
fn new_span(&self, attributes: &Attributes<'_>) -> Id {
let mut fields = String::new();
attributes.record(&mut json::FieldWriter::new(&mut fields));
let data = SpanData {
name: attributes.metadata().name(),
fields,
refs: 1,
};
let mut spans = self
.spans
.lock()
.unwrap_or_else(|poison| poison.into_inner());
let index = spans.iter().position(Option::is_none);
match index {
Some(index) => {
spans[index] = Some(data);
Id::from_u64(index as u64 + 1)
}
None => {
spans.push(Some(data));
self.next_id
.store(spans.len() as u64 + 1, Ordering::Relaxed);
Id::from_u64(spans.len() as u64)
}
}
}
fn record(&self, id: &Id, values: &Record<'_>) {
let mut spans = self
.spans
.lock()
.unwrap_or_else(|poison| poison.into_inner());
if let Some(Some(span)) = spans.get_mut(index_of(id)) {
values.record(&mut json::FieldWriter::appending(&mut span.fields));
}
}
fn record_follows_from(&self, _span: &Id, _follows: &Id) {}
fn event(&self, event: &Event<'_>) {
let metadata = event.metadata();
let mut line = String::with_capacity(256);
line.push_str("{\"timestamp\":\"");
time::write_rfc3339_micros(&mut line, std::time::SystemTime::now());
line.push_str("\",\"level\":\"");
line.push_str(metadata.level().as_str());
line.push_str("\",\"fields\":{");
event.record(&mut json::FieldWriter::new(&mut line));
line.push_str("},\"target\":");
json::write_string(&mut line, metadata.target());
self.write_spans(&mut line);
line.push_str("}\n");
let _ = self.writer.make_writer().write_all(line.as_bytes());
}
fn enter(&self, id: &Id) {
STACK.with(|stack| stack.borrow_mut().push(id.into_u64()));
}
fn exit(&self, id: &Id) {
STACK.with(|stack| {
let mut stack = stack.borrow_mut();
if let Some(position) = stack.iter().rposition(|open| *open == id.into_u64()) {
stack.remove(position);
}
});
}
fn clone_span(&self, id: &Id) -> Id {
let mut spans = self
.spans
.lock()
.unwrap_or_else(|poison| poison.into_inner());
if let Some(Some(span)) = spans.get_mut(index_of(id)) {
span.refs += 1;
}
id.clone()
}
fn try_close(&self, id: Id) -> bool {
let mut spans = self
.spans
.lock()
.unwrap_or_else(|poison| poison.into_inner());
let Some(slot) = spans.get_mut(index_of(&id)) else {
return false;
};
let Some(span) = slot else { return false };
span.refs -= 1;
if span.refs == 0 {
*slot = None;
return true;
}
false
}
}
impl<W: MakeWriter> Penna<W> {
fn write_spans(&self, line: &mut String) {
let open = STACK.with(|stack| stack.borrow().clone());
if open.is_empty() {
return;
}
let spans = self
.spans
.lock()
.unwrap_or_else(|poison| poison.into_inner());
let rendered: Vec<String> = open
.iter()
.filter_map(|id| spans.get(*id as usize - 1).and_then(Option::as_ref))
.map(|span| {
let mut out = String::with_capacity(span.fields.len() + 32);
out.push('{');
out.push_str(&span.fields);
if !span.fields.is_empty() {
out.push(',');
}
out.push_str("\"name\":");
json::write_string(&mut out, span.name);
out.push('}');
out
})
.collect();
if rendered.is_empty() {
return;
}
line.push_str(",\"span\":");
line.push_str(rendered.last().expect("checked non-empty"));
line.push_str(",\"spans\":[");
line.push_str(&rendered.join(","));
line.push(']');
}
}
fn index_of(id: &Id) -> usize {
id.into_u64() as usize - 1
}
trait WriteAll {
fn write_all(self, bytes: &[u8]) -> io::Result<()>;
}
impl<T: io::Write> WriteAll for T {
fn write_all(mut self, bytes: &[u8]) -> io::Result<()> {
io::Write::write_all(&mut self, bytes)
}
}