use std::{
fs::{self, File},
io::Write,
};
use eyre::{eyre, Report};
use tokio::sync::watch;
use tracing::{field::Visit, Level};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
use zakura_chain::parameters::Network;
use crate::{application::build_version, components::tracing::Config};
use super::non_blocking::{non_blocking, WorkerGuard};
static ZAKURA_ART: [u8; include_bytes!("zakura.utf8").len()] = *include_bytes!("zakura.utf8");
fn io_error(error: std::io::Error) -> Report {
eyre!("I/O operation failed: {error}")
}
pub type BoxWrite = Box<dyn Write + Send + Sync + 'static>;
pub struct Tracing {
_guard: Option<WorkerGuard>,
}
impl Tracing {
#[allow(clippy::print_stdout, clippy::print_stderr, clippy::unwrap_in_result)]
pub fn new(network: &Network, config: Config, uses_intro: bool) -> Result<Self, Report> {
let use_color = config.use_color_stdout();
let use_color_stderr = config.use_color_stderr();
let filter = config.filter.clone().unwrap_or_default();
if uses_intro && !network.is_regtest() {
if use_color_stderr {
eprint!("\x1B[2J");
eprintln!(
"{}",
std::str::from_utf8(&ZAKURA_ART)
.expect("should always work on a UTF-8 encoded constant")
);
}
eprintln!(
"Thank you for running a {} zakurad {} node!",
network.lowercase_name(),
build_version()
);
eprintln!(
"You're helping to strengthen the network and contributing to a social good :)"
);
}
let writer = if let Some(log_file) = config.log_file.as_ref() {
let log_file_dir = log_file.parent();
if let Some(log_file_dir) = log_file_dir {
if !log_file_dir.exists() {
eprintln!("Directory for log file {log_file:?} does not exist, trying to create it...");
if let Err(create_dir_error) = fs::create_dir_all(log_file_dir) {
eprintln!("Failed to create directory for log file: {create_dir_error}");
eprintln!("Trying log file anyway...");
}
}
}
if uses_intro {
println!("Sending logs to {log_file:?}...");
}
let log_file = File::options()
.append(true)
.create(true)
.open(log_file)
.map_err(io_error)?;
Box::new(log_file) as BoxWrite
} else {
let stdout = std::io::stdout();
Box::new(stdout) as BoxWrite
};
let (non_blocking, worker_guard) =
non_blocking(writer, config.buffer_limit.max(100)).map_err(io_error)?;
let subscriber = {
use tracing_subscriber::FmtSubscriber;
let logger = FmtSubscriber::builder()
.with_ansi(use_color)
.with_writer(non_blocking)
.with_env_filter(&filter);
let warn_error_layer = LastWarnErrorLayer {
last_warn_error_sender: crate::application::LAST_WARN_ERROR_LOG_SENDER.clone(),
};
logger.finish().with(warn_error_layer)
};
subscriber.init();
tracing::info!(
?filter,
TRACING_STATIC_MAX_LEVEL = ?tracing::level_filters::STATIC_MAX_LEVEL,
LOG_STATIC_MAX_LEVEL = ?log::STATIC_MAX_LEVEL,
"started tracing component",
);
Ok(Self {
_guard: Some(worker_guard),
})
}
pub fn shutdown(&mut self) {
self._guard.take();
}
}
impl Drop for Tracing {
fn drop(&mut self) {
self.shutdown();
let _ = std::io::stdout().lock().flush();
let _ = std::io::stderr().lock().flush();
}
}
impl std::fmt::Debug for Tracing {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Tracing").finish()
}
}
struct MessageVisitor {
message: Option<String>,
}
impl Visit for MessageVisitor {
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
if field.name() == "message" {
self.message = Some(format!("{value:?}"));
}
}
}
#[derive(Debug, Clone)]
struct LastWarnErrorLayer {
last_warn_error_sender: watch::Sender<Option<(String, Level, chrono::DateTime<chrono::Utc>)>>,
}
impl<S> Layer<S> for LastWarnErrorLayer
where
S: tracing::Subscriber + for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
{
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
let level = *event.metadata().level();
let timestamp = chrono::Utc::now();
if level == Level::WARN || level == Level::ERROR {
let mut visitor = MessageVisitor { message: None };
event.record(&mut visitor);
if let Some(message) = visitor.message {
let _ = self
.last_warn_error_sender
.send(Some((message, level, timestamp)));
}
}
}
}