diff --git a/gui/Cargo.lock b/gui/Cargo.lock index b08bde9de..2e050e4d1 100644 --- a/gui/Cargo.lock +++ b/gui/Cargo.lock @@ -2458,6 +2458,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 7831f6333..4ddaba437 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -15,6 +15,7 @@ path = "src/main.rs" [dependencies] async-hwi = "0.0.13" +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 99441b9a6..a8c18dac2 100644 --- a/gui/src/loader.rs +++ b/gui/src/loader.rs @@ -6,7 +6,8 @@ use std::sync::Arc; use std::time::Duration; use iced::{Alignment, Command, Length, Subscription}; -use tracing::{debug, info, warn}; +use tracing::{debug, info, instrument::WithSubscriber, warn}; +use tracing_subscriber::{filter, prelude::*, Layer}; use liana::{ config::{Config, ConfigError}, @@ -31,6 +32,7 @@ use crate::{ internal_bitcoind_debug_log_path, stop_bitcoind, Bitcoind, StartInternalBitcoindError, }, daemon::{client, embedded::EmbeddedDaemon, model::*, Daemon, DaemonError}, + logger::LogStream, }; const SYNCING_PROGRESS_1: &str = "Bitcoin Core is synchronising the blockchain. A full synchronisation typically take a few days, and is resource intensive. Once the initial synchronisation is done, the next ones will be much faster."; @@ -46,13 +48,20 @@ pub struct Loader { pub daemon_started: bool, pub internal_bitcoind: Option, pub waiting_daemon_bitcoind: bool, + /// Sender and receiver for Liana log stream. + pub log_channel: ( + crossbeam_channel::Sender, + crossbeam_channel::Receiver, + ), step: Step, } pub enum Step { Connecting, - StartingDaemon, + StartingDaemon { + liana_logs: String, + }, Syncing { daemon: Arc, progress: f64, @@ -79,7 +88,7 @@ pub enum Message { ), Started(Result<(Arc, Option), Error>), Loaded(Result, Error>), - BitcoindLog(Option), + LogMessage(Option), Failure(DaemonError), None, } @@ -96,6 +105,7 @@ impl Loader { .clone() .unwrap_or_else(|| socket_path(&datadir_path, network)); let network = network; + let (sender, receiver) = crossbeam_channel::unbounded::(); ( Loader { network, @@ -105,6 +115,7 @@ impl Loader { daemon_started: false, internal_bitcoind, waiting_daemon_bitcoind: false, + log_channel: (sender, receiver), }, Command::perform(connect(path), Message::Loaded), ) @@ -131,16 +142,24 @@ 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; + let streamer_log = LogStream { + sender: self.log_channel.0.clone(), + } + .with_filter(filter::LevelFilter::INFO); + let streamer_layer = tracing_subscriber::registry().with(streamer_log); return Command::perform( start_bitcoind_and_daemon( daemon_config_path, self.datadir_path.clone(), self.gui_config.start_internal_bitcoind && self.internal_bitcoind.is_none(), - ), + ) + .with_subscriber(streamer_layer), Message::Started, ); } else { @@ -156,7 +175,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; } @@ -268,7 +291,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() @@ -283,7 +306,15 @@ impl Loader { } pub fn subscription(&self) -> Subscription { - if self.internal_bitcoind.is_some() { + if let Step::StartingDaemon { .. } = self.step { + iced::subscription::unfold(1, self.log_channel.1.clone(), move |recv| async { + let log_msg = match recv.recv() { + Ok(msg) => msg, + Err(e) => e.to_string(), + }; + (Message::LogMessage(Some(log_msg)), recv) + }) + } else if self.internal_bitcoind.is_some() { let log_path = internal_bitcoind_debug_log_path(&self.datadir_path, self.network); iced::subscription::unfold(0, log_path, move |log_path| async move { // Reduce the io load. @@ -329,7 +360,7 @@ impl Loader { }) .last(); match last_update_tip { - Some(Ok(line)) => (Message::BitcoindLog(Some(line)), log_path), + Some(Ok(line)) => (Message::LogMessage(Some(line)), log_path), res => { if let Some(Err(e)) = res { log::error!("Reading bitcoind log file: {}", e); @@ -392,12 +423,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..56f98d0f0 100644 --- a/gui/src/logger.rs +++ b/gui/src/logger.rs @@ -1,10 +1,11 @@ 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, }; @@ -118,3 +119,34 @@ impl Logger { Ok(()) } } + +/// Used as a layer to send log messages to a channel. +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, + }; + event.record(&mut visitor); + } +} + +/// Used to record log messages by sending them to the channel. +struct LogStreamVisitor<'a> { + sender: &'a crossbeam_channel::Sender, +} + +impl<'a> tracing::field::Visit for LogStreamVisitor<'a> { + 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); + } + } +}