mod appender;
mod error;
mod layers;
#[cfg(feature = "process-metrics")]
pub mod metrics;
use crate::error::Result;
use layers::TracingLayers;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tracing::info;
use tracing_core::dispatcher::DefaultGuard;
use tracing_subscriber::{prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt};
pub use error::Error;
pub use layers::ReloadHandle;
pub use tracing_appender::non_blocking::WorkerGuard;
pub use tracing_core::Level;
#[derive(Debug, Clone)]
pub enum LogOutputDest {
Stderr,
Stdout,
Path(PathBuf),
}
impl LogOutputDest {
pub fn parse_from_str(val: &str) -> Result<Self> {
match val {
"stdout" => Ok(LogOutputDest::Stdout),
"data-dir" => {
let timestamp = chrono::Local::now().format("%Y-%m-%d_%H-%M-%S").to_string();
let dir = match dirs_next::data_dir() {
Some(dir) => dir
.join("autonomi")
.join("client")
.join("logs")
.join(format!("log_{timestamp}")),
None => {
return Err(Error::LoggingConfiguration(
"could not obtain data directory path".to_string(),
))
}
};
Ok(LogOutputDest::Path(dir))
}
value => Ok(LogOutputDest::Path(PathBuf::from(value))),
}
}
}
impl std::fmt::Display for LogOutputDest {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
LogOutputDest::Stderr => write!(f, "stderr"),
LogOutputDest::Stdout => write!(f, "stdout"),
LogOutputDest::Path(p) => write!(f, "{}", p.to_string_lossy()),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum LogFormat {
Default,
Json,
}
impl LogFormat {
pub fn parse_from_str(val: &str) -> Result<Self> {
match val {
"default" => Ok(LogFormat::Default),
"json" => Ok(LogFormat::Json),
_ => Err(Error::LoggingConfiguration(
"The only valid values for this argument are \"default\" or \"json\"".to_string(),
)),
}
}
pub fn as_str(&self) -> &'static str {
match self {
LogFormat::Default => "default",
LogFormat::Json => "json",
}
}
}
pub struct LogBuilder {
default_logging_targets: Vec<(String, Level)>,
output_dest: LogOutputDest,
format: LogFormat,
max_log_files: Option<usize>,
max_archived_log_files: Option<usize>,
print_updates_to_stdout: bool,
}
impl LogBuilder {
pub fn new(default_logging_targets: Vec<(String, Level)>) -> Self {
Self {
default_logging_targets,
output_dest: LogOutputDest::Stderr,
format: LogFormat::Default,
max_log_files: None,
max_archived_log_files: None,
print_updates_to_stdout: true,
}
}
pub fn output_dest(&mut self, output_dest: LogOutputDest) {
self.output_dest = output_dest;
}
pub fn format(&mut self, format: LogFormat) {
self.format = format
}
pub fn max_log_files(&mut self, files: usize) {
self.max_log_files = Some(files);
}
pub fn max_archived_log_files(&mut self, files: usize) {
self.max_archived_log_files = Some(files);
}
pub fn print_updates_to_stdout(&mut self, print: bool) {
self.print_updates_to_stdout = print;
}
pub fn initialize(self) -> Result<(ReloadHandle, Option<WorkerGuard>)> {
let mut layers = TracingLayers::default();
let reload_handle = layers.fmt_layer(
self.default_logging_targets.clone(),
&self.output_dest,
self.format,
self.max_log_files,
self.max_archived_log_files,
self.print_updates_to_stdout,
)?;
#[cfg(feature = "otlp")]
{
match std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT") {
Ok(_) => layers.otlp_layer(self.default_logging_targets)?,
Err(_) => println!(
"The OTLP feature is enabled but the OTEL_EXPORTER_OTLP_ENDPOINT variable is not \
set, so traces will not be submitted."
),
}
}
if tracing_subscriber::registry()
.with(layers.layers)
.try_init()
.is_err()
{
eprintln!("Tried to initialize and set global default subscriber more than once");
}
Ok((reload_handle, layers.log_appender_guard))
}
pub fn init_single_threaded_tokio_test(
test_file_name: &str,
disable_networking_logs: bool,
) -> (Option<WorkerGuard>, DefaultGuard) {
let layers = Self::get_test_layers(test_file_name, disable_networking_logs);
let log_guard = tracing_subscriber::registry()
.with(layers.layers)
.set_default();
if let Some(test_name) = std::thread::current().name() {
info!("Running test: {test_name}");
}
(layers.log_appender_guard, log_guard)
}
pub fn init_multi_threaded_tokio_test(
test_file_name: &str,
disable_networking_logs: bool,
) -> Option<WorkerGuard> {
let layers = Self::get_test_layers(test_file_name, disable_networking_logs);
tracing_subscriber::registry()
.with(layers.layers)
.try_init()
.expect("You have tried to init multi_threaded tokio logging twice\nRefer ant_logging::get_test_layers docs for more.");
layers.log_appender_guard
}
fn get_test_layers(test_file_name: &str, disable_networking_logs: bool) -> TracingLayers {
if disable_networking_logs {
std::env::set_var(
"ANT_LOG",
format!("{test_file_name}=TRACE,all,ant_networking=WARN,all"),
);
} else {
std::env::set_var("ANT_LOG", format!("{test_file_name}=TRACE,all"));
}
let output_dest = match dirs_next::data_dir() {
Some(dir) => {
let timestamp = chrono::Local::now().format("%Y-%m-%d_%H-%M-%S").to_string();
let path = dir
.join("autonomi")
.join("client")
.join("logs")
.join(format!("log_{timestamp}"));
LogOutputDest::Path(path)
}
None => LogOutputDest::Stdout,
};
println!("Logging test at {test_file_name:?} to {output_dest:?}");
let mut layers = TracingLayers::default();
let _reload_handle = layers
.fmt_layer(vec![], &output_dest, LogFormat::Default, None, None, false)
.expect("Failed to get TracingLayers");
layers
}
}
#[cfg(test)]
mod tests {
use crate::{layers::LogFormatter, ReloadHandle};
use color_eyre::Result;
use tracing::{trace, warn, Level};
use tracing_subscriber::{
filter::Targets,
fmt as tracing_fmt,
layer::{Filter, SubscriberExt},
reload,
util::SubscriberInitExt,
Layer, Registry,
};
use tracing_test::internal::global_buf;
#[test]
fn reload_handle_should_change_log_levels() -> Result<()> {
let mock_writer = tracing_test::internal::MockWriter::new(global_buf());
let layer = tracing_fmt::layer()
.with_ansi(false)
.with_target(false)
.event_format(LogFormatter)
.with_writer(mock_writer)
.boxed();
let test_target = "ant_logging::tests".to_string();
let target_filters: Box<dyn Filter<Registry> + Send + Sync> =
Box::new(Targets::new().with_targets(vec![(test_target.clone(), Level::TRACE)]));
let (filter, handle) = reload::Layer::new(target_filters);
let reload_handle = ReloadHandle(handle);
let layer = layer.with_filter(filter);
tracing_subscriber::registry().with(layer).try_init()?;
let _span = tracing::info_span!("info span");
trace!("First trace event");
{
let buf = global_buf().lock().unwrap();
let events: Vec<&str> = std::str::from_utf8(&buf)
.expect("Logs contain invalid UTF8")
.lines()
.collect();
assert_eq!(events.len(), 1);
assert!(events[0].contains("First trace event"));
}
reload_handle.modify_log_level("ant_logging::tests=WARN")?;
trace!("Second trace event");
warn!("First warn event");
{
let buf = global_buf().lock().unwrap();
let events: Vec<&str> = std::str::from_utf8(&buf)
.expect("Logs contain invalid UTF8")
.lines()
.collect();
assert_eq!(events.len(), 2);
assert!(events[0].contains("First trace event"));
assert!(events[1].contains("First warn event"));
}
Ok(())
}
}