Skip to content

Commit

Permalink
feat: adds configurable telemetry settings for error reporting and me…
Browse files Browse the repository at this point in the history
…trics tracking
  • Loading branch information
krlvi committed Jan 28, 2024
1 parent 5f46306 commit 7a05d69
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 27 deletions.
17 changes: 15 additions & 2 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions gitbutler-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ tauri = { version = "1.5.4", features = ["dialog-open", "fs-read-file", "path-al
tauri-plugin-context-menu = { git = "https://github.com/gitbutlerapp/tauri-plugin-context-menu", branch = "main" }
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
thiserror.workspace = true
tokio = { workspace = true, features = [ "full", "sync", "tracing" ] }
tokio-util = "0.7.10"
Expand Down
8 changes: 8 additions & 0 deletions gitbutler-app/src/analytics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,11 @@ impl Client {
}
}
}

impl Default for Client {
fn default() -> Self {
Self {
client: Arc::new(Box::<posthog::mock::Client>::default()),
}
}
}
56 changes: 40 additions & 16 deletions gitbutler-app/src/bin.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
use std::path::PathBuf;

use anyhow::Context;
use tauri::{generate_context, Manager};
use tauri::{generate_context, Manager, Wry};

use gblib::{
analytics, app, assets, commands, database, deltas, github, keys, logs, menu, projects, sentry,
sessions, storage, users, virtual_branches, watcher, zip,
};
use tauri_plugin_store::{with_store, JsonValue, StoreCollection};

