diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc4676f83..342f48721 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -464,6 +464,8 @@ jobs: CORE_MANIFEST: ${{ steps.core-paths.outputs.core_manifest }} CORE_BIN_NAME: ${{ steps.core-paths.outputs.core_bin_name }} OPENHUMAN_APP_ENV: ${{ inputs.build_target == 'staging' && 'staging' || 'production' }} + # Core (Rust openhuman sidecar) Sentry DSN — separate Sentry project + # from the React frontend and the Tauri shell. OPENHUMAN_SENTRY_DSN: ${{ vars.OPENHUMAN_CORE_SENTRY_DSN }} # Sentry release tracking (#405): `option_env!("OPENHUMAN_BUILD_SHA")` # in src/main.rs bakes the short SHA into the release tag @@ -511,7 +513,17 @@ jobs: # (`vite build`) bakes Sentry + debug flags into the final bundle. # Vite is invoked via tauri.conf.json's beforeBuildCommand during # `cargo tauri build`, so these env vars must be present here. + # React frontend Sentry DSN — separate Sentry project. VITE_SENTRY_DSN: ${{ vars.OPENHUMAN_REACT_SENTRY_DSN }} + # Tauri shell (desktop host) Sentry DSN — separate Sentry project + # from the React frontend and the Rust core. Baked into the shell + # binary via `option_env!("OPENHUMAN_TAURI_SENTRY_DSN")` at compile + # time so the released desktop app reports to its own project. + OPENHUMAN_TAURI_SENTRY_DSN: ${{ vars.OPENHUMAN_TAURI_SENTRY_DSN }} + # Bake the build SHA into the Tauri shell so its Sentry release tag + # (`openhuman@+`) matches the React bundle and core + # sidecar — events across all three surfaces group under one release. + OPENHUMAN_BUILD_SHA: ${{ needs.prepare-build.outputs.sha }} VITE_DEBUG: ${{ vars.VITE_DEBUG }} # Sentry release tracking (#405) — must match the Vite build triggered # by Tauri so source maps uploaded there resolve correctly against the @@ -522,7 +534,12 @@ jobs: needs.prepare-build.outputs.sha }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: ${{ vars.SENTRY_ORG }} - SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT_FRONTEND }} + # SENTRY_PROJECT here is consumed by sentry-vite-plugin during the + # Vite build that runs inside `cargo tauri build` (beforeBuildCommand) + # — it uploads frontend source maps to the React Sentry project. + # Core and Tauri-shell debug symbols are uploaded to their own + # projects in the dedicated steps below. + SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT_REACT }} MACOSX_DEPLOYMENT_TARGET: ${{ matrix.settings.platform == 'macos-latest' && '10.15' || '' }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD || secrets.UPDATER_PRIVATE_KEY_PASSWORD }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY || secrets.UPDATER_PRIVATE_KEY }} @@ -540,9 +557,11 @@ jobs: # Upload Rust debug info to Sentry so backend + Tauri-shell stack traces # symbolicate in production. The frontend source maps are handled by # sentry-vite-plugin in the build step above; this is the Rust half. - # Keyed by debug-ID, so it's safe to run per-matrix-target without - # collisions — Sentry merges artifacts across platforms. - - name: Upload Rust debug symbols to Sentry + # Core sidecar and Tauri shell go to **separate Sentry projects** + # (matching their separate DSNs) so events show up in the right place. + # Symbols are keyed by debug-ID, so it's safe to run per-matrix-target + # without collisions — Sentry merges artifacts across platforms. + - name: Upload core sidecar debug symbols to Sentry if: needs.prepare-build.outputs.release_enabled == 'true' && env.SENTRY_AUTH_TOKEN != '' @@ -550,24 +569,53 @@ jobs: env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: ${{ vars.SENTRY_ORG }} - SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }} + SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT_CORE }} + # Must match the release tag the running core binary reports + # (`openhuman@+`, see `build_release_tag` in + # src/main.rs). If this drifts, DIFs attach to a different + # release than events and stack traces stay un-symbolicated. + SENTRY_RELEASE: + openhuman@${{ needs.prepare-build.outputs.version }}+${{ + needs.prepare-build.outputs.sha }} VERSION: ${{ needs.prepare-build.outputs.version }} MATRIX_TARGET: ${{ matrix.settings.target }} run: | set -euo pipefail - # Core crate deps (sidecar symbols) + Tauri shell deps (app binary - # symbols) both live under the per-target release dir. upload-dif - # scans recursively. - for deps_dir in \ - "target/${MATRIX_TARGET}/release/deps" \ - "app/src-tauri/target/${MATRIX_TARGET}/release/deps"; do - if [ -d "$deps_dir" ]; then - echo "==> Uploading symbols from $deps_dir" - bash scripts/upload_sentry_symbols.sh "$VERSION" "$deps_dir" - else - echo "==> Skipping $deps_dir (not present)" - fi - done + deps_dir="target/${MATRIX_TARGET}/release/deps" + if [ -d "$deps_dir" ]; then + echo "==> Uploading core symbols from $deps_dir to ${SENTRY_PROJECT}" + bash scripts/upload_sentry_symbols.sh "$VERSION" "$deps_dir" + else + echo "==> Skipping $deps_dir (not present)" + fi + - name: Upload Tauri shell debug symbols to Sentry + if: + needs.prepare-build.outputs.release_enabled == 'true' && env.SENTRY_AUTH_TOKEN + != '' + shell: bash + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ vars.SENTRY_ORG }} + SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT_TAURI }} + # Must match the release tag the running shell binary reports + # (`openhuman@+`, see `build_sentry_release_tag` in + # app/src-tauri/src/lib.rs). If this drifts, DIFs attach to a + # different release than events and stack traces stay + # un-symbolicated. + SENTRY_RELEASE: + openhuman@${{ needs.prepare-build.outputs.version }}+${{ + needs.prepare-build.outputs.sha }} + VERSION: ${{ needs.prepare-build.outputs.version }} + MATRIX_TARGET: ${{ matrix.settings.target }} + run: | + set -euo pipefail + deps_dir="app/src-tauri/target/${MATRIX_TARGET}/release/deps" + if [ -d "$deps_dir" ]; then + echo "==> Uploading Tauri shell symbols from $deps_dir to ${SENTRY_PROJECT}" + bash scripts/upload_sentry_symbols.sh "$VERSION" "$deps_dir" + else + echo "==> Skipping $deps_dir (not present)" + fi # tauri-action previously uploaded non-macOS installer assets directly # to the release. Replicate that for Linux + Windows here (macOS goes diff --git a/app/src-tauri/Cargo.lock b/app/src-tauri/Cargo.lock index 9eee180da..3dba4655e 100644 --- a/app/src-tauri/Cargo.lock +++ b/app/src-tauri/Cargo.lock @@ -18,7 +18,7 @@ dependencies = [ "futures-util", "log", "mac-notification-sys", - "nix", + "nix 0.29.0", "notify-rust", "objc2", "objc2-app-kit", @@ -29,6 +29,7 @@ dependencies = [ "rusqlite", "rustls", "semver", + "sentry", "serde", "serde_json", "tar", @@ -47,6 +48,15 @@ dependencies = [ "zip 2.4.2", ] +[[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" @@ -302,6 +312,43 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[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", + "rustc-demangle", + "windows-link 0.2.1", +] + [[package]] name = "base64" version = "0.21.7" @@ -485,6 +532,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -902,6 +951,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid", +] + [[package]] name = "deranged" version = "0.5.8" @@ -1391,6 +1450,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futf" version = "0.1.5" @@ -1408,6 +1473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1637,6 +1703,12 @@ dependencies = [ "wasip3", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "gio" version = "0.18.4" @@ -1803,6 +1875,25 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1863,6 +1954,17 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hostname" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" +dependencies = [ + "cfg-if", + "libc", + "windows-link 0.2.1", +] + [[package]] name = "html5ever" version = "0.29.1" @@ -1924,6 +2026,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.8.1" @@ -1934,6 +2042,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -2316,6 +2425,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.92" @@ -2683,6 +2802,18 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -2954,9 +3085,18 @@ 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]] @@ -2988,6 +3128,15 @@ dependencies = [ "objc2-security", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -3044,6 +3193,22 @@ dependencies = [ "pin-project-lite", ] +[[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 = "osakit" version = "0.3.1" @@ -3553,6 +3718,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", @@ -3854,8 +4020,10 @@ checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64 0.22.1", "bytes", + "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", @@ -3866,6 +4034,7 @@ dependencies = [ "log", "percent-encoding", "pin-project-lite", + "quinn", "rustls", "rustls-pki-types", "rustls-platform-verifier", @@ -3923,6 +4092,12 @@ dependencies = [ "ordered-multimap", ] +[[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" @@ -3957,6 +4132,7 @@ version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", @@ -4021,6 +4197,7 @@ version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -4171,6 +4348,103 @@ dependencies = [ "serde_core", ] +[[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", +] + +[[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" @@ -5428,6 +5702,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "tracing-core", ] [[package]] @@ -5499,6 +5783,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "uname" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" +dependencies = [ + "libc", +] + [[package]] name = "unic-char-property" version = "0.9.0" @@ -5670,6 +5963,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/app/src-tauri/Cargo.toml b/app/src-tauri/Cargo.toml index fb36522ec..495a1c89a 100644 --- a/app/src-tauri/Cargo.toml +++ b/app/src-tauri/Cargo.toml @@ -90,6 +90,14 @@ zip = { version = "2", default-features = false, features = ["deflate"] } log = "0.4" env_logger = "0.11" +# Sentry for the Tauri shell (desktop host) process — separate Sentry project +# from the React frontend and the Rust core sidecar. DSN is baked at compile +# time via `option_env!("OPENHUMAN_TAURI_SENTRY_DSN")` in `lib.rs::run()` and +# can be overridden at runtime via the same env var. Feature set mirrors the +# core sidecar (`Cargo.toml` at repo root) minus `tracing` since the shell +# uses `log` + `env_logger`. +sentry = { version = "0.47.0", default-features = false, features = ["backtrace", "contexts", "panic", "reqwest", "rustls"] } + # Used by the imessage_scanner module. anyhow = "1.0" parking_lot = "0.12" diff --git a/app/src-tauri/src/lib.rs b/app/src-tauri/src/lib.rs index 6096aa0f2..fca2b3d06 100644 --- a/app/src-tauri/src/lib.rs +++ b/app/src-tauri/src/lib.rs @@ -870,6 +870,56 @@ fn teardown_cef_prewarm(app: &AppHandle) -> Result<(), Str } pub fn run() { + // Initialize Sentry for the Tauri shell (desktop host) process before any + // other startup work. Reads `OPENHUMAN_TAURI_SENTRY_DSN` at runtime first, + // then falls back to the value baked in at compile time via the release + // workflow. Missing/empty DSN ⇒ `sentry::init` returns a no-op guard. + // + // The guard is held for the entire lifetime of `run()` so events queued + // during shutdown still flush. Only invoked here (and not in `main.rs`) + // so renderer/GPU CEF helper subprocesses (re-exec'd via + // `tauri::cef_entry_point`) and the `OpenHuman core …` in-process core + // path do NOT spin up a second client — those have their own reporting + // surfaces. + let _sentry_guard = sentry::init(sentry::ClientOptions { + dsn: std::env::var("OPENHUMAN_TAURI_SENTRY_DSN") + .ok() + .filter(|s| !s.is_empty()) + .or_else(|| option_env!("OPENHUMAN_TAURI_SENTRY_DSN").map(|s| s.to_string())) + .filter(|s| !s.is_empty()) + .and_then(|s| s.parse().ok()), + release: Some(std::borrow::Cow::Owned(build_sentry_release_tag())), + environment: Some(std::borrow::Cow::Owned(resolve_sentry_environment())), + 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; + event.user = None; + Some(event) + })), + sample_rate: 1.0, + ..sentry::ClientOptions::default() + }); + + // Optional smoke trigger for verifying the Sentry pipeline end-to-end. + // Run with `OPENHUMAN_TAURI_SENTRY_TEST=panic` to fire a panic, or + // `=message` to send a captured-message event. No-op when unset. + if let Ok(mode) = std::env::var("OPENHUMAN_TAURI_SENTRY_TEST") { + match mode.as_str() { + "panic" => panic!("OPENHUMAN_TAURI_SENTRY_TEST=panic — local Sentry smoke test"), + "message" => { + sentry::capture_message( + "OPENHUMAN_TAURI_SENTRY_TEST=message — local Sentry smoke test", + sentry::Level::Error, + ); + let _ = sentry::Hub::current().client().map(|c| c.flush(None)); + } + other => log::warn!( + "OPENHUMAN_TAURI_SENTRY_TEST={other:?} — unknown mode (use 'panic' or 'message')" + ), + } + } + let daemon_mode = is_daemon_mode(); let default_filter = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()); @@ -1575,6 +1625,48 @@ pub fn run_core_from_args(args: &[String]) -> Result<(), String> { Ok(()) } +// --------------------------------------------------------------------------- +// Sentry release / environment resolution (Tauri shell) +// --------------------------------------------------------------------------- + +/// Canonical release tag: `openhuman@[+]`. +/// +/// Mirrors `build_release_tag` in the core sidecar's `src/main.rs` and the +/// `SENTRY_RELEASE` value computed in `app/vite.config.ts` so events from +/// every surface (React frontend, core sidecar, Tauri shell) group under the +/// same release in Sentry and benefit from the same source-map / debug-info +/// upload. +fn build_sentry_release_tag() -> String { + let version = env!("CARGO_PKG_VERSION"); + let sha = option_env!("OPENHUMAN_BUILD_SHA").unwrap_or("").trim(); + let sha_short: String = sha.chars().take(12).collect(); + if sha_short.is_empty() { + format!("openhuman@{version}") + } else { + format!("openhuman@{version}+{sha_short}") + } +} + +/// Resolve the Sentry environment tag from `OPENHUMAN_APP_ENV` (runtime) or +/// `VITE_OPENHUMAN_APP_ENV` (compile-time fallback). Defaults to +/// `production` so unmarked release builds don't pollute the dev/staging +/// streams. +fn resolve_sentry_environment() -> String { + if let Ok(value) = std::env::var("OPENHUMAN_APP_ENV") { + let trimmed = value.trim(); + if !trimmed.is_empty() { + return trimmed.to_string(); + } + } + if let Some(value) = option_env!("VITE_OPENHUMAN_APP_ENV") { + let trimmed = value.trim(); + if !trimmed.is_empty() { + return trimmed.to_string(); + } + } + "production".to_string() +} + #[cfg(test)] mod tests { use super::*; diff --git a/app/vite.config.ts b/app/vite.config.ts index 2e749882f..c1257d926 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -39,12 +39,22 @@ function maybeSentryPlugin(): PluginOption | null { project: process.env.SENTRY_PROJECT, release: { name: computeSentryRelease() }, sourcemaps: { - // Vite emits hashed asset files under dist/assets/ — upload every + // Vite emits hashed asset files into `app/dist/assets/`. Upload every // .js / .map the build produces. - assets: ["../dist/**/*.js", "../dist/**/*.map"], + // + // `assets` is resolved by sentry-vite-plugin against `process.cwd()`, + // not the Vite `root` — so a relative path like `../dist/**` would + // miss when `pnpm tauri build` runs with cwd=`app/` and silently emit + // `Didn't find any matching sources for debug ID upload`. Use absolute + // paths anchored at this config file's directory (`app/`) to be + // immune to whatever cwd the parent process sets. + assets: [ + resolve(__dirname, "dist/**/*.js"), + resolve(__dirname, "dist/**/*.map"), + ], // Never ship raw .map files to end users; the upload keeps a copy // server-side for symbolication while the bundled app strips them. - filesToDeleteAfterUpload: ["../dist/**/*.map"], + filesToDeleteAfterUpload: [resolve(__dirname, "dist/**/*.map")], }, // Release tagging + commits are handled by sentry-cli / the plugin // itself when AUTH_TOKEN and CI env (GITHUB_SHA etc.) are present. diff --git a/scripts/upload_sentry_symbols.sh b/scripts/upload_sentry_symbols.sh index 553a4d23e..a81b0f716 100644 --- a/scripts/upload_sentry_symbols.sh +++ b/scripts/upload_sentry_symbols.sh @@ -169,7 +169,14 @@ upload_symbols() { exit 1 fi - local release_name="openhuman@${version}" + # Honor SENTRY_RELEASE if set so DIFs attach to the same release name + # the running binaries report (`openhuman@+`). Without this, + # CI uploads to `openhuman@` while events are tagged + # `openhuman@+` — a different release, so Sentry never + # joins frames to symbols and stack traces stay un-symbolicated. + # Falls back to the bare-version tag for local invocations that don't + # set SENTRY_RELEASE. + local release_name="${SENTRY_RELEASE:-openhuman@${version}}" log_info "Uploading Rust debug symbols for release: ${release_name}" log_info "Symbols path: ${symbols_path}" @@ -180,12 +187,18 @@ upload_symbols() { # Use --ignore-missing for shallow clones or CI environments sentry-cli releases set-commits --auto --ignore-missing "${release_name}" || true - # Upload debug symbols + # Upload debug symbols + source bundles. `--include-sources` makes + # `sentry-cli` package the referenced source files into a `.src.zip` + # alongside the DIF, so Sentry renders surrounding source lines in + # Rust stack traces instead of bare `function + 0xNNN`. CI runs from a + # full workspace checkout, so the source paths embedded in the DWARF + # resolve and the bundle is built correctly. log_info "Uploading debug symbols..." local upload_args=( "upload-dif" "--org" "${SENTRY_ORG}" "--project" "${SENTRY_PROJECT}" + "--include-sources" "--log-level=warning" )