add basic crash analytics and very light telemetry to dioxus-cli (#4224)

* wip

* wip: basic telemetry

* fix merge

* keep errors for telemetry

* fix upload when queued events are empty

* add per session id and keep track of build timing

* keep track of what commands are run

* capture panics

* keep track of panics

* better path stripping

* better tracing error telemetry

* only sync every week and check settings before sending any telemetry

* keep track of the args passed to commands

* anonymize commands

* more error tolerant

* wip

* wip

* wip

* merge cnflict

* other merg conflict

* migrate everything from telemetry to logging

* update docs

* wip... about to merge trace controller

* lil bit more refactor to clean up tracer

* more performant log writer

* cleanup ci detecting

* wire up to posthog

* re-uploads work

* yoink out the anon from each file

* cleanup sig

* lil bit cleaner

* dont write empty files

* wip log handler

* fmt

* cleanup logging of backtraces

* add proper backtrace support

* use stable reporter

* adhere to posthog format

* fix stacktraces

* use anyhow for capturing backtraces

* I think we're done?

* cleanup logging

* properly collect!

* move out keycode handling

* she works

* typo

* add extra fields

* fix cargo doc

---------

Co-authored-by: Evan Almloff <evanalmloff@gmail.com>
This commit is contained in:
Jonathan KelleyandEvan Almloff authored and GitHub committed 2025-08-08 19:00:00 -07:00
1 parent a610aaefc3
commit e43bf05b69
30 files changed
+1748 -441

No files matched your search

+66 -34
View File
@@ -5,8 +5,6 @@ use std::sync::LazyLock;
use std::{fs, path::PathBuf, sync::Arc};
use tracing::{error, trace, warn};
const GLOBAL_SETTINGS_FILE_NAME: &str = "dioxus/settings.toml";
/// Describes cli settings from project or global level.
/// The order of priority goes:
/// 1. CLI Flags/Arguments
@@ -29,6 +27,8 @@ pub(crate) struct CliSettings {
pub(crate) no_downloads: Option<bool>,
/// Ignore updates for this version
pub(crate) ignore_version_update: Option<String>,
/// Disable telemetry
pub(crate) disable_telemetry: Option<bool>,
}
impl CliSettings {
@@ -43,38 +43,37 @@ impl CliSettings {
CliSettings::from_global().unwrap_or_default()
}
/// Get the path to the settings toml file.
pub(crate) fn get_settings_path() -> PathBuf {
crate::Workspace::global_settings_file()
}
/// Get the current settings structure from global.
pub(crate) fn from_global() -> Option<Self> {
let Some(path) = dirs::data_local_dir() else {
warn!("failed to get local data directory, some config keys may be missing");
return None;
};
let settings = crate::Workspace::global_settings_file();
let path = path.join(GLOBAL_SETTINGS_FILE_NAME);
let Some(data) = fs::read_to_string(path).ok() else {
// We use a debug here because we expect the file to not exist.
trace!("failed to read `{}` config file", GLOBAL_SETTINGS_FILE_NAME);
if !settings.exists() {
trace!("global settings file does not exist, returning None");
return None;
};
let data = toml::from_str::<CliSettings>(&data).ok();
if data.is_none() {
warn!(
"failed to parse `{}` config file",
GLOBAL_SETTINGS_FILE_NAME
);
}
data
let Some(data) = fs::read_to_string(&settings).ok() else {
warn!("failed to read global settings file");
return None;
};
let Some(data) = serde_json5::from_str::<CliSettings>(&data).ok() else {
warn!("failed to parse global settings file");
return None;
};
Some(data)
}
/// Save the current structure to the global settings toml.
/// This does not save to project-level settings.
pub(crate) fn save(&self) -> Result<()> {
let path = Self::get_settings_path().ok_or_else(|| {
error!(dx_src = ?TraceSrc::Dev, "failed to get settings path");
anyhow::anyhow!("failed to get settings path")
})?;
let path = Self::get_settings_path();
let data = toml::to_string_pretty(&self).map_err(|e| {
error!(dx_src = ?TraceSrc::Dev, ?self, "failed to parse config into toml");
@@ -103,16 +102,6 @@ impl CliSettings {
Ok(())
}
/// Get the path to the settings toml file.
pub(crate) fn get_settings_path() -> Option<PathBuf> {
let Some(path) = dirs::data_local_dir() else {
warn!("failed to get local data directory, some config keys may be missing");
return None;
};
Some(path.join(GLOBAL_SETTINGS_FILE_NAME))
}
/// Modify the settings toml file - doesn't change the settings for this session
pub(crate) fn modify_settings(with: impl FnOnce(&mut CliSettings)) -> Result<()> {
let mut _settings = CliSettings::load();
@@ -129,12 +118,55 @@ impl CliSettings {
return true;
}
if crate::devcfg::no_downloads() {
if std::env::var("NO_DOWNLOADS").is_ok() {
return true;
}
CliSettings::load().no_downloads.unwrap_or_default()
}
/// Check if telemetry is disabled
pub(crate) fn telemetry_disabled() -> bool {
use std::env::var;
static TELEMETRY_DISABLED: LazyLock<bool> = LazyLock::new(|| {
if cfg!(feature = "disable-telemetry") {
return true;
}
if matches!(var("DX_TELEMETRY_ENABLED"), Ok(val) if val.eq_ignore_ascii_case("false") || val == "0")
{
return true;
}
if matches!(var("TELEMETRY"), Ok(val) if val.eq_ignore_ascii_case("false") || val == "0")
{
return true;
}
CliSettings::load().disable_telemetry.unwrap_or_default()
});
*TELEMETRY_DISABLED
}
pub(crate) fn is_ci() -> bool {
static CI: LazyLock<bool> = LazyLock::new(|| {
if matches!(std::env::var("CI"), Ok(val) if val.eq_ignore_ascii_case("true") || val == "1")
{
return true;
}
if matches!(std::env::var("DX_CI"), Ok(val) if val.eq_ignore_ascii_case("true") || val == "1")
{
return true;
}
false
});
*CI
}
}
fn default_wsl_file_poll_interval() -> Option<u16> {