diff --git a/Cargo.lock b/Cargo.lock index 24fd29c..fe1478b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,6 +67,12 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "async-trait" version = "0.1.89" @@ -213,6 +219,19 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "console" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.61.2", +] + [[package]] name = "convert_case" version = "0.10.0" @@ -702,6 +721,7 @@ dependencies = [ "tokio", "toml", "tracing", + "tracing-indicatif", "tracing-subscriber", "vergen-gitcl", ] @@ -986,13 +1006,27 @@ version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" dependencies = [ - "console", + "console 0.15.11", "number_prefix", "portable-atomic", "unicode-width", "web-time", ] +[[package]] +name = "indicatif" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88" +dependencies = [ + "console 0.16.2", + "portable-atomic", + "unicode-width", + "unit-prefix", + "vt100", + "web-time", +] + [[package]] name = "inquire" version = "0.9.2" @@ -1753,7 +1787,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d832c086ece0dacc29fb2947bb4219b8f6e12fe9e40b7108f9e57c4224e47b5c" dependencies = [ "hyper", - "indicatif", + "indicatif 0.17.11", "log", "quick-xml", "regex", @@ -2261,6 +2295,18 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-indicatif" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ef6990e0438749f0080573248e96631171a0b5ddfddde119aa5ba8c3a9c47e" +dependencies = [ + "indicatif 0.18.3", + "tracing", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "tracing-log" version = "0.2.0" @@ -2320,6 +2366,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + [[package]] name = "untrusted" version = "0.9.0" @@ -2422,6 +2474,27 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vt100" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ff75fb8fa83e609e685106df4faeffdf3a735d3c74ebce97ec557d5d36fd9" +dependencies = [ + "itoa", + "unicode-width", + "vte", +] + +[[package]] +name = "vte" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd" +dependencies = [ + "arrayvec", + "memchr", +] + [[package]] name = "want" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index 266c76c..8fe234b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ bimap = "0.6.3" self_update = { version = "0.42", default-features = false, features = ["rustls"] } semver = "1.0" inquire = "0.9.2" +tracing-indicatif = "0.3.14" [build-dependencies] vergen-gitcl = { version = "1", features = [] } @@ -46,7 +47,7 @@ missing_docs = "warn" unreachable_pub = "allow" unused_qualifications = "warn" elided_lifetimes_in_paths = "warn" -non_ascii_idents = "forbid" +non_ascii_idents = "allow" keyword_idents_2024 = "forbid" let_underscore_drop = "warn" trivial_numeric_casts = "warn" diff --git a/src/daemon.rs b/src/daemon.rs index a68d0c4..e56e708 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -52,6 +52,7 @@ mod managed_fuse { fuser::MountOption::Exec, fuser::MountOption::AutoUnmount, fuser::MountOption::DefaultPermissions, + fuser::MountOption::AllowOther, ]; fuser::spawn_mount2(fuse_adapter, config.mount_point, &mount_opts) @@ -188,11 +189,12 @@ pub async fn run( prepare_mount_point(&config.mount_point).await?; - debug!(config = ?config, "Starting git-fs daemon..."); + info!("Mounting filesystem at {}.", config.mount_point.display()); let fuse = managed_fuse::ManagedFuse::new(&config); { let _session = fuse.spawn(config, handle.clone())?; + info!("git-fs is running. Press Ctrl+C to stop."); wait_for_exit().await?; } diff --git a/src/main.rs b/src/main.rs index cf44898..2c04e06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,15 +3,16 @@ use std::path::PathBuf; use clap::{Parser, Subcommand}; use tracing::{debug, error}; -use tracing_subscriber::{EnvFilter, fmt::format::FmtSpan}; mod app_config; mod daemon; mod fs; mod onboarding; +mod trc; mod updates; use crate::app_config::Config; +use crate::trc::Trc; #[derive(Parser)] #[command( @@ -47,10 +48,13 @@ enum Command { /// Main entry point for the application. fn main() { - tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .with_span_events(FmtSpan::ENTER | FmtSpan::CLOSE) - .init(); + let trc_handle = Trc::default().init().unwrap_or_else(|e| { + eprintln!( + "Failed to initialize logging. Without logging, we can't provide any useful error \ + messages, so we have to exit: {e}" + ); + std::process::exit(1); + }); updates::check_for_updates(); @@ -91,7 +95,10 @@ fn main() { // TODO(markovejnovic): Handle stdout, stderr match daemonize.start() { - Ok(()) => daemon::spawn(config), + Ok(()) => { + trc_handle.reconfigure_for_daemon(); + daemon::spawn(config); + } Err(e) => { error!("Failed to spawn the daemon: {e}"); } diff --git a/src/trc.rs b/src/trc.rs new file mode 100644 index 0000000..078e1c5 --- /dev/null +++ b/src/trc.rs @@ -0,0 +1,136 @@ +//! Tracing configuration and initialization. +//! +//! The tracing subscriber is built with a [`reload::Layer`] wrapping the fmt layer so that the +//! output format can be switched at runtime (e.g. from pretty mode to ugly mode when daemonizing). + +use tracing_indicatif::IndicatifLayer; +use tracing_subscriber::{ + EnvFilter, Registry, + fmt::format::FmtSpan, + layer::SubscriberExt as _, + reload, + util::{SubscriberInitExt as _, TryInitError}, +}; + +/// The type-erased fmt layer that lives inside the reload handle. +type BoxedFmtLayer = Box + Send + Sync>; + +/// The reload handle type used to swap the fmt layer at runtime. +type FmtReloadHandle = reload::Handle; + +/// Controls the output format of the tracing subscriber. +enum TrcMode { + /// User-friendly, compact, colorful output with spinners. + 丑, + /// Plain, verbose, machine-readable logging. + Ugly, +} + +/// A handle that allows reconfiguring the tracing subscriber at runtime. +pub struct TrcHandle { + fmt_handle: FmtReloadHandle, +} + +impl TrcHandle { + /// Reconfigure the tracing subscriber to use the given mode. + /// + /// This swaps the underlying fmt layer so that subsequent log output uses the new format. + /// Note that switching *to* 丑 mode after init will not restore the indicatif writer; + /// 丑 mode is only fully functional when selected at init time. + fn reconfigure(&self, mode: &TrcMode) { + let new_layer: BoxedFmtLayer = match mode { + TrcMode::丑 => Box::new( + tracing_subscriber::fmt::layer() + .with_target(false) + .without_time() + .compact(), + ), + TrcMode::Ugly => Box::new( + tracing_subscriber::fmt::layer().with_span_events(FmtSpan::ENTER | FmtSpan::CLOSE), + ), + }; + + if let Err(e) = self.fmt_handle.reload(new_layer) { + eprintln!("Failed to reconfigure tracing: {e}"); + } + } + + pub fn reconfigure_for_daemon(&self) { + self.reconfigure(&TrcMode::Ugly); + } +} + +/// Builder for the tracing subscriber. +pub struct Trc { + mode: TrcMode, + env_filter: EnvFilter, +} + +impl Default for Trc { + fn default() -> Self { + let maybe_env_filter = + EnvFilter::try_from_env("GIT_FS_LOG").or_else(|_| EnvFilter::try_from_default_env()); + + match maybe_env_filter { + Ok(env_filter) => Self { + // If the user provided an env_filter, they probably know what they're doing and + // don't want any fancy formatting, spinners or bullshit like that. So we default + // to the ugly mode. + mode: TrcMode::Ugly, + env_filter, + }, + Err(_) => Self { + // If the user didn't provide an env_filter, we assume they just want a nice + // out-of-the-box experience, and default to 丑 mode with an info level filter. + mode: TrcMode::丑, + env_filter: EnvFilter::new("info"), + }, + } + } +} + +impl Trc { + /// Initialize the global tracing subscriber and return a handle for runtime reconfiguration. + pub fn init(self) -> Result { + // Start with a plain ugly-mode layer as a placeholder. In 丑 mode this gets swapped + // out before `try_init` is called so the subscriber never actually uses it. + let initial_layer: BoxedFmtLayer = Box::new( + tracing_subscriber::fmt::layer().with_span_events(FmtSpan::ENTER | FmtSpan::CLOSE), + ); + + let (reload_layer, fmt_handle) = reload::Layer::new(initial_layer); + + match self.mode { + TrcMode::丑 => { + let indicatif_layer = IndicatifLayer::new(); + let pretty_with_indicatif: BoxedFmtLayer = Box::new( + tracing_subscriber::fmt::layer() + .with_writer(indicatif_layer.get_stderr_writer()) + .with_target(false) + .without_time() + .compact(), + ); + + // Replace the initial placeholder with the correct writer before init. + if let Err(e) = fmt_handle.reload(pretty_with_indicatif) { + eprintln!("Failed to configure 丑-mode writer: {e}"); + } + + tracing_subscriber::registry() + .with(reload_layer) + .with(self.env_filter) + .with(indicatif_layer) + .try_init()?; + } + TrcMode::Ugly => { + // The initial layer is already configured for ugly mode, so just init directly. + tracing_subscriber::registry() + .with(reload_layer) + .with(self.env_filter) + .try_init()?; + } + } + + Ok(TrcHandle { fmt_handle }) + } +} diff --git a/src/updates.rs b/src/updates.rs index 84e643e..752678a 100644 --- a/src/updates.rs +++ b/src/updates.rs @@ -1,6 +1,6 @@ //! Checks whether the running binary is the latest released version. -use tracing::{debug, error, info, warn}; +use tracing::{debug, error, warn}; /// The git SHA baked in at compile time by `vergen-gitcl`. const BUILD_SHA: &str = env!("VERGEN_GIT_SHA"); @@ -23,19 +23,19 @@ pub fn check_for_updates() { Ok(list) => match list.fetch() { Ok(releases) => releases, Err(e) => { - info!("Could not check for updates: {e}"); + error!("Could not check for updates: {e}"); return; } }, Err(e) => { - info!("Could not configure update check: {e}"); + error!("Could not configure update check: {e}"); return; } }; // Find the stable release (tagged "latest" on GitHub). let Some(stable) = releases.iter().find(|r| r.version == "latest") else { - info!("No stable release found on GitHub."); + error!("No stable release found on GitHub."); return; };