gui: add logger module and handle log file destinations
Installer has its own log file installer.log that is removed after successful install. When changing network, the destination log file change to <datadir>/<network>/liana-gui.log
This commit is contained in:
parent
9ccb22b2ac
commit
a13bb0272b
102
gui/Cargo.lock
generated
102
gui/Cargo.lock
generated
@ -1686,7 +1686,6 @@ dependencies = [
|
||||
"base64",
|
||||
"chrono",
|
||||
"dirs",
|
||||
"fern",
|
||||
"iced",
|
||||
"iced_lazy",
|
||||
"iced_native",
|
||||
@ -1696,6 +1695,8 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2107,6 +2108,16 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
@ -2254,6 +2265,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "owned_ttf_parser"
|
||||
version = "0.15.0"
|
||||
@ -2849,6 +2866,15 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_library"
|
||||
version = "0.1.9"
|
||||
@ -3076,6 +3102,16 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.44"
|
||||
@ -3178,6 +3214,64 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"sharded-slab",
|
||||
"smallvec 1.9.0",
|
||||
"thread_local",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.12.3"
|
||||
@ -3297,6 +3391,12 @@ dependencies = [
|
||||
"xmlwriter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
|
||||
@ -28,8 +28,9 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
# Logging stuff
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = "0.3.16"
|
||||
log = "0.4"
|
||||
fern = "0.6"
|
||||
|
||||
dirs = "3.0.1"
|
||||
toml = "0.5"
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use crate::hw::HardwareWalletConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing_subscriber::filter;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
@ -38,12 +39,35 @@ impl Config {
|
||||
ConfigError::ReadingFile(format!("Parsing configuration file: {}", e))
|
||||
})
|
||||
})?;
|
||||
|
||||
// check if log_level field is valid
|
||||
config.log_level()?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// TODO: Deserialize directly in the struct.
|
||||
pub fn log_level(&self) -> Result<filter::LevelFilter, ConfigError> {
|
||||
if let Some(level) = &self.log_level {
|
||||
match level.as_ref() {
|
||||
"info" => Ok(filter::LevelFilter::INFO),
|
||||
"debug" => Ok(filter::LevelFilter::DEBUG),
|
||||
"trace" => Ok(filter::LevelFilter::TRACE),
|
||||
_ => Err(ConfigError::InvalidField(
|
||||
"log_level",
|
||||
format!("Unknown value '{}'", level),
|
||||
)),
|
||||
}
|
||||
} else if let Some(true) = self.debug {
|
||||
Ok(filter::LevelFilter::DEBUG)
|
||||
} else {
|
||||
Ok(filter::LevelFilter::INFO)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum ConfigError {
|
||||
InvalidField(&'static str, String),
|
||||
NotFound,
|
||||
ReadingFile(String),
|
||||
Unexpected(String),
|
||||
@ -53,6 +77,9 @@ impl std::fmt::Display for ConfigError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::NotFound => write!(f, "Config file not found"),
|
||||
Self::InvalidField(field, message) => {
|
||||
write!(f, "Config field {} is invalid: {}", field, message)
|
||||
}
|
||||
Self::ReadingFile(e) => write!(f, "Error while reading file: {}", e),
|
||||
Self::Unexpected(e) => write!(f, "Unexpected error: {}", e),
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use iced::{clipboard, time, Command, Element, Subscription};
|
||||
use tracing::{info, warn};
|
||||
|
||||
pub use liana::config::Config as DaemonConfig;
|
||||
|
||||
@ -98,12 +99,12 @@ impl App {
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
log::info!("Close requested");
|
||||
info!("Close requested");
|
||||
if !self.daemon.is_external() {
|
||||
log::info!("Stopping internal daemon...");
|
||||
info!("Stopping internal daemon...");
|
||||
if let Some(d) = Arc::get_mut(&mut self.daemon) {
|
||||
d.stop().expect("Daemon is internal");
|
||||
log::info!("Internal daemon stopped");
|
||||
info!("Internal daemon stopped");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -165,7 +166,7 @@ impl App {
|
||||
daemon_config_file
|
||||
.write_all(content.as_bytes())
|
||||
.map_err(|e| {
|
||||
log::warn!("failed to write to file: {:?}", e);
|
||||
warn!("failed to write to file: {:?}", e);
|
||||
Error::Config(e.to_string())
|
||||
})?;
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ use std::sync::Arc;
|
||||
|
||||
use chrono::prelude::*;
|
||||
use iced::{Command, Element};
|
||||
use tracing::info;
|
||||
|
||||
use liana::config::{BitcoinConfig, BitcoindConfig, Config};
|
||||
|
||||
@ -338,7 +339,7 @@ impl Setting for RescanSetting {
|
||||
.and_hms(0, 0, 0);
|
||||
let t = date_time.timestamp() as u32;
|
||||
self.processing = true;
|
||||
log::info!("Asking deamon to rescan with timestamp: {}", t);
|
||||
info!("Asking deamon to rescan with timestamp: {}", t);
|
||||
return Command::perform(
|
||||
async move { daemon.start_rescan(t).map_err(|e| e.into()) },
|
||||
Message::StartRescan,
|
||||
|
||||
@ -31,7 +31,7 @@ use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Deserializer;
|
||||
|
||||
use log::debug;
|
||||
use tracing::debug;
|
||||
|
||||
/// A handle to a remote JSONRPC server
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use log::{error, info};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use tracing::{error, info};
|
||||
|
||||
pub mod error;
|
||||
pub mod jsonrpc;
|
||||
|
||||
@ -5,8 +5,8 @@ use liana::miniscript::bitcoin::{
|
||||
hashes::hex::{FromHex, ToHex},
|
||||
util::bip32::Fingerprint,
|
||||
};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::debug;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HardwareWallet {
|
||||
|
||||
@ -6,6 +6,7 @@ mod view;
|
||||
|
||||
use iced::{clipboard, Command, Element, Subscription};
|
||||
use liana::miniscript::bitcoin;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use context::Context;
|
||||
use std::io::Write;
|
||||
@ -141,15 +142,15 @@ impl Installer {
|
||||
data_dir.push(self.context.bitcoin_config.network.to_string());
|
||||
// In case of failure during install, block the thread to
|
||||
// deleted the data_dir/network directory in order to start clean again.
|
||||
log::warn!("Installation failed. Cleaning up the leftover data directory.");
|
||||
warn!("Installation failed. Cleaning up the leftover data directory.");
|
||||
if let Err(e) = std::fs::remove_dir_all(&data_dir) {
|
||||
log::error!(
|
||||
error!(
|
||||
"Failed to completely delete the data directory (path: '{}'): {}",
|
||||
data_dir.to_string_lossy(),
|
||||
e
|
||||
);
|
||||
} else {
|
||||
log::warn!(
|
||||
warn!(
|
||||
"Successfully deleted data directory at '{}'.",
|
||||
data_dir.to_string_lossy()
|
||||
);
|
||||
@ -208,18 +209,19 @@ pub fn daemon_check(cfg: liana::config::Config) -> Result<(), Error> {
|
||||
}
|
||||
|
||||
pub async fn install(ctx: Context) -> Result<PathBuf, Error> {
|
||||
log::info!("installing");
|
||||
let mut cfg: liana::config::Config = ctx.extract_daemon_config();
|
||||
daemon_check(cfg.clone())?;
|
||||
log::info!("daemon checked");
|
||||
|
||||
cfg.data_dir =
|
||||
Some(cfg.data_dir.unwrap().canonicalize().map_err(|e| {
|
||||
let data_dir =
|
||||
cfg.data_dir.unwrap().canonicalize().map_err(|e| {
|
||||
Error::Unexpected(format!("Failed to canonicalize datadir path: {}", e))
|
||||
})?);
|
||||
})?;
|
||||
cfg.data_dir = Some(data_dir.clone());
|
||||
|
||||
let mut datadir_path = cfg.data_dir.clone().unwrap();
|
||||
datadir_path.push(cfg.bitcoin_config.network.to_string());
|
||||
daemon_check(cfg.clone())?;
|
||||
|
||||
info!("daemon checked");
|
||||
|
||||
let mut network_datadir_path = data_dir;
|
||||
network_datadir_path.push(cfg.bitcoin_config.network.to_string());
|
||||
|
||||
// Step needed because of ValueAfterTable error in the toml serialize implementation.
|
||||
let daemon_config = toml::Value::try_from(&cfg)
|
||||
@ -227,12 +229,12 @@ pub async fn install(ctx: Context) -> Result<PathBuf, Error> {
|
||||
|
||||
// create lianad configuration file
|
||||
let daemon_config_path = create_and_write_file(
|
||||
datadir_path.clone(),
|
||||
network_datadir_path.clone(),
|
||||
"daemon.toml",
|
||||
daemon_config.to_string().as_bytes(),
|
||||
)?;
|
||||
|
||||
log::info!("Daemon configuration file created");
|
||||
info!("Daemon configuration file created");
|
||||
|
||||
if let Some(signer) = &ctx.signer {
|
||||
signer
|
||||
@ -242,12 +244,12 @@ pub async fn install(ctx: Context) -> Result<PathBuf, Error> {
|
||||
)
|
||||
.map_err(|e| Error::Unexpected(format!("Failed to store mnemonic: {}", e)))?;
|
||||
|
||||
log::info!("Hot signer mnemonic stored");
|
||||
info!("Hot signer mnemonic stored");
|
||||
}
|
||||
|
||||
// create liana GUI configuration file
|
||||
let gui_config_path = create_and_write_file(
|
||||
datadir_path.clone(),
|
||||
network_datadir_path.clone(),
|
||||
gui_config::DEFAULT_FILE_NAME,
|
||||
toml::to_string(&gui_config::Config::new(
|
||||
daemon_config_path.canonicalize().map_err(|e| {
|
||||
@ -258,19 +260,19 @@ pub async fn install(ctx: Context) -> Result<PathBuf, Error> {
|
||||
.as_bytes(),
|
||||
)?;
|
||||
|
||||
log::info!("Gui configuration file created");
|
||||
info!("Gui configuration file created");
|
||||
|
||||
// create liana GUI settings file
|
||||
let settings: gui_settings::Settings = ctx.extract_gui_settings();
|
||||
create_and_write_file(
|
||||
datadir_path,
|
||||
network_datadir_path,
|
||||
gui_settings::DEFAULT_FILE_NAME,
|
||||
serde_json::to_string_pretty(&settings)
|
||||
.map_err(|e| Error::Unexpected(format!("Failed to serialize settings: {}", e)))?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
|
||||
log::info!("Settings file created");
|
||||
info!("Settings file created");
|
||||
|
||||
Ok(gui_config_path)
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ pub mod hw;
|
||||
pub mod installer;
|
||||
pub mod launcher;
|
||||
pub mod loader;
|
||||
pub mod logger;
|
||||
pub mod signer;
|
||||
pub mod ui;
|
||||
pub mod utils;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use std::convert::From;
|
||||
use std::io::ErrorKind;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use iced::{
|
||||
@ -8,7 +8,7 @@ use iced::{
|
||||
Element,
|
||||
};
|
||||
use iced::{Alignment, Command, Length, Subscription};
|
||||
use log::{debug, info};
|
||||
use tracing::{debug, info};
|
||||
|
||||
use liana::{
|
||||
config::{Config, ConfigError},
|
||||
@ -20,7 +20,7 @@ use liana::{
|
||||
use crate::{
|
||||
app::{
|
||||
cache::Cache,
|
||||
config::{default_datadir, Config as GUIConfig},
|
||||
config::Config as GUIConfig,
|
||||
settings::{self, Settings},
|
||||
wallet::Wallet,
|
||||
},
|
||||
@ -36,7 +36,7 @@ use crate::{
|
||||
type Lianad = client::Lianad<client::jsonrpc::JsonRPCClient>;
|
||||
|
||||
pub struct Loader {
|
||||
pub datadir_path: Option<PathBuf>,
|
||||
pub datadir_path: PathBuf,
|
||||
pub network: bitcoin::Network,
|
||||
pub gui_config: GUIConfig,
|
||||
pub daemon_started: bool,
|
||||
@ -68,18 +68,15 @@ pub enum Message {
|
||||
|
||||
impl Loader {
|
||||
pub fn new(
|
||||
datadir_path: Option<PathBuf>,
|
||||
datadir_path: PathBuf,
|
||||
gui_config: GUIConfig,
|
||||
daemon_config: Config,
|
||||
) -> (Self, Command<Message>) {
|
||||
let path = socket_path(
|
||||
&daemon_config.data_dir,
|
||||
daemon_config.bitcoin_config.network,
|
||||
)
|
||||
.unwrap();
|
||||
let path = socket_path(&datadir_path, daemon_config.bitcoin_config.network);
|
||||
let network = daemon_config.bitcoin_config.network;
|
||||
(
|
||||
Loader {
|
||||
network: daemon_config.bitcoin_config.network,
|
||||
network,
|
||||
datadir_path,
|
||||
daemon_config: daemon_config.clone(),
|
||||
gui_config,
|
||||
@ -171,13 +168,13 @@ impl Loader {
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
log::info!("Close requested");
|
||||
info!("Close requested");
|
||||
if let Step::Syncing { daemon, .. } = &mut self.step {
|
||||
if !daemon.is_external() {
|
||||
log::info!("Stopping internal daemon...");
|
||||
info!("Stopping internal daemon...");
|
||||
if let Some(d) = Arc::get_mut(daemon) {
|
||||
d.stop().expect("Daemon is internal");
|
||||
log::info!("Internal daemon stopped");
|
||||
info!("Internal daemon stopped");
|
||||
} else {
|
||||
}
|
||||
}
|
||||
@ -215,7 +212,7 @@ impl Loader {
|
||||
}
|
||||
|
||||
pub fn view(&self) -> Element<Message> {
|
||||
view(self.datadir_path.as_ref(), &self.step).map(Message::View)
|
||||
view(&self.step).map(Message::View)
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,7 +220,7 @@ pub async fn load_application(
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
info: GetInfoResult,
|
||||
gui_config: GUIConfig,
|
||||
datadir_path: Option<PathBuf>,
|
||||
datadir_path: PathBuf,
|
||||
network: bitcoin::Network,
|
||||
) -> Result<(Arc<Wallet>, Cache, Arc<dyn Daemon + Sync + Send>), Error> {
|
||||
let coins = daemon.list_coins().map(|res| res.coins)?;
|
||||
@ -235,7 +232,7 @@ pub async fn load_application(
|
||||
spend_txs,
|
||||
..Default::default()
|
||||
};
|
||||
let settings_path = settings_path(&datadir_path, network).unwrap();
|
||||
let settings_path = settings_path(&datadir_path, network);
|
||||
let gui_config_hws = gui_config
|
||||
.hardware_wallets
|
||||
.as_ref()
|
||||
@ -258,7 +255,7 @@ pub async fn load_application(
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
let hot_signers = match HotSigner::from_datadir(&get_datadir_path(&datadir_path)?, network) {
|
||||
let hot_signers = match HotSigner::from_datadir(&datadir_path, network) {
|
||||
Ok(signers) => signers,
|
||||
Err(e) => match e {
|
||||
liana::signer::SignerError::MnemonicStorage(e) => {
|
||||
@ -290,7 +287,7 @@ pub enum ViewMessage {
|
||||
SwitchNetwork,
|
||||
}
|
||||
|
||||
pub fn view<'a>(datadir_path: Option<&'a PathBuf>, step: &'a Step) -> Element<'a, ViewMessage> {
|
||||
pub fn view(step: &Step) -> Element<ViewMessage> {
|
||||
match &step {
|
||||
Step::StartingDaemon => cover(
|
||||
None,
|
||||
@ -333,10 +330,10 @@ pub fn view<'a>(datadir_path: Option<&'a PathBuf>, step: &'a Step) -> Element<'a
|
||||
.push(
|
||||
Row::new()
|
||||
.spacing(10)
|
||||
.push_maybe(datadir_path.map(|_| {
|
||||
.push(
|
||||
button::border(None, "Use another Bitcoin network")
|
||||
.on_press(ViewMessage::SwitchNetwork)
|
||||
}))
|
||||
.on_press(ViewMessage::SwitchNetwork),
|
||||
)
|
||||
.push(
|
||||
button::primary(None, "Retry")
|
||||
.width(Length::Units(200))
|
||||
@ -438,32 +435,18 @@ impl From<DaemonError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_datadir_path(datadir_path: &Option<PathBuf>) -> Result<PathBuf, ConfigError> {
|
||||
if let Some(ref datadir) = datadir_path {
|
||||
Ok(datadir.clone())
|
||||
} else {
|
||||
default_datadir().map_err(|_| ConfigError::DatadirNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
/// default lianad socket path is .liana/bitcoin/lianad_rpc
|
||||
fn socket_path(
|
||||
datadir: &Option<PathBuf>,
|
||||
network: bitcoin::Network,
|
||||
) -> Result<PathBuf, ConfigError> {
|
||||
let mut path = get_datadir_path(datadir)?;
|
||||
fn socket_path(datadir: &Path, network: bitcoin::Network) -> PathBuf {
|
||||
let mut path = datadir.to_path_buf();
|
||||
path.push(network.to_string());
|
||||
path.push("lianad_rpc");
|
||||
Ok(path)
|
||||
path
|
||||
}
|
||||
|
||||
/// default liana settings path is .liana/bitcoin/settings.json
|
||||
fn settings_path(
|
||||
datadir: &Option<PathBuf>,
|
||||
network: bitcoin::Network,
|
||||
) -> Result<PathBuf, ConfigError> {
|
||||
let mut path = get_datadir_path(datadir)?;
|
||||
fn settings_path(datadir: &Path, network: bitcoin::Network) -> PathBuf {
|
||||
let mut path = datadir.to_path_buf();
|
||||
path.push(network.to_string());
|
||||
path.push(settings::DEFAULT_FILE_NAME);
|
||||
Ok(path)
|
||||
path
|
||||
}
|
||||
|
||||
115
gui/src/logger.rs
Normal file
115
gui/src/logger.rs
Normal file
@ -0,0 +1,115 @@
|
||||
use liana::miniscript::bitcoin::Network;
|
||||
use std::path::PathBuf;
|
||||
use std::{fs::File, sync::Arc};
|
||||
use tracing::error;
|
||||
use tracing_subscriber::{
|
||||
filter,
|
||||
fmt::{format, writer::BoxMakeWriter, Layer},
|
||||
prelude::*,
|
||||
reload, Registry,
|
||||
};
|
||||
|
||||
const INSTALLER_LOG_FILE_NAME: &str = "installer.log";
|
||||
const GUI_LOG_FILE_NAME: &str = "liana-gui.log";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LoggerError {
|
||||
Io(std::io::Error),
|
||||
Reload(reload::Error),
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for LoggerError {
|
||||
fn from(e: std::io::Error) -> LoggerError {
|
||||
LoggerError::Io(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reload::Error> for LoggerError {
|
||||
fn from(e: reload::Error) -> LoggerError {
|
||||
LoggerError::Reload(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Logger {
|
||||
file_handle: reload::Handle<
|
||||
Layer<Registry, format::DefaultFields, format::Format, BoxMakeWriter>,
|
||||
Registry,
|
||||
>,
|
||||
level_handle: reload::Handle<filter::LevelFilter, Registry>,
|
||||
}
|
||||
|
||||
impl Logger {
|
||||
pub fn setup(log_level: filter::LevelFilter) -> Logger {
|
||||
let (log_level, level_handle) = reload::Layer::new(log_level);
|
||||
let writer = BoxMakeWriter::new(std::io::stderr);
|
||||
let file_log = tracing_subscriber::fmt::layer()
|
||||
.with_writer(writer)
|
||||
.with_file(false);
|
||||
let (file_log, file_handle) = reload::Layer::new(file_log);
|
||||
let stdout_log = tracing_subscriber::fmt::layer().pretty().with_file(false);
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
stdout_log
|
||||
.and_then(file_log)
|
||||
.with_filter(log_level)
|
||||
// Add a filter to *both* layers that rejects spans and
|
||||
// events whose targets start with `<prefix>`.
|
||||
.with_filter(filter::filter_fn(|metadata| {
|
||||
!metadata.target().starts_with("iced_wgpu")
|
||||
&& !metadata.target().starts_with("iced_winit")
|
||||
&& !metadata.target().starts_with("wgpu_core")
|
||||
&& !metadata.target().starts_with("wgpu_hal")
|
||||
&& !metadata.target().starts_with("gfx_backend_vulkan")
|
||||
&& !metadata.target().starts_with("iced_glutin")
|
||||
&& !metadata.target().starts_with("iced_glow")
|
||||
&& !metadata.target().starts_with("glow_glyph")
|
||||
&& !metadata.target().starts_with("naga")
|
||||
&& !metadata.target().starts_with("mio")
|
||||
})),
|
||||
)
|
||||
.init();
|
||||
Self {
|
||||
file_handle,
|
||||
level_handle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_installer_mode(&self, mut datadir: PathBuf, log_level: filter::LevelFilter) {
|
||||
datadir.push(INSTALLER_LOG_FILE_NAME);
|
||||
if let Err(e) = self.set_layer(datadir, log_level) {
|
||||
error!("Failed to change logger settings: {:#?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_running_mode(
|
||||
&self,
|
||||
mut datadir: PathBuf,
|
||||
network: Network,
|
||||
log_level: filter::LevelFilter,
|
||||
) {
|
||||
datadir.push(network.to_string());
|
||||
datadir.push(GUI_LOG_FILE_NAME);
|
||||
if let Err(e) = self.set_layer(datadir, log_level) {
|
||||
error!("Failed to change logger settings: {:#?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_install_log_file(&self, mut datadir: PathBuf) {
|
||||
datadir.push(INSTALLER_LOG_FILE_NAME);
|
||||
if let Err(e) = std::fs::remove_file(datadir) {
|
||||
error!("Failed to remove installer log file: {:#?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_layer(
|
||||
&self,
|
||||
destination_path: PathBuf,
|
||||
log_level: filter::LevelFilter,
|
||||
) -> Result<(), LoggerError> {
|
||||
let file = File::create(destination_path)?;
|
||||
self.file_handle
|
||||
.modify(|layer| *layer.writer_mut() = BoxMakeWriter::new(Arc::new(file)))?;
|
||||
self.level_handle.modify(|filter| *filter = log_level)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
135
gui/src/main.rs
135
gui/src/main.rs
@ -1,8 +1,10 @@
|
||||
#![windows_subsystem = "windows"]
|
||||
|
||||
use std::{error::Error, path::PathBuf, str::FromStr};
|
||||
use std::{error::Error, io::Write, path::PathBuf, str::FromStr};
|
||||
|
||||
use iced::{executor, Application, Command, Element, Settings, Subscription};
|
||||
use tracing::{error, info};
|
||||
use tracing_subscriber::filter::LevelFilter;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
|
||||
@ -17,6 +19,7 @@ use liana_gui::{
|
||||
installer::{self, Installer},
|
||||
launcher::{self, Launcher},
|
||||
loader::{self, Loader},
|
||||
logger::Logger,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@ -50,23 +53,9 @@ fn parse_args(args: Vec<String>) -> Result<Vec<Arg>, Box<dyn Error>> {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn log_level_from_config(config: &app::Config) -> Result<log::LevelFilter, Box<dyn Error>> {
|
||||
if let Some(level) = &config.log_level {
|
||||
match level.as_ref() {
|
||||
"info" => Ok(log::LevelFilter::Info),
|
||||
"debug" => Ok(log::LevelFilter::Debug),
|
||||
"trace" => Ok(log::LevelFilter::Trace),
|
||||
_ => Err(format!("Unknown loglevel '{:?}'.", level).into()),
|
||||
}
|
||||
} else if let Some(true) = config.debug {
|
||||
Ok(log::LevelFilter::Debug)
|
||||
} else {
|
||||
Ok(log::LevelFilter::Info)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GUI {
|
||||
state: State,
|
||||
logger: Logger,
|
||||
}
|
||||
|
||||
enum State {
|
||||
@ -88,9 +77,9 @@ pub enum Message {
|
||||
|
||||
async fn ctrl_c() -> Result<(), ()> {
|
||||
if let Err(e) = tokio::signal::ctrl_c().await {
|
||||
log::error!("{}", e);
|
||||
error!("{}", e);
|
||||
};
|
||||
log::info!("Signal received, exiting");
|
||||
info!("Signal received, exiting");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -108,21 +97,25 @@ impl Application for GUI {
|
||||
}
|
||||
|
||||
fn new(config: Config) -> (GUI, Command<Self::Message>) {
|
||||
let logger = Logger::setup(LevelFilter::INFO);
|
||||
match config {
|
||||
Config::Launcher(datadir_path) => {
|
||||
let launcher = Launcher::new(datadir_path);
|
||||
(
|
||||
Self {
|
||||
state: State::Launcher(Box::new(launcher)),
|
||||
logger,
|
||||
},
|
||||
Command::perform(ctrl_c(), |_| Message::CtrlC),
|
||||
)
|
||||
}
|
||||
Config::Install(datadir_path, network) => {
|
||||
logger.set_installer_mode(datadir_path.clone(), LevelFilter::INFO);
|
||||
let (install, command) = Installer::new(datadir_path, network);
|
||||
(
|
||||
Self {
|
||||
state: State::Installer(Box::new(install)),
|
||||
logger,
|
||||
},
|
||||
Command::batch(vec![
|
||||
command.map(|msg| Message::Install(Box::new(msg))),
|
||||
@ -133,10 +126,16 @@ impl Application for GUI {
|
||||
Config::Run(datadir_path, cfg) => {
|
||||
let daemon_cfg =
|
||||
DaemonConfig::from_file(Some(cfg.daemon_config_path.clone())).unwrap();
|
||||
logger.set_running_mode(
|
||||
datadir_path.clone(),
|
||||
daemon_cfg.bitcoin_config.network,
|
||||
cfg.log_level().unwrap_or(LevelFilter::INFO),
|
||||
);
|
||||
let (loader, command) = Loader::new(datadir_path, cfg, daemon_cfg);
|
||||
(
|
||||
Self {
|
||||
state: State::Loader(Box::new(loader)),
|
||||
logger,
|
||||
},
|
||||
Command::batch(vec![
|
||||
command.map(|msg| Message::Load(Box::new(msg))),
|
||||
@ -166,13 +165,20 @@ impl Application for GUI {
|
||||
}
|
||||
(State::Launcher(l), Message::Launch(msg)) => match *msg {
|
||||
launcher::Message::Install(datadir_path) => {
|
||||
self.logger
|
||||
.set_installer_mode(datadir_path.clone(), LevelFilter::INFO);
|
||||
let (install, command) =
|
||||
Installer::new(datadir_path, bitcoin::Network::Bitcoin);
|
||||
self.state = State::Installer(Box::new(install));
|
||||
command.map(|msg| Message::Install(Box::new(msg)))
|
||||
}
|
||||
launcher::Message::Run(datadir_path, cfg, daemon_cfg) => {
|
||||
let (loader, command) = Loader::new(Some(datadir_path), cfg, daemon_cfg);
|
||||
self.logger.set_running_mode(
|
||||
datadir_path.clone(),
|
||||
daemon_cfg.bitcoin_config.network,
|
||||
cfg.log_level().unwrap_or(LevelFilter::INFO),
|
||||
);
|
||||
let (loader, command) = Loader::new(datadir_path, cfg, daemon_cfg);
|
||||
self.state = State::Loader(Box::new(loader));
|
||||
command.map(|msg| Message::Load(Box::new(msg)))
|
||||
}
|
||||
@ -183,7 +189,19 @@ impl Application for GUI {
|
||||
let cfg = app::Config::from_file(&path).unwrap();
|
||||
let daemon_cfg =
|
||||
DaemonConfig::from_file(Some(cfg.daemon_config_path.clone())).unwrap();
|
||||
let (loader, command) = Loader::new(None, cfg, daemon_cfg);
|
||||
let datadir_path = daemon_cfg
|
||||
.data_dir
|
||||
.as_ref()
|
||||
.expect("Installer must have set it")
|
||||
.clone();
|
||||
|
||||
self.logger.set_running_mode(
|
||||
datadir_path.clone(),
|
||||
daemon_cfg.bitcoin_config.network,
|
||||
cfg.log_level().unwrap_or(LevelFilter::INFO),
|
||||
);
|
||||
self.logger.remove_install_log_file(datadir_path.clone());
|
||||
let (loader, command) = Loader::new(datadir_path, cfg, daemon_cfg);
|
||||
self.state = State::Loader(Box::new(loader));
|
||||
command.map(|msg| Message::Load(Box::new(msg)))
|
||||
} else {
|
||||
@ -192,9 +210,8 @@ impl Application for GUI {
|
||||
}
|
||||
(State::Loader(loader), Message::Load(msg)) => match *msg {
|
||||
loader::Message::View(loader::ViewMessage::SwitchNetwork) => {
|
||||
self.state = State::Launcher(Box::new(Launcher::new(
|
||||
loader.datadir_path.clone().unwrap(),
|
||||
)));
|
||||
self.state =
|
||||
State::Launcher(Box::new(Launcher::new(loader.datadir_path.clone())));
|
||||
Command::none()
|
||||
}
|
||||
loader::Message::Synced(Ok((wallet, cache, daemon))) => {
|
||||
@ -238,8 +255,7 @@ impl Application for GUI {
|
||||
}
|
||||
|
||||
pub enum Config {
|
||||
/// Datadir is optional because app can run with the config path only.
|
||||
Run(Option<PathBuf>, app::Config),
|
||||
Run(PathBuf, app::Config),
|
||||
Launcher(PathBuf),
|
||||
Install(PathBuf, bitcoin::Network),
|
||||
}
|
||||
@ -254,7 +270,7 @@ impl Config {
|
||||
path.push(network.to_string());
|
||||
path.push(app::config::DEFAULT_FILE_NAME);
|
||||
match app::Config::from_file(&path) {
|
||||
Ok(cfg) => Ok(Config::Run(Some(datadir_path), cfg)),
|
||||
Ok(cfg) => Ok(Config::Run(datadir_path, cfg)),
|
||||
Err(ConfigError::NotFound) => Ok(Config::Install(datadir_path, network)),
|
||||
Err(e) => Err(format!("Failed to read configuration file: {}", e).into()),
|
||||
}
|
||||
@ -277,7 +293,14 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
let datadir_path = default_datadir().unwrap();
|
||||
Config::new(datadir_path, Some(*network))
|
||||
}
|
||||
[Arg::ConfigPath(path)] => Ok(Config::Run(None, app::Config::from_file(path)?)),
|
||||
[Arg::ConfigPath(path)] => {
|
||||
let cfg = app::Config::from_file(path)?;
|
||||
let daemon_cfg = DaemonConfig::from_file(Some(cfg.daemon_config_path.clone()))?;
|
||||
let datadir_path = daemon_cfg
|
||||
.data_dir
|
||||
.unwrap_or_else(|| default_datadir().unwrap());
|
||||
Ok(Config::Run(datadir_path, cfg))
|
||||
}
|
||||
[Arg::DatadirPath(datadir_path)] => Config::new(datadir_path.clone(), None),
|
||||
[Arg::DatadirPath(datadir_path), Arg::Network(network)]
|
||||
| [Arg::Network(network), Arg::DatadirPath(datadir_path)] => {
|
||||
@ -288,12 +311,6 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
}?;
|
||||
|
||||
let level = if let Config::Run(_, cfg) = &config {
|
||||
log_level_from_config(cfg)?
|
||||
} else {
|
||||
log::LevelFilter::Info
|
||||
};
|
||||
setup_logger(level)?;
|
||||
setup_panic_hook();
|
||||
|
||||
let mut settings = Settings::with_flags(config);
|
||||
@ -323,62 +340,16 @@ fn setup_panic_hook() {
|
||||
.downcast_ref::<&str>()
|
||||
.map(|s| s.to_string())
|
||||
.or_else(|| panic_info.payload().downcast_ref::<String>().cloned());
|
||||
log::error!(
|
||||
error!(
|
||||
"panic occurred at line {} of file {}: {:?}\n{:?}",
|
||||
line,
|
||||
file,
|
||||
info,
|
||||
bt
|
||||
line, file, info, bt
|
||||
);
|
||||
|
||||
std::io::stdout().flush().expect("Flushing stdout");
|
||||
std::process::exit(1);
|
||||
}));
|
||||
}
|
||||
|
||||
// This creates the log file automagically if it doesn't exist, and logs on stdout
|
||||
// if None is given
|
||||
pub fn setup_logger(log_level: log::LevelFilter) -> Result<(), fern::InitError> {
|
||||
let dispatcher = fern::Dispatch::new()
|
||||
.format(|out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"[{}][{}][{}] {}",
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_else(|e| {
|
||||
println!("Can't get time since epoch: '{}'. Using a dummy value.", e);
|
||||
std::time::Duration::from_secs(0)
|
||||
})
|
||||
.as_secs(),
|
||||
record.target(),
|
||||
record.level(),
|
||||
message
|
||||
))
|
||||
})
|
||||
.level(log_level)
|
||||
.level_for("iced_wgpu", log::LevelFilter::Off)
|
||||
.level_for("iced_winit", log::LevelFilter::Off)
|
||||
.level_for("wgpu_core", log::LevelFilter::Off)
|
||||
.level_for("wgpu_hal", log::LevelFilter::Off)
|
||||
.level_for("gfx_backend_vulkan", log::LevelFilter::Off)
|
||||
.level_for("iced_glutin", log::LevelFilter::Off)
|
||||
.level_for("iced_glow", log::LevelFilter::Off)
|
||||
.level_for("glow_glyph", log::LevelFilter::Off)
|
||||
.level_for("naga", log::LevelFilter::Off)
|
||||
.level_for(
|
||||
"ledger_transport_hid",
|
||||
if log_level == log::LevelFilter::Info {
|
||||
log::LevelFilter::Off
|
||||
} else {
|
||||
log_level
|
||||
},
|
||||
)
|
||||
.level_for("mio", log::LevelFilter::Off);
|
||||
|
||||
dispatcher.chain(std::io::stdout()).apply()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user