fn main() {
let tauri_context = generate_context!();

let _guard = sentry::init(tauri_context.package_info());

let app_name = tauri_context.package_info().name.clone();
let app_version = tauri_context.package_info().version.clone().to_string();
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
Expand Down Expand Up @@ -80,18 +83,6 @@ fn main() {

tracing::info!(version = %app_handle.package_info().version, name = %app_handle.package_info().name, "starting app");

let analytics_cfg = if cfg!(debug_assertions) {
analytics::Config {
posthog_token: Some("phc_t7VDC9pQELnYep9IiDTxrq2HLseY5wyT7pn0EpHM7rr"),
}
} else {
analytics::Config {
posthog_token: Some("phc_yJx46mXv6kA5KTuM2eEQ6IwNTgl5YW3feKV5gi7mfGG"),
}
};
let analytics_client = analytics::Client::new(&app_handle, &analytics_cfg);
tauri_app.manage(analytics_client);

let watchers = watcher::Watchers::try_from(&app_handle)
.expect("failed to initialize watchers");
tauri_app.manage(watchers);
Expand Down Expand Up @@ -132,7 +123,39 @@ fn main() {
app_handle.manage(keys_controller);

let users_controller = users::Controller::from(&app_handle);
sentry::configure_scope(users_controller.get_user().context("failed to get user")?.as_ref());

let stores = tauri_app.state::<StoreCollection<Wry>>();
if let Some(path) = app_handle.path_resolver().app_config_dir().map(|path| path.join(PathBuf::from("settings.json"))) {
if let Ok((metrics_enabled, error_reporting_enabled)) = with_store(app_handle.clone(), stores, path, |store| {
let metrics_enabled = store.get("appMetricsEnabled")
.and_then(JsonValue::as_bool)
.unwrap_or(true);
let error_reporting_enabled = store.get("appErrorReportingEnabled")
.and_then(JsonValue::as_bool)
.unwrap_or(true);
Ok((metrics_enabled, error_reporting_enabled))
}) {
if metrics_enabled {
let analytics_cfg = if cfg!(debug_assertions) {
analytics::Config {
posthog_token: Some("phc_t7VDC9pQELnYep9IiDTxrq2HLseY5wyT7pn0EpHM7rr"),
}
} else {
analytics::Config {
posthog_token: Some("phc_yJx46mXv6kA5KTuM2eEQ6IwNTgl5YW3feKV5gi7mfGG"),
}
};
let analytics_client = analytics::Client::new(&app_handle, &analytics_cfg);
tauri_app.manage(analytics_client);
}

if error_reporting_enabled {
let _guard = sentry::init(app_name.as_str(), app_version);
sentry::configure_scope(users_controller.get_user().context("failed to get user")?.as_ref());
}
};
}

app_handle.manage(users_controller);

let app: app::App = app::App::try_from(&tauri_app.app_handle())
Expand All @@ -147,6 +170,7 @@ fn main() {
.plugin(tauri_plugin_window_state::Builder::default().build())
.plugin(tauri_plugin_single_instance::init(|_, _, _| {}))
.plugin(tauri_plugin_context_menu::init())
.plugin(tauri_plugin_store::Builder::default().build())
.invoke_handler(tauri::generate_handler![
commands::list_session_files,
commands::git_remote_branches,
Expand Down
9 changes: 4 additions & 5 deletions gitbutler-app/src/sentry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use nonzero_ext::nonzero;
use once_cell::sync::OnceCell;
use sentry::ClientInitGuard;
use sentry_tracing::SentryLayer;
use tauri::PackageInfo;
use tracing::Subscriber;
use tracing_subscriber::registry::LookupSpan;

Expand All @@ -20,19 +19,19 @@ static SENTRY_LIMIT: OnceCell<RateLimiter<NotKeyed, InMemoryState, QuantaClock>>

/// Should be called once on application startup, and the returned guard should be kept alive for
/// the lifetime of the application.
pub fn init(package_info: &PackageInfo) -> ClientInitGuard {
pub fn init(name: &str, version: String) -> ClientInitGuard {
sentry::init(("https://9d407634d26b4d30b6a42d57a136d255@o4504644069687296.ingest.sentry.io/4504649768108032", sentry::ClientOptions {
environment: Some(match package_info.name.as_str() {
environment: Some(match name {
"GitButler" => "production",
"GitButler Nightly" => "nightly",
"GitButler Dev" => "development",
_ => "unknown",
}.into()),
release: Some(package_info.version.to_string().into()),
release: Some(version.into()),
before_send: Some({
Arc::new(|event| SENTRY_LIMIT.get_or_init(|| RateLimiter::direct(SENTRY_QUOTA)).check().is_ok().then_some(event))}),
attach_stacktrace: true,
traces_sample_rate: match package_info.name.as_str() {
traces_sample_rate: match name {
"GitButler Dev" | "GitButler Nightly" => 0.2_f32,
_ => 0.05_f32,
},
Expand Down
8 changes: 7 additions & 1 deletion gitbutler-app/src/watcher/handlers/analytics_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ pub struct Handler {

impl From<&AppHandle> for Handler {
fn from(value: &AppHandle) -> Self {
let client = value
.try_state::<analytics::Client>()
.map_or(analytics::Client::default(), |client| {
client.inner().clone()
});

Self {
client: value.state::<analytics::Client>().inner().clone(),
client,
users: users::Controller::from(value),
}
}
Expand Down
1 change: 1 addition & 0 deletions gitbutler-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"svelte-resize-observer": "^2.0.0",
"tailwindcss": "^3.4.1",
"tauri-plugin-context-menu": "^0.7.0",
"tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#v1",
"tinykeys": "^2.1.0",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
Expand Down
84 changes: 84 additions & 0 deletions gitbutler-ui/src/lib/config/appSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* This file contains functions for managing application settings.
* Settings are persisted in <Application Data>/settings.json and are used by both the UI and the backend.
*
* @module appSettings
*/

import { writable, type Writable } from 'svelte/store';
import { Store } from 'tauri-plugin-store-api';

const store = new Store('settings.json');

/**
* Provides a writable store for obtaining or setting the current state of application metrics.
* The application metrics can be enabled or disabled by setting the value of the store to true or false.
* @returns A writable store with the appMetricsEnabled config.
*/
export function appMetricsEnabled() {
return persisted(true, 'appMetricsEnabled');
}

/**
* Provides a writable store for obtaining or setting the current state of application error reporting.
* The application error reporting can be enabled or disabled by setting the value of the store to true or false.
* @returns A writable store with the appErrorReportingEnabled config.
*/
export function appErrorReportingEnabled() {
return persisted(true, 'appErrorReportingEnabled');
}

function persisted<T>(initial: T, key: string): Writable<T> & { onDisk: () => Promise<T> } {
const setAndPersist = async (value: T, set: (value: T) => void) => {
await store.set(key, value);
await store.save();

set(value);
};

const synchronize = async (set: (value: T) => void): Promise<void> => {
const value = await storeValueWithDefault(initial, key);
set(value);
};

const update = () => {
throw 'Not implemented';
};

const thisStore = writable<T>(initial, (set) => {
synchronize(set);
});

const set = async (value: T) => {
setAndPersist(value, thisStore.set);
};

const onDisk = async () => {
return storeValueWithDefault(initial, key);
};

const subscribe = thisStore.subscribe;

return {
subscribe,
set,
update,
onDisk
};
}

async function storeValueWithDefault<T>(initial: T, key: string): Promise<T> {
try {
await store.load();
} catch (e) {
// If file does not exist, reset it
store.reset();
}
const stored = (await store.get(key)) as T;

if (stored === null) {
return initial;
} else {
return stored;
}
}
13 changes: 11 additions & 2 deletions gitbutler-ui/src/routes/+layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { UserService } from '$lib/stores/user';
import { config } from 'rxjs';
import { initPostHog } from '$lib/analytics/posthog';
import { initSentry } from '$lib/analytics/sentry';
import { appMetricsEnabled, appErrorReportingEnabled } from '$lib/config/appSettings';

// call on startup so we don't accumulate old items
lscache.flushExpired();
Expand All @@ -20,8 +21,16 @@ export const csr = true;
let homeDir: () => Promise<string>;

export const load: LayoutLoad = async ({ fetch: realFetch }: { fetch: typeof fetch }) => {
initSentry();
initPostHog();
appErrorReportingEnabled()
.onDisk()
.then((enabled) => {
if (enabled) initSentry();
});
appMetricsEnabled()
.onDisk()
.then((enabled) => {
if (enabled) initPostHog();
});
const userService = new UserService();

// TODO: Find a workaround to avoid this dynamic import
Expand Down
Loading

0 comments on commit 7a05d69

Please sign in to comment.