use crate::{
LogFormat, LogOutputDest, appender,
error::{Error, Result},
};
use std::collections::BTreeMap;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_core::{Event, Level, Subscriber};
use tracing_subscriber::{
Layer, Registry,
filter::Targets,
fmt::{
self as tracing_fmt, FmtContext, FormatEvent, FormatFields,
format::Writer,
time::{FormatTime, SystemTime},
},
layer::Filter,
registry::LookupSpan,
reload::{self, Handle},
};
const MAX_LOG_SIZE: usize = 20 * 1024 * 1024;
const MAX_UNCOMPRESSED_LOG_FILES: usize = 10;
const MAX_LOG_FILES: usize = 1000;
const ALL_ANT_LOGS: &str = "all";
const VERBOSE_ANT_LOGS: &str = "v";
pub struct ReloadHandle(pub(crate) Handle<Box<dyn Filter<Registry> + Send + Sync>, Registry>);
impl ReloadHandle {
pub fn modify_log_level(&self, logging_value: &str) -> Result<()> {
let targets: Vec<(String, Level)> = get_logging_targets(logging_value)?;
self.0.modify(|old_filter| {
let new_filter: Box<dyn Filter<Registry> + Send + Sync> =
Box::new(Targets::new().with_targets(targets));
*old_filter = new_filter;
})?;
Ok(())
}
}
#[derive(Default)]
pub(crate) struct LogFormatter;
impl<S, N> FormatEvent<S, N> for LogFormatter
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(
&self,
ctx: &FmtContext<'_, S, N>,
mut writer: Writer,
event: &Event<'_>,
) -> std::fmt::Result {
let level = *event.metadata().level();
let module = event.metadata().module_path().unwrap_or("<unknown module>");
let lno = event.metadata().line().unwrap_or(0);
let time = SystemTime;
write!(writer, "[")?;
time.format_time(&mut writer)?;
write!(writer, " {level} {module} {lno}")?;
ctx.visit_spans(|span| write!(writer, "/{}", span.name()))?;
write!(writer, "] ")?;
ctx.field_format().format_fields(writer.by_ref(), event)?;
writeln!(writer)
}
}
#[derive(Default)]
pub(crate) struct TracingLayers {
pub(crate) layers: Vec<Box<dyn Layer<Registry> + Send + Sync>>,
pub(crate) log_appender_guard: Option<WorkerGuard>,
}
impl TracingLayers {
pub(crate) fn fmt_layer(
&mut self,
default_logging_targets: Vec<(String, Level)>,
output_dest: &LogOutputDest,
format: LogFormat,
max_uncompressed_log_files: Option<usize>,
max_compressed_log_files: Option<usize>,
print_updates_to_stdout: bool,
) -> Result<ReloadHandle> {
let layer = match output_dest {
LogOutputDest::Stdout => {
if print_updates_to_stdout {
println!("Logging to stdout");
}
tracing_fmt::layer()
.with_ansi(false)
.with_target(false)
.event_format(LogFormatter)
.boxed()
}
LogOutputDest::Stderr => tracing_fmt::layer()
.with_ansi(false)
.with_target(false)
.event_format(LogFormatter)
.with_writer(std::io::stderr)
.boxed(),
LogOutputDest::Path(path) => {
if path.extension() == Some(std::ffi::OsStr::new("log")) {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
if print_updates_to_stdout {
println!("Logging to file: {path:?}");
}
let file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)?;
match format {
LogFormat::Json => tracing_fmt::layer()
.json()
.flatten_event(true)
.with_writer(file)
.boxed(),
LogFormat::Default => tracing_fmt::layer()
.with_ansi(false)
.with_writer(file)
.event_format(LogFormatter)
.boxed(),
}
} else {
std::fs::create_dir_all(path)?;
if print_updates_to_stdout {
println!("Logging to directory: {path:?}");
}
let max_uncompressed_log_files =
max_uncompressed_log_files.unwrap_or(MAX_UNCOMPRESSED_LOG_FILES);
let max_log_files =
if let Some(max_compressed_log_files) = max_compressed_log_files {
max_compressed_log_files + max_uncompressed_log_files
} else {
std::cmp::max(max_uncompressed_log_files, MAX_LOG_FILES)
};
let (file_rotation, worker_guard) = appender::file_rotater(
path,
MAX_LOG_SIZE,
max_uncompressed_log_files,
max_log_files,
);
self.log_appender_guard = Some(worker_guard);
match format {
LogFormat::Json => tracing_fmt::layer()
.json()
.flatten_event(true)
.with_writer(file_rotation)
.boxed(),
LogFormat::Default => tracing_fmt::layer()
.with_ansi(false)
.with_writer(file_rotation)
.event_format(LogFormatter)
.boxed(),
}
}
}
};
let targets = match std::env::var("ANT_LOG") {
Ok(sn_log_val) => {
if print_updates_to_stdout {
println!("Using ANT_LOG={sn_log_val}");
}
get_logging_targets(&sn_log_val)?
}
Err(_) => default_logging_targets,
};
let target_filters: Box<dyn Filter<Registry> + Send + Sync> =
Box::new(Targets::new().with_targets(targets));
let (filter, reload_handle) = reload::Layer::new(target_filters);
let layer = layer.with_filter(filter);
self.layers.push(Box::new(layer));
Ok(ReloadHandle(reload_handle))
}
#[cfg(feature = "otlp")]
pub(crate) fn otlp_layer(
&mut self,
default_logging_targets: Vec<(String, Level)>,
) -> Result<()> {
use opentelemetry::{
KeyValue,
sdk::{Resource, trace},
};
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_semantic_conventions::resource::{SERVICE_INSTANCE_ID, SERVICE_NAME};
use rand::{Rng, distributions::Alphanumeric, thread_rng};
let service_name = std::env::var("OTLP_SERVICE_NAME").unwrap_or_else(|_| {
let random_node_name: String = thread_rng()
.sample_iter(&Alphanumeric)
.take(10)
.map(char::from)
.collect();
random_node_name
});
println!("The opentelemetry traces are logged under the name: {service_name}");
let tracer = opentelemetry_otlp::new_pipeline()
.tracing()
.with_exporter(opentelemetry_otlp::new_exporter().tonic().with_env())
.with_trace_config(trace::config().with_resource(Resource::new(vec![
KeyValue::new(SERVICE_NAME, service_name),
KeyValue::new(SERVICE_INSTANCE_ID, std::process::id().to_string()),
])))
.install_batch(opentelemetry::runtime::Tokio)?;
let targets = match std::env::var("ANT_LOG_OTLP") {
Ok(sn_log_val) => {
println!("Using ANT_LOG_OTLP={sn_log_val}");
get_logging_targets(&sn_log_val)?
}
Err(_) => default_logging_targets,
};
let target_filters: Box<dyn Filter<Registry> + Send + Sync> =
Box::new(Targets::new().with_targets(targets));
let otlp_layer = tracing_opentelemetry::layer()
.with_tracer(tracer)
.with_filter(target_filters)
.boxed();
self.layers.push(otlp_layer);
Ok(())
}
}
fn get_logging_targets(logging_env_value: &str) -> Result<Vec<(String, Level)>> {
let mut targets = BTreeMap::new();
let mut contains_keyword_all_sn_logs = false;
let mut contains_keyword_verbose_sn_logs = false;
for crate_log_level in logging_env_value.split(',') {
if crate_log_level == ALL_ANT_LOGS {
contains_keyword_all_sn_logs = true;
continue;
} else if crate_log_level == VERBOSE_ANT_LOGS {
contains_keyword_verbose_sn_logs = true;
continue;
}
let mut split = crate_log_level.split('=');
let crate_name = split.next().ok_or_else(|| {
Error::LoggingConfiguration("Could not obtain crate name in logging string".to_string())
})?;
let log_level = split.next().unwrap_or("trace");
targets.insert(crate_name.to_string(), get_log_level_from_str(log_level)?);
}
let mut to_be_overriden_targets =
if contains_keyword_all_sn_logs || contains_keyword_verbose_sn_logs {
let mut t = BTreeMap::from_iter(vec![
("ant".to_string(), Level::TRACE),
("evm_testnet".to_string(), Level::TRACE),
("antnode".to_string(), Level::TRACE),
("antctl".to_string(), Level::TRACE),
("node_launchpad".to_string(), Level::DEBUG),
("ant_bootstrap".to_string(), Level::TRACE),
("ant_build_info".to_string(), Level::TRACE),
("ant_evm".to_string(), Level::TRACE),
("ant_logging".to_string(), Level::TRACE),
("ant_node_manager".to_string(), Level::TRACE),
("ant_node_rpc_client".to_string(), Level::TRACE),
("ant_protocol".to_string(), Level::TRACE),
("ant_service_management".to_string(), Level::TRACE),
("service-manager".to_string(), Level::DEBUG),
("autonomi".to_string(), Level::TRACE),
("evmlib".to_string(), Level::TRACE),
]);
if !t.contains_key("ant_node") {
if contains_keyword_all_sn_logs {
t.insert("ant_node".to_string(), Level::TRACE)
} else if contains_keyword_verbose_sn_logs {
t.insert("ant_node".to_string(), Level::DEBUG)
} else {
t.insert("ant_node".to_string(), Level::INFO)
};
}
t
} else {
Default::default()
};
to_be_overriden_targets.extend(targets);
Ok(to_be_overriden_targets.into_iter().collect())
}
fn get_log_level_from_str(log_level: &str) -> Result<Level> {
match log_level.to_lowercase().as_str() {
"info" => Ok(Level::INFO),
"debug" => Ok(Level::DEBUG),
"trace" => Ok(Level::TRACE),
"warn" => Ok(Level::WARN),
"error" => Ok(Level::WARN),
_ => Err(Error::LoggingConfiguration(format!(
"Log level {log_level} is not supported"
))),
}
}