feat(windows): count log files (#2964)

There's a 200 ms delay between each file, for debugging. It's nice to
demo how it thinks, but it needs to go behind a fault injection flag or
be removed completely before merging.


![image](https://github.com/firezone/firezone/assets/13400041/af364170-0e76-45fa-83f2-521b3de679de)
This commit is contained in:
Reactor Scram
2023-12-20 16:38:37 -06:00
committed by GitHub
parent 386a2010cc
commit 6ebbe746e8
7 changed files with 146 additions and 50 deletions

1
rust/Cargo.lock generated
View File

@@ -2025,6 +2025,7 @@ version = "1.0.0"
dependencies = [
"anyhow",
"arboard",
"chrono",
"clap",
"connlib-client-shared",
"connlib-shared",

View File

@@ -12,6 +12,7 @@ tauri-build = { version = "1.5", features = [] }
[dependencies]
arboard = { version = "3.3.0", default-features = false }
anyhow = { version = "1.0" }
chrono = { workspace = true }
clap = { version = "4.4", features = ["derive", "env"] }
connlib-client-shared = { workspace = true }
connlib-shared = { workspace = true }
@@ -20,7 +21,7 @@ firezone-cli-utils = { workspace = true }
ipconfig = "0.3.2"
keyring = "2.0.5"
ring = "0.17"
secrecy.workspace = true
secrecy = { workspace = true }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = { version = "1.0", default-features = false }

View File

@@ -5,7 +5,10 @@
use crate::client::{self, deep_link, AppLocalDataDir};
use anyhow::{anyhow, Context, Result};
use client::settings::{self, AdvancedSettings};
use client::{
logging,
settings::{self, AdvancedSettings},
};
use connlib_client_shared::file_logger;
use connlib_shared::messages::ResourceId;
use secrecy::{ExposeSecret, SecretString};
@@ -79,9 +82,10 @@ pub(crate) fn run(params: client::GuiParams) -> Result<()> {
}
})
.invoke_handler(tauri::generate_handler![
logging::clear_logs,
logging::export_logs,
logging::start_stop_log_counting,
settings::apply_advanced_settings,
settings::clear_logs,
settings::export_logs,
settings::get_advanced_settings,
])
.system_tray(tray)
@@ -205,6 +209,7 @@ pub(crate) enum ControllerRequest {
GetAdvancedSettings(oneshot::Sender<AdvancedSettings>),
SchemeRequest(url::Url),
SignIn,
StartStopLogCounting(bool),
SignOut,
UpdateResources(Vec<connlib_client_shared::ResourceDescription>),
}
@@ -395,6 +400,7 @@ async fn run_controller(
.await
.context("couldn't create Controller")?;
let mut log_counting_task = None;
let mut resources: Vec<ResourceDisplay> = vec![];
tracing::debug!("GUI controller main loop start");
@@ -409,7 +415,7 @@ async fn run_controller(
let mut clipboard = arboard::Clipboard::new()?;
clipboard.set_text(&res.pastable)?;
}
Req::ExportLogs(file_path) => settings::export_logs_to(file_path).await?,
Req::ExportLogs(file_path) => logging::export_logs_to(file_path).await?,
Req::GetAdvancedSettings(tx) => {
tx.send(controller.advanced_settings.clone()).ok();
}
@@ -441,6 +447,19 @@ async fn run_controller(
None,
)?;
}
Req::StartStopLogCounting(enable) => {
if enable {
if log_counting_task.is_none() {
let app = app.clone();
log_counting_task = Some(tokio::spawn(logging::count_logs(app)));
tracing::debug!("started log counting");
}
} else if let Some(t) = log_counting_task {
t.abort();
log_counting_task = None;
tracing::debug!("cancelled log counting");
}
}
Req::SignOut => {
keyring_entry()?.delete_password()?;
if let Some(mut session) = controller.connlib_session.take() {

View File

@@ -1,8 +1,17 @@
//! Separate module to contain all the `use` statements for setting up logging
//! Everything for logging to files, zipping up the files for export, and counting the files
use crate::client::gui::{ControllerRequest, CtlrTx, Managed};
use anyhow::Result;
use connlib_client_shared::file_logger;
use std::{path::Path, str::FromStr};
use serde::Serialize;
use std::{
path::{Path, PathBuf},
result::Result as StdResult,
str::FromStr,
time::Duration,
};
use tauri::Manager;
use tokio::fs;
use tracing::subscriber::set_global_default;
use tracing_log::LogTracer;
use tracing_subscriber::{fmt, layer::SubscriberExt, reload, EnvFilter, Layer, Registry};
@@ -28,3 +37,88 @@ pub(crate) fn setup(log_filter: &str) -> Result<Handles> {
_reloader: reloader,
})
}
#[tauri::command]
pub(crate) async fn start_stop_log_counting(
managed: tauri::State<'_, Managed>,
enable: bool,
) -> StdResult<(), String> {
managed
.ctlr_tx
.send(ControllerRequest::StartStopLogCounting(enable))
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
pub(crate) async fn clear_logs() -> StdResult<(), String> {
clear_logs_inner().await.map_err(|e| e.to_string())
}
#[tauri::command]
pub(crate) async fn export_logs(managed: tauri::State<'_, Managed>) -> StdResult<(), String> {
export_logs_inner(managed.ctlr_tx.clone())
.await
.map_err(|e| e.to_string())
}
pub(crate) async fn clear_logs_inner() -> Result<()> {
todo!()
}
/// Pops up the "Save File" dialog
pub(crate) async fn export_logs_inner(ctlr_tx: CtlrTx) -> Result<()> {
let now = chrono::Local::now();
let datetime_string = now.format("%Y_%m_%d-%H-%M");
let filename = format!("connlib-{datetime_string}.zip");
tauri::api::dialog::FileDialogBuilder::new()
.add_filter("Zip", &["zip"])
.set_file_name(&filename)
.save_file(move |file_path| match file_path {
None => {}
Some(x) => {
// blocking_send here because we're in a sync callback within Tauri somewhere
ctlr_tx
.blocking_send(ControllerRequest::ExportLogs(x))
.unwrap()
}
});
Ok(())
}
/// Exports logs to a zip file
pub(crate) async fn export_logs_to(file_path: PathBuf) -> Result<()> {
tracing::trace!("Exporting logs to {file_path:?}");
let mut entries = fs::read_dir("logs").await?;
while let Some(entry) = entries.next_entry().await? {
let _path = entry.path();
// TODO: Actually add log files to a zip file
}
tokio::time::sleep(Duration::from_secs(1)).await;
// TODO: Somehow signal back to the GUI to unlock the log buttons when the export completes, or errors out
Ok(())
}
#[derive(Clone, Serialize)]
struct FileCount {
files: u64,
bytes: u64,
}
pub(crate) async fn count_logs(app: tauri::AppHandle) -> Result<()> {
let mut dir = fs::read_dir("logs").await?;
let mut files: u64 = 0;
let mut bytes: u64 = 0;
while let Some(entry) = dir.next_entry().await? {
// TODO: Remove sleep before merging
// This is useful for debugging to show how the GUI thinks and make sure nothing else blocks on this
tokio::time::sleep(Duration::from_millis(200)).await;
let md = entry.metadata().await?;
files += 1;
bytes += md.len();
app.emit_all("file_count_progress", FileCount { files, bytes })?;
}
Ok(())
}

View File

@@ -55,18 +55,6 @@ pub(crate) async fn apply_advanced_settings(
.map_err(|e| e.to_string())
}
#[tauri::command]
pub(crate) async fn clear_logs() -> StdResult<(), String> {
clear_logs_inner().await.map_err(|e| e.to_string())
}
#[tauri::command]
pub(crate) async fn export_logs(managed: tauri::State<'_, Managed>) -> StdResult<(), String> {
export_logs_inner(managed.ctlr_tx.clone())
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
pub(crate) async fn get_advanced_settings(
managed: tauri::State<'_, Managed>,
@@ -105,32 +93,3 @@ pub(crate) async fn load_advanced_settings(app: &tauri::AppHandle) -> Result<Adv
let settings = serde_json::from_str(&text)?;
Ok(settings)
}
pub(crate) async fn clear_logs_inner() -> Result<()> {
todo!()
}
pub(crate) async fn export_logs_inner(ctlr_tx: gui::CtlrTx) -> Result<()> {
tauri::api::dialog::FileDialogBuilder::new()
.add_filter("Zip", &["zip"])
.save_file(move |file_path| match file_path {
None => {}
Some(x) => ctlr_tx
.blocking_send(ControllerRequest::ExportLogs(x))
.unwrap(),
});
Ok(())
}
pub(crate) async fn export_logs_to(file_path: PathBuf) -> Result<()> {
tracing::trace!("Exporting logs to {file_path:?}");
let mut entries = tokio::fs::read_dir("logs").await?;
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
tracing::trace!("Export {path:?}");
}
tokio::time::sleep(Duration::from_secs(1)).await;
// TODO: Somehow signal back to the GUI to unlock the log buttons when the export completes, or errors out
Ok(())
}

View File

@@ -36,6 +36,9 @@
<button type="button" id="export-logs-btn">Export Logs</button>
<button type="button" id="clear-logs-btn">Clear Logs</button>
</div>
<div class="row">
<p id="log-count-output"></p>
</div>
</div>
<div id="tab_advanced" class="tabcontent">

View File

@@ -1,5 +1,6 @@
let auth_base_url_input;
let api_url_input;
let log_count_output;
let log_filter_input;
let reset_advanced_settings_btn;
let apply_advanced_settings_btn;
@@ -11,6 +12,7 @@ const querySel = function(id) {
};
const { invoke } = window.__TAURI__.tauri;
const { listen } = window.__TAURI__.event;
// Lock the UI when we're saving to disk, since disk writes are technically async.
// Parameters:
@@ -116,12 +118,21 @@ function openTab(evt, tabName) {
document.getElementById(tabName).style.display = "block";
// TODO: There's a better way to do this
evt.currentTarget.className += " active";
invoke("start_stop_log_counting", {"enable": tabName == "tab_logs"})
.then(() => {
// Good
})
.catch((e) => {
console.error(e);
});
}
window.addEventListener("DOMContentLoaded", () => {
async function setup() {
// Advanced tab
auth_base_url_input = querySel("#auth-base-url-input");
api_url_input = querySel("#api-url-input");
log_count_output = querySel("#log-count-output");
log_filter_input = querySel("#log-filter-input");
reset_advanced_settings_btn = querySel("#reset-advanced-settings-btn");
apply_advanced_settings_btn = querySel("#apply-advanced-settings-btn");
@@ -142,8 +153,16 @@ window.addEventListener("DOMContentLoaded", () => {
clear_logs();
});
await listen("file_count_progress", (event) => {
const pl = event.payload;
const megabytes = Math.round(pl.bytes / 100000) / 10;
log_count_output.innerText = `${pl.files} files, ${megabytes} MB`;
});
// TODO: Why doesn't this open the Advanced tab by default?
querySel("#tab_advanced").click();
get_advanced_settings().await;
});
}
window.addEventListener("DOMContentLoaded",setup);