feat(telemetry): Implement structured telemetry logging and reporting

Adds comprehensive client-side telemetry capabilities for usage, errors, and performance metrics. This includes integrating OpenTelemetry standards into both the frontend (Svelte) and backend (Tauri/Rust) layers to capture events, spans, and system resource utilization.

The implementation ensures that all collected data is privacy-filtered by design, explicitly excluding sensitive information like repository paths, credentials, source code, or email addresses from being logged.

- Updates README with detailed SigNoz telemetry guide
- Adds process metrics collection (CPU/Memory) in Rust backend
- Exposes `setTelemetryEnabled` state management to the frontend
This commit is contained in:
Christoph Brandau
2026-07-21 23:40:39 +02:00
parent 5c9f67700b
commit cae8390b54
12 changed files with 728 additions and 135 deletions
+10 -5
View File
@@ -1,7 +1,6 @@
<script lang="ts">
import { onDestroy, onMount, tick } from "svelte";
import { getVersion } from "@tauri-apps/api/app";
import { invoke } from "@tauri-apps/api/core";
import { open as openDialog } from "@tauri-apps/plugin-dialog";
import { check, type DownloadEvent, type Update } from "@tauri-apps/plugin-updater";
import { AlertCircle, Cherry, Download, FolderOpen, GitBranch, GitMerge, LoaderCircle, Plus, Search, Star, X } from "@lucide/svelte";
@@ -160,6 +159,7 @@
summarizeGitError,
} from "./lib/credentials";
import { trackAnalyticsEvent, type AnalyticsEventProperties } from "./lib/analytics";
import { setTelemetryEnabled, tracedInvoke } from "./lib/telemetry";
type UpdateToastState = "available" | "downloading" | "installed" | "error";
type AppView = "management" | "repository";
@@ -503,7 +503,7 @@
async function closeStartupSplashscreen() {
try {
await invoke("close_splashscreen");
await tracedInvoke("close_splashscreen");
} catch {
// Browser preview and failed startup paths should keep working without Tauri.
}
@@ -815,6 +815,7 @@
function initAnalytics() {
analyticsSettings = loadAnalyticsSettings();
setTelemetryEnabled(analyticsSettings.noticeSeen && analyticsSettings.enabled);
if (!analyticsSettings.noticeSeen) {
analyticsNoticeOpen = true;
return;
@@ -831,6 +832,7 @@
analyticsSettings = { enabled, noticeSeen: true };
persistAnalyticsSettings(analyticsSettings);
analyticsNoticeOpen = false;
setTelemetryEnabled(enabled);
trackEvent("app_started", { first_run: 1 });
}
@@ -900,6 +902,7 @@
persistThemePreference(nextTheme);
persistLanguagePreference(nextLanguage);
persistStoredBoolean(AUTO_REFRESH_ENABLED_KEY, nextAutoRefresh);
setTelemetryEnabled(next.enabled);
appSettingsOpen = false;
if (nextAutoRefresh && !autoRefreshWasEnabled) void autoRefreshTick();
if (next.enabled) trackEvent("settings_saved", { analytics_enabled: 1, theme: nextTheme, language: nextLanguage, auto_refresh: nextAutoRefresh ? 1 : 0 });
@@ -1746,9 +1749,11 @@
}
function errorToMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === "string") return error;
try { return JSON.stringify(error) ?? "Unknown error"; } catch { return "Unknown error"; }
let message: string;
if (error instanceof Error) message = error.message;
else if (typeof error === "string") message = error;
else try { message = JSON.stringify(error) ?? "Unknown error"; } catch { message = "Unknown error"; }
return message;
}
function isNonFastForwardPushError(message: string): boolean {