Explorar el Código

util/logger: allow bins to set alternative terminal logger writers

skoupidi hace 8 meses
padre
commit
dd575f261f
Se han modificado 4 ficheros con 106 adiciones y 21 borrados
  1. 1 0
      bin/drk/src/cli_util.rs
  2. 12 14
      bin/drk/src/interactive.rs
  3. 22 3
      bin/drk/src/main.rs
  4. 71 4
      src/util/logger.rs

+ 1 - 0
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 <https://www.gnu.org/licenses/>.
  */
+
 use std::{
     io::{stdin, Cursor, Read},
     str::FromStr,

+ 12 - 14
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<Vec<String>>,
+    shell_receiver: &Receiver<Vec<String>>,
+    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(),
     );

+ 22 - 3
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(())
         }

+ 71 - 4
src/util/logger.rs

@@ -16,23 +16,31 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-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<S: Subscriber> Layer<S> for TargetFilter {
     }
 }
 
+/// Global singleton holding the tracing terminal layer handle.
+static TERMINAL_LAYER_HANDLE: OnceLock<Handle<Box<dyn Layer<Registry> + 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<Vec<String>>,
+}
+
+impl Write for ChannelWriter {
+    fn write(&mut self, buf: &[u8]) -> IOResult<usize> {
+        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<W: for<'writer> 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<NonBlocking>) -> 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<NonBlocking>) -> 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") {