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:
edouard 2023-02-16 11:45:52 +01:00
parent 9ccb22b2ac
commit a13bb0272b
13 changed files with 355 additions and 153 deletions

102
gui/Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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),
}

View File

@ -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())
})?;

View File

@ -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,

View File

@ -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)]

View File

@ -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;

View File

@ -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 {

View File

@ -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)
}

View File

@ -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;

View File

@ -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
View 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(())
}
}

View File

@ -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::*;