pub mod aggregation;
pub mod config;
pub mod fields;
pub(crate) mod format;
pub mod redaction;
pub mod sampling;
pub mod writer;
use tracing_subscriber::{
EnvFilter,
fmt::{
self,
format::{DefaultFields, FmtSpan, Format},
writer::BoxMakeWriter,
},
layer::SubscriberExt,
util::SubscriberInitExt,
};
use crate::core::{CoreError, CoreResult};
pub use aggregation::{ErrorAggregationSnapshot, ErrorAggregator, ErrorGroup};
pub use config::{LogConfig, LogFormat, LogSpanEvents};
pub use fields::LogFields;
use format::{RedactingJsonFields, RedactingJsonFormat, RedactingTextFormat};
pub use redaction::{RedactionConfig, redact_text};
pub use sampling::{LogSampler, SamplingConfig};
pub use writer::{
LogWriterConfig, PreparedLogWriter, RollingFileConfig, RuntimeRollingFileWriter,
validate_writer,
};
pub fn init_tracing(config: LogConfig) -> CoreResult<()> {
build_subscriber(config)?
.try_init()
.map_err(|_| CoreError::SubscriberInit)
}
pub fn with_scoped_tracing<T>(config: LogConfig, run: impl FnOnce() -> T) -> CoreResult<T> {
let subscriber = build_subscriber(config)?;
let guard = subscriber.set_default();
let result = run();
drop(guard);
Ok(result)
}
fn build_subscriber(config: LogConfig) -> CoreResult<tracing::Dispatch> {
let filter =
EnvFilter::try_new(config.filter.clone()).unwrap_or_else(|_| EnvFilter::new("info"));
let span_events = span_events(config.span_events);
let writer = build_writer(&config.writer)?;
match config.format {
LogFormat::Text => build_text_subscriber(config, filter, span_events, writer),
LogFormat::Json => build_json_subscriber(config, filter, span_events, writer),
}
}
fn build_text_subscriber(
config: LogConfig,
filter: EnvFilter,
span_events: FmtSpan,
writer: BoxMakeWriter,
) -> CoreResult<tracing::Dispatch> {
let formatter = Format::default()
.with_ansi(config.ansi)
.with_target(config.with_target);
let layer = fmt::layer()
.event_format(RedactingTextFormat::new(
formatter,
config.redaction.clone(),
))
.fmt_fields(DefaultFields::new())
.with_writer(writer);
let mut layer = layer;
layer.set_span_events(span_events);
Ok(tracing::Dispatch::new(
tracing_subscriber::registry().with(layer).with(filter),
))
}
fn build_json_subscriber(
config: LogConfig,
filter: EnvFilter,
span_events: FmtSpan,
writer: BoxMakeWriter,
) -> CoreResult<tracing::Dispatch> {
let formatter = Format::default()
.json()
.with_ansi(false)
.with_target(config.with_target)
.with_current_span(config.include_current_span)
.with_span_list(config.include_span_list);
let layer = fmt::layer()
.fmt_fields(RedactingJsonFields::new(config.redaction.clone()))
.event_format(RedactingJsonFormat::new(
formatter,
config.redaction.clone(),
))
.with_writer(writer);
let mut layer = layer;
layer.set_span_events(span_events);
Ok(tracing::Dispatch::new(
tracing_subscriber::registry().with(layer).with(filter),
))
}
fn build_writer(config: &LogWriterConfig) -> CoreResult<BoxMakeWriter> {
validate_writer(config)?;
match config {
LogWriterConfig::Stdout => Ok(BoxMakeWriter::new(std::io::stdout)),
LogWriterConfig::Stderr => Ok(BoxMakeWriter::new(std::io::stderr)),
LogWriterConfig::File(path) => {
let file = writer::open_append_file(path)?;
Ok(BoxMakeWriter::new(move || {
file.try_clone().expect("clone log file")
}))
}
LogWriterConfig::RollingFile(rolling) => {
let writer = RuntimeRollingFileWriter::new(rolling.clone())?;
Ok(BoxMakeWriter::new(move || writer.clone()))
}
}
}
fn span_events(events: LogSpanEvents) -> FmtSpan {
match events {
LogSpanEvents::None => FmtSpan::NONE,
LogSpanEvents::Close => FmtSpan::CLOSE,
LogSpanEvents::NewAndClose => FmtSpan::NEW | FmtSpan::CLOSE,
LogSpanEvents::Full => FmtSpan::FULL,
}
}
#[cfg(test)]
mod tests {
use super::{LogConfig, init_tracing};
#[test]
fn init_tracing_is_callable() {
let _ = init_tracing(LogConfig {
filter: "debug".to_string(),
ansi: false,
..LogConfig::default()
});
}
}