From dd575f261fbd31f49ab80cffdeca10319772b217 Mon Sep 17 00:00:00 2001 From: skoupidi Date: Sat, 15 Nov 2025 00:36:56 +0200 Subject: [PATCH] util/logger: allow bins to set alternative terminal logger writers --- bin/drk/src/cli_util.rs | 1 + bin/drk/src/interactive.rs | 26 ++++++------- bin/drk/src/main.rs | 25 +++++++++++-- src/util/logger.rs | 75 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 106 insertions(+), 21 deletions(-) diff --git a/bin/drk/src/cli_util.rs b/bin/drk/src/cli_util.rs index 0e6f7062d..6fccefe45 100644 --- a/bin/drk/src/cli_util.rs +++ b/bin/drk/src/cli_util.rs @@ -15,6 +15,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + use std::{ io::{stdin, Cursor, Read}, str::FromStr, diff --git a/bin/drk/src/interactive.rs b/bin/drk/src/interactive.rs index d55db9f17..d03949228 100644 --- a/bin/drk/src/interactive.rs +++ b/bin/drk/src/interactive.rs @@ -30,7 +30,7 @@ use linenoise_rs::{ }; use prettytable::{format, row, Table}; use rand::rngs::OsRng; -use smol::channel::{unbounded, Receiver, Sender}; +use smol::channel::{Receiver, Sender}; use url::Url; use darkfi::{ @@ -376,7 +376,14 @@ fn hints(buffer: &str) -> Option<(String, i32, bool)> { /// Auxiliary function to start provided Drk as an interactive shell. /// Only sane/linenoise terminals are suported. -pub async fn interactive(drk: &DrkPtr, endpoint: &Url, history_path: &str, ex: &ExecutorPtr) { +pub async fn interactive( + drk: &DrkPtr, + endpoint: &Url, + history_path: &str, + shell_sender: &Sender>, + shell_receiver: &Receiver>, + ex: &ExecutorPtr, +) { // Expand the history file path let history_path = match expand_path(history_path) { Ok(p) => p, @@ -404,14 +411,10 @@ pub async fn interactive(drk: &DrkPtr, endpoint: &Url, history_path: &str, ex: & let mut snooze_active = false; let subscription_tasks = [StoppableTask::new(), StoppableTask::new()]; - // Create an unbounded smol channel, so we can have a printing - // queue the background task can submit messages to the shell. - let (shell_sender, shell_receiver) = unbounded(); - // Start the interactive shell loop { // Wait for next line to process - let line = listen_for_line(&snooze_active, &shell_receiver).await; + let line = listen_for_line(&snooze_active, shell_receiver).await; // Grab input or end if Ctrl-D or Ctrl-C was pressed let Some(line) = line else { break }; @@ -533,7 +536,7 @@ pub async fn interactive(drk: &DrkPtr, endpoint: &Url, history_path: &str, ex: & endpoint, &mut subscription_active, &subscription_tasks, - &shell_sender, + shell_sender, ex, ) .await @@ -2351,12 +2354,7 @@ async fn handle_subscribe( let ex_ = ex.clone(); subscription_tasks[0].clone().start( async move { subscribe_blocks(&drk_, rpc_task_, shell_sender_, endpoint_, &ex_).await }, - |res| async { - match res { - Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ } - Err(e) => println!("Failed starting subscription task: {e}"), - } - }, + |_| async { /* Do nothing */ }, Error::DetachedTaskStopped, ex.clone(), ); diff --git a/bin/drk/src/main.rs b/bin/drk/src/main.rs index 7bb197cb6..280764a81 100644 --- a/bin/drk/src/main.rs +++ b/bin/drk/src/main.rs @@ -24,9 +24,10 @@ use std::{ use prettytable::{format, row, Table}; use rand::rngs::OsRng; -use smol::{fs::read_to_string, stream::StreamExt}; +use smol::{channel::unbounded, fs::read_to_string, stream::StreamExt}; use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml}; use tracing::info; +use tracing_appender::non_blocking; use url::Url; use darkfi::{ @@ -34,6 +35,7 @@ use darkfi::{ system::ExecutorPtr, util::{ encoding::base64, + logger::{set_terminal_writer, ChannelWriter}, parse::{decode_base10, encode_base10}, path::{expand_path, get_config_path}, }, @@ -655,6 +657,16 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { match args.command { Subcmd::Interactive => { + // Create an unbounded smol channel, so we can have a + // printing queue the background logger and tasks can + // submit messages to so the shell prints them. + let (shell_sender, shell_receiver) = unbounded(); + + // Set the logging writer + let (non_blocking, _guard) = + non_blocking(ChannelWriter { sender: shell_sender.clone() }); + set_terminal_writer(args.verbose, non_blocking)?; + let drk = new_wallet( blockchain_config.cache_path, blockchain_config.wallet_path, @@ -665,8 +677,15 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { ) .await .into_ptr(); - interactive(&drk, &blockchain_config.endpoint, &blockchain_config.history_path, &ex) - .await; + interactive( + &drk, + &blockchain_config.endpoint, + &blockchain_config.history_path, + &shell_sender, + &shell_receiver, + &ex, + ) + .await; drk.read().await.stop_rpc_client().await?; Ok(()) } diff --git a/src/util/logger.rs b/src/util/logger.rs index 3e8e3b7ae..329c207a7 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -16,23 +16,31 @@ * along with this program. If not, see . */ -use std::{env, fmt, time::UNIX_EPOCH}; +use std::{ + env, fmt, + io::{Result as IOResult, Write}, + marker::{Send, Sync}, + sync::OnceLock, + time::UNIX_EPOCH, +}; use nu_ansi_term::{Color, Style}; +use smol::channel::Sender; use tracing::{field::Field, Event, Level as TracingLevel, Metadata, Subscriber}; use tracing_appender::non_blocking::NonBlocking; use tracing_subscriber::{ fmt::{ format, format::FmtSpan, time::FormatTime, FmtContext, FormatEvent, FormatFields, - FormattedFields, Layer as FmtLayer, + FormattedFields, Layer as FmtLayer, MakeWriter, }, layer::{Context, Filter, SubscriberExt}, registry::LookupSpan, + reload::{Handle, Layer as ReloadLayer}, util::SubscriberInitExt, Layer, Registry, }; -use crate::{util::time::DateTime, Result}; +use crate::{util::time::DateTime, Error, Result}; // Creates a `verbose` log level by wrapping an info! macro and // adding a `verbose` field. @@ -433,6 +441,58 @@ impl Layer for TargetFilter { } } +/// Global singleton holding the tracing terminal layer handle. +static TERMINAL_LAYER_HANDLE: OnceLock + Send + Sync>, Registry>> = + OnceLock::new(); + +/// Wrapper structure over a smol channel sender to use as a logs +/// writer for the interactive shell. +pub struct ChannelWriter { + pub sender: Sender>, +} + +impl Write for ChannelWriter { + fn write(&mut self, buf: &[u8]) -> IOResult { + let string = String::from_utf8_lossy(buf).to_string(); + let mut message = vec![]; + for line in string.lines() { + message.push(line.to_string()); + } + let _ = self.sender.send_blocking(message); + Ok(buf.len()) + } + + fn flush(&mut self) -> IOResult<()> { + Ok(()) + } +} + +/// Trait definitions for cleaner code. +pub trait TerminalWriter: for<'writer> MakeWriter<'writer> + 'static + Sized + Send + Sync {} +impl MakeWriter<'writer> + 'static + Sized + Send + Sync> TerminalWriter for W {} + +/// Auxiliary function to set the tracing terminal layer writer. +pub fn set_terminal_writer(verbosity_level: u8, writer: impl TerminalWriter) -> Result<()> { + // Grab the singleton + let Some(reload_handle) = TERMINAL_LAYER_HANDLE.get() else { + return Err(Error::Custom(String::from("Tracing terminal layer reload handle is not set"))) + }; + + // Generate the terminal layer again using provided writer + let terminal_layer = FmtLayer::new() + .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE) + .event_format(EventFormatter::new(true, verbosity_level != 0)) + .fmt_fields(format::debug_fn(terminal_field_formatter)) + .with_writer(writer) + .boxed(); + + // Reload the layer in the handler + if let Err(e) = reload_handle.reload(terminal_layer) { + return Err(Error::Custom(format!("Unable to reload terminal layer: {e}"))) + } + Ok(()) +} + /// Helper for setting up logging for bins. pub fn setup_logging(verbosity_level: u8, log_file: Option) -> Result<()> { let terminal_field_format = format::debug_fn(terminal_field_formatter); @@ -442,7 +502,14 @@ pub fn setup_logging(verbosity_level: u8, log_file: Option) -> Resu .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE) .event_format(EventFormatter::new(true, verbosity_level != 0)) .fmt_fields(terminal_field_format) - .with_writer(std::io::stdout); + .with_writer(std::io::stdout) + .boxed(); + let (terminal_layer, reload_handle) = ReloadLayer::new(terminal_layer); + if TERMINAL_LAYER_HANDLE.set(reload_handle).is_err() { + return Err(Error::Custom(String::from( + "Could not set tracing terminal layer reload handle", + ))) + } let mut target_filter = TargetFilter::default().with_verbosity(verbosity_level); if let Ok(log_targets) = env::var("LOG_TARGETS") {