#![doc = include_str!("../FEATURES.mkd")]
#![cfg_attr(tracing_tracy_docs, feature(doc_auto_cfg))]
use std::{borrow::Cow, cell::RefCell, collections::VecDeque, fmt::Write};
use tracing_core::{
field::{Field, Visit},
span::{Attributes, Id, Record},
Event, Subscriber,
};
use tracing_subscriber::fmt::format::{DefaultFields, FormatFields};
use tracing_subscriber::{
fmt::FormattedFields,
layer::{Context, Layer},
registry,
};
use client::{Client, Span};
pub use client;
thread_local! {
static TRACY_SPAN_STACK: RefCell<VecDeque<(Span, u64)>> =
RefCell::new(VecDeque::with_capacity(16));
}
#[derive(Clone)]
pub struct TracyLayer<F = DefaultFields> {
fmt: F,
stack_depth: u16,
client: Client,
}
impl TracyLayer<DefaultFields> {
pub fn new() -> Self {
Self {
fmt: DefaultFields::default(),
stack_depth: 0,
client: Client::start(),
}
}
}
impl<F> TracyLayer<F> {
pub fn with_stackdepth(mut self, stack_depth: u16) -> Self {
self.stack_depth = stack_depth;
self
}
pub fn with_formatter<Fmt>(self, fmt: Fmt) -> TracyLayer<Fmt> {
TracyLayer {
fmt,
stack_depth: self.stack_depth,
client: self.client,
}
}
fn truncate_to_length<'d>(
&self,
data: &'d str,
file: &str,
function: &str,
error_msg: &'static str,
) -> &'d str {
let mut max_len =
usize::from(u16::max_value()) - 2 - 4 - 4 - function.len() - 1 - file.len() - 1;
if data.len() >= max_len {
while !data.is_char_boundary(max_len) {
max_len -= 1;
}
self.client
.color_message(error_msg, 0xFF000000, self.stack_depth);
&data[..max_len]
} else {
data
}
}
}
impl Default for TracyLayer {
fn default() -> Self {
Self::new()
}
}
impl<S, F> Layer<S> for TracyLayer<F>
where
S: Subscriber + for<'a> registry::LookupSpan<'a>,
F: for<'writer> FormatFields<'writer> + 'static,
{
fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
if let Some(span) = ctx.span(id) {
let mut extensions = span.extensions_mut();
if extensions.get_mut::<FormattedFields<F>>().is_none() {
let mut fields = FormattedFields::<F>::new(String::with_capacity(64));
if self.fmt.format_fields(fields.as_writer(), attrs).is_ok() {
extensions.insert(fields);
}
}
}
}
fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) {
if let Some(span) = ctx.span(id) {
let mut extensions = span.extensions_mut();
if let Some(fields) = extensions.get_mut::<FormattedFields<F>>() {
let _ = self.fmt.add_fields(fields, values);
} else {
let mut fields = FormattedFields::<F>::new(String::with_capacity(64));
if self.fmt.format_fields(fields.as_writer(), values).is_ok() {
extensions.insert(fields);
}
}
}
}
fn on_enter(&self, id: &Id, ctx: Context<S>) {
if let Some(span_data) = ctx.span(id) {
let metadata = span_data.metadata();
let file = metadata.file().unwrap_or("<not available>");
let line = metadata.line().unwrap_or(0);
let name: Cow<str> =
if let Some(fields) = span_data.extensions().get::<FormattedFields<F>>() {
if fields.fields.as_str().is_empty() {
metadata.name().into()
} else {
format!("{}{{{}}}", metadata.name(), fields.fields.as_str()).into()
}
} else {
metadata.name().into()
};
TRACY_SPAN_STACK.with(|s| {
s.borrow_mut().push_back((
self.client.clone().span_alloc(
Some(self.truncate_to_length(
&name,
file,
"",
"span information is too long and was truncated",
)),
"",
file,
line,
self.stack_depth,
),
id.into_u64(),
));
});
}
}
fn on_exit(&self, id: &Id, _: Context<S>) {
TRACY_SPAN_STACK.with(|s| {
if let Some((span, span_id)) = s.borrow_mut().pop_back() {
if id.into_u64() != span_id {
self.client.color_message(
"Tracing spans exited out of order! \
Trace may not be accurate for this span stack.",
0xFF000000,
self.stack_depth,
);
}
drop(span);
} else {
self.client.color_message(
"Exiting a tracing span, but got nothing on the tracy span stack!",
0xFF000000,
self.stack_depth,
);
}
});
}
fn on_event(&self, event: &Event, _: Context<'_, S>) {
let mut visitor = TracyEventFieldVisitor {
dest: String::with_capacity(64),
first: true,
frame_mark: false,
};
event.record(&mut visitor);
if !visitor.first {
self.client.message(
self.truncate_to_length(
&visitor.dest,
"",
"",
"event message is too long and was truncated",
),
self.stack_depth,
);
}
if visitor.frame_mark {
self.client.frame_mark();
}
}
}
struct TracyEventFieldVisitor {
dest: String,
frame_mark: bool,
first: bool,
}
impl Visit for TracyEventFieldVisitor {
fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
if self.first {
let _ = write!(&mut self.dest, "{} = {:?}", field.name(), value);
self.first = false;
} else {
let _ = write!(&mut self.dest, ", {} = {:?}", field.name(), value);
}
}
fn record_bool(&mut self, field: &Field, value: bool) {
match (value, field.name()) {
(true, "tracy.frame_mark") => self.frame_mark = true,
_ => self.record_debug(field, &value),
}
}
}
#[cfg(test)]
mod tests;
#[cfg(test)]
fn main() {
if std::env::args_os().any(|p| p == std::ffi::OsStr::new("--bench")) {
tests::bench();
} else {
tests::test();
}
}