mod layer;
mod storage;
pub use layer::*;
use tracing_core::Subscriber;
use tracing_subscriber::registry::LookupSpan;
#[derive(Debug, Default)]
pub enum TimestampFormat {
Unix,
UnixMillis,
#[default]
Rfc3339,
Rfc3339Nanos,
Custom(String),
}
impl TimestampFormat {
fn format_string(&self, now: &chrono::DateTime<chrono::Utc>) -> String {
match self {
TimestampFormat::Unix => now.timestamp().to_string(),
TimestampFormat::UnixMillis => now.timestamp_millis().to_string(),
TimestampFormat::Rfc3339 => now.to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
TimestampFormat::Rfc3339Nanos => {
now.to_rfc3339_opts(chrono::SecondsFormat::Nanos, true)
}
TimestampFormat::Custom(format) => now.format(format).to_string(),
}
}
fn format_number(&self, now: &chrono::DateTime<chrono::Utc>) -> u64 {
match self {
TimestampFormat::Unix => now.timestamp() as u64,
TimestampFormat::UnixMillis => now.timestamp_millis() as u64,
TimestampFormat::Rfc3339 => unreachable!("rfc3339 is not a number"),
TimestampFormat::Rfc3339Nanos => unreachable!("rfc3339_nanos is not a number"),
TimestampFormat::Custom(_) => unreachable!("custom is not a number"),
}
}
}
#[derive(Debug, Default)]
pub enum Casing {
#[default]
Lowercase,
Uppercase,
}
pub struct Builder {
layer: crate::JsonFormattingLayer,
}
impl Builder {
pub fn new() -> Self {
Self {
layer: crate::JsonFormattingLayer::default(),
}
}
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
pub fn builder() -> Builder {
Builder::default()
}
impl Builder {
pub fn with_level_name(mut self, level_name: &'static str) -> Self {
self.layer.level_name = level_name;
self
}
pub fn with_level_value_casing(mut self, casing: Casing) -> Self {
self.layer.level_value_casing = casing;
self
}
pub fn with_message_name(mut self, message_name: &'static str) -> Self {
self.layer.message_name = message_name;
self
}
pub fn with_target_name(mut self, target_name: &'static str) -> Self {
self.layer.target_name = target_name;
self
}
pub fn with_timestamp_name(mut self, timestamp_name: &'static str) -> Self {
self.layer.timestamp_name = timestamp_name;
self
}
pub fn with_timestamp_format(mut self, timestamp_format: TimestampFormat) -> Self {
self.layer.timestamp_format = timestamp_format;
self
}
pub fn with_flatten_fields(mut self, flatten_fields: bool) -> Self {
self.layer.flatten_fields = flatten_fields;
self
}
pub fn with_flatten_spans(mut self, flatten_spans: bool) -> Self {
self.layer.flatten_spans = flatten_spans;
self
}
pub fn with_line_numbers(mut self, line_numbers: bool) -> Self {
self.layer.line_numbers = line_numbers;
self
}
pub fn with_file_names(mut self, file_names: bool) -> Self {
self.layer.file_names = file_names;
self
}
pub fn with_field_filter(
mut self,
filter: impl Fn(&str) -> bool + Send + Sync + 'static,
) -> Self {
self.layer.field_filter = Some(Box::new(filter));
self
}
pub fn layer<S>(self) -> impl tracing_subscriber::Layer<S>
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
self.layer
}
}
pub fn layer<S>() -> impl tracing_subscriber::Layer<S>
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
crate::builder().layer
}
#[cfg(test)]
mod tests {
use tracing::{debug, error, info, info_span, instrument, trace, warn};
use tracing_subscriber::prelude::*;
use super::*;
#[instrument]
fn some_function(a: u32, b: u32) {
let span = info_span!("some_span", a = a, b = b);
span.in_scope(|| {
info!("some message from inside a span");
});
}
#[test]
fn test_json_event_formatter() {
let subscriber = tracing_subscriber::registry().with(builder().layer());
tracing::subscriber::with_default(subscriber, || {
trace!(a = "b", "hello world from trace");
debug!("hello world from debug");
info!("hello world from info");
warn!("hello world from warn");
error!("hello world from error");
let span = info_span!(
"test_span",
person.firstname = "cole",
person.lastname = "mackenzie",
later = tracing::field::Empty,
);
span.in_scope(|| {
info!("some message from inside a info_span");
let inner = info_span!("inner_span", a = "b", c = "d", inner_span = true);
inner.in_scope(|| {
info!(
inner_span_field = true,
later = "populated from inside a span",
"some message from inside a info_span",
);
});
});
});
let subscriber = tracing_subscriber::registry().with(
builder()
.with_level_name("severity")
.with_level_value_casing(Casing::Uppercase)
.with_message_name("msg")
.with_timestamp_name("ts")
.with_timestamp_format(TimestampFormat::Unix)
.with_flatten_fields(false)
.layer(),
);
tracing::subscriber::with_default(subscriber, || {
trace!(a = "b", "hello world from trace");
debug!("hello world from debug");
info!("hello world from info");
warn!("hello world from warn");
error!("hello world from error");
let span = info_span!(
"test_span",
person.firstname = "cole",
person.lastname = "mackenzie",
later = tracing::field::Empty,
);
span.in_scope(|| {
info!("some message from inside a info_span");
let inner = info_span!("inner_span", a = "b", c = "d", inner_span = true);
inner.in_scope(|| {
info!(
inner_span_field = true,
later = "populated from inside a span",
"some message from inside a info_span",
);
});
});
});
}
#[test]
fn test_nested_spans() {
let subscriber = tracing_subscriber::registry().with(builder().layer());
tracing::subscriber::with_default(subscriber, || {
let span = info_span!(
"test_span",
person.firstname = "cole",
person.lastname = "mackenzie",
later = tracing::field::Empty,
);
span.in_scope(|| {
info!("some message from inside a info_span");
let inner = info_span!("inner_span", a = "b", c = "d", inner_span = true);
inner.in_scope(|| {
info!(
inner_span_field = true,
later = "populated from inside a span",
"some message from inside a info_span",
);
});
});
some_function(1, 2);
});
}
#[test]
fn test_field_filter() {
let subscriber = tracing_subscriber::registry().with(
builder()
.with_field_filter(|name| !name.starts_with("__"))
.layer(),
);
tracing::subscriber::with_default(subscriber, || {
info!(
__sentry_fn = "MyService::handle",
__sentry_label = "request failed",
correlation_id = "abc-123",
"something went wrong"
);
let span = info_span!("request", __internal = "hidden", request_id = "req-456",);
span.in_scope(|| {
info!("processing request");
});
});
}
}