feat(observability): add Sentry error reporting to Rust core (#131)

* chore(workflows): comment out Windows smoke tests in installer and release workflows

* feat(observability): integrate Sentry for error reporting and add configuration support

- Added Sentry integration for error reporting in the application, initializing it in the main function.
- Introduced a new environment variable `OPENHUMAN_SENTRY_DSN` to configure Sentry DSN.
- Updated the observability configuration schema to include a field for Sentry DSN.
- Enhanced logging to include Sentry event filtering based on log levels.
- Implemented secret scrubbing to protect sensitive information in error reports.

* feat(analytics): add support for anonymized analytics settings

- Introduced new environment variable `OPENHUMAN_ANALYTICS_ENABLED` to enable or disable anonymized analytics and crash reports.
- Updated the PrivacyPanel component to sync analytics consent with the core configuration.
- Added new functions to handle analytics settings updates and retrieval in the Tauri backend.
- Enhanced observability configuration to include analytics settings, defaulting to enabled.
- Updated relevant schemas and handlers for analytics settings in the backend.

* refactor(tauriCommands): improve formatting and structure of analytics settings function

- Reformatted the `openhumanUpdateAnalyticsSettings` function for better readability by adjusting the parameter structure.
- Enhanced the output formatting in the `handle_get_analytics_settings` function for improved clarity in the JSON response.

* feat(analytics): sync analytics consent to core RPC and improve anonymization copy

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Steven Enamakel
2026-03-31 12:18:50 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 52b59f62d7
commit 906b55b63e
14 changed files with 856 additions and 36 deletions
+8
View File
@@ -111,6 +111,14 @@ OLLAMA_BIN=
# [optional] Override skills registry URL
SKILLS_REGISTRY_URL=
# ---------------------------------------------------------------------------
# Error Reporting (Sentry)
# ---------------------------------------------------------------------------
# [optional] Sentry DSN for Rust core error reporting (no PII is sent)
OPENHUMAN_SENTRY_DSN=
# [optional] Default: true — set to false to disable anonymized analytics & crash reports
OPENHUMAN_ANALYTICS_ENABLED=true
# ---------------------------------------------------------------------------
# Logging
# ---------------------------------------------------------------------------
+9 -9
View File
@@ -23,13 +23,13 @@ jobs:
- name: Run installer dry-run
run: bash scripts/install.sh --dry-run --verbose
smoke-windows:
name: Smoke install.ps1 (windows-latest)
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
# smoke-windows:
# name: Smoke install.ps1 (windows-latest)
# runs-on: windows-latest
# steps:
# - name: Checkout
# uses: actions/checkout@v4
- name: Run installer dry-run
shell: pwsh
run: ./scripts/install.ps1 -DryRun
# - name: Run installer dry-run
# shell: pwsh
# run: ./scripts/install.ps1 -DryRun
+4 -4
View File
@@ -221,10 +221,10 @@ jobs:
args: --target x86_64-unknown-linux-gnu
target: x86_64-unknown-linux-gnu
artifact_suffix: ubuntu
- platform: windows-latest
args: --target x86_64-pc-windows-msvc
target: x86_64-pc-windows-msvc
artifact_suffix: windows
# - platform: windows-latest
# args: --target x86_64-pc-windows-msvc
# target: x86_64-pc-windows-msvc
# artifact_suffix: windows
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
Generated
+569 -6
View File
@@ -14,6 +14,15 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "addr2line"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.1"
@@ -453,6 +462,21 @@ dependencies = [
"tokio",
]
[[package]]
name = "backtrace"
version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object 0.37.3",
"rustc-demangle",
"windows-link",
]
[[package]]
name = "base64"
version = "0.13.1"
@@ -594,6 +618,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "block2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
"objc2",
]
[[package]]
name = "bs58"
version = "0.5.1"
@@ -686,6 +719,12 @@ dependencies = [
"shlex",
]
[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cff-parser"
version = "0.1.0"
@@ -863,6 +902,16 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "combine"
version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
dependencies = [
"bytes",
"memchr",
]
[[package]]
name = "compact_str"
version = "0.9.0"
@@ -1289,6 +1338,16 @@ dependencies = [
"deadpool-runtime",
]
[[package]]
name = "debugid"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
dependencies = [
"serde",
"uuid",
]
[[package]]
name = "decancer"
version = "3.3.3"
@@ -1528,6 +1587,16 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "dispatch2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [
"bitflags 2.11.0",
"objc2",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
@@ -2410,7 +2479,7 @@ dependencies = [
"log",
"native-tls",
"rand 0.9.2",
"reqwest",
"reqwest 0.12.28",
"serde",
"serde_json",
"thiserror 2.0.18",
@@ -3074,6 +3143,50 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "jni"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
dependencies = [
"cesu8",
"cfg-if",
"combine",
"jni-sys 0.3.1",
"log",
"thiserror 1.0.69",
"walkdir",
"windows-sys 0.45.0",
]
[[package]]
name = "jni-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
dependencies = [
"jni-sys 0.4.1",
]
[[package]]
name = "jni-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
dependencies = [
"jni-sys-macros",
]
[[package]]
name = "jni-sys-macros"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
dependencies = [
"quote",
"syn 2.0.117",
]
[[package]]
name = "jobserver"
version = "0.1.34"
@@ -3518,7 +3631,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
"reqwest",
"reqwest 0.12.28",
"ruma",
"serde",
"serde_html_form",
@@ -4170,7 +4283,7 @@ dependencies = [
"getrandom 0.2.17",
"http 1.4.0",
"rand 0.8.5",
"reqwest",
"reqwest 0.12.28",
"serde",
"serde_json",
"serde_path_to_error",
@@ -4179,6 +4292,36 @@ dependencies = [
"url",
]
[[package]]
name = "objc2"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
dependencies = [
"objc2-encode",
]
[[package]]
name = "objc2-cloud-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-data"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
@@ -4186,6 +4329,95 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags 2.11.0",
"dispatch2",
"objc2",
]
[[package]]
name = "objc2-core-graphics"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [
"bitflags 2.11.0",
"dispatch2",
"objc2",
"objc2-core-foundation",
"objc2-io-surface",
]
[[package]]
name = "objc2-core-image"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-location"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-text"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-core-foundation",
"objc2-core-graphics",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "objc2-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags 2.11.0",
"block2",
"libc",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-surface"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-quartz-core"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
@@ -4197,6 +4429,37 @@ dependencies = [
"objc2-core-foundation",
]
[[package]]
name = "objc2-ui-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
dependencies = [
"bitflags 2.11.0",
"block2",
"objc2",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-core-image",
"objc2-core-location",
"objc2-core-text",
"objc2-foundation",
"objc2-quartz-core",
"objc2-user-notifications",
]
[[package]]
name = "objc2-user-notifications"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "object"
version = "0.37.3"
@@ -4308,7 +4571,7 @@ dependencies = [
"prost 0.14.3",
"rand 0.9.2",
"regex",
"reqwest",
"reqwest 0.12.28",
"ring",
"rppal",
"rquickjs",
@@ -4317,6 +4580,7 @@ dependencies = [
"rustls-pki-types",
"rustyline",
"schemars",
"sentry",
"serde",
"serde-big-array",
"serde_json",
@@ -4417,7 +4681,7 @@ dependencies = [
"bytes",
"http 1.4.0",
"opentelemetry",
"reqwest",
"reqwest 0.12.28",
]
[[package]]
@@ -4432,7 +4696,7 @@ dependencies = [
"opentelemetry-proto",
"opentelemetry_sdk",
"prost 0.14.3",
"reqwest",
"reqwest 0.12.28",
"thiserror 2.0.18",
]
@@ -4489,6 +4753,22 @@ version = "2.0.0-rc.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06503bb33f294c5f1ba484011e053bfa6ae227074bdb841e9863492dc5960d4b"
[[package]]
name = "os_info"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224"
dependencies = [
"android_system_properties",
"log",
"nix 0.30.1",
"objc2",
"objc2-foundation",
"objc2-ui-kit",
"serde",
"windows-sys 0.61.2",
]
[[package]]
name = "overload"
version = "0.1.1"
@@ -5124,6 +5404,7 @@ version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
dependencies = [
"aws-lc-rs",
"bytes",
"getrandom 0.3.4",
"lru-slab",
@@ -5468,6 +5749,46 @@ dependencies = [
"webpki-roots 1.0.6",
]
[[package]]
name = "reqwest"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
dependencies = [
"base64 0.22.1",
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http 1.4.0",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-util",
"js-sys",
"log",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls",
"rustls-pki-types",
"rustls-platform-verifier",
"serde",
"serde_json",
"sync_wrapper",
"tokio",
"tokio-rustls",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "ring"
version = "0.17.14"
@@ -5728,6 +6049,12 @@ dependencies = [
"smallvec",
]
[[package]]
name = "rustc-demangle"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
[[package]]
name = "rustc-hash"
version = "2.1.2"
@@ -5807,6 +6134,33 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-platform-verifier"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
dependencies = [
"core-foundation 0.10.1",
"core-foundation-sys",
"jni",
"log",
"once_cell",
"rustls",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki",
"security-framework",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.103.10"
@@ -5873,6 +6227,15 @@ dependencies = [
"serde_json",
]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.29"
@@ -5965,6 +6328,103 @@ version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "sentry"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb25f439f97d26fea01d717fa626167ceffcd981addaa670001e70505b72acbb"
dependencies = [
"cfg_aliases",
"httpdate",
"reqwest 0.13.2",
"rustls",
"sentry-backtrace",
"sentry-contexts",
"sentry-core",
"sentry-panic",
"sentry-tracing",
"tokio",
"ureq 3.3.0",
]
[[package]]
name = "sentry-backtrace"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46a8c2c1bd5c1f735e84f28b48e7d72efcaafc362b7541bc8253e60e8fcdffc6"
dependencies = [
"backtrace",
"regex",
"sentry-core",
]
[[package]]
name = "sentry-contexts"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b88a90baa654d7f0e1f4b667f6b434293d9f72c71bef16b197c76af5b7d5803"
dependencies = [
"hostname",
"libc",
"os_info",
"rustc_version",
"sentry-core",
"uname",
]
[[package]]
name = "sentry-core"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ac170a5bba8bec6e3339c90432569d89641fa7a3d3e4f44987d24f0762e6adf"
dependencies = [
"rand 0.9.2",
"sentry-types",
"serde",
"serde_json",
"url",
]
[[package]]
name = "sentry-panic"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6127d3d304ba5ce0409401e85aae538e303a569f8dbb031bf64f9ba0f7174346"
dependencies = [
"sentry-backtrace",
"sentry-core",
]
[[package]]
name = "sentry-tracing"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27701acc51e68db5281802b709010395bfcbcb128b1d0a4e5873680d3b47ff0c"
dependencies = [
"bitflags 2.11.0",
"sentry-backtrace",
"sentry-core",
"tracing-core",
"tracing-subscriber",
]
[[package]]
name = "sentry-types"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56780cb5597d676bf22e6c11d1f062eb4def46390ea3bfb047bcbcf7dfd19bdb"
dependencies = [
"debugid",
"hex",
"rand 0.9.2",
"serde",
"serde_json",
"thiserror 2.0.18",
"time",
"url",
"uuid",
]
[[package]]
name = "serde"
version = "1.0.228"
@@ -7255,6 +7715,15 @@ dependencies = [
"web-time",
]
[[package]]
name = "uname"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8"
dependencies = [
"libc",
]
[[package]]
name = "unescaper"
version = "0.1.8"
@@ -7741,6 +8210,16 @@ dependencies = [
"wa-rs-core",
]
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.1"
@@ -7965,6 +8444,15 @@ dependencies = [
"url",
]
[[package]]
name = "webpki-root-certs"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "webpki-roots"
version = "0.26.11"
@@ -8024,6 +8512,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@@ -8153,6 +8650,15 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
@@ -8198,6 +8704,21 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
@@ -8246,6 +8767,12 @@ dependencies = [
"windows_x86_64_msvc 0.53.1",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
@@ -8264,6 +8791,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
@@ -8282,6 +8815,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
@@ -8312,6 +8851,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
@@ -8330,6 +8875,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
@@ -8348,6 +8899,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
@@ -8366,6 +8923,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
+1
View File
@@ -82,6 +82,7 @@ tower = { version = "0.5", default-features = false }
opentelemetry = { version = "0.31", default-features = false, features = ["trace", "metrics"] }
opentelemetry_sdk = { version = "0.31", default-features = false, features = ["trace", "metrics"] }
opentelemetry-otlp = { version = "0.31", default-features = false, features = ["trace", "metrics", "http-proto", "reqwest-client", "reqwest-rustls-webpki-roots"] }
sentry = { version = "0.47.0", default-features = false, features = ["backtrace", "contexts", "panic", "tracing", "reqwest", "rustls"] }
tokio-stream = { version = "0.1.18", features = ["full"] }
url = "2"
socketioxide = { version = "0.15", features = ["extensions"] }
@@ -1,6 +1,7 @@
import { syncAnalyticsConsent } from '../../../services/analytics';
import { setAnalyticsForUser } from '../../../store/authSlice';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
import { isTauri, openhumanUpdateAnalyticsSettings } from '../../../utils/tauriCommands';
import SettingsHeader from '../components/SettingsHeader';
import { useSettingsNavigation } from '../hooks/useSettingsNavigation';
@@ -19,6 +20,13 @@ const PrivacyPanel = () => {
const newValue = !analyticsEnabled;
dispatch(setAnalyticsForUser({ userId: user._id, enabled: newValue }));
syncAnalyticsConsent(newValue);
// Sync to core config so the Rust process also respects the setting
if (isTauri()) {
openhumanUpdateAnalyticsSettings({ enabled: newValue }).catch(() => {
/* best-effort */
});
}
};
return (
@@ -30,7 +38,7 @@ const PrivacyPanel = () => {
{/* Analytics Section */}
<div>
<h3 className="text-xs font-semibold uppercase tracking-wider text-stone-400 mb-3 px-1">
Analytics
Anonymized Analytics
</h3>
<div className="bg-stone-800/50 rounded-xl border border-stone-700/50 overflow-hidden">
<div className="flex items-center justify-between p-4">
@@ -38,7 +46,8 @@ const PrivacyPanel = () => {
<p className="text-sm font-medium text-white">Share Anonymized Usage Data</p>
<p className="text-xs text-stone-400 mt-1 leading-relaxed">
Help improve OpenHuman by sharing anonymous crash reports and usage analytics.
No personal data, messages, or wallet information is ever collected.
All data is fully anonymized &mdash; no personal data, messages, wallet keys, or
session information is ever collected.
</p>
</div>
<button
@@ -73,9 +82,10 @@ const PrivacyPanel = () => {
</svg>
<div>
<p className="text-xs text-stone-400 leading-relaxed">
When enabled, we collect only crash information, device type, and the file
location of errors. We never access your messages, session data, wallet keys, or
any personally identifiable information.
All analytics and bug reports are fully anonymized. When enabled, we collect only
crash information, device type, and the file location of errors. We never access
your messages, session data, wallet keys, API keys, or any personally identifiable
information. You can change this setting at any time.
</p>
</div>
</div>
@@ -1,5 +1,7 @@
import { useState } from 'react';
import { isTauri, openhumanUpdateAnalyticsSettings } from '../../../utils/tauriCommands';
interface AnalyticsStepProps {
onNext: (analyticsEnabled: boolean) => void;
}
@@ -7,13 +9,26 @@ interface AnalyticsStepProps {
const AnalyticsStep = ({ onNext }: AnalyticsStepProps) => {
const [selectedOption, setSelectedOption] = useState('shareAnalytics');
const handleContinue = () => {
const enabled = selectedOption === 'shareAnalytics';
// Sync to core config so the Rust process also respects the setting
if (isTauri()) {
openhumanUpdateAnalyticsSettings({ enabled }).catch(() => {
/* best-effort */
});
}
onNext(enabled);
};
return (
<div className="glass rounded-3xl p-8 shadow-large animate-fade-up">
<div className="text-center mb-4">
<h1 className="text-xl font-bold mb-2">Analytics</h1>
<h1 className="text-xl font-bold mb-2">Anonymized Analytics</h1>
<p className="opacity-70 text-sm">
We collect anonymized usage data to help us improve the app for you and for others. You
can choose to skip this.
We collect fully anonymized usage data to help improve the app. No personal data,
messages, or wallet keys are ever collected. You can change this anytime in Settings.
</p>
</div>
@@ -41,8 +56,8 @@ const AnalyticsStep = ({ onNext }: AnalyticsStepProps) => {
<div>
<h3 className="font-semibold mb-1 text-sm">Share Anonymized Usage Data</h3>
<p className="opacity-70 text-xs leading-relaxed">
Share anonymized usage data to help us improve features and performance of the app.
This helps us improve the app for you and for others.
Share anonymized crash reports and usage analytics to help us improve features and
performance. All data is fully anonymized.
</p>
</div>
</div>
@@ -71,8 +86,8 @@ const AnalyticsStep = ({ onNext }: AnalyticsStepProps) => {
<div>
<h3 className="font-semibold mb-1 text-sm">Don't Collect Anything</h3>
<p className="opacity-70 text-xs leading-relaxed">
We won't collect any usage analytics, ensuring total anonymity. Keep all your data
completely private.
We won't collect any usage analytics or crash reports. Keep all your data completely
private.
</p>
</div>
</div>
@@ -91,14 +106,14 @@ const AnalyticsStep = ({ onNext }: AnalyticsStepProps) => {
<div>
<p className="font-medium text-sm">You can change this setting anytime</p>
<p className="opacity-70 text-xs mt-1">
Your privacy preferences can be updated in your account settings
Your privacy preferences can be updated in Settings &gt; Privacy &amp; Security
</p>
</div>
</div>
</div>
<button
onClick={() => onNext(selectedOption === 'shareAnalytics')}
onClick={handleContinue}
className="btn-primary w-full py-2.5 text-sm font-medium rounded-xl">
Continue
</button>
+23
View File
@@ -1065,6 +1065,29 @@ export async function openhumanUpdateScreenIntelligenceSettings(
});
}
export async function openhumanUpdateAnalyticsSettings(update: {
enabled?: boolean;
}): Promise<CommandResponse<ConfigSnapshot>> {
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await callCoreRpc<CommandResponse<ConfigSnapshot>>({
method: 'openhuman.update_analytics_settings',
params: update,
});
}
export async function openhumanGetAnalyticsSettings(): Promise<
CommandResponse<{ enabled: boolean }>
> {
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await callCoreRpc<CommandResponse<{ enabled: boolean }>>({
method: 'openhuman.get_analytics_settings',
});
}
export async function openhumanGetRuntimeFlags(): Promise<CommandResponse<RuntimeFlags>> {
if (!isTauri()) {
throw new Error('Not running in Tauri');
+20 -3
View File
@@ -10,7 +10,9 @@ use nu_ansi_term::{Color, Style};
use tracing::{Event, Level};
use tracing_subscriber::fmt::format::{FormatEvent, FormatFields, Writer};
use tracing_subscriber::fmt::FmtContext;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::util::SubscriberInitExt;
static INIT: Once = Once::new();
@@ -89,10 +91,25 @@ pub fn init_for_cli_run(verbose: bool) {
let use_color = io::stderr().is_terminal();
let _ = tracing_subscriber::fmt()
let fmt_layer = tracing_subscriber::fmt::layer()
.with_ansi(use_color)
.with_env_filter(filter)
.event_format(CleanCliFormat)
.event_format(CleanCliFormat);
let sentry_layer =
sentry::integrations::tracing::layer().event_filter(|md: &tracing::Metadata<'_>| {
match *md.level() {
Level::ERROR => sentry::integrations::tracing::EventFilter::Event,
Level::WARN | Level::INFO => {
sentry::integrations::tracing::EventFilter::Breadcrumb
}
_ => sentry::integrations::tracing::EventFilter::Ignore,
}
});
let _ = tracing_subscriber::registry()
.with(filter)
.with(fmt_layer)
.with(sentry_layer)
.try_init();
let _ = tracing_log::LogTracer::init();
+62
View File
@@ -1,7 +1,69 @@
use once_cell::sync::Lazy;
use regex::Regex;
fn main() {
// Initialize Sentry as the very first operation so the guard outlives everything.
// If OPENHUMAN_SENTRY_DSN is unset or empty, sentry::init returns a no-op guard.
let _sentry_guard = sentry::init(sentry::ClientOptions {
dsn: std::env::var("OPENHUMAN_SENTRY_DSN")
.ok()
.filter(|s| !s.is_empty())
.and_then(|s| s.parse().ok()),
release: Some(std::borrow::Cow::Borrowed(env!("CARGO_PKG_VERSION"))),
environment: Some(if cfg!(debug_assertions) {
"development".into()
} else {
"production".into()
}),
send_default_pii: false,
before_send: Some(std::sync::Arc::new(|mut event| {
// Strip server_name (hostname) to avoid leaking machine identity
event.server_name = None;
// Strip user context entirely
event.user = None;
// Scrub exception messages for secrets
for exc in &mut event.exception.values {
if let Some(ref value) = exc.value {
exc.value = Some(scrub_secrets(value));
}
}
Some(event)
})),
sample_rate: 1.0,
..sentry::ClientOptions::default()
});
let args: Vec<String> = std::env::args().skip(1).collect();
if let Err(err) = openhuman_core::run_core_from_args(&args) {
eprintln!("{err}");
std::process::exit(1);
}
}
// ---------------------------------------------------------------------------
// Secret scrubbing
// ---------------------------------------------------------------------------
static SECRET_PATTERNS: Lazy<Vec<(Regex, &'static str)>> = Lazy::new(|| {
vec![
(Regex::new(r"(?i)(bearer\s+)\S+").unwrap(), "${1}[REDACTED]"),
(
Regex::new(r"(?i)(api[_-]?key[=:\s]+)\S+").unwrap(),
"${1}[REDACTED]",
),
(
Regex::new(r"(?i)(token[=:\s]+)\S+").unwrap(),
"${1}[REDACTED]",
),
(Regex::new(r"sk-[a-zA-Z0-9]{20,}").unwrap(), "[REDACTED]"),
]
});
/// Replace patterns that look like secrets with `[REDACTED]`.
fn scrub_secrets(input: &str) -> String {
let mut result = input.to_string();
for (re, replacement) in SECRET_PATTERNS.iter() {
result = re.replace_all(&result, *replacement).into_owned();
}
result
}
+30
View File
@@ -88,6 +88,11 @@ pub struct ScreenIntelligenceSettingsPatch {
pub denylist: Option<Vec<String>>,
}
#[derive(Debug, Clone, Default)]
pub struct AnalyticsSettingsPatch {
pub enabled: Option<bool>,
}
#[derive(Debug, Clone, Serialize)]
pub struct RuntimeFlagsOut {
pub browser_allow_all: bool,
@@ -313,6 +318,31 @@ pub async fn load_and_apply_runtime_settings(
apply_runtime_settings(&mut config, update).await
}
pub async fn apply_analytics_settings(
config: &mut Config,
update: AnalyticsSettingsPatch,
) -> Result<RpcOutcome<serde_json::Value>, String> {
if let Some(enabled) = update.enabled {
config.observability.analytics_enabled = enabled;
}
config.save().await.map_err(|e| e.to_string())?;
let snapshot = snapshot_config_json(config)?;
Ok(RpcOutcome::new(
snapshot,
vec![format!(
"analytics settings saved to {}",
config.config_path.display()
)],
))
}
pub async fn load_and_apply_analytics_settings(
update: AnalyticsSettingsPatch,
) -> Result<RpcOutcome<serde_json::Value>, String> {
let mut config = load_config_with_timeout().await?;
apply_analytics_settings(&mut config, update).await
}
pub async fn load_and_apply_browser_settings(
update: BrowserSettingsPatch,
) -> Result<RpcOutcome<serde_json::Value>, String> {
+16
View File
@@ -592,6 +592,22 @@ impl Config {
}
}
if let Ok(dsn) = std::env::var("OPENHUMAN_SENTRY_DSN") {
let dsn = dsn.trim();
if !dsn.is_empty() {
self.observability.sentry_dsn = Some(dsn.to_string());
}
}
if let Ok(flag) = std::env::var("OPENHUMAN_ANALYTICS_ENABLED") {
let normalized = flag.trim().to_ascii_lowercase();
match normalized.as_str() {
"1" | "true" | "yes" | "on" => self.observability.analytics_enabled = true,
"0" | "false" | "no" | "off" => self.observability.analytics_enabled = false,
_ => {}
}
}
if self.proxy.enabled && self.proxy.scope == ProxyScope::Environment {
self.proxy.apply_to_process_env();
}
@@ -15,6 +15,19 @@ pub struct ObservabilityConfig {
/// Service name reported to the OTel collector. Defaults to "openhuman".
#[serde(default)]
pub otel_service_name: Option<String>,
/// Sentry DSN for error reporting. Overridden by `OPENHUMAN_SENTRY_DSN` env var.
#[serde(default)]
pub sentry_dsn: Option<String>,
/// Whether anonymized analytics and error reporting is enabled.
/// Defaults to `true`. Users can disable via settings or CLI.
#[serde(default = "default_analytics_enabled")]
pub analytics_enabled: bool,
}
fn default_analytics_enabled() -> bool {
true
}
impl Default for ObservabilityConfig {
@@ -23,6 +36,8 @@ impl Default for ObservabilityConfig {
backend: "none".into(),
otel_endpoint: None,
otel_service_name: None,
sentry_dsn: None,
analytics_enabled: true,
}
}
}
+60
View File
@@ -50,6 +50,11 @@ struct ScreenIntelligenceSettingsUpdate {
denylist: Option<Vec<String>>,
}
#[derive(Debug, Deserialize)]
struct AnalyticsSettingsUpdate {
enabled: Option<bool>,
}
#[derive(Debug, Deserialize)]
struct SetBrowserAllowAllParams {
enabled: bool,
@@ -80,6 +85,8 @@ pub fn all_controller_schemas() -> Vec<ControllerSchema> {
schemas("set_browser_allow_all"),
schemas("workspace_onboarding_flag_exists"),
schemas("workspace_onboarding_flag_set"),
schemas("update_analytics_settings"),
schemas("get_analytics_settings"),
schemas("agent_server_status"),
]
}
@@ -134,6 +141,14 @@ pub fn all_registered_controllers() -> Vec<RegisteredController> {
schema: schemas("workspace_onboarding_flag_set"),
handler: handle_workspace_onboarding_flag_set,
},
RegisteredController {
schema: schemas("update_analytics_settings"),
handler: handle_update_analytics_settings,
},
RegisteredController {
schema: schemas("get_analytics_settings"),
handler: handle_get_analytics_settings,
},
RegisteredController {
schema: schemas("agent_server_status"),
handler: handle_agent_server_status,
@@ -363,6 +378,28 @@ pub fn schemas(function: &str) -> ControllerSchema {
required: true,
}],
},
"update_analytics_settings" => ControllerSchema {
namespace: "config",
function: "update_analytics_settings",
description: "Enable or disable anonymized analytics and error reporting.",
inputs: vec![optional_bool(
"enabled",
"Enable anonymized analytics and crash reports.",
)],
outputs: vec![json_output("snapshot", "Updated config snapshot.")],
},
"get_analytics_settings" => ControllerSchema {
namespace: "config",
function: "get_analytics_settings",
description: "Read current analytics settings.",
inputs: vec![],
outputs: vec![FieldSchema {
name: "enabled",
ty: TypeSchema::Bool,
comment: "Whether anonymized analytics is enabled.",
required: true,
}],
},
"agent_server_status" => ControllerSchema {
namespace: "config",
function: "agent_server_status",
@@ -503,6 +540,29 @@ fn handle_workspace_onboarding_flag_set(params: Map<String, Value>) -> Controlle
})
}
fn handle_update_analytics_settings(params: Map<String, Value>) -> ControllerFuture {
Box::pin(async move {
let update = deserialize_params::<AnalyticsSettingsUpdate>(params)?;
let patch = config_rpc::AnalyticsSettingsPatch {
enabled: update.enabled,
};
to_json(config_rpc::load_and_apply_analytics_settings(patch).await?)
})
}
fn handle_get_analytics_settings(_params: Map<String, Value>) -> ControllerFuture {
Box::pin(async {
let config = config_rpc::load_config_with_timeout().await?;
let result = serde_json::json!({
"enabled": config.observability.analytics_enabled,
});
to_json(RpcOutcome::new(
result,
vec!["analytics settings read".to_string()],
))
})
}
fn handle_agent_server_status(_params: Map<String, Value>) -> ControllerFuture {
Box::pin(async { to_json(config_rpc::agent_server_status()) })
}