diff --git a/gui/Cargo.lock b/gui/Cargo.lock index edbd18950..9b583c50e 100644 --- a/gui/Cargo.lock +++ b/gui/Cargo.lock @@ -2441,6 +2441,7 @@ dependencies = [ "base64 0.13.1", "bitcoin_hashes 0.12.0", "chrono", + "crossbeam-channel", "dirs 3.0.2", "flate2", "hex", diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 89b2c4fb2..3920c8abb 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -15,6 +15,7 @@ path = "src/main.rs" [dependencies] async-hwi = "0.0.12" +crossbeam-channel = "0.5" liana = { git = "https://github.com/wizardsardine/liana", branch = "master", default-features = false, features = ["nonblocking_shutdown"] } liana_ui = { path = "ui" } backtrace = "0.3" diff --git a/gui/src/loader.rs b/gui/src/loader.rs index b8825088d..440a71834 100644 --- a/gui/src/loader.rs +++ b/gui/src/loader.rs @@ -44,13 +44,16 @@ pub struct Loader { pub daemon_started: bool, pub internal_bitcoind: Option, pub waiting_daemon_bitcoind: bool, + pub receiver: crossbeam_channel::Receiver, step: Step, } pub enum Step { Connecting, - StartingDaemon, + StartingDaemon { + liana_logs: String, + }, Syncing { daemon: Arc, progress: f64, @@ -77,7 +80,7 @@ pub enum Message { ), Started(Result<(Arc, Option), Error>), Loaded(Result, Error>), - BitcoindLog(Option), + LogMessage(Option), Failure(DaemonError), } @@ -87,6 +90,7 @@ impl Loader { gui_config: GUIConfig, network: bitcoin::Network, internal_bitcoind: Option, + receiver: crossbeam_channel::Receiver, ) -> (Self, Command) { let path = gui_config .daemon_rpc_path @@ -102,6 +106,7 @@ impl Loader { daemon_started: false, internal_bitcoind, waiting_daemon_bitcoind: false, + receiver, }, Command::perform(connect(path), Message::Loaded), ) @@ -128,7 +133,9 @@ impl Loader { | Error::Daemon(DaemonError::Transport(Some(ErrorKind::ConnectionRefused), _)) | Error::Daemon(DaemonError::Transport(Some(ErrorKind::NotFound), _)) => { if let Some(daemon_config_path) = self.gui_config.daemon_config_path.clone() { - self.step = Step::StartingDaemon; + self.step = Step::StartingDaemon { + liana_logs: String::new(), + }; self.daemon_started = true; self.waiting_daemon_bitcoind = true; return Command::perform( @@ -153,7 +160,11 @@ impl Loader { } fn on_log(&mut self, log: Option) -> Command { - if let Step::Syncing { bitcoind_logs, .. } = &mut self.step { + if let Step::StartingDaemon { liana_logs } = &mut self.step { + if let Some(l) = log { + *liana_logs = l; + } + } else if let Step::Syncing { bitcoind_logs, .. } = &mut self.step { if let Some(l) = log { *bitcoind_logs = l; } @@ -254,6 +265,7 @@ impl Loader { self.gui_config.clone(), self.network, self.internal_bitcoind.clone(), + self.receiver.clone(), ); *self = loader; cmd @@ -261,7 +273,7 @@ impl Loader { Message::Started(res) => self.on_start(res), Message::Loaded(res) => self.on_load(res), Message::Syncing(res) => self.on_sync(res), - Message::BitcoindLog(log) => self.on_log(log), + Message::LogMessage(log) => self.on_log(log), Message::Synced(Err(e)) => { self.step = Step::Error(Box::new(e)); Command::none() @@ -275,6 +287,16 @@ impl Loader { } pub fn subscription(&self) -> Subscription { + if let Step::StartingDaemon { .. } = self.step { + return iced::subscription::unfold(1, self.receiver.clone(), move |recv| async { + let log_msg = match recv.recv() { + Ok(msg) => msg, + Err(e) => e.to_string(), + }; + + (Message::LogMessage(Some(log_msg)), recv) + }); + } if let Some(Some(bitcoind_stdout)) = self.internal_bitcoind.as_ref().map(|b| b.stdout.clone()) { @@ -284,8 +306,8 @@ impl Loader { let mut s = std::io::BufReader::new(s.deref_mut()); let mut buffer = String::new(); match s.read_line(&mut buffer) { - Err(e) => Message::BitcoindLog(Some(e.to_string())), - Ok(_) => Message::BitcoindLog(Some(buffer)), + Err(e) => Message::LogMessage(Some(e.to_string())), + Ok(_) => Message::LogMessage(Some(buffer)), } }; (msg, stdout) @@ -342,12 +364,13 @@ pub enum ViewMessage { pub fn view(step: &Step) -> Element { match &step { - Step::StartingDaemon => cover( + Step::StartingDaemon { liana_logs } => cover( None, Column::new() .width(Length::Fill) .push(ProgressBar::new(0.0..=1.0, 0.0).width(Length::Fill)) - .push(text("Starting daemon...")), + .push(text("Starting daemon...")) + .push(p2_regular(liana_logs).style(color::GREY_3)), ), Step::Connecting => cover( None, diff --git a/gui/src/logger.rs b/gui/src/logger.rs index 66ef12ca5..fc8aaa2e4 100644 --- a/gui/src/logger.rs +++ b/gui/src/logger.rs @@ -1,14 +1,17 @@ use liana::miniscript::bitcoin::Network; use std::path::PathBuf; use std::{fs::File, sync::Arc}; -use tracing::error; +use tracing::{error, Event, Subscriber}; use tracing_subscriber::{ filter, fmt::{format, writer::BoxMakeWriter, Layer}, + layer, prelude::*, reload, Registry, }; +use crossbeam_channel::unbounded; + const INSTALLER_LOG_FILE_NAME: &str = "installer.log"; const GUI_LOG_FILE_NAME: &str = "liana-gui.log"; @@ -36,6 +39,7 @@ pub struct Logger { Registry, >, level_handle: reload::Handle, + receiver: crossbeam_channel::Receiver, } impl Logger { @@ -47,6 +51,8 @@ impl Logger { .with_file(false); let (file_log, file_handle) = reload::Layer::new(file_log); let stdout_log = tracing_subscriber::fmt::layer().pretty().with_file(false); + let channel = unbounded::(); + let streamer_log = LogStream { sender: channel.0 }.with_filter(filter::LevelFilter::INFO); tracing_subscriber::registry() .with( stdout_log @@ -68,10 +74,12 @@ impl Logger { && !metadata.target().starts_with("ledger_transport_hid") })), ) + .with(streamer_log) .init(); Self { file_handle, level_handle, + receiver: channel.1, } } @@ -117,4 +125,37 @@ impl Logger { self.level_handle.modify(|filter| *filter = log_level)?; Ok(()) } + + pub fn receiver(&self) -> crossbeam_channel::Receiver { + self.receiver.clone() + } +} + +pub struct LogStream { + pub sender: crossbeam_channel::Sender, +} + +impl layer::Layer for LogStream +where + S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>, +{ + fn on_event(&self, event: &Event<'_>, _ctx: layer::Context<'_, S>) { + let mut visitor = LogStreamVisitor { + sender: self.sender.clone(), + }; + event.record(&mut visitor); + } +} + +struct LogStreamVisitor { + sender: crossbeam_channel::Sender, +} + +impl tracing::field::Visit for LogStreamVisitor { + fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { + if field.name() == "message" { + let msg = format!("{:?}", value); + let _ = self.sender.send(msg); + } + } } diff --git a/gui/src/main.rs b/gui/src/main.rs index efde6ab61..9da8308bc 100644 --- a/gui/src/main.rs +++ b/gui/src/main.rs @@ -142,7 +142,8 @@ impl Application for GUI { network, cfg.log_level().unwrap_or(LevelFilter::INFO), ); - let (loader, command) = Loader::new(datadir_path, cfg, network, None); + let (loader, command) = + Loader::new(datadir_path, cfg, network, None, logger.receiver()); ( Self { state: State::Loader(Box::new(loader)), @@ -189,7 +190,8 @@ impl Application for GUI { network, cfg.log_level().unwrap_or(LevelFilter::INFO), ); - let (loader, command) = Loader::new(datadir_path, cfg, network, None); + let (loader, command) = + Loader::new(datadir_path, cfg, network, None, self.logger.receiver()); self.state = State::Loader(Box::new(loader)); command.map(|msg| Message::Load(Box::new(msg))) } @@ -217,6 +219,7 @@ impl Application for GUI { cfg, daemon_cfg.bitcoin_config.network, internal_bitcoind, + self.logger.receiver(), ); self.state = State::Loader(Box::new(loader)); command.map(|msg| Message::Load(Box::new(msg)))