Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 75 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [] }
Expand All @@ -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"
Expand Down
4 changes: 3 additions & 1 deletion src/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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?;
}
Expand Down
19 changes: 13 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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}");
}
Expand Down
136 changes: 136 additions & 0 deletions src/trc.rs
Original file line number Diff line number Diff line change
@@ -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<dyn tracing_subscriber::Layer<Registry> + Send + Sync>;

/// The reload handle type used to swap the fmt layer at runtime.
type FmtReloadHandle = reload::Handle<BoxedFmtLayer, Registry>;

/// 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<TrcHandle, TryInitError> {
// 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 })
}
}
8 changes: 4 additions & 4 deletions src/updates.rs
Original file line number Diff line number Diff line change
@@ -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");
Expand All @@ -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;
};

Expand Down