From 385a22a6fe5a62672c8788c9d675d648c54df3f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Fri, 17 Jul 2026 19:24:40 +0200 Subject: [PATCH 01/29] Update libnode to 22.23.1 and fix RelWithDebInfo builds. --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 46d5eecb627..a75d5a6cc1d 100644 --- a/conanfile.py +++ b/conanfile.py @@ -65,7 +65,7 @@ def requirements(self): self.requires("gli/cci.20210515") # NOTE: not maintained for 4 years self.requires("glslang/1.4.350.0") self.requires("liblo/0.35@overte/stable") # For hifiOSC - self.requires("libnode/22.22.3@overte/stable#12c9d377b2df64060e312a93bf14592f") + self.requires("libnode/22.23.1@overte/stable#d73b93ec99eae5b0c1d85296e0453d8e") self.requires("nlohmann_json/3.11.2") self.requires("nvidia-texture-tools/2023.01@overte/stable#bb4a28e5438f69332299cc23b770fc07") self.requires("onetbb/2021.10.0") From dee94c50593fc481b34f4eaa468e0902902aae52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Fri, 17 Jul 2026 19:24:57 +0200 Subject: [PATCH 02/29] Fix usage of conan-relwithdebinfo in CLion. --- .idea/cmake.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.idea/cmake.xml b/.idea/cmake.xml index 28ac684d530..7ef1b58b772 100644 --- a/.idea/cmake.xml +++ b/.idea/cmake.xml @@ -3,21 +3,21 @@ - + - + - + - + - + From d58689b4d1283bb720809bcc4e30331a84107d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Fri, 17 Jul 2026 19:26:39 +0200 Subject: [PATCH 03/29] Switch to RelWithDebInfo builds. --- .github/actions/setup-windows/action.yml | 2 +- .github/workflows/build.yml | 10 +++++----- BUILD_LINUX.md | 8 ++++---- BUILD_WIN.md | 6 +++--- winprepareVS22.bat | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/actions/setup-windows/action.yml b/.github/actions/setup-windows/action.yml index 4a78138ce83..101bd2e4dcc 100644 --- a/.github/actions/setup-windows/action.yml +++ b/.github/actions/setup-windows/action.yml @@ -7,7 +7,7 @@ runs: shell: bash run: | if [[ "${{ matrix.os }}" = "Windows 2022" ]]; then - echo "CONAN_PROFILE='./tools/conan-profiles/vs-22-release'" >> $GITHUB_ENV + echo "CONAN_PROFILE='./tools/conan-profiles/vs-22-relwithdebinfo'" >> $GITHUB_ENV else echo "Not sure which Conan profile to use. Exiting." && exit 1 fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7802c54e580..a209844bbd1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,7 +68,7 @@ jobs: # We use a third-party runner hosted by depot.dev to avoid timing out in case Qt has to be built from source. runner: depot-windows-2022-16 # Which GitHub Actions Runner to use. When using `image` this just needs to run the build container. arch: x86_64 - cmake_build_type: Release + cmake_build_type: RelWithDebInfo qt_source: source # Which Qt Conan package to use. `system`, `source`, or `aqt`. rendering_backend: OpenGL # Which OVERTE_RENDERING_BACKEND to build. - id: ubuntu-opengl-amd64 @@ -76,7 +76,7 @@ jobs: image: docker.io/overte/overte-full-build:2026-06-21-ubuntu-22.04-amd64 # Container image used for building. Built from /tools/ci-script/linux-ci/Dockerfile_x runner: depot-ubuntu-24.04-16 arch: amd64 - cmake_build_type: Release + cmake_build_type: RelWithDebInfo qt_source: source rendering_backend: OpenGL - id: ubuntu-vulkan-amd64 @@ -84,7 +84,7 @@ jobs: image: docker.io/overte/overte-full-build:2026-06-21-ubuntu-22.04-amd64 runner: depot-ubuntu-24.04-16 arch: amd64 - cmake_build_type: Release + cmake_build_type: RelWithDebInfo qt_source: source rendering_backend: Vulkan - id: ubuntu-opengl-aarch64 @@ -92,7 +92,7 @@ jobs: image: docker.io/overte/overte-full-build:2026-06-21-ubuntu-22.04-aarch64 runner: depot-ubuntu-24.04-arm-16 arch: aarch64 - cmake_build_type: Release + cmake_build_type: RelWithDebInfo qt_source: source rendering_backend: OpenGL - id: ubuntu-vulkan-aarch64 @@ -100,7 +100,7 @@ jobs: image: docker.io/overte/overte-full-build:2026-06-21-ubuntu-22.04-aarch64 runner: depot-ubuntu-24.04-arm-16 arch: aarch64 - cmake_build_type: Release + cmake_build_type: RelWithDebInfo qt_source: source rendering_backend: Vulkan - id: codechecker diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 772fdf2ee9e..b2dc61609b5 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: Apache-2.0 # Build Linux -*Last Updated on 2026-04-16* +*Last Updated on 2026-07-17* Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Linux specific instructions are found in this file. @@ -119,10 +119,10 @@ If you don't do this, Conan will still complain if it notices system packages be Install the dependencies with conan ```bash cd overte -conan install . -s build_type=Release -b missing -pr:a=tools/conan-profiles/linux -of build -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" +conan install . -s build_type=RelWithDebInfo -b missing -pr:a=tools/conan-profiles/linux -of build -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" ``` -If you want to build Debug or RelWithDebInfo versions, change the `build_type` to `Debug` or `RelWithDebInfo` and run the command again. E.g.: +If you want to build Debug or Release versions, change the `build_type` to `Debug` or `Release` and run the command again. E.g.: ```bash conan install . -s build_type=Debug -b missing -pr:a=tools/conan-profiles/linux -of build -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" ``` @@ -136,7 +136,7 @@ cmake --preset conan-default To compile the Domain server: ```bash -cmake --build --preset conan-release --target domain-server assignment-client +cmake --build --preset conan-relwithdebinfo --target domain-server assignment-client ``` *Note: For a server, it is not necessary to compile the Interface.* diff --git a/BUILD_WIN.md b/BUILD_WIN.md index e4d00d2f522..99fd96b8b75 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: Apache-2.0 # Build Windows -*Last Updated on 2025-08-01* +*Last Updated on 2026-07-15* This is a stand-alone guide for creating your first Overte build for Windows 64-bit. @@ -97,7 +97,7 @@ Run the Command Prompt from Start and run the following commands: ```bash cd "%OVERTE_DIR%" -conan install . -b missing -pr=tools/conan-profiles/vs-22-release -of build +conan install . -b missing -pr=tools/conan-profiles/vs-22-relwithdebinfo -of build conan install . -b missing -pr=tools/conan-profiles/vs-22-debug -of build cmake --preset conan-default ``` @@ -188,4 +188,4 @@ To fix this, install "C++ vXX.XX (...) ATL (...)" replacing XX.XX with the first ### `warning : cannot resolve item 'api-ms-win-(...)-l1-1-0.dll'` You may be able to ignore this warning. My assumption is that the MSVC version used is incompatible with the currently installed Windows SDK, -and Overte will just use the newer SDK during runtime. \ No newline at end of file +and Overte will just use the newer SDK during runtime. diff --git a/winprepareVS22.bat b/winprepareVS22.bat index 69d31e58004..b0eea5c8437 100644 --- a/winprepareVS22.bat +++ b/winprepareVS22.bat @@ -1,7 +1,7 @@ ECHO Running conan -conan install . -b missing -pr=tools/conan-profiles/vs-22-release -of build +conan install . -b missing -pr=tools/conan-profiles/vs-22-relwithdebinfo -of build conan install . -b missing -pr=tools/conan-profiles/vs-22-debug -of build -conan cache clean "*" -sbd +conan cache clean "*" -sbdt ECHO Running cmake cmake --preset conan-default ECHO CMake has finished From 38015c0a246ee65684b9fea793c6e4ad832a038f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Fri, 17 Jul 2026 19:26:47 +0200 Subject: [PATCH 04/29] Fix maybe-uninitialized warning on GCC-11 with RelWithDebInfo. --- libraries/gpu/src/gpu/Texture_ktx.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 9ddf41e26e7..1032df719a7 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -594,7 +594,7 @@ std::pair Texture::build(const ktx::KTXDescriptor& d // Assing the mips availables texture->setStoredMipFormat(mipFormat); - IrradianceKTXPayload irradianceKtxKeyValue; + IrradianceKTXPayload irradianceKtxKeyValue{}; if (IrradianceKTXPayload::findInKeyValues(descriptor.keyValues, irradianceKtxKeyValue)) { texture->overrideIrradiance(std::make_shared(irradianceKtxKeyValue._irradianceSH)); } From 7e275e41c8e2e0c854b273dd40b968141a9817aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Fri, 17 Jul 2026 19:27:27 +0200 Subject: [PATCH 05/29] Remove outdated Visual Studio 2019 files. --- tools/conan-profiles/vs-19-debug | 14 -------------- tools/conan-profiles/vs-19-debug-ninja | 16 ---------------- tools/conan-profiles/vs-19-release | 14 -------------- tools/conan-profiles/vs-19-release-ninja | 16 ---------------- tools/conan-profiles/vs-19-relwithdebinfo | 16 ---------------- winprepareVS19.bat | 8 -------- 6 files changed, 84 deletions(-) delete mode 100644 tools/conan-profiles/vs-19-debug delete mode 100644 tools/conan-profiles/vs-19-debug-ninja delete mode 100644 tools/conan-profiles/vs-19-release delete mode 100644 tools/conan-profiles/vs-19-release-ninja delete mode 100644 tools/conan-profiles/vs-19-relwithdebinfo delete mode 100644 winprepareVS19.bat diff --git a/tools/conan-profiles/vs-19-debug b/tools/conan-profiles/vs-19-debug deleted file mode 100644 index 7e2a340e70e..00000000000 --- a/tools/conan-profiles/vs-19-debug +++ /dev/null @@ -1,14 +0,0 @@ -[settings] -os=Windows -arch=x86_64 -compiler=msvc -# Both libnode and webrtc-audio-processing require C++20 -compiler.cppstd=20 -compiler.version=192 -compiler.runtime=dynamic -compiler.runtime_type=Debug -build_type=Debug -[options] -Overte/*:qt_source=source -# Required for building oneTBB. -hwloc/*:shared=True diff --git a/tools/conan-profiles/vs-19-debug-ninja b/tools/conan-profiles/vs-19-debug-ninja deleted file mode 100644 index 46df1bcc1e7..00000000000 --- a/tools/conan-profiles/vs-19-debug-ninja +++ /dev/null @@ -1,16 +0,0 @@ -[settings] -os=Windows -arch=x86_64 -compiler=msvc -# Both libnode and webrtc-audio-processing require C++20 -compiler.cppstd=20 -compiler.version=192 -compiler.runtime=dynamic -compiler.runtime_type=Debug -build_type=Debug -[conf] -tools.cmake.cmaketoolchain:generator=Ninja -[options] -Overte/*:qt_source=source -# Required for building oneTBB. -hwloc/*:shared=True diff --git a/tools/conan-profiles/vs-19-release b/tools/conan-profiles/vs-19-release deleted file mode 100644 index a7bce7c6cc8..00000000000 --- a/tools/conan-profiles/vs-19-release +++ /dev/null @@ -1,14 +0,0 @@ -[settings] -os=Windows -arch=x86_64 -compiler=msvc -# Both libnode and webrtc-audio-processing require C++20 -compiler.cppstd=20 -compiler.version=192 -compiler.runtime=dynamic -compiler.runtime_type=Release -build_type=Release -[options] -Overte/*:qt_source=source -# Required for building oneTBB. -hwloc/*:shared=True diff --git a/tools/conan-profiles/vs-19-release-ninja b/tools/conan-profiles/vs-19-release-ninja deleted file mode 100644 index da6a90bda23..00000000000 --- a/tools/conan-profiles/vs-19-release-ninja +++ /dev/null @@ -1,16 +0,0 @@ -[settings] -os=Windows -arch=x86_64 -compiler=msvc -# Both libnode and webrtc-audio-processing require C++20 -compiler.cppstd=20 -compiler.version=192 -compiler.runtime=dynamic -compiler.runtime_type=Release -build_type=Release -[conf] -tools.cmake.cmaketoolchain:generator=Ninja -[options] -Overte/*:qt_source=source -# Required for building oneTBB. -hwloc/*:shared=True diff --git a/tools/conan-profiles/vs-19-relwithdebinfo b/tools/conan-profiles/vs-19-relwithdebinfo deleted file mode 100644 index c89fd597d52..00000000000 --- a/tools/conan-profiles/vs-19-relwithdebinfo +++ /dev/null @@ -1,16 +0,0 @@ -[settings] -os=Windows -os_build=Windows -arch=x86_64 -arch_build=x86_64 -compiler=Visual Studio -# Both libnode and webrtc-audio-processing require C++20 -compiler.cppstd=20 -compiler.version=16 -build_type=Release -# libnode's build system seems to only support Release and Debug on Windows. RelWithDebInfo is not a valid option. -libnode/*:build_type=Release -[options] -Overte/*:qt_source=source -# Required for building oneTBB. -hwloc/*:shared=True diff --git a/winprepareVS19.bat b/winprepareVS19.bat deleted file mode 100644 index d0c68112933..00000000000 --- a/winprepareVS19.bat +++ /dev/null @@ -1,8 +0,0 @@ -ECHO Running conan -conan install . -b missing -pr=tools/conan-profiles/vs-19-release -of build -conan install . -b missing -pr=tools/conan-profiles/vs-19-debug -of build -conan cache clean "*" -sbd -ECHO Running cmake -cmake --preset conan-default -ECHO CMake has finished -pause From 47ae941a90864e6e463e79b002086f6189c08cab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Fri, 17 Jul 2026 19:28:08 +0200 Subject: [PATCH 06/29] Upload debugging symbols to Sentry and enable crash reporting. --- .../sentry-debugging-symbols/action.yml | 14 +++++++++++ .github/actions/setup-macos/action.yml | 4 +++ .github/actions/setup-windows/action.yml | 4 +++ .github/workflows/build.yml | 25 +++++++++++++++---- .../linux-ci/Dockerfile_build_ubuntu-22.04 | 10 ++++++-- 5 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 .github/actions/sentry-debugging-symbols/action.yml diff --git a/.github/actions/sentry-debugging-symbols/action.yml b/.github/actions/sentry-debugging-symbols/action.yml new file mode 100644 index 00000000000..e9273146855 --- /dev/null +++ b/.github/actions/sentry-debugging-symbols/action.yml @@ -0,0 +1,14 @@ +name: "Upload debugging symbols to Sentry" +description: | + Uses Sentry-cli to upload debugging symbols to Sentry. +inputs: + SENTRY_DEBUG_TOKEN: + required: true + description: "Access token for uploading debugging symbols to Sentry." +runs: + using: "composite" + steps: + - name: Upload debugging symbols to Sentry + shell: bash + run: | + sentry-cli debug-files upload -o overte -p overte --include-sources --wait-for 120 --auth-token ${{ inputs.SENTRY_DEBUG_TOKEN }} build/ diff --git a/.github/actions/setup-macos/action.yml b/.github/actions/setup-macos/action.yml index cb47192b890..854f4b3ad3a 100644 --- a/.github/actions/setup-macos/action.yml +++ b/.github/actions/setup-macos/action.yml @@ -22,3 +22,7 @@ runs: shell: bash run: | python3 -m pip install boto3 PyGithub conan html5lib setuptools + + - name: Install Sentry-cli + shell: bash + run: curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION="3.6.0" sh diff --git a/.github/actions/setup-windows/action.yml b/.github/actions/setup-windows/action.yml index 101bd2e4dcc..537d3dc09d4 100644 --- a/.github/actions/setup-windows/action.yml +++ b/.github/actions/setup-windows/action.yml @@ -66,3 +66,7 @@ runs: echo "Printing Conan global.conf…" && cat ${CONAN_HOME}/global.conf echo "Printing Conan remotes.json…" && cat ${CONAN_HOME}/remotes.json echo "Printing Conan source_credentials.json…" && cat ${CONAN_HOME}/source_credentials.json + + - name: Install Sentry-cli + shell: bash + run: curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION="3.6.0" sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a209844bbd1..751c9e39b42 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -73,7 +73,7 @@ jobs: rendering_backend: OpenGL # Which OVERTE_RENDERING_BACKEND to build. - id: ubuntu-opengl-amd64 os: Ubuntu 22.04 - image: docker.io/overte/overte-full-build:2026-06-21-ubuntu-22.04-amd64 # Container image used for building. Built from /tools/ci-script/linux-ci/Dockerfile_x + image: docker.io/overte/overte-full-build:2026-07-17-ubuntu-22.04-amd64 # Container image used for building. Built from /tools/ci-script/linux-ci/Dockerfile_x runner: depot-ubuntu-24.04-16 arch: amd64 cmake_build_type: RelWithDebInfo @@ -81,7 +81,7 @@ jobs: rendering_backend: OpenGL - id: ubuntu-vulkan-amd64 os: Ubuntu 22.04 - image: docker.io/overte/overte-full-build:2026-06-21-ubuntu-22.04-amd64 + image: docker.io/overte/overte-full-build:2026-07-17-ubuntu-22.04-amd64 runner: depot-ubuntu-24.04-16 arch: amd64 cmake_build_type: RelWithDebInfo @@ -89,7 +89,7 @@ jobs: rendering_backend: Vulkan - id: ubuntu-opengl-aarch64 os: Ubuntu 22.04 - image: docker.io/overte/overte-full-build:2026-06-21-ubuntu-22.04-aarch64 + image: docker.io/overte/overte-full-build:2026-07-17-ubuntu-22.04-aarch64 runner: depot-ubuntu-24.04-arm-16 arch: aarch64 cmake_build_type: RelWithDebInfo @@ -97,7 +97,7 @@ jobs: rendering_backend: OpenGL - id: ubuntu-vulkan-aarch64 os: Ubuntu 22.04 - image: docker.io/overte/overte-full-build:2026-06-21-ubuntu-22.04-aarch64 + image: docker.io/overte/overte-full-build:2026-07-17-ubuntu-22.04-aarch64 runner: depot-ubuntu-24.04-arm-16 arch: aarch64 cmake_build_type: RelWithDebInfo @@ -105,7 +105,7 @@ jobs: rendering_backend: Vulkan - id: codechecker os: Ubuntu 22.04 - image: docker.io/overte/overte-full-build:2026-06-21-ubuntu-22.04-amd64 + image: docker.io/overte/overte-full-build:2026-07-17-ubuntu-22.04-amd64 # depot-ubuntu-24.04-16 runs out of disk space when building debug Qt. runner: depot-ubuntu-24.04-32 arch: amd64 @@ -152,10 +152,15 @@ jobs: echo "OVERTE_RELEASE_TYPE=PR" >> $GITHUB_ENV echo "OVERTE_RELEASE_NUMBER=${{ github.event.number }}" >> $GITHUB_ENV echo "GIT_COMMIT_SHORT=${PULL_REQUEST_SHA::7}" >> $GITHUB_ENV + echo "OVERTE_BACKTRACE_URL=https://o4504831972343808.ingest.sentry.io/api/4504832427950080/minidump/?sentry_key=f511de295975461b8f92a36f4a4a4f32" >> $GITHUB_ENV + echo "OVERTE_BACKTRACE_TOKEN=PR_${{ github.event.number }}_${PULL_REQUEST_SHA::7}" >> $GITHUB_ENV + elif [ "${{github.event_name}}" = "push" ] && [ "${{github.event.action}}" == "tag" ]; then echo "OVERTE_RELEASE_TYPE=RELEASE" >> $GITHUB_ENV echo "OVERTE_RELEASE_NUMBER=${{ github.ref_name }}" >> $GITHUB_ENV echo "GIT_COMMIT_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV + echo "OVERTE_BACKTRACE_URL=${{ secrets.SENTRY_MINIDUMP_ENDPOINT }}" + echo "OVERTE_BACKTRACE_TOKEN=${{ github.ref_name }}_${GITHUB_SHA::7}" >> $GITHUB_ENV if [[ "${{ github.ref_name }}" == *"rc"* ]]; then # release candidate # The uploader already creates a subfolder for each RELEASE_NUMBER. echo "UPLOAD_PREFIX=build/overte/release-candidate/" >> $GITHUB_ENV @@ -167,6 +172,8 @@ jobs: # Nightlies ignore OVERTE_RELEASE_NUMBER and instead use the build date. echo "OVERTE_RELEASE_NUMBER=''" >> $GITHUB_ENV echo "GIT_COMMIT_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV + echo "OVERTE_BACKTRACE_URL=${{ secrets.SENTRY_MINIDUMP_ENDPOINT }}" + echo "OVERTE_BACKTRACE_TOKEN=Nightly-$(date +%Y-%m-%d)_${GITHUB_SHA::7}" >> $GITHUB_ENV else echo "Unexpected Action event! Failing…" exit 1; @@ -318,6 +325,14 @@ jobs: shell: bash run: cmake --build --target packaged-server-console --preset conan-${CMAKE_BUILD_TYPE,,} + - name: Upload debugging symbols to Sentry + # We don't have access to the access token on Pull Request builds. + if: github.event_name != 'pull_request' + continue-on-error: true + uses: ./.github/actions/sentry-debugging-symbols + with: + SENTRY_DEBUG_TOKEN: ${{ secrets.SENTRY_DEBUG_UPLOAD_TOKEN }} + - name: Build installer shell: bash run: | diff --git a/tools/ci-scripts/linux-ci/Dockerfile_build_ubuntu-22.04 b/tools/ci-scripts/linux-ci/Dockerfile_build_ubuntu-22.04 index 760759ba5c9..1d2c186d45a 100644 --- a/tools/ci-scripts/linux-ci/Dockerfile_build_ubuntu-22.04 +++ b/tools/ci-scripts/linux-ci/Dockerfile_build_ubuntu-22.04 @@ -4,7 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # Docker file for building Overte -# Example build: docker build --no-cache --progress=plain -t overte/overte-full-build:2026-06-21-ubuntu-22.04-amd64 -f Dockerfile_build_ubuntu-22.04 . +# Example build: docker build --no-cache --progress=plain -t overte/overte-full-build:2026-07-17-ubuntu-22.04-amd64 -f Dockerfile_build_ubuntu-22.04 . FROM ubuntu:22.04 LABEL maintainer="Julian Groß (julian.gro@overte.org)" LABEL description="Development image for full Overte builds" @@ -152,7 +152,7 @@ if [ $(uname -m) = "x86_64" ]; then # ERROR: Could not install packages due to an OSError: [Errno 2] No such file or directory: '/tmp/tmpz4phh0l0/output.json' pip3 install --upgrade pip # The CodeChecker versions needs to be the same as requested by the CodeChecker-Action. - pip3 install codechecker==6.28.0 + pip3 install codechecker==6.28.2 # Get new CppCheck, instead of Ubuntu's outdated version. wget -O cppcheck.tar.gz https://github.com/cppcheck-opensource/cppcheck/archive/refs/tags/2.21.0.tar.gz @@ -178,6 +178,12 @@ else fi EOF +# +# Sentry +# +# Install Sentry-cli for uploading debugging symbols. +RUN curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION="3.6.0" sh + # # Platform specific build variables # From 0e9a015acb971eaa2fd92bc5f568d22628ed33df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Wed, 22 Jul 2026 15:15:57 +0200 Subject: [PATCH 07/29] Fix backtrace token and backtrace URL not being applied. --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 751c9e39b42..a095387692c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -159,7 +159,7 @@ jobs: echo "OVERTE_RELEASE_TYPE=RELEASE" >> $GITHUB_ENV echo "OVERTE_RELEASE_NUMBER=${{ github.ref_name }}" >> $GITHUB_ENV echo "GIT_COMMIT_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV - echo "OVERTE_BACKTRACE_URL=${{ secrets.SENTRY_MINIDUMP_ENDPOINT }}" + echo "OVERTE_BACKTRACE_URL=${{ secrets.SENTRY_MINIDUMP_ENDPOINT }}" >> $GITHUB_ENV echo "OVERTE_BACKTRACE_TOKEN=${{ github.ref_name }}_${GITHUB_SHA::7}" >> $GITHUB_ENV if [[ "${{ github.ref_name }}" == *"rc"* ]]; then # release candidate # The uploader already creates a subfolder for each RELEASE_NUMBER. @@ -172,7 +172,7 @@ jobs: # Nightlies ignore OVERTE_RELEASE_NUMBER and instead use the build date. echo "OVERTE_RELEASE_NUMBER=''" >> $GITHUB_ENV echo "GIT_COMMIT_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV - echo "OVERTE_BACKTRACE_URL=${{ secrets.SENTRY_MINIDUMP_ENDPOINT }}" + echo "OVERTE_BACKTRACE_URL=${{ secrets.SENTRY_MINIDUMP_ENDPOINT }}" >> $GITHUB_ENV echo "OVERTE_BACKTRACE_TOKEN=Nightly-$(date +%Y-%m-%d)_${GITHUB_SHA::7}" >> $GITHUB_ENV else echo "Unexpected Action event! Failing…" @@ -250,9 +250,9 @@ jobs: shell: bash run: | if [[ "${{ matrix.os }}" =~ "Windows" ]]; then - cmake --preset conan-default -DOVERTE_RENDERING_BACKEND=${{ matrix.rendering_backend }} -DOVERTE_RELEASE_NUMBER=$OVERTE_RELEASE_NUMBER -DOVERTE_GIT_COMMIT_SHORT=$GIT_COMMIT_SHORT -DOVERTE_RELEASE_TYPE=$OVERTE_RELEASE_TYPE $CMAKE_EXTRA + cmake --preset conan-default -DOVERTE_RENDERING_BACKEND=${{ matrix.rendering_backend }} -DOVERTE_RELEASE_NUMBER=$OVERTE_RELEASE_NUMBER -DOVERTE_GIT_COMMIT_SHORT=$GIT_COMMIT_SHORT -DOVERTE_RELEASE_TYPE=$OVERTE_RELEASE_TYPE -DOVERTE_BACKTRACE_URL=$OVERTE_BACKTRACE_URL -DOVERTE_BACKTRACE_TOKEN=$OVERTE_BACKTRACE_TOKEN $CMAKE_EXTRA else - cmake --preset conan-${CMAKE_BUILD_TYPE,,} -DOVERTE_RENDERING_BACKEND=${{ matrix.rendering_backend }} -DOVERTE_RELEASE_NUMBER=$OVERTE_RELEASE_NUMBER -DOVERTE_GIT_COMMIT_SHORT=$GIT_COMMIT_SHORT -DOVERTE_RELEASE_TYPE=$OVERTE_RELEASE_TYPE $CMAKE_EXTRA + cmake --preset conan-${CMAKE_BUILD_TYPE,,} -DOVERTE_RENDERING_BACKEND=${{ matrix.rendering_backend }} -DOVERTE_RELEASE_NUMBER=$OVERTE_RELEASE_NUMBER -DOVERTE_GIT_COMMIT_SHORT=$GIT_COMMIT_SHORT -DOVERTE_RELEASE_TYPE=$OVERTE_RELEASE_TYPE -DOVERTE_BACKTRACE_URL=$OVERTE_BACKTRACE_URL -DOVERTE_BACKTRACE_TOKEN=$OVERTE_BACKTRACE_TOKEN $CMAKE_EXTRA fi - name: Run CodeChecker static analysis From 5244e15a4805bac54d8c64d23741d1cac2c23451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Mon, 3 Aug 2026 12:12:02 +0200 Subject: [PATCH 08/29] Add Sentry-Native. --- BUILD.md | 6 +- CMakeLists.txt | 2 +- assignment-client/CMakeLists.txt | 2 +- cmake/macros/AddCrashpad.cmake | 62 ------------------- cmake/macros/AddSentry.cmake | 54 ++++++++++++++++ conanfile.py | 2 +- domain-server/CMakeLists.txt | 2 +- interface/CMakeLists.txt | 2 +- libraries/networking/CMakeLists.txt | 2 +- .../CrashHandlerBackend_Crashpad.cpp | 4 +- .../CrashHandlerBackend_None.cpp | 3 +- tools/oven/CMakeLists.txt | 2 +- 12 files changed, 69 insertions(+), 74 deletions(-) delete mode 100644 cmake/macros/AddCrashpad.cmake create mode 100644 cmake/macros/AddSentry.cmake diff --git a/BUILD.md b/BUILD.md index c6ff29c10bc..cf98adc3857 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,13 +1,13 @@ # General Build Information -*Last Updated on 2025-03-17* +*Last Updated on 2026-07-29* ## OS Specific Build Guides @@ -78,6 +78,8 @@ BUILD_GLOBAL_SERVICES=STABLE #### Possible CMake Variables ```text +// Build with crash reporting. +OVERTE_USE_SENTRY // The URL to post the dump to. OVERTE_BACKTRACE_URL // The identifying tag of the release. diff --git a/CMakeLists.txt b/CMakeLists.txt index 968f8a0826b..6686e47cd62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,7 +96,7 @@ set(OVERTE_USE_OPTIMIZED_IK OFF CACHE BOOL "Use optimized IK.") set(OVERTE_DISABLE_KTX_CACHE OFF CACHE BOOL "Disable KTX Cache.") set(OVERTE_USE_KHR_ROBUSTNESS OFF CACHE BOOL "Use KHR_robustness.") set(OVERTE_USE_SYSTEM_LIBS OFF CACHE BOOL "Build with system dependencies.") - +set(OVERTE_USE_SENTRY OFF CACHE BOOL "Build with Sentry crash reporting.") set(OVERTE_BACKTRACE_URL "" CACHE STRING "URL to an endpoint for uploading crash-dumps. For example Sentry.") set(OVERTE_BACKTRACE_TOKEN "" CACHE STRING "Token used to identify this build, for use in uploading crash-dumps.") diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index f8656670b62..1da0e538f07 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -24,7 +24,7 @@ link_hifi_libraries( include_hifi_library_headers(procedural) include_hifi_library_headers(entities) -add_crashpad() +add_sentry() target_breakpad() if (OVERTE_BUILD_TOOLS) diff --git a/cmake/macros/AddCrashpad.cmake b/cmake/macros/AddCrashpad.cmake deleted file mode 100644 index 2543c0b507c..00000000000 --- a/cmake/macros/AddCrashpad.cmake +++ /dev/null @@ -1,62 +0,0 @@ -# -# AddCrashpad.cmake -# cmake/macros -# -# Created by Clement Brisset on 01/19/18. -# Copyright 2018 High Fidelity, Inc. -# Copyright 2025 Overte e.V. -# -# Distributed under the Apache License, Version 2.0. -# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html -# - -macro(add_crashpad) - set (USE_CRASHPAD TRUE) - message(STATUS "Checking crashpad config") - - if (OVERTE_BACKTRACE_URL STREQUAL "") - message(STATUS "Checking crashpad config - -DOVERTE_BACKTRACE_URL is empty, disabled.") - set(USE_CRASHPAD FALSE) - endif() - - if (OVERTE_BACKTRACE_TOKEN STREQUAL "") - message(STATUS "Checking crashpad config - -DOVERTE_BACKTRACE_TOKEN is empty, disabled.") - set(USE_CRASHPAD FALSE) - endif() - - if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") - message(STATUS "Checking crashpad config - Linux aarch64 is not supported by crashpad, disabled.") - set(USE_CRASHPAD FALSE) - endif() - - if (USE_CRASHPAD) - message(STATUS "Checking crashpad config - enabled.") - get_property(CRASHPAD_CHECKED GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE) - if (NOT CRASHPAD_CHECKED) - find_package(Crashpad REQUIRED) - - set_property(GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE TRUE) - endif() - - add_definitions(-DHAS_CRASHPAD) - add_definitions(-DOVERTE_BACKTRACE_URL=\"${OVERTE_BACKTRACE_URL}\") - add_definitions(-DOVERTE_BACKTRACE_TOKEN=\"${OVERTE_BACKTRACE_TOKEN}\") - - target_include_directories(${TARGET_NAME} PRIVATE ${CRASHPAD_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${CRASHPAD_LIBRARY} ${CRASHPAD_UTIL_LIBRARY} ${CRASHPAD_BASE_LIBRARY}) - - if (WIN32) - set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "/ignore:4099") - elseif (APPLE) - find_library(Security Security) - target_link_libraries(${TARGET_NAME} ${Security}) - target_link_libraries(${TARGET_NAME} "-lbsm") - endif() - - add_custom_command( - TARGET ${TARGET_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$/" - ) - endif () -endmacro() diff --git a/cmake/macros/AddSentry.cmake b/cmake/macros/AddSentry.cmake new file mode 100644 index 00000000000..4c1c05a6aff --- /dev/null +++ b/cmake/macros/AddSentry.cmake @@ -0,0 +1,54 @@ +# +# AddSentry.cmake +# cmake/macros +# +# Created by Clement Brisset on 01/19/18. +# Copyright 2018 High Fidelity, Inc. +# Copyright 2025-2026 Overte e.V. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html +# + +macro(add_sentry) + if (OVERTE_USE_SENTRY) + if (OVERTE_BACKTRACE_URL STREQUAL "") + message(FATAL_ERROR "Crashpad is enabled, but -DOVERTE_BACKTRACE_URL is empty!") + endif() + if (OVERTE_BACKTRACE_TOKEN STREQUAL "") + message(FATAL_ERROR "Crashpad is enabled, but -DOVERTE_BACKTRACE_TOKEN is empty!") + endif() + + find_package(Sentry QUIET REQUIRED) + + add_definitions(-DHAS_SENTRY) + add_definitions(-DOVERTE_BACKTRACE_URL=\"${OVERTE_BACKTRACE_URL}\") + add_definitions(-DOVERTE_BACKTRACE_TOKEN=\"${OVERTE_BACKTRACE_TOKEN}\") + + target_link_libraries(${TARGET_NAME} sentry-native::sentry-native) + + + # if (WIN32) + # set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "/ignore:4099") + # elseif (APPLE) + # find_library(Security Security) + # target_link_libraries(${TARGET_NAME} ${Security}) + # target_link_libraries(${TARGET_NAME} "-lbsm") + # endif() + + + # Find and pass crashpad_hander to CPack for packaging. crashpad_handler is used for sending crash reports to Sentry. + find_program(CRASHPAD_HANDLER_EXECUTABLE crashpad_handler PATHS "${sentry_INCLUDE_DIR}/../bin/" NO_DEFAULT_PATH) + if (NOT CRASHPAD_HANDLER_EXECUTABLE) + message(FATAL_ERROR "Could not find Crashpad-handler at ${sentry_INCLUDE_DIR}/../bin/. Crashpad-handler is required for submitting crash reports to Sentry.") + endif () + # TODO: Make sure we are packaging crashpad_handler into our AppImages/installers. + set(CPACK_CRASHPAD_HANDLER_EXECUTABLE ${CRASHPAD_HANDLER_EXECUTABLE}) + + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXECUTABLE} "$/" + ) + endif () +endmacro() diff --git a/conanfile.py b/conanfile.py index a75d5a6cc1d..1751e5a6264 100644 --- a/conanfile.py +++ b/conanfile.py @@ -56,7 +56,6 @@ def requirements(self): self.requires("artery-font-format/1.0.1") # FIXME: update to 1.1 self.requires("bullet3/3.25") self.requires("cgltf/1.14@overte/stable") - # self.requires("crashpad/cci.20220219" ) # Broken self.requires("discord-rpc/3.4.0@overte/stable") self.requires("draco/1.3.5") # FIXME: update to newer version self.requires("etc2comp/cci.20170424") # NOTE: archived upstream @@ -76,6 +75,7 @@ def requirements(self): self.requires("quazip/1.4") self.requires("scribe/2019.02@overte/stable") self.requires("sdl/2.32.10") + self.requires("sentry-native/0.14.2") # Crash reporting self.requires("spirv-cross/1.4.350.0") self.requires("spirv-tools/1.4.350.0") self.requires("steamworks/158a@overte/prebuild") diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index 011fd115bd3..e1e8717d0c6 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -36,7 +36,7 @@ target_zlib() target_quazip() target_openssl() -add_crashpad() +add_sentry() target_breakpad() # libcrypto uses dlopen in libdl diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index b61ea38306f..0796d220263 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -242,7 +242,7 @@ target_bullet() set(OpenGL_GL_PREFERENCE "GLVND") target_opengl() -add_crashpad() +add_sentry() target_breakpad() target_json() target_glm() diff --git a/libraries/networking/CMakeLists.txt b/libraries/networking/CMakeLists.txt index 3550cf424e7..4bcf1b209cb 100644 --- a/libraries/networking/CMakeLists.txt +++ b/libraries/networking/CMakeLists.txt @@ -4,7 +4,7 @@ link_hifi_libraries(shared platform) target_openssl() target_tbb() -add_crashpad() +add_sentry() target_breakpad() if (WIN32 OR (UNIX AND NOT APPLE)) diff --git a/libraries/networking/src/crash-handler/CrashHandlerBackend_Crashpad.cpp b/libraries/networking/src/crash-handler/CrashHandlerBackend_Crashpad.cpp index cf893f267b4..1f45eb3346b 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerBackend_Crashpad.cpp +++ b/libraries/networking/src/crash-handler/CrashHandlerBackend_Crashpad.cpp @@ -10,7 +10,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#if HAS_CRASHPAD +#if HAS_SENTRY #include "CrashHandler.h" @@ -519,4 +519,4 @@ void startCrashHookMonitor(QCoreApplication* app) { #endif // Q_OS_WIN } -#endif // HAS_CRASHPAD +#endif // HAS_SENTRY diff --git a/libraries/networking/src/crash-handler/CrashHandlerBackend_None.cpp b/libraries/networking/src/crash-handler/CrashHandlerBackend_None.cpp index 59441255756..cdf015f2c65 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerBackend_None.cpp +++ b/libraries/networking/src/crash-handler/CrashHandlerBackend_None.cpp @@ -4,12 +4,13 @@ // // Created by Clement Brisset on 01/19/18. // Copyright 2018 High Fidelity, Inc. +// Copyright 2026 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#if !defined(HAS_CRASHPAD) && !defined(HAS_BREAKPAD) +#if !defined(HAS_SENTRY) && !defined(HAS_CRASHPAD) && !defined(HAS_BREAKPAD) #include "CrashHandler.h" diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 3cf954b2139..81e2f9e2d61 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -11,7 +11,7 @@ include_hifi_library_headers(script-engine) setup_memory_debugger() setup_thread_debugger() -add_crashpad() +add_sentry() target_breakpad() From 2d01ac67f7f45771781ea8996bd98889218fddb7 Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Fri, 7 Aug 2026 17:32:49 +0200 Subject: [PATCH 09/29] Initial Sentry implementation Works, but still needs settings, more data, a bit of refactoring, etc --- cmake/macros/AddSentry.cmake | 4 +- .../src/crash-handler/CrashHandler.cpp | 20 +- .../src/crash-handler/CrashHandler.h | 17 + .../CrashHandlerBackend_Breakpad.cpp | 4 + .../CrashHandlerBackend_Crashpad.cpp | 7 +- .../CrashHandlerBackend_None.cpp | 4 + .../CrashHandlerBackend_Sentry.cpp | 476 ++++++++++++++++++ 7 files changed, 528 insertions(+), 4 deletions(-) create mode 100644 libraries/networking/src/crash-handler/CrashHandlerBackend_Sentry.cpp diff --git a/cmake/macros/AddSentry.cmake b/cmake/macros/AddSentry.cmake index 4c1c05a6aff..ac19cb01867 100644 --- a/cmake/macros/AddSentry.cmake +++ b/cmake/macros/AddSentry.cmake @@ -13,10 +13,10 @@ macro(add_sentry) if (OVERTE_USE_SENTRY) if (OVERTE_BACKTRACE_URL STREQUAL "") - message(FATAL_ERROR "Crashpad is enabled, but -DOVERTE_BACKTRACE_URL is empty!") + message(FATAL_ERROR "Sentry is enabled, but -DOVERTE_BACKTRACE_URL is empty!") endif() if (OVERTE_BACKTRACE_TOKEN STREQUAL "") - message(FATAL_ERROR "Crashpad is enabled, but -DOVERTE_BACKTRACE_TOKEN is empty!") + message(FATAL_ERROR "Sentry is enabled, but -DOVERTE_BACKTRACE_TOKEN is empty!") endif() find_package(Sentry QUIET REQUIRED) diff --git a/libraries/networking/src/crash-handler/CrashHandler.cpp b/libraries/networking/src/crash-handler/CrashHandler.cpp index 4dc724fe1c6..877f831534e 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.cpp +++ b/libraries/networking/src/crash-handler/CrashHandler.cpp @@ -13,6 +13,11 @@ #include "CrashHandlerBackend.h" #include #include +#include +#include "LogHandler.h" + + +static void crashHandlerLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg); CrashHandler& CrashHandler::getInstance() { @@ -64,6 +69,11 @@ bool CrashHandler::start() { qCDebug(crash_handler) << "Forwarded" << countAdded << "annotations"; + + qInstallMessageHandler(crashHandlerLogMessage); + qCInfo(crash_handler) << "Installed crash handler log message handler"; + + } else { qCWarning(crash_handler) << "Crash handler failed to start"; } @@ -122,4 +132,12 @@ void CrashHandler::setAnnotation(const std::string &key, const std::string &valu } setCrashAnnotation(key, value); -} \ No newline at end of file +} + + +static void crashHandlerLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + CrashHandler::getInstance().logMessage(type, context, msg); + + // We only get one log handler, so call the normal one here since we replaced it + LogHandler::getInstance().verboseMessageHandler(type, context, msg); +} diff --git a/libraries/networking/src/crash-handler/CrashHandler.h b/libraries/networking/src/crash-handler/CrashHandler.h index 75213a4dd84..6329290a772 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.h +++ b/libraries/networking/src/crash-handler/CrashHandler.h @@ -202,6 +202,19 @@ public slots: */ void setAnnotation(const std::string &key, const std::string &value); + + /** + * @brief Log a message to the crash handler + * + * This can be called from the logging system, to add log messages + * to crash reports. + * + * @param type Qt Log message type + * @param context Qt logging context + * @param msg Message to log + */ + virtual void logMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg); + signals: /** @@ -231,10 +244,14 @@ public slots: std::atomic _crashReportingEnabled {false}; std::unordered_map _annotations{}; std::mutex _annotationsMutex{}; + std::mutex _logMutex{}; QString _path; QString _crashUrl; QString _crashToken; + + QString _previousMessage{}; + int _repeatCount { 0 }; }; diff --git a/libraries/networking/src/crash-handler/CrashHandlerBackend_Breakpad.cpp b/libraries/networking/src/crash-handler/CrashHandlerBackend_Breakpad.cpp index 984cabe5078..d303324d843 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerBackend_Breakpad.cpp +++ b/libraries/networking/src/crash-handler/CrashHandlerBackend_Breakpad.cpp @@ -89,4 +89,8 @@ void setCrashReportingEnabled(bool value) { void startCrashHookMonitor(QCoreApplication* app) { } +void CrashHandler::logMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + +} + #endif diff --git a/libraries/networking/src/crash-handler/CrashHandlerBackend_Crashpad.cpp b/libraries/networking/src/crash-handler/CrashHandlerBackend_Crashpad.cpp index 1f45eb3346b..cf56e10ae55 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerBackend_Crashpad.cpp +++ b/libraries/networking/src/crash-handler/CrashHandlerBackend_Crashpad.cpp @@ -10,7 +10,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#if HAS_SENTRY +#if HAS_CRASHPAD + #include "CrashHandler.h" @@ -519,4 +520,8 @@ void startCrashHookMonitor(QCoreApplication* app) { #endif // Q_OS_WIN } +void CrashHandler::logMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + +} + #endif // HAS_SENTRY diff --git a/libraries/networking/src/crash-handler/CrashHandlerBackend_None.cpp b/libraries/networking/src/crash-handler/CrashHandlerBackend_None.cpp index cdf015f2c65..2be25bf6700 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerBackend_None.cpp +++ b/libraries/networking/src/crash-handler/CrashHandlerBackend_None.cpp @@ -34,5 +34,9 @@ void startCrashHookMonitor(QCoreApplication* app) { void setCrashReportingEnabled(bool value) { +} + +void CrashHandler::logMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + } #endif diff --git a/libraries/networking/src/crash-handler/CrashHandlerBackend_Sentry.cpp b/libraries/networking/src/crash-handler/CrashHandlerBackend_Sentry.cpp new file mode 100644 index 00000000000..3e0c9032e65 --- /dev/null +++ b/libraries/networking/src/crash-handler/CrashHandlerBackend_Sentry.cpp @@ -0,0 +1,476 @@ +// +// CrashHandler_Sentry.cpp +// interface/src +// +// Created by Vadim Troschinsky on 05/08/2026. +// Copyright 2018 High Fidelity, Inc. +// Copyright 2025 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#define HAS_SENTRY 1 + +#if HAS_SENTRY + + +#include "sentry.h" +#include "CrashHandler.h" + +Q_LOGGING_CATEGORY(crash_handler, "overte.crash_handler") + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++14-extensions" +#endif + + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#include +#include "../FingerprintUtils.h" +#include "../UserActivityLogger.h" +#include + + + +/** + * @brief Sentry implementation + * + * Sentry is a crash reporting and metrics system. + * + * Documentation for the API is at https://docs.sentry.io/platforms/native/usage/ + * + * For reporting, we use the sentry DSN, not the minidump endpoint. + * + * + */ + +static const std::string BACKTRACE_URL{ OVERTE_BACKTRACE_URL }; +static const std::string BACKTRACE_TOKEN{ OVERTE_BACKTRACE_TOKEN }; + +std::string custom_backtrace_url; +std::string custom_backtrace_token; + + +// ------------------------------------------------------------------------------------------------ +// SpinLock - a lock that can timeout attempting to lock a block of code, and is in a busy-wait cycle while trying to acquire +// note that this code will malfunction if you attempt to grab a lock while already holding it + +class SpinLock { +public: + SpinLock(); + void lock(); + bool lock(int msecs); + void unlock(); + +private: + QAtomicInteger _lock{ 0 }; + + Q_DISABLE_COPY(SpinLock) +}; + +class SpinLockLocker { +public: + SpinLockLocker(SpinLock& lock, int msecs = -1); + ~SpinLockLocker(); + bool isLocked() const; + void unlock(); + bool relock(int msecs = -1); + +private: + SpinLock* _lock; + bool _isLocked; + + Q_DISABLE_COPY(SpinLockLocker) +}; + +SpinLock::SpinLock() { +} + +void SpinLock::lock() { + while (!_lock.testAndSetAcquire(0, 1)) + ; +} + +bool SpinLock::lock(int msecs) { + QDeadlineTimer deadline(msecs); + for (;;) { + if (_lock.testAndSetAcquire(0, 1)) { + return true; + } + if (deadline.hasExpired()) { + return false; + } + } +} + +void SpinLock::unlock() { + _lock.storeRelease(0); +} + +SpinLockLocker::SpinLockLocker(SpinLock& lock, int msecs /* = -1 */ ) : _lock(&lock) { + _isLocked = _lock->lock(msecs); +} + +SpinLockLocker::~SpinLockLocker() { + if (_isLocked) { + _lock->unlock(); + } +} + +bool SpinLockLocker::isLocked() const { + return _isLocked; +} + +void SpinLockLocker::unlock() { + if (_isLocked) { + _lock->unlock(); + _isLocked = false; + } +} + +bool SpinLockLocker::relock(int msecs /* = -1 */ ) { + if (!_isLocked) { + _isLocked = _lock->lock(msecs); + } + return _isLocked; +} + +// ------------------------------------------------------------------------------------------------ + + + +#if defined(Q_OS_WIN) +static const QString CRASHPAD_HANDLER_NAME{ "crashpad_handler.exe" }; +#else +static const QString CRASHPAD_HANDLER_NAME{ "crashpad_handler" }; +#endif + +#ifdef Q_OS_LINUX +#include +#endif + +#ifdef Q_OS_WIN +// ------------------------------------------------------------------------------------------------ +// The area within this #ifdef is specific to the Microsoft C++ compiler + +#include +#include +#include +#include + +#include +#include + +static constexpr DWORD STATUS_MSVC_CPP_EXCEPTION = 0xE06D7363; +static constexpr ULONG_PTR MSVC_CPP_EXCEPTION_SIGNATURE = 0x19930520; +static constexpr int ANNOTATION_LOCK_WEAK_ATTEMPT = 5000; // attempt to lock the annotations list, but give up if it takes more than 5 seconds + +LPTOP_LEVEL_EXCEPTION_FILTER gl_crashpadUnhandledExceptionFilter = nullptr; +QTimer unhandledExceptionTimer; // checks occasionally in case loading an external DLL reset the unhandled exception pointer + +void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo); // extracts type information from a thrown C++ exception +LONG WINAPI firstChanceExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo); // called on any thrown exception (whether or not it's caught) +LONG WINAPI unhandledExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo); // called on any exception without a corresponding catch + +static LONG WINAPI firstChanceExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { + // we're catching these exceptions on first-chance as the system state is corrupted at this point and they may not survive the exception handling mechanism + if (client && (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_HEAP_CORRUPTION || + pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_BUFFER_OVERRUN)) { + client->DumpAndCrash(pExceptionInfo); + } + + return EXCEPTION_CONTINUE_SEARCH; +} + +static LONG WINAPI unhandledExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { + if (client && pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_MSVC_CPP_EXCEPTION) { + fatalCxxException(pExceptionInfo); + client->DumpAndCrash(pExceptionInfo); + } + + if (gl_crashpadUnhandledExceptionFilter != nullptr) { + return gl_crashpadUnhandledExceptionFilter(pExceptionInfo); + } + + return EXCEPTION_CONTINUE_SEARCH; +} + +// The following structures are modified versions of structs defined inplicitly by the Microsoft C++ compiler +// as described at http://www.geoffchappell.com/studies/msvc/language/predefined/ +// They are redefined here as the definitions the compiler gives only work in 32-bit contexts and are out-of-sync +// with the internal structures when operating in a 64-bit environment +// as discovered and described here: https://stackoverflow.com/questions/39113168/c-rtti-in-a-windows-64-bit-vectoredexceptionhandler-ms-visual-studio-2015 + +#pragma pack(push, ehdata, 4) + +struct PMD_internal { // internal name: _PMD (no changes, so could in theory just use the original) + int mdisp; + int pdisp; + int vdisp; +}; + +struct ThrowInfo_internal { // internal name: _ThrowInfo (changed all pointers into __int32) + __int32 attributes; + __int32 pmfnUnwind; // 32-bit RVA + __int32 pForwardCompat; // 32-bit RVA + __int32 pCatchableTypeArray; // 32-bit RVA +}; + +struct CatchableType_internal { // internal name: _CatchableType (changed all pointers into __int32) + __int32 properties; + __int32 pType; // 32-bit RVA + PMD_internal thisDisplacement; + __int32 sizeOrOffset; + __int32 copyFunction; // 32-bit RVA +}; + +#pragma warning(disable : 4200) +struct CatchableTypeArray_internal { // internal name: _CatchableTypeArray (changed all pointers into __int32) + int nCatchableTypes; + __int32 arrayOfCatchableTypes[0]; // 32-bit RVA +}; +#pragma warning(default : 4200) + +#pragma pack(pop, ehdata) + +// everything inside this function is extremely undocumented, attempting to extract +// the underlying C++ exception type (or at least its name) before throwing the whole +// mess at crashpad +// Some links describing how C++ exception handling works in an SEH context +// (since C++ exceptions are a figment of the Microsoft compiler): +// - https://www.codeproject.com/Articles/175482/Compiler-Internals-How-Try-Catch-Throw-are-Interpr +// - https://stackoverflow.com/questions/21888076/how-to-find-the-context-record-for-user-mode-exception-on-x64 + +static void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo) { + SpinLockLocker guard(crashpadAnnotationsProtect, ANNOTATION_LOCK_WEAK_ATTEMPT); + if (!guard.isLocked()) { + return; + } + + PEXCEPTION_RECORD ExceptionRecord = pExceptionInfo->ExceptionRecord; + /* + Exception arguments for Microsoft C++ exceptions: + [0] signature - magic number + [1] void* - variable that is being thrown + [2] ThrowInfo* - description of the variable that was thrown + [3] HMODULE - (64-bit only) base address that all 32bit pointers are added to + */ + + if (ExceptionRecord->NumberParameters != 4 || ExceptionRecord->ExceptionInformation[0] != MSVC_CPP_EXCEPTION_SIGNATURE) { + // doesn't match expected parameter counts or magic numbers + return; + } + + // get the ThrowInfo struct from the exception arguments + ThrowInfo_internal* pThrowInfo = reinterpret_cast(ExceptionRecord->ExceptionInformation[2]); + ULONG_PTR moduleBase = ExceptionRecord->ExceptionInformation[3]; + if (moduleBase == 0 || pThrowInfo == NULL) { + return; // broken assumption + } + + // get the CatchableTypeArray* struct from ThrowInfo + if (pThrowInfo->pCatchableTypeArray == 0) { + return; // broken assumption + } + CatchableTypeArray_internal* pCatchableTypeArray = + reinterpret_cast(moduleBase + pThrowInfo->pCatchableTypeArray); + if (pCatchableTypeArray->nCatchableTypes == 0 || pCatchableTypeArray->arrayOfCatchableTypes[0] == 0) { + return; // broken assumption + } + + // get the CatchableType struct for the actual exception type from CatchableTypeArray + CatchableType_internal* pCatchableType = + reinterpret_cast(moduleBase + pCatchableTypeArray->arrayOfCatchableTypes[0]); + if (pCatchableType->pType == 0) { + return; // broken assumption + } + const std::type_info* type = reinterpret_cast(moduleBase + pCatchableType->pType); + + crashpadAnnotations->SetKeyValue("thrownObject", type->name()); + + // After annotating the name of the actual object type, go through the other entries in CatcahleTypeArray and itemize the list of possible + // catch() commands that could have caught this so we can find the list of its superclasses + QString compatibleObjects; + for (int catchTypeIdx = 1; catchTypeIdx < pCatchableTypeArray->nCatchableTypes; catchTypeIdx++) { + CatchableType_internal* pCatchableSuperclassType = + reinterpret_cast(moduleBase + pCatchableTypeArray->arrayOfCatchableTypes[catchTypeIdx]); + if (pCatchableSuperclassType->pType == 0) { + return; // broken assumption + } + const std::type_info* superclassType = reinterpret_cast(moduleBase + pCatchableSuperclassType->pType); + + if (!compatibleObjects.isEmpty()) { + compatibleObjects += ", "; + } + compatibleObjects += superclassType->name(); + } + crashpadAnnotations->SetKeyValue("thrownObjectLike", compatibleObjects.toStdString()); +} + +void checkUnhandledExceptionHook() { + LPTOP_LEVEL_EXCEPTION_FILTER prevExceptionFilter = SetUnhandledExceptionFilter(unhandledExceptionHandler); + if (prevExceptionFilter != unhandledExceptionHandler) { + qWarning() << QString("Restored unhandled exception filter (which had been changed to %1)") + .arg(reinterpret_cast(prevExceptionFilter), 16, 16, QChar('0')); + } +} + +// End of code specific to the Microsoft C++ compiler +// ------------------------------------------------------------------------------------------------ +#endif // Q_OS_WIN + +// Locate the full path to the binary's directory +static QString findBinaryDir() { + // Normally we'd just use QCoreApplication::applicationDirPath(), but we can't. + // That function needs the QApplication to be created first, and Crashpad is initialized as early as possible, + // which is well before QApplication, so that function throws out a warning and returns ".". + // + // So we must do things the hard way here. In particular this is needed to correctly handle things in AppImage + // on Linux. On Windows and MacOS falling back to argv[0] should be fine. + +#ifdef Q_OS_LINUX + // Find outselves by looking at /proc//exe + pid_t ourPid = getpid(); + QString exeLink = QString("/proc/%1/exe").arg(ourPid); + qCDebug(crash_handler) << "Looking at" << exeLink; + + QFileInfo exeLinkInfo(exeLink); + if (exeLinkInfo.isSymLink()) { + QFileInfo exeInfo(exeLinkInfo.symLinkTarget()); + qCDebug(crash_handler) << "exe symlink points at" << exeInfo; + return exeInfo.absoluteDir().absolutePath(); + } else { + qCWarning(crash_handler) << exeLink << "isn't a symlink. /proc not mounted?"; + } + +#endif + + return QString(); +} + +bool startCrashHandler(std::string appPath, std::string crashURL, std::string crashToken) { + if (crashURL.empty()) { + crashURL = BACKTRACE_URL; + } + + if (crashToken.empty()) { + crashToken = BACKTRACE_TOKEN; + } + + if (crashURL.empty() || crashToken.empty()) { + qCCritical(crash_handler) << "Backtrace URL or token not set, crash handler disabled."; + return false; + } + + sentry_options_t *options = sentry_options_new(); + sentry_options_set_dsn(options, crashURL.c_str()); + sentry_options_set_release(options, crashToken.c_str()); + sentry_options_set_debug(options, 1); // Debug for Sentry Native SDK itself + sentry_options_set_enable_logs(options, 1); + sentry_options_set_minidump_mode(options, SENTRY_MINIDUMP_MODE_FULL); // TODO: a way to choose SENTRY_MINIDUMP_MODE_SMART, which sends smaller reports + + if (sentry_init(options) == 0) { + qCInfo(crash_handler) << "Sentry init successful"; + } else { + qCCritical(crash_handler) << "Sentry init failed"; + return false; + } + + + sentry_value_t release_info = sentry_value_new_object(); + sentry_value_set_by_key(release_info, "version", sentry_value_new_string(BuildInfo::VERSION.toStdString().c_str())); + sentry_value_set_by_key(release_info, "build", sentry_value_new_string(BuildInfo::BUILD_NUMBER.toStdString().c_str())); + + sentry_value_t machine_info = sentry_value_new_object(); + sentry_value_set_by_key(machine_info, "fingerprint", sentry_value_new_string(uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()).toStdString().c_str())) ; + + + sentry_set_context("Release", release_info); + sentry_set_context("Machine", machine_info); + + + qCInfo(crash_handler) << "Sentry crash handler initialized. SDK version" << sentry_sdk_version() << ". Will send reports to " << crashURL.c_str() << " with token " << crashToken.c_str(); + return true; + + //qCInfo(crash_handler) << "Crashpad uploads " << (enabled ? QString("enabled") : QString("disabled")); +} + +void setCrashAnnotation(std::string name, std::string value) { + +} + +void startCrashHookMonitor(QCoreApplication* app) { +#ifdef Q_OS_WIN + // create a timer that checks to see if our exception handler has been reset. This may occur when a new CRT + // is initialized, which could happen any time a DLL not compiled with the same compiler is loaded. + // It would be nice if this were replaced with a more intelligent response; this fires once a minute which + // may be too much (extra code running) and too little (leaving up to a 1min gap after the hook is broken) + checkUnhandledExceptionHook(); + + unhandledExceptionTimer.moveToThread(app->thread()); + QObject::connect(&unhandledExceptionTimer, &QTimer::timeout, checkUnhandledExceptionHook); + unhandledExceptionTimer.start(60000); +#endif // Q_OS_WIN +} + + +void CrashHandler::logMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + + // Some of the logging is very spammy and Sentry seems to have a problem with shutting down + // in that case. + { + std::lock_guard lock(_logMutex); + + if (msg == _previousMessage) { + _repeatCount++; + return; + } + + if (_repeatCount > 0) { + sentry_log_warn("Previous log message repeated %i times", _repeatCount); + } + + _previousMessage = msg; + _repeatCount = 0; + } + + + switch(type) { + case QtMsgType::QtFatalMsg: + sentry_log_fatal(msg.toStdString().c_str()); + break; + case QtMsgType::QtCriticalMsg: + sentry_log_error(msg.toStdString().c_str()); + break; + case QtMsgType::QtWarningMsg: + sentry_log_warn(msg.toStdString().c_str()); + break; + case QtMsgType::QtInfoMsg: + sentry_log_info(msg.toStdString().c_str()); + break; + case QtMsgType::QtDebugMsg: + sentry_log_debug(msg.toStdString().c_str()); + break; + default: + sentry_log_warn(msg.toStdString().c_str()); + sentry_log_warn("Previous log message was of unknown type %i", (int)type); + } +} + +#endif // HAS_SENTRY From baa1191be1c8a258d7cb5524154a957fb796523b Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Fri, 7 Aug 2026 18:14:03 +0200 Subject: [PATCH 10/29] Remove previous Crashpad and Breakpad implementation This is the start of a plan to refactor the API to better fit the features Sentry Native exposes. If we want to bring back Crashpad and Breakpad later, they should be adapted to the new API. It appears that we can also do cleaner exception handling based on the API docs, so that's removed here as well. --- .../CrashHandlerBackend_Breakpad.cpp | 96 ---- .../CrashHandlerBackend_Crashpad.cpp | 527 ------------------ .../CrashHandlerBackend_Sentry.cpp | 269 +-------- 3 files changed, 1 insertion(+), 891 deletions(-) delete mode 100644 libraries/networking/src/crash-handler/CrashHandlerBackend_Breakpad.cpp delete mode 100644 libraries/networking/src/crash-handler/CrashHandlerBackend_Crashpad.cpp diff --git a/libraries/networking/src/crash-handler/CrashHandlerBackend_Breakpad.cpp b/libraries/networking/src/crash-handler/CrashHandlerBackend_Breakpad.cpp deleted file mode 100644 index d303324d843..00000000000 --- a/libraries/networking/src/crash-handler/CrashHandlerBackend_Breakpad.cpp +++ /dev/null @@ -1,96 +0,0 @@ -// -// CrashHandler_Breakpad.cpp -// interface/src -// -// Created by Clement Brisset on 01/19/18. -// Copyright 2018 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#if HAS_BREAKPAD - -#include "CrashHandler.h" - -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -google_breakpad::ExceptionHandler* gBreakpadHandler; - -std::mutex annotationMutex; -QMap annotations; - -static bool breakpad_dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) { - return succeeded; -} - -QString obbDir() { - QAndroidJniObject mediaDir = QAndroidJniObject::callStaticObjectMethod("android/os/Environment", "getExternalStorageDirectory", "()Ljava/io/File;"); - QAndroidJniObject mediaPath = mediaDir.callObjectMethod( "getAbsolutePath", "()Ljava/lang/String;" ); - QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); - QAndroidJniObject package = activity.callObjectMethod("getPackageName", "()Ljava/lang/String;"); - QString dataAbsPath = mediaPath.toString()+"/Android/obb/" + package.toString(); - return dataAbsPath; -} - -void flushAnnotations() { - QSettings settings(obbDir() + "/annotations.json", JSON_FORMAT); - settings.clear(); - settings.beginGroup("Annotations"); - for (auto k : annotations.keys()) { - settings.setValue(k, annotations.value(k)); - } - settings.endGroup(); - settings.sync(); -} - -bool startCrashHandler(std::string appPath, std::string crashURL, std::string crashToken) { - annotations["version"] = BuildInfo::VERSION; - annotations["build_number"] = BuildInfo::BUILD_NUMBER; - annotations["build_type"] = BuildInfo::BUILD_TYPE_STRING; - - auto machineFingerPrint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); - annotations["machine_fingerprint"] = machineFingerPrint; - - flushAnnotations(); - - gBreakpadHandler = new google_breakpad::ExceptionHandler( - google_breakpad::MinidumpDescriptor(obbDir().toStdString()), - nullptr, breakpad_dumpCallback, nullptr, true, -1); - return true; -} - -void setCrashAnnotation(std::string name, std::string value) { - std::lock_guard guard(annotationMutex); - QString qName = QString::fromStdString(name); - QString qValue = QString::fromStdString(value); - annotations[qName] = qValue; - flushAnnotations(); -} - -void setCrashReportingEnabled(bool value) { - qCritical() << "Can't set crash reporting status on Breakpad."; - qCritical() << "Breakpad is deprecated and needs replacing!"; -} - -void startCrashHookMonitor(QCoreApplication* app) { -} - -void CrashHandler::logMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) { - -} - -#endif diff --git a/libraries/networking/src/crash-handler/CrashHandlerBackend_Crashpad.cpp b/libraries/networking/src/crash-handler/CrashHandlerBackend_Crashpad.cpp deleted file mode 100644 index cf56e10ae55..00000000000 --- a/libraries/networking/src/crash-handler/CrashHandlerBackend_Crashpad.cpp +++ /dev/null @@ -1,527 +0,0 @@ -// -// CrashHandler_Crashpad.cpp -// interface/src -// -// Created by Clement Brisset on 01/19/18. -// Copyright 2018 High Fidelity, Inc. -// Copyright 2025 Overte e.V. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#if HAS_CRASHPAD - - -#include "CrashHandler.h" - -Q_LOGGING_CATEGORY(crash_handler, "overte.crash_handler") - -#include - -#include -#include - -#include -#include -#include -#include -#include - -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wc++14-extensions" -#endif - -#include -#include -#include -#include - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif - -#include -#include "../FingerprintUtils.h" -#include "../UserActivityLogger.h" -#include - - - -static const std::string BACKTRACE_URL{ OVERTE_BACKTRACE_URL }; -static const std::string BACKTRACE_TOKEN{ OVERTE_BACKTRACE_TOKEN }; - -std::string custom_backtrace_url; -std::string custom_backtrace_token; - - -// ------------------------------------------------------------------------------------------------ -// SpinLock - a lock that can timeout attempting to lock a block of code, and is in a busy-wait cycle while trying to acquire -// note that this code will malfunction if you attempt to grab a lock while already holding it - -class SpinLock { -public: - SpinLock(); - void lock(); - bool lock(int msecs); - void unlock(); - -private: - QAtomicInteger _lock{ 0 }; - - Q_DISABLE_COPY(SpinLock) -}; - -class SpinLockLocker { -public: - SpinLockLocker(SpinLock& lock, int msecs = -1); - ~SpinLockLocker(); - bool isLocked() const; - void unlock(); - bool relock(int msecs = -1); - -private: - SpinLock* _lock; - bool _isLocked; - - Q_DISABLE_COPY(SpinLockLocker) -}; - -SpinLock::SpinLock() { -} - -void SpinLock::lock() { - while (!_lock.testAndSetAcquire(0, 1)) - ; -} - -bool SpinLock::lock(int msecs) { - QDeadlineTimer deadline(msecs); - for (;;) { - if (_lock.testAndSetAcquire(0, 1)) { - return true; - } - if (deadline.hasExpired()) { - return false; - } - } -} - -void SpinLock::unlock() { - _lock.storeRelease(0); -} - -SpinLockLocker::SpinLockLocker(SpinLock& lock, int msecs /* = -1 */ ) : _lock(&lock) { - _isLocked = _lock->lock(msecs); -} - -SpinLockLocker::~SpinLockLocker() { - if (_isLocked) { - _lock->unlock(); - } -} - -bool SpinLockLocker::isLocked() const { - return _isLocked; -} - -void SpinLockLocker::unlock() { - if (_isLocked) { - _lock->unlock(); - _isLocked = false; - } -} - -bool SpinLockLocker::relock(int msecs /* = -1 */ ) { - if (!_isLocked) { - _isLocked = _lock->lock(msecs); - } - return _isLocked; -} - -// ------------------------------------------------------------------------------------------------ - -crashpad::CrashpadClient* client{ nullptr }; -std::unique_ptr< crashpad::CrashReportDatabase > crashpadDatabase; - - -SpinLock crashpadAnnotationsProtect; -crashpad::SimpleStringDictionary* crashpadAnnotations{ nullptr }; - -#if defined(Q_OS_WIN) -static const QString CRASHPAD_HANDLER_NAME{ "crashpad_handler.exe" }; -#else -static const QString CRASHPAD_HANDLER_NAME{ "crashpad_handler" }; -#endif - -#ifdef Q_OS_LINUX -#include -#endif - -#ifdef Q_OS_WIN -// ------------------------------------------------------------------------------------------------ -// The area within this #ifdef is specific to the Microsoft C++ compiler - -#include -#include -#include -#include - -#include -#include - -static constexpr DWORD STATUS_MSVC_CPP_EXCEPTION = 0xE06D7363; -static constexpr ULONG_PTR MSVC_CPP_EXCEPTION_SIGNATURE = 0x19930520; -static constexpr int ANNOTATION_LOCK_WEAK_ATTEMPT = 5000; // attempt to lock the annotations list, but give up if it takes more than 5 seconds - -LPTOP_LEVEL_EXCEPTION_FILTER gl_crashpadUnhandledExceptionFilter = nullptr; -QTimer unhandledExceptionTimer; // checks occasionally in case loading an external DLL reset the unhandled exception pointer - -void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo); // extracts type information from a thrown C++ exception -LONG WINAPI firstChanceExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo); // called on any thrown exception (whether or not it's caught) -LONG WINAPI unhandledExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo); // called on any exception without a corresponding catch - -static LONG WINAPI firstChanceExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { - // we're catching these exceptions on first-chance as the system state is corrupted at this point and they may not survive the exception handling mechanism - if (client && (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_HEAP_CORRUPTION || - pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_BUFFER_OVERRUN)) { - client->DumpAndCrash(pExceptionInfo); - } - - return EXCEPTION_CONTINUE_SEARCH; -} - -static LONG WINAPI unhandledExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { - if (client && pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_MSVC_CPP_EXCEPTION) { - fatalCxxException(pExceptionInfo); - client->DumpAndCrash(pExceptionInfo); - } - - if (gl_crashpadUnhandledExceptionFilter != nullptr) { - return gl_crashpadUnhandledExceptionFilter(pExceptionInfo); - } - - return EXCEPTION_CONTINUE_SEARCH; -} - -// The following structures are modified versions of structs defined inplicitly by the Microsoft C++ compiler -// as described at http://www.geoffchappell.com/studies/msvc/language/predefined/ -// They are redefined here as the definitions the compiler gives only work in 32-bit contexts and are out-of-sync -// with the internal structures when operating in a 64-bit environment -// as discovered and described here: https://stackoverflow.com/questions/39113168/c-rtti-in-a-windows-64-bit-vectoredexceptionhandler-ms-visual-studio-2015 - -#pragma pack(push, ehdata, 4) - -struct PMD_internal { // internal name: _PMD (no changes, so could in theory just use the original) - int mdisp; - int pdisp; - int vdisp; -}; - -struct ThrowInfo_internal { // internal name: _ThrowInfo (changed all pointers into __int32) - __int32 attributes; - __int32 pmfnUnwind; // 32-bit RVA - __int32 pForwardCompat; // 32-bit RVA - __int32 pCatchableTypeArray; // 32-bit RVA -}; - -struct CatchableType_internal { // internal name: _CatchableType (changed all pointers into __int32) - __int32 properties; - __int32 pType; // 32-bit RVA - PMD_internal thisDisplacement; - __int32 sizeOrOffset; - __int32 copyFunction; // 32-bit RVA -}; - -#pragma warning(disable : 4200) -struct CatchableTypeArray_internal { // internal name: _CatchableTypeArray (changed all pointers into __int32) - int nCatchableTypes; - __int32 arrayOfCatchableTypes[0]; // 32-bit RVA -}; -#pragma warning(default : 4200) - -#pragma pack(pop, ehdata) - -// everything inside this function is extremely undocumented, attempting to extract -// the underlying C++ exception type (or at least its name) before throwing the whole -// mess at crashpad -// Some links describing how C++ exception handling works in an SEH context -// (since C++ exceptions are a figment of the Microsoft compiler): -// - https://www.codeproject.com/Articles/175482/Compiler-Internals-How-Try-Catch-Throw-are-Interpr -// - https://stackoverflow.com/questions/21888076/how-to-find-the-context-record-for-user-mode-exception-on-x64 - -static void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo) { - SpinLockLocker guard(crashpadAnnotationsProtect, ANNOTATION_LOCK_WEAK_ATTEMPT); - if (!guard.isLocked()) { - return; - } - - PEXCEPTION_RECORD ExceptionRecord = pExceptionInfo->ExceptionRecord; - /* - Exception arguments for Microsoft C++ exceptions: - [0] signature - magic number - [1] void* - variable that is being thrown - [2] ThrowInfo* - description of the variable that was thrown - [3] HMODULE - (64-bit only) base address that all 32bit pointers are added to - */ - - if (ExceptionRecord->NumberParameters != 4 || ExceptionRecord->ExceptionInformation[0] != MSVC_CPP_EXCEPTION_SIGNATURE) { - // doesn't match expected parameter counts or magic numbers - return; - } - - // get the ThrowInfo struct from the exception arguments - ThrowInfo_internal* pThrowInfo = reinterpret_cast(ExceptionRecord->ExceptionInformation[2]); - ULONG_PTR moduleBase = ExceptionRecord->ExceptionInformation[3]; - if (moduleBase == 0 || pThrowInfo == NULL) { - return; // broken assumption - } - - // get the CatchableTypeArray* struct from ThrowInfo - if (pThrowInfo->pCatchableTypeArray == 0) { - return; // broken assumption - } - CatchableTypeArray_internal* pCatchableTypeArray = - reinterpret_cast(moduleBase + pThrowInfo->pCatchableTypeArray); - if (pCatchableTypeArray->nCatchableTypes == 0 || pCatchableTypeArray->arrayOfCatchableTypes[0] == 0) { - return; // broken assumption - } - - // get the CatchableType struct for the actual exception type from CatchableTypeArray - CatchableType_internal* pCatchableType = - reinterpret_cast(moduleBase + pCatchableTypeArray->arrayOfCatchableTypes[0]); - if (pCatchableType->pType == 0) { - return; // broken assumption - } - const std::type_info* type = reinterpret_cast(moduleBase + pCatchableType->pType); - - crashpadAnnotations->SetKeyValue("thrownObject", type->name()); - - // After annotating the name of the actual object type, go through the other entries in CatcahleTypeArray and itemize the list of possible - // catch() commands that could have caught this so we can find the list of its superclasses - QString compatibleObjects; - for (int catchTypeIdx = 1; catchTypeIdx < pCatchableTypeArray->nCatchableTypes; catchTypeIdx++) { - CatchableType_internal* pCatchableSuperclassType = - reinterpret_cast(moduleBase + pCatchableTypeArray->arrayOfCatchableTypes[catchTypeIdx]); - if (pCatchableSuperclassType->pType == 0) { - return; // broken assumption - } - const std::type_info* superclassType = reinterpret_cast(moduleBase + pCatchableSuperclassType->pType); - - if (!compatibleObjects.isEmpty()) { - compatibleObjects += ", "; - } - compatibleObjects += superclassType->name(); - } - crashpadAnnotations->SetKeyValue("thrownObjectLike", compatibleObjects.toStdString()); -} - -void checkUnhandledExceptionHook() { - LPTOP_LEVEL_EXCEPTION_FILTER prevExceptionFilter = SetUnhandledExceptionFilter(unhandledExceptionHandler); - if (prevExceptionFilter != unhandledExceptionHandler) { - qWarning() << QString("Restored unhandled exception filter (which had been changed to %1)") - .arg(reinterpret_cast(prevExceptionFilter), 16, 16, QChar('0')); - } -} - -// End of code specific to the Microsoft C++ compiler -// ------------------------------------------------------------------------------------------------ -#endif // Q_OS_WIN - -// Locate the full path to the binary's directory -static QString findBinaryDir() { - // Normally we'd just use QCoreApplication::applicationDirPath(), but we can't. - // That function needs the QApplication to be created first, and Crashpad is initialized as early as possible, - // which is well before QApplication, so that function throws out a warning and returns ".". - // - // So we must do things the hard way here. In particular this is needed to correctly handle things in AppImage - // on Linux. On Windows and MacOS falling back to argv[0] should be fine. - -#ifdef Q_OS_LINUX - // Find outselves by looking at /proc//exe - pid_t ourPid = getpid(); - QString exeLink = QString("/proc/%1/exe").arg(ourPid); - qCDebug(crash_handler) << "Looking at" << exeLink; - - QFileInfo exeLinkInfo(exeLink); - if (exeLinkInfo.isSymLink()) { - QFileInfo exeInfo(exeLinkInfo.symLinkTarget()); - qCDebug(crash_handler) << "exe symlink points at" << exeInfo; - return exeInfo.absoluteDir().absolutePath(); - } else { - qCWarning(crash_handler) << exeLink << "isn't a symlink. /proc not mounted?"; - } - -#endif - - return QString(); -} - -bool startCrashHandler(std::string appPath, std::string crashURL, std::string crashToken) { - if (crashURL.empty()) { - crashURL = BACKTRACE_URL; - } - - if (crashToken.empty()) { - crashToken = BACKTRACE_TOKEN; - } - - if (crashURL.empty() || crashToken.empty()) { - qCCritical(crash_handler) << "Backtrace URL or token not set, crash handler disabled."; - return false; - } - - assert(!client); - client = new crashpad::CrashpadClient(); - std::vector arguments; - - std::map annotations; - annotations["sentry[release]"] = crashToken; - annotations["sentry[contexts][app][app_version]"] = BuildInfo::VERSION.toStdString(); - annotations["sentry[contexts][app][app_build]"] = BuildInfo::BUILD_NUMBER.toStdString(); - annotations["build_type"] = BuildInfo::BUILD_TYPE_STRING.toStdString(); - - auto machineFingerPrint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); - annotations["machine_fingerprint"] = machineFingerPrint.toStdString(); - - arguments.push_back("--no-rate-limit"); - - // Setup Crashpad DB directory - const auto crashpadDbName = "crashpad-db"; - const auto crashpadDbDir = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); - QDir(crashpadDbDir).mkpath(crashpadDbName); // Make sure the directory exists - const auto crashpadDbPath = crashpadDbDir.toStdString() + "/" + crashpadDbName; - - // Locate Crashpad handler - QString binaryDir = findBinaryDir(); - QDir interfaceDir; - - if (!binaryDir.isEmpty()) { - // Locating ourselves by argv[0] fails in the case of AppImage on Linux, as we get the AppImage - // itself in there. If we have a platform-specific method, and it succeeds, we use that instead - // of argv. - qCDebug(crash_handler) << "Locating own directory by platform-specific method"; - interfaceDir.setPath(binaryDir); - } else { - // Getting the base dir from argv[0] is already handled by CrashHandler, so we use - // the path as-is here. - qCDebug(crash_handler) << "Locating own directory by argv[0]"; - interfaceDir.setPath(QString::fromStdString(appPath)); - } - - if (!interfaceDir.exists(CRASHPAD_HANDLER_NAME)) { - qCCritical(crash_handler) << "Failed to find" << CRASHPAD_HANDLER_NAME << "in" << interfaceDir << ", can't start crash handler"; - return false; - } - - const std::string CRASHPAD_HANDLER_PATH = interfaceDir.filePath(CRASHPAD_HANDLER_NAME).toStdString(); - - qCDebug(crash_handler) << "Crashpad handler found at" << QString::fromStdString(CRASHPAD_HANDLER_PATH); - - // Setup different file paths - base::FilePath::StringType dbPath; - base::FilePath::StringType handlerPath; - dbPath.assign(crashpadDbPath.cbegin(), crashpadDbPath.cend()); - handlerPath.assign(CRASHPAD_HANDLER_PATH.cbegin(), CRASHPAD_HANDLER_PATH.cend()); - - base::FilePath db(dbPath); - base::FilePath handler(handlerPath); - - qCDebug(crash_handler) << "Opening crashpad database" << QString::fromStdString(crashpadDbPath); - crashpadDatabase = crashpad::CrashReportDatabase::Initialize(db); - if (crashpadDatabase == nullptr || crashpadDatabase->GetSettings() == nullptr) { - qCCritical(crash_handler) << "Failed to open crashpad database" << QString::fromStdString(crashpadDbPath); - return false; - } - - // Enable automated uploads. - // QObject::connect(&UserActivityLogger::getInstance(), &UserActivityLogger::crashReportingEnabledChanged, []() { - // auto &ual = UserActivityLogger::getInstance(); - // setCrashReportingEnabled(ual.isCrashReportingEnabled()); - // }); - - crashpadDatabase->GetSettings()->SetUploadsEnabled(CrashHandler::getInstance().isEnabled()); - - - if (!client->StartHandler(handler, db, db, crashURL, annotations, arguments, true, true)) { - qCCritical(crash_handler) << "Failed to start crashpad handler"; - return false; - } - -#ifdef Q_OS_WIN - AddVectoredExceptionHandler(0, firstChanceExceptionHandler); - gl_crashpadUnhandledExceptionFilter = SetUnhandledExceptionFilter(unhandledExceptionHandler); -#endif - - qCInfo(crash_handler) << "Crashpad initialized"; - return true; -} - -void setCrashReportingEnabled(bool enabled) { - if (!crashpadDatabase) { - qCCritical(crash_handler) << "Can't set to enabled, crash handler not initialized!"; - return; - } - - auto settings = crashpadDatabase->GetSettings(); - settings->SetUploadsEnabled(enabled); - - if (enabled) { - // Enabled now, was disabled before - // - // Reports are generated while uploads are disabled, so if we didn't do this we could - // send something the user doesn't want sent. So if reporting was disabled then enabled, - // remove any pending reports. - qCDebug(crash_handler) << "Removing any pending crash reports"; - - std::vector pendingReports; - crashpad::CrashReportDatabase::OperationStatus status; - - status = crashpadDatabase->GetPendingReports(&pendingReports); - - if (status != crashpad::CrashReportDatabase::kNoError) { - qCWarning(crash_handler) << "Failed to get pending reports"; - } else { - for (const auto& report : pendingReports) { - qCDebug(crash_handler) << "Deleted crash report" << QString::fromStdString(report.uuid.ToString()); - crashpadDatabase->DeleteReport(report.uuid); - } - } - } - - qCInfo(crash_handler) << "Crashpad uploads " << (enabled ? QString("enabled") : QString("disabled")); -} - -void setCrashAnnotation(std::string name, std::string value) { - if (client) { - SpinLockLocker guard(crashpadAnnotationsProtect); - if (!crashpadAnnotations) { - crashpadAnnotations = new crashpad::SimpleStringDictionary(); // don't free this, let it leak - crashpad::CrashpadInfo* crashpad_info = crashpad::CrashpadInfo::GetCrashpadInfo(); - crashpad_info->set_simple_annotations(crashpadAnnotations); - } - std::replace(value.begin(), value.end(), ',', ';'); - crashpadAnnotations->SetKeyValue(name, value); - } -} - -void startCrashHookMonitor(QCoreApplication* app) { -#ifdef Q_OS_WIN - // create a timer that checks to see if our exception handler has been reset. This may occur when a new CRT - // is initialized, which could happen any time a DLL not compiled with the same compiler is loaded. - // It would be nice if this were replaced with a more intelligent response; this fires once a minute which - // may be too much (extra code running) and too little (leaving up to a 1min gap after the hook is broken) - checkUnhandledExceptionHook(); - - unhandledExceptionTimer.moveToThread(app->thread()); - QObject::connect(&unhandledExceptionTimer, &QTimer::timeout, checkUnhandledExceptionHook); - unhandledExceptionTimer.start(60000); -#endif // Q_OS_WIN -} - -void CrashHandler::logMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) { - -} - -#endif // HAS_SENTRY diff --git a/libraries/networking/src/crash-handler/CrashHandlerBackend_Sentry.cpp b/libraries/networking/src/crash-handler/CrashHandlerBackend_Sentry.cpp index 3e0c9032e65..ecb7af2c1a6 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerBackend_Sentry.cpp +++ b/libraries/networking/src/crash-handler/CrashHandlerBackend_Sentry.cpp @@ -63,95 +63,6 @@ Q_LOGGING_CATEGORY(crash_handler, "overte.crash_handler") static const std::string BACKTRACE_URL{ OVERTE_BACKTRACE_URL }; static const std::string BACKTRACE_TOKEN{ OVERTE_BACKTRACE_TOKEN }; -std::string custom_backtrace_url; -std::string custom_backtrace_token; - - -// ------------------------------------------------------------------------------------------------ -// SpinLock - a lock that can timeout attempting to lock a block of code, and is in a busy-wait cycle while trying to acquire -// note that this code will malfunction if you attempt to grab a lock while already holding it - -class SpinLock { -public: - SpinLock(); - void lock(); - bool lock(int msecs); - void unlock(); - -private: - QAtomicInteger _lock{ 0 }; - - Q_DISABLE_COPY(SpinLock) -}; - -class SpinLockLocker { -public: - SpinLockLocker(SpinLock& lock, int msecs = -1); - ~SpinLockLocker(); - bool isLocked() const; - void unlock(); - bool relock(int msecs = -1); - -private: - SpinLock* _lock; - bool _isLocked; - - Q_DISABLE_COPY(SpinLockLocker) -}; - -SpinLock::SpinLock() { -} - -void SpinLock::lock() { - while (!_lock.testAndSetAcquire(0, 1)) - ; -} - -bool SpinLock::lock(int msecs) { - QDeadlineTimer deadline(msecs); - for (;;) { - if (_lock.testAndSetAcquire(0, 1)) { - return true; - } - if (deadline.hasExpired()) { - return false; - } - } -} - -void SpinLock::unlock() { - _lock.storeRelease(0); -} - -SpinLockLocker::SpinLockLocker(SpinLock& lock, int msecs /* = -1 */ ) : _lock(&lock) { - _isLocked = _lock->lock(msecs); -} - -SpinLockLocker::~SpinLockLocker() { - if (_isLocked) { - _lock->unlock(); - } -} - -bool SpinLockLocker::isLocked() const { - return _isLocked; -} - -void SpinLockLocker::unlock() { - if (_isLocked) { - _lock->unlock(); - _isLocked = false; - } -} - -bool SpinLockLocker::relock(int msecs /* = -1 */ ) { - if (!_isLocked) { - _isLocked = _lock->lock(msecs); - } - return _isLocked; -} - -// ------------------------------------------------------------------------------------------------ @@ -165,175 +76,7 @@ static const QString CRASHPAD_HANDLER_NAME{ "crashpad_handler" }; #include #endif -#ifdef Q_OS_WIN -// ------------------------------------------------------------------------------------------------ -// The area within this #ifdef is specific to the Microsoft C++ compiler - -#include -#include -#include -#include -#include -#include - -static constexpr DWORD STATUS_MSVC_CPP_EXCEPTION = 0xE06D7363; -static constexpr ULONG_PTR MSVC_CPP_EXCEPTION_SIGNATURE = 0x19930520; -static constexpr int ANNOTATION_LOCK_WEAK_ATTEMPT = 5000; // attempt to lock the annotations list, but give up if it takes more than 5 seconds - -LPTOP_LEVEL_EXCEPTION_FILTER gl_crashpadUnhandledExceptionFilter = nullptr; -QTimer unhandledExceptionTimer; // checks occasionally in case loading an external DLL reset the unhandled exception pointer - -void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo); // extracts type information from a thrown C++ exception -LONG WINAPI firstChanceExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo); // called on any thrown exception (whether or not it's caught) -LONG WINAPI unhandledExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo); // called on any exception without a corresponding catch - -static LONG WINAPI firstChanceExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { - // we're catching these exceptions on first-chance as the system state is corrupted at this point and they may not survive the exception handling mechanism - if (client && (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_HEAP_CORRUPTION || - pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_BUFFER_OVERRUN)) { - client->DumpAndCrash(pExceptionInfo); - } - - return EXCEPTION_CONTINUE_SEARCH; -} - -static LONG WINAPI unhandledExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { - if (client && pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_MSVC_CPP_EXCEPTION) { - fatalCxxException(pExceptionInfo); - client->DumpAndCrash(pExceptionInfo); - } - - if (gl_crashpadUnhandledExceptionFilter != nullptr) { - return gl_crashpadUnhandledExceptionFilter(pExceptionInfo); - } - - return EXCEPTION_CONTINUE_SEARCH; -} - -// The following structures are modified versions of structs defined inplicitly by the Microsoft C++ compiler -// as described at http://www.geoffchappell.com/studies/msvc/language/predefined/ -// They are redefined here as the definitions the compiler gives only work in 32-bit contexts and are out-of-sync -// with the internal structures when operating in a 64-bit environment -// as discovered and described here: https://stackoverflow.com/questions/39113168/c-rtti-in-a-windows-64-bit-vectoredexceptionhandler-ms-visual-studio-2015 - -#pragma pack(push, ehdata, 4) - -struct PMD_internal { // internal name: _PMD (no changes, so could in theory just use the original) - int mdisp; - int pdisp; - int vdisp; -}; - -struct ThrowInfo_internal { // internal name: _ThrowInfo (changed all pointers into __int32) - __int32 attributes; - __int32 pmfnUnwind; // 32-bit RVA - __int32 pForwardCompat; // 32-bit RVA - __int32 pCatchableTypeArray; // 32-bit RVA -}; - -struct CatchableType_internal { // internal name: _CatchableType (changed all pointers into __int32) - __int32 properties; - __int32 pType; // 32-bit RVA - PMD_internal thisDisplacement; - __int32 sizeOrOffset; - __int32 copyFunction; // 32-bit RVA -}; - -#pragma warning(disable : 4200) -struct CatchableTypeArray_internal { // internal name: _CatchableTypeArray (changed all pointers into __int32) - int nCatchableTypes; - __int32 arrayOfCatchableTypes[0]; // 32-bit RVA -}; -#pragma warning(default : 4200) - -#pragma pack(pop, ehdata) - -// everything inside this function is extremely undocumented, attempting to extract -// the underlying C++ exception type (or at least its name) before throwing the whole -// mess at crashpad -// Some links describing how C++ exception handling works in an SEH context -// (since C++ exceptions are a figment of the Microsoft compiler): -// - https://www.codeproject.com/Articles/175482/Compiler-Internals-How-Try-Catch-Throw-are-Interpr -// - https://stackoverflow.com/questions/21888076/how-to-find-the-context-record-for-user-mode-exception-on-x64 - -static void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo) { - SpinLockLocker guard(crashpadAnnotationsProtect, ANNOTATION_LOCK_WEAK_ATTEMPT); - if (!guard.isLocked()) { - return; - } - - PEXCEPTION_RECORD ExceptionRecord = pExceptionInfo->ExceptionRecord; - /* - Exception arguments for Microsoft C++ exceptions: - [0] signature - magic number - [1] void* - variable that is being thrown - [2] ThrowInfo* - description of the variable that was thrown - [3] HMODULE - (64-bit only) base address that all 32bit pointers are added to - */ - - if (ExceptionRecord->NumberParameters != 4 || ExceptionRecord->ExceptionInformation[0] != MSVC_CPP_EXCEPTION_SIGNATURE) { - // doesn't match expected parameter counts or magic numbers - return; - } - - // get the ThrowInfo struct from the exception arguments - ThrowInfo_internal* pThrowInfo = reinterpret_cast(ExceptionRecord->ExceptionInformation[2]); - ULONG_PTR moduleBase = ExceptionRecord->ExceptionInformation[3]; - if (moduleBase == 0 || pThrowInfo == NULL) { - return; // broken assumption - } - - // get the CatchableTypeArray* struct from ThrowInfo - if (pThrowInfo->pCatchableTypeArray == 0) { - return; // broken assumption - } - CatchableTypeArray_internal* pCatchableTypeArray = - reinterpret_cast(moduleBase + pThrowInfo->pCatchableTypeArray); - if (pCatchableTypeArray->nCatchableTypes == 0 || pCatchableTypeArray->arrayOfCatchableTypes[0] == 0) { - return; // broken assumption - } - - // get the CatchableType struct for the actual exception type from CatchableTypeArray - CatchableType_internal* pCatchableType = - reinterpret_cast(moduleBase + pCatchableTypeArray->arrayOfCatchableTypes[0]); - if (pCatchableType->pType == 0) { - return; // broken assumption - } - const std::type_info* type = reinterpret_cast(moduleBase + pCatchableType->pType); - - crashpadAnnotations->SetKeyValue("thrownObject", type->name()); - - // After annotating the name of the actual object type, go through the other entries in CatcahleTypeArray and itemize the list of possible - // catch() commands that could have caught this so we can find the list of its superclasses - QString compatibleObjects; - for (int catchTypeIdx = 1; catchTypeIdx < pCatchableTypeArray->nCatchableTypes; catchTypeIdx++) { - CatchableType_internal* pCatchableSuperclassType = - reinterpret_cast(moduleBase + pCatchableTypeArray->arrayOfCatchableTypes[catchTypeIdx]); - if (pCatchableSuperclassType->pType == 0) { - return; // broken assumption - } - const std::type_info* superclassType = reinterpret_cast(moduleBase + pCatchableSuperclassType->pType); - - if (!compatibleObjects.isEmpty()) { - compatibleObjects += ", "; - } - compatibleObjects += superclassType->name(); - } - crashpadAnnotations->SetKeyValue("thrownObjectLike", compatibleObjects.toStdString()); -} - -void checkUnhandledExceptionHook() { - LPTOP_LEVEL_EXCEPTION_FILTER prevExceptionFilter = SetUnhandledExceptionFilter(unhandledExceptionHandler); - if (prevExceptionFilter != unhandledExceptionHandler) { - qWarning() << QString("Restored unhandled exception filter (which had been changed to %1)") - .arg(reinterpret_cast(prevExceptionFilter), 16, 16, QChar('0')); - } -} - -// End of code specific to the Microsoft C++ compiler -// ------------------------------------------------------------------------------------------------ -#endif // Q_OS_WIN // Locate the full path to the binary's directory static QString findBinaryDir() { @@ -416,17 +159,7 @@ void setCrashAnnotation(std::string name, std::string value) { } void startCrashHookMonitor(QCoreApplication* app) { -#ifdef Q_OS_WIN - // create a timer that checks to see if our exception handler has been reset. This may occur when a new CRT - // is initialized, which could happen any time a DLL not compiled with the same compiler is loaded. - // It would be nice if this were replaced with a more intelligent response; this fires once a minute which - // may be too much (extra code running) and too little (leaving up to a 1min gap after the hook is broken) - checkUnhandledExceptionHook(); - - unhandledExceptionTimer.moveToThread(app->thread()); - QObject::connect(&unhandledExceptionTimer, &QTimer::timeout, checkUnhandledExceptionHook); - unhandledExceptionTimer.start(60000); -#endif // Q_OS_WIN + } From db780add145a9a1e3c02538129ea86146ec768fb Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Sat, 8 Aug 2026 19:09:29 +0200 Subject: [PATCH 11/29] Refactor the structure of the crash handler --- .../src/crash-handler/CrashHandler.cpp | 75 ++++++- .../src/crash-handler/CrashHandler.h | 68 +++++- .../src/crash-handler/CrashHandlerBackend.h | 27 --- .../CrashHandlerBackend_None.cpp | 42 ---- .../CrashHandlerBackend_Sentry.cpp | 209 ------------------ .../src/crash-handler/CrashHandlerNone.h | 34 +++ .../src/crash-handler/CrashHandlerSentry.cpp | 90 ++++++++ .../src/crash-handler/CrashHandlerSentry.h | 28 +++ 8 files changed, 286 insertions(+), 287 deletions(-) delete mode 100644 libraries/networking/src/crash-handler/CrashHandlerBackend.h delete mode 100644 libraries/networking/src/crash-handler/CrashHandlerBackend_None.cpp delete mode 100644 libraries/networking/src/crash-handler/CrashHandlerBackend_Sentry.cpp create mode 100644 libraries/networking/src/crash-handler/CrashHandlerNone.h create mode 100644 libraries/networking/src/crash-handler/CrashHandlerSentry.cpp create mode 100644 libraries/networking/src/crash-handler/CrashHandlerSentry.h diff --git a/libraries/networking/src/crash-handler/CrashHandler.cpp b/libraries/networking/src/crash-handler/CrashHandler.cpp index 877f831534e..477751f758a 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.cpp +++ b/libraries/networking/src/crash-handler/CrashHandler.cpp @@ -10,18 +10,33 @@ // #include "CrashHandler.h" -#include "CrashHandlerBackend.h" +#include "CrashHandlerNone.h" +#include "CrashHandlerSentry.h" + #include #include #include +#include + #include "LogHandler.h" +#ifdef Q_OS_LINUX +#include +#endif + +Q_LOGGING_CATEGORY(crash_handler, "overte.crash_handler") + static void crashHandlerLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg); CrashHandler& CrashHandler::getInstance() { - static CrashHandler sharedInstance; +#if HAS_SENTRY + static CrashHandlerSentry sharedInstance; +#else + static CrashHandlerNone sharedInstance; +#endif + return sharedInstance; } @@ -50,7 +65,7 @@ bool CrashHandler::start() { return false; } - auto started = startCrashHandler(_path.toStdString(), _crashUrl.toStdString(), _crashToken.toStdString()); + auto started = startCrashHandler(); setStarted(started); if ( started ) { @@ -82,7 +97,7 @@ bool CrashHandler::start() { } void CrashHandler::startMonitor(QCoreApplication *app) { - startCrashHookMonitor(app); + // startCrashHookMonitor(app); } void CrashHandler::setEnabled(bool enabled) { @@ -135,6 +150,58 @@ void CrashHandler::setAnnotation(const std::string &key, const std::string &valu } +void CrashHandler::logMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + // Some of the logging is very spammy and Sentry seems to have a problem with shutting down + // in that case. + { + std::lock_guard lock(_logMutex); + + if (msg == _previousMessage) { + _repeatCount++; + return; + } + + if (_repeatCount > 0) { + sendLogMessage(QtMsgType::QtWarningMsg, QMessageLogContext(), QString("Previous log message repeated %1 times").arg(_repeatCount)); + } + + _previousMessage = msg; + _repeatCount = 0; + } + + sendLogMessage(type, context, msg); +} + +// Locate the full path to the binary's directory +QString CrashHandler::findBinaryDir() { + // Normally we'd just use QCoreApplication::applicationDirPath(), but we can't. + // That function needs the QApplication to be created first, and Crashpad is initialized as early as possible, + // which is well before QApplication, so that function throws out a warning and returns ".". + // + // So we must do things the hard way here. In particular this is needed to correctly handle things in AppImage + // on Linux. On Windows and MacOS falling back to argv[0] should be fine. + +#ifdef Q_OS_LINUX + // Find outselves by looking at /proc//exe + pid_t ourPid = getpid(); + QString exeLink = QString("/proc/%1/exe").arg(ourPid); + qCDebug(crash_handler) << "Looking at" << exeLink; + + QFileInfo exeLinkInfo(exeLink); + if (exeLinkInfo.isSymLink()) { + QFileInfo exeInfo(exeLinkInfo.symLinkTarget()); + qCDebug(crash_handler) << "exe symlink points at" << exeInfo; + return exeInfo.absoluteDir().absolutePath(); + } else { + qCWarning(crash_handler) << exeLink << "isn't a symlink. /proc not mounted?"; + } + +#endif + + return QString(); +} + + static void crashHandlerLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) { CrashHandler::getInstance().logMessage(type, context, msg); diff --git a/libraries/networking/src/crash-handler/CrashHandler.h b/libraries/networking/src/crash-handler/CrashHandler.h index 6329290a772..a79506a7fd7 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.h +++ b/libraries/networking/src/crash-handler/CrashHandler.h @@ -14,10 +14,24 @@ #include #include #include +#include #include #include #include +Q_DECLARE_LOGGING_CATEGORY(crash_handler) + + +// We get these from CMake if crash reporting is enabled + +#ifndef OVERTE_BACKTRACE_URL +#define OVERTE_BACKTRACE_URL "" +#endif + +#ifndef OVERTE_BACKTRACE_TOKEN +#define OVERTE_BACKTRACE_TOKEN "" +#endif + @@ -57,6 +71,10 @@ class CrashHandler : public QObject { public: static CrashHandler& getInstance(); + QString path() const { return _path; } + QString url() const { return _crashUrl; } + QString token() const { return _crashToken; } + public slots: @@ -71,6 +89,8 @@ public slots: */ void setPath(const QString &path); + + /** * @brief Start the crash handler * @@ -213,7 +233,7 @@ public slots: * @param context Qt logging context * @param msg Message to log */ - virtual void logMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg); + void logMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg); signals: @@ -226,8 +246,39 @@ public slots: */ void enabledChanged(bool enabled); -private: - CrashHandler(QObject *parent = nullptr); +protected: + + /** + * @brief Start the crash handler + * + * This is implementation dependent, and is called by start(). + * + */ + virtual bool startCrashHandler() = 0; + + /** + * @brief Set whether to enable crash reporting + * + * This is the implementation dependent part, called by setEnabled(). + * + * @param value Whether to enable crash reporting + */ + virtual void setCrashReportingEnabled(bool value) = 0; + + + /** + * @brief Send a log message to the crash handler. + * + * This will be logged even without a crash. + * + * @param type + * @param context + * @param msg + */ + virtual void sendLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) = 0; + + + virtual void setCrashAnnotation(std::string name, std::string value) = 0; /** @@ -239,6 +290,13 @@ public slots: */ void setStarted(bool started) { _crashMonitorStarted = started; } + // Move this to PathUtils + static QString findBinaryDir(); + + CrashHandler(QObject *parent = nullptr); + + +private: std::atomic _crashMonitorStarted {false}; std::atomic _crashReportingEnabled {false}; @@ -247,8 +305,8 @@ public slots: std::mutex _logMutex{}; QString _path; - QString _crashUrl; - QString _crashToken; + QString _crashUrl{OVERTE_BACKTRACE_URL}; + QString _crashToken{OVERTE_BACKTRACE_TOKEN}; QString _previousMessage{}; int _repeatCount { 0 }; diff --git a/libraries/networking/src/crash-handler/CrashHandlerBackend.h b/libraries/networking/src/crash-handler/CrashHandlerBackend.h deleted file mode 100644 index 55c1ef2760c..00000000000 --- a/libraries/networking/src/crash-handler/CrashHandlerBackend.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// CrashHandler.h -// interface/src -// -// Created by Clement Brisset on 01/19/18. -// Copyright 2018 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_CrashHandlerBackend_h -#define hifi_CrashHandlerBackend_h - -#include -#include -#include - -Q_DECLARE_LOGGING_CATEGORY(crash_handler) - -bool startCrashHandler(std::string appPath, std::string url="", std::string token=""); -void setCrashAnnotation(std::string name, std::string value); -void startCrashHookMonitor(QCoreApplication* app); -void setCrashReportingEnabled(bool value); - - -#endif // hifi_CrashHandlerBackend_h diff --git a/libraries/networking/src/crash-handler/CrashHandlerBackend_None.cpp b/libraries/networking/src/crash-handler/CrashHandlerBackend_None.cpp deleted file mode 100644 index 2be25bf6700..00000000000 --- a/libraries/networking/src/crash-handler/CrashHandlerBackend_None.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// -// CrashHandler_None.cpp -// interface/src -// -// Created by Clement Brisset on 01/19/18. -// Copyright 2018 High Fidelity, Inc. -// Copyright 2026 Overte e.V. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#if !defined(HAS_SENTRY) && !defined(HAS_CRASHPAD) && !defined(HAS_BREAKPAD) - -#include "CrashHandler.h" - -#include - -#include - - -Q_LOGGING_CATEGORY(crash_handler, "overte.crash_handler") - -bool startCrashHandler(std::string appPath, std::string crashURL, std::string crashToken) { - qCWarning(crash_handler) << "No crash handler available."; - return false; -} - -void setCrashAnnotation(std::string name, std::string value) { -} - -void startCrashHookMonitor(QCoreApplication* app) { -} - -void setCrashReportingEnabled(bool value) { - -} - -void CrashHandler::logMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) { - -} -#endif diff --git a/libraries/networking/src/crash-handler/CrashHandlerBackend_Sentry.cpp b/libraries/networking/src/crash-handler/CrashHandlerBackend_Sentry.cpp deleted file mode 100644 index ecb7af2c1a6..00000000000 --- a/libraries/networking/src/crash-handler/CrashHandlerBackend_Sentry.cpp +++ /dev/null @@ -1,209 +0,0 @@ -// -// CrashHandler_Sentry.cpp -// interface/src -// -// Created by Vadim Troschinsky on 05/08/2026. -// Copyright 2018 High Fidelity, Inc. -// Copyright 2025 Overte e.V. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#define HAS_SENTRY 1 - -#if HAS_SENTRY - - -#include "sentry.h" -#include "CrashHandler.h" - -Q_LOGGING_CATEGORY(crash_handler, "overte.crash_handler") - -#include - -#include -#include - -#include -#include -#include -#include -#include - -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wc++14-extensions" -#endif - - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif - -#include -#include "../FingerprintUtils.h" -#include "../UserActivityLogger.h" -#include - - - -/** - * @brief Sentry implementation - * - * Sentry is a crash reporting and metrics system. - * - * Documentation for the API is at https://docs.sentry.io/platforms/native/usage/ - * - * For reporting, we use the sentry DSN, not the minidump endpoint. - * - * - */ - -static const std::string BACKTRACE_URL{ OVERTE_BACKTRACE_URL }; -static const std::string BACKTRACE_TOKEN{ OVERTE_BACKTRACE_TOKEN }; - - - - -#if defined(Q_OS_WIN) -static const QString CRASHPAD_HANDLER_NAME{ "crashpad_handler.exe" }; -#else -static const QString CRASHPAD_HANDLER_NAME{ "crashpad_handler" }; -#endif - -#ifdef Q_OS_LINUX -#include -#endif - - - -// Locate the full path to the binary's directory -static QString findBinaryDir() { - // Normally we'd just use QCoreApplication::applicationDirPath(), but we can't. - // That function needs the QApplication to be created first, and Crashpad is initialized as early as possible, - // which is well before QApplication, so that function throws out a warning and returns ".". - // - // So we must do things the hard way here. In particular this is needed to correctly handle things in AppImage - // on Linux. On Windows and MacOS falling back to argv[0] should be fine. - -#ifdef Q_OS_LINUX - // Find outselves by looking at /proc//exe - pid_t ourPid = getpid(); - QString exeLink = QString("/proc/%1/exe").arg(ourPid); - qCDebug(crash_handler) << "Looking at" << exeLink; - - QFileInfo exeLinkInfo(exeLink); - if (exeLinkInfo.isSymLink()) { - QFileInfo exeInfo(exeLinkInfo.symLinkTarget()); - qCDebug(crash_handler) << "exe symlink points at" << exeInfo; - return exeInfo.absoluteDir().absolutePath(); - } else { - qCWarning(crash_handler) << exeLink << "isn't a symlink. /proc not mounted?"; - } - -#endif - - return QString(); -} - -bool startCrashHandler(std::string appPath, std::string crashURL, std::string crashToken) { - if (crashURL.empty()) { - crashURL = BACKTRACE_URL; - } - - if (crashToken.empty()) { - crashToken = BACKTRACE_TOKEN; - } - - if (crashURL.empty() || crashToken.empty()) { - qCCritical(crash_handler) << "Backtrace URL or token not set, crash handler disabled."; - return false; - } - - sentry_options_t *options = sentry_options_new(); - sentry_options_set_dsn(options, crashURL.c_str()); - sentry_options_set_release(options, crashToken.c_str()); - sentry_options_set_debug(options, 1); // Debug for Sentry Native SDK itself - sentry_options_set_enable_logs(options, 1); - sentry_options_set_minidump_mode(options, SENTRY_MINIDUMP_MODE_FULL); // TODO: a way to choose SENTRY_MINIDUMP_MODE_SMART, which sends smaller reports - - if (sentry_init(options) == 0) { - qCInfo(crash_handler) << "Sentry init successful"; - } else { - qCCritical(crash_handler) << "Sentry init failed"; - return false; - } - - - sentry_value_t release_info = sentry_value_new_object(); - sentry_value_set_by_key(release_info, "version", sentry_value_new_string(BuildInfo::VERSION.toStdString().c_str())); - sentry_value_set_by_key(release_info, "build", sentry_value_new_string(BuildInfo::BUILD_NUMBER.toStdString().c_str())); - - sentry_value_t machine_info = sentry_value_new_object(); - sentry_value_set_by_key(machine_info, "fingerprint", sentry_value_new_string(uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()).toStdString().c_str())) ; - - - sentry_set_context("Release", release_info); - sentry_set_context("Machine", machine_info); - - - qCInfo(crash_handler) << "Sentry crash handler initialized. SDK version" << sentry_sdk_version() << ". Will send reports to " << crashURL.c_str() << " with token " << crashToken.c_str(); - return true; - - //qCInfo(crash_handler) << "Crashpad uploads " << (enabled ? QString("enabled") : QString("disabled")); -} - -void setCrashAnnotation(std::string name, std::string value) { - -} - -void startCrashHookMonitor(QCoreApplication* app) { - -} - - -void CrashHandler::logMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) { - - // Some of the logging is very spammy and Sentry seems to have a problem with shutting down - // in that case. - { - std::lock_guard lock(_logMutex); - - if (msg == _previousMessage) { - _repeatCount++; - return; - } - - if (_repeatCount > 0) { - sentry_log_warn("Previous log message repeated %i times", _repeatCount); - } - - _previousMessage = msg; - _repeatCount = 0; - } - - - switch(type) { - case QtMsgType::QtFatalMsg: - sentry_log_fatal(msg.toStdString().c_str()); - break; - case QtMsgType::QtCriticalMsg: - sentry_log_error(msg.toStdString().c_str()); - break; - case QtMsgType::QtWarningMsg: - sentry_log_warn(msg.toStdString().c_str()); - break; - case QtMsgType::QtInfoMsg: - sentry_log_info(msg.toStdString().c_str()); - break; - case QtMsgType::QtDebugMsg: - sentry_log_debug(msg.toStdString().c_str()); - break; - default: - sentry_log_warn(msg.toStdString().c_str()); - sentry_log_warn("Previous log message was of unknown type %i", (int)type); - } -} - -#endif // HAS_SENTRY diff --git a/libraries/networking/src/crash-handler/CrashHandlerNone.h b/libraries/networking/src/crash-handler/CrashHandlerNone.h new file mode 100644 index 00000000000..ba70d67c2a9 --- /dev/null +++ b/libraries/networking/src/crash-handler/CrashHandlerNone.h @@ -0,0 +1,34 @@ + + +#ifndef hifi_CrashHandlerNone_h +#define hifi_CrashHandlerNone_h + +#include "CrashHandler.h" + +class CrashHandlerNone : public CrashHandler { + Q_OBJECT + + public: + + + protected: + + virtual bool startCrashHandler() override { + qCWarning(crash_handler) << "No crash handler available."; + return false; + } + + virtual void setCrashReportingEnabled([[maybe_unused]] bool value) override { + // This space intentionally left blank. + } + + virtual void sendLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) override { + // This space intentionally left blank. + } + + virtual void setCrashAnnotation(std::string name, std::string value) override { + // This space intentionally left blank. + } +}; + +#endif diff --git a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp new file mode 100644 index 00000000000..84c169ca91b --- /dev/null +++ b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp @@ -0,0 +1,90 @@ +#include "CrashHandlerSentry.h" + +#include "sentry.h" +#include +#include "../FingerprintUtils.h" +#include "../UserActivityLogger.h" +#include + + +bool CrashHandlerSentry::startCrashHandler() { + std::string appPath = path().toStdString(); + std::string crashURL = url().toStdString(); + std::string crashToken = token().toStdString(); + + + if (crashURL.empty() || crashToken.empty()) { + qCCritical(crash_handler) << "Backtrace URL or token not set, crash handler disabled."; + return false; + } + + sentry_options_t *options = sentry_options_new(); + sentry_options_set_dsn(options, crashURL.c_str()); + sentry_options_set_release(options, crashToken.c_str()); + sentry_options_set_debug(options, 1); // Debug for Sentry Native SDK itself + sentry_options_set_enable_logs(options, 1); + sentry_options_set_enable_metrics(options, 1); + + sentry_options_set_require_user_consent(options, 1); + sentry_options_set_minidump_mode(options, SENTRY_MINIDUMP_MODE_FULL); // TODO: a way to choose SENTRY_MINIDUMP_MODE_SMART, which sends smaller reports + + if (sentry_init(options) == 0) { + qCInfo(crash_handler) << "Sentry init successful"; + } else { + qCCritical(crash_handler) << "Sentry init failed"; + return false; + } + + + sentry_value_t release_info = sentry_value_new_object(); + sentry_value_set_by_key(release_info, "version", sentry_value_new_string(BuildInfo::VERSION.toStdString().c_str())); + sentry_value_set_by_key(release_info, "build", sentry_value_new_string(BuildInfo::BUILD_NUMBER.toStdString().c_str())); + + sentry_value_t machine_info = sentry_value_new_object(); + sentry_value_set_by_key(machine_info, "fingerprint", sentry_value_new_string(uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()).toStdString().c_str())) ; + + + sentry_set_context("Release", release_info); + sentry_set_context("Machine", machine_info); + + + qCInfo(crash_handler) << "Sentry crash handler initialized. SDK version" << sentry_sdk_version() << ". Will send reports to " << crashURL.c_str() << " with token " << crashToken.c_str(); + return true; + + //qCInfo(crash_handler) << "Crashpad uploads " << (enabled ? QString("enabled") : QString("disabled")); +} + +void CrashHandlerSentry::sendLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + switch(type) { + case QtMsgType::QtFatalMsg: + sentry_log_fatal(msg.toStdString().c_str()); + break; + case QtMsgType::QtCriticalMsg: + sentry_log_error(msg.toStdString().c_str()); + break; + case QtMsgType::QtWarningMsg: + sentry_log_warn(msg.toStdString().c_str()); + break; + case QtMsgType::QtInfoMsg: + sentry_log_info(msg.toStdString().c_str()); + break; + case QtMsgType::QtDebugMsg: + sentry_log_debug(msg.toStdString().c_str()); + break; + default: + sentry_log_warn(msg.toStdString().c_str()); + sentry_log_warn("Previous log message was of unknown type %i", (int)type); + } +} + +void CrashHandlerSentry::setCrashReportingEnabled(bool value) { + if (value) { + sentry_user_consent_give(); + } else { + sentry_user_consent_revoke(); + } +} + +void CrashHandlerSentry::setCrashAnnotation(std::string name, std::string value) { + sentry_set_tag(name.c_str(), value.c_str()); +} diff --git a/libraries/networking/src/crash-handler/CrashHandlerSentry.h b/libraries/networking/src/crash-handler/CrashHandlerSentry.h new file mode 100644 index 00000000000..cc76b2f93ef --- /dev/null +++ b/libraries/networking/src/crash-handler/CrashHandlerSentry.h @@ -0,0 +1,28 @@ + + +#ifndef hifi_CrashHandlerSentry_h +#define hifi_CrashHandlerSentry_h + +#include "CrashHandler.h" + +class CrashHandlerSentry : public CrashHandler { + Q_OBJECT + + public: + + + + + protected: + + virtual bool startCrashHandler() override; + + virtual void setCrashReportingEnabled([[maybe_unused]] bool value) override; + + virtual void sendLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) override; + + virtual void setCrashAnnotation(std::string name, std::string value) override; +}; + + +#endif From 11cc5406faa7b7a39fe24d9562d4fc7dfd4ab788 Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Sat, 8 Aug 2026 21:58:21 +0200 Subject: [PATCH 12/29] Enable crash handler as early as possible --- interface/src/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 65c3d828caf..e669d35f65a 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -657,6 +657,8 @@ int main(int argc, const char* argv[]) { auto& ual = UserActivityLogger::getInstance(); auto& ch = CrashHandler::getInstance(); + ch.start(); + QObject::connect(&ch, &CrashHandler::enabledChanged, [](bool enabled) { Settings s; s.beginGroup("Crash"); From 85852a89325f3d527b63a8648235b655b9f625a3 Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Sat, 8 Aug 2026 21:59:07 +0200 Subject: [PATCH 13/29] Call them "tags", not "annotations", less confusion with Sentry --- .../networking/src/crash-handler/CrashHandler.cpp | 4 ++-- .../networking/src/crash-handler/CrashHandler.h | 14 +++++++++++++- .../src/crash-handler/CrashHandlerNone.h | 2 +- .../src/crash-handler/CrashHandlerSentry.cpp | 2 +- .../src/crash-handler/CrashHandlerSentry.h | 2 +- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/libraries/networking/src/crash-handler/CrashHandler.cpp b/libraries/networking/src/crash-handler/CrashHandler.cpp index 477751f758a..c151297f028 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.cpp +++ b/libraries/networking/src/crash-handler/CrashHandler.cpp @@ -75,7 +75,7 @@ bool CrashHandler::start() { { std::lock_guard lock(_annotationsMutex); for(const auto &item : _annotations) { - setCrashAnnotation(item.first, item.second); + setTag(item.first, item.second); } countAdded = _annotations.size(); @@ -146,7 +146,7 @@ void CrashHandler::setAnnotation(const std::string &key, const std::string &valu return; } - setCrashAnnotation(key, value); + setTag(key, value); } diff --git a/libraries/networking/src/crash-handler/CrashHandler.h b/libraries/networking/src/crash-handler/CrashHandler.h index a79506a7fd7..1792b3653fe 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.h +++ b/libraries/networking/src/crash-handler/CrashHandler.h @@ -185,6 +185,8 @@ public slots: * Annotations add extra information, such as the application's version number, * the current user, or any other information of interest. * + * In Sentry, this creates a tag and they are searchable in the Sentry web interface. + * * @note Annotations made before the crash handler are remembered, and sent to the * crash handler as soon as it's initialized. * @@ -199,6 +201,8 @@ public slots: * Annotations add extra information, such as the application's version number, * the current user, or any other information of interest. * + * In Sentry, this creates a tag and they are searchable in the Sentry web interface. + * * @note Annotations made before the crash handler are remembered, and sent to the * crash handler as soon as it's initialized. * @@ -213,6 +217,8 @@ public slots: * Annotations add extra information, such as the application's version number, * the current user, or any other information of interest. * + * In Sentry, this creates a tag and they are searchable in the Sentry web interface. + * * @note Annotations made before the crash handler are remembered, and sent to the * crash handler as soon as it's initialized. * @@ -278,7 +284,13 @@ public slots: virtual void sendLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) = 0; - virtual void setCrashAnnotation(std::string name, std::string value) = 0; + /** + * @brief Attach a tag to the crash report + * + * @param name Key + * @param value Value + */ + virtual void setTag(std::string name, std::string value) = 0; /** diff --git a/libraries/networking/src/crash-handler/CrashHandlerNone.h b/libraries/networking/src/crash-handler/CrashHandlerNone.h index ba70d67c2a9..5c2db6d1664 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerNone.h +++ b/libraries/networking/src/crash-handler/CrashHandlerNone.h @@ -26,7 +26,7 @@ class CrashHandlerNone : public CrashHandler { // This space intentionally left blank. } - virtual void setCrashAnnotation(std::string name, std::string value) override { + virtual void setTag(std::string name, std::string value) override { // This space intentionally left blank. } }; diff --git a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp index 84c169ca91b..8ef8e095b3a 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp +++ b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp @@ -85,6 +85,6 @@ void CrashHandlerSentry::setCrashReportingEnabled(bool value) { } } -void CrashHandlerSentry::setCrashAnnotation(std::string name, std::string value) { +void CrashHandlerSentry::setTag(std::string name, std::string value) { sentry_set_tag(name.c_str(), value.c_str()); } diff --git a/libraries/networking/src/crash-handler/CrashHandlerSentry.h b/libraries/networking/src/crash-handler/CrashHandlerSentry.h index cc76b2f93ef..a675776262b 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerSentry.h +++ b/libraries/networking/src/crash-handler/CrashHandlerSentry.h @@ -21,7 +21,7 @@ class CrashHandlerSentry : public CrashHandler { virtual void sendLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) override; - virtual void setCrashAnnotation(std::string name, std::string value) override; + virtual void setTag(std::string name, std::string value) override; }; From e77f94d826bc03a05e21f2e1ccda70a7eb7b852f Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Sat, 8 Aug 2026 21:59:46 +0200 Subject: [PATCH 14/29] Send build time and type --- libraries/networking/src/crash-handler/CrashHandlerSentry.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp index 8ef8e095b3a..ac230cca7d1 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp +++ b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp @@ -39,6 +39,8 @@ bool CrashHandlerSentry::startCrashHandler() { sentry_value_t release_info = sentry_value_new_object(); sentry_value_set_by_key(release_info, "version", sentry_value_new_string(BuildInfo::VERSION.toStdString().c_str())); sentry_value_set_by_key(release_info, "build", sentry_value_new_string(BuildInfo::BUILD_NUMBER.toStdString().c_str())); + sentry_value_set_by_key(release_info, "build_time", sentry_value_new_string(BuildInfo::BUILD_TIME.toStdString().c_str())); + sentry_value_set_by_key(release_info, "build_type", sentry_value_new_string(BuildInfo::BUILD_TYPE_STRING.toStdString().c_str())); sentry_value_t machine_info = sentry_value_new_object(); sentry_value_set_by_key(machine_info, "fingerprint", sentry_value_new_string(uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()).toStdString().c_str())) ; From 721bc6091f5430cddaa006a59a42f05e5a04ab30 Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Sat, 8 Aug 2026 23:01:26 +0200 Subject: [PATCH 15/29] Improve crash recovery screen Add log streaming and stats submission --- interface/src/CrashRecoveryHandler.cpp | 14 ++++++ .../src/crash-handler/CrashHandler.cpp | 4 +- .../src/crash-handler/CrashHandler.h | 46 +++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/interface/src/CrashRecoveryHandler.cpp b/interface/src/CrashRecoveryHandler.cpp index 179116c9ef2..f2216c30c6c 100644 --- a/interface/src/CrashRecoveryHandler.cpp +++ b/interface/src/CrashRecoveryHandler.cpp @@ -182,11 +182,20 @@ CrashRecoveryHandler::Action CrashRecoveryHandler::promptUserForAction(bool show } QCheckBox* crashReportCheckbox = new QCheckBox("Enable automatic crash reporting"); + QCheckBox* logStatsCheckbox = new QCheckBox("Enable statistics collection"); + QCheckBox* logStreamingCheckbox = new QCheckBox("Stream logs to the developers for debugging"); + crashReportCheckbox->setChecked(ch.isEnabled()); crashReportCheckbox->setEnabled(ch.isStarted()); + logStatsCheckbox->setChecked(ch.isStatsStreamingEnabled()); + logStatsCheckbox->setEnabled(ch.isStarted()); + + logStreamingCheckbox->setChecked(ch.isLogStreamingEnabled()); + logStatsCheckbox->setEnabled(ch.isStarted()); + option3->setChecked(true); layout->addWidget(option1); layout->addWidget(option2); @@ -195,6 +204,8 @@ CrashRecoveryHandler::Action CrashRecoveryHandler::promptUserForAction(bool show layout->addWidget(crashReportLabel); layout->addWidget(crashReportCheckbox); + layout->addWidget(logStatsCheckbox); + layout->addWidget(logStreamingCheckbox); layout->addSpacing(12); layout->addStretch(); @@ -216,6 +227,9 @@ CrashRecoveryHandler::Action CrashRecoveryHandler::promptUserForAction(bool show } ch.setEnabled(crashReportCheckbox->isChecked()); + ch.setLogStreamingEnabled(logStreamingCheckbox->isChecked()); + ch.setStatsStreamingEnabled(logStatsCheckbox->isChecked()); + // Dialog cancelled or "do nothing" option chosen return CrashRecoveryHandler::DO_NOTHING; diff --git a/libraries/networking/src/crash-handler/CrashHandler.cpp b/libraries/networking/src/crash-handler/CrashHandler.cpp index c151297f028..b287d5c859b 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.cpp +++ b/libraries/networking/src/crash-handler/CrashHandler.cpp @@ -169,7 +169,9 @@ void CrashHandler::logMessage(QtMsgType type, const QMessageLogContext &context, _repeatCount = 0; } - sendLogMessage(type, context, msg); + if (isLogStreamingEnabled()) { + sendLogMessage(type, context, msg); + } } // Locate the full path to the binary's directory diff --git a/libraries/networking/src/crash-handler/CrashHandler.h b/libraries/networking/src/crash-handler/CrashHandler.h index 1792b3653fe..b74ed9f8703 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.h +++ b/libraries/networking/src/crash-handler/CrashHandler.h @@ -154,6 +154,46 @@ public slots: */ void setEnabled(bool enabled); + + + /** + * @brief Whether logs are streamed to the server for debugging. + * + * Logs are sent even in the absence of a crash if this is enabled. + * + * @return true + * @return false + */ + bool isLogStreamingEnabled() const { return _logStreamingEnabled; } + + /** + * @brief Set whether logs are streamed to the server for debugging. + * + * Logs are sent even in the absence of a crash if this is enabled. + * + * @param enabled + */ + void setLogStreamingEnabled(bool enabled) { + _logStreamingEnabled = enabled; + } + + /** + * @brief Whether stats are streamed to the server for debugging. + * + * @return true + * @return false + */ + bool isStatsStreamingEnabled() const { return _statsStreamingEnabled; } + + /** + * @brief Set whether stats are streamed to the server for debugging. + * + * @param enabled + */ + void setStatsStreamingEnabled(bool enabled) { + _statsStreamingEnabled = enabled; + } + /** * @brief Set the URL where to send crash reports to * @@ -229,6 +269,9 @@ public slots: void setAnnotation(const std::string &key, const std::string &value); + void setContext(const QString §ionName, const QString &key, const QString &value); + + /** * @brief Log a message to the crash handler * @@ -312,6 +355,9 @@ public slots: std::atomic _crashMonitorStarted {false}; std::atomic _crashReportingEnabled {false}; + std::atomic _logStreamingEnabled {false}; + std::atomic _statsStreamingEnabled {false}; + std::unordered_map _annotations{}; std::mutex _annotationsMutex{}; std::mutex _logMutex{}; From ed7546c3f4c0389dfa91774a081cdae84651f7b4 Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Sat, 8 Aug 2026 23:48:28 +0200 Subject: [PATCH 16/29] Implement support tag Generates a random but easily pronounceable tag that can be used to pass to a developer, to let them easily locate a matching report in the Sentry (or similar) database. --- interface/src/CrashRecoveryHandler.cpp | 6 +- .../src/crash-handler/CrashHandler.cpp | 132 +++++++++++++++++- .../src/crash-handler/CrashHandler.h | 21 +++ .../src/crash-handler/CrashHandlerSentry.cpp | 1 + 4 files changed, 157 insertions(+), 3 deletions(-) diff --git a/interface/src/CrashRecoveryHandler.cpp b/interface/src/CrashRecoveryHandler.cpp index f2216c30c6c..8e492499161 100644 --- a/interface/src/CrashRecoveryHandler.cpp +++ b/interface/src/CrashRecoveryHandler.cpp @@ -174,9 +174,11 @@ CrashRecoveryHandler::Action CrashRecoveryHandler::promptUserForAction(bool show QLabel* crashReportLabel = nullptr; if (ch.isStarted()) { - crashReportLabel = new QLabel("To help us with debugging, you can enable automatic crash reports.\n" + crashReportLabel = new QLabel(QString("To help us with debugging, you can enable automatic crash reports.\n" "They'll only be seen by developers trusted by the Overte e.V. organization,\n" - "and will only be used for improving the code."); + "and will only be used for improving the code.\n" + "\n" + "Support tag: %1").arg(ch.getSupportTag())); } else { crashReportLabel = new QLabel("Unfortunately, crash reporting isn't built into this release."); } diff --git a/libraries/networking/src/crash-handler/CrashHandler.cpp b/libraries/networking/src/crash-handler/CrashHandler.cpp index b287d5c859b..30c106d0837 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.cpp +++ b/libraries/networking/src/crash-handler/CrashHandler.cpp @@ -17,6 +17,8 @@ #include #include #include +#include + #include "LogHandler.h" @@ -24,12 +26,136 @@ #include #endif + + Q_LOGGING_CATEGORY(crash_handler, "overte.crash_handler") static void crashHandlerLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg); +// https://www.eff.org/files/2016/09/08/eff_short_wordlist_1.txt +const QStringList CrashHandler::EFF_SHORT_WORDLIST = { +"acid", "acorn", "acre", "acts", "afar", "affix", "aged", "agent", "agile", "aging", "agony", +"ahead", "aide", "aids", "aim", "ajar", "alarm", "alias", "alibi", "alien", "alike", "alive", +"aloe", "aloft", "aloha", "alone", "amend", "amino", "ample", "amuse", "angel", "anger", "angle", +"ankle", "apple", "april", "apron", "aqua", "area", "arena", "argue", "arise", "armed", "armor", +"army", "aroma", "array", "arson", "art", "ashen", "ashes", "atlas", "atom", "attic", "audio", +"avert", "avoid", "awake", "award", "awoke", "axis", "bacon", "badge", "bagel", "baggy", "baked", +"baker", "balmy", "banjo", "barge", "barn", "bash", "basil", "bask", "batch", "bath", "baton", +"bats", "blade", "blank", "blast", "blaze", "bleak", "blend", "bless", "blimp", "blink", "bloat", +"blob", "blog", "blot", "blunt", "blurt", "blush", "boast", "boat", "body", "boil", "bok", "bolt", +"boned", "boney", "bonus", "bony", "book", "booth", "boots", "boss", "botch", "both", "boxer", +"breed", "bribe", "brick", "bride", "brim", "bring", "brink", "brisk", "broad", "broil", "broke", +"brook", "broom", "brush", "buck", "bud", "buggy", "bulge", "bulk", "bully", "bunch", "bunny", +"bunt", "bush", "bust", "busy", "buzz", "cable", "cache", "cadet", "cage", "cake", "calm", "cameo", +"canal", "candy", "cane", "canon", "cape", "card", "cargo", "carol", "carry", "carve", "case", +"cash", "cause", "cedar", "chain", "chair", "chant", "chaos", "charm", "chase", "cheek", "cheer", +"chef", "chess", "chest", "chew", "chief", "chili", "chill", "chip", "chomp", "chop", "chow", +"chuck", "chump", "chunk", "churn", "chute", "cider", "cinch", "city", "civic", "civil", "clad", +"claim", "clamp", "clap", "clash", "clasp", "class", "claw", "clay", "clean", "clear", "cleat", +"cleft", "clerk", "click", "cling", "clink", "clip", "cloak", "clock", "clone", "cloth", "cloud", +"clump", "coach", "coast", "coat", "cod", "coil", "coke", "cola", "cold", "colt", "coma", "come", +"comic", "comma", "cone", "cope", "copy", "coral", "cork", "cost", "cot", "couch", "cough", +"cover", "cozy", "craft", "cramp", "crane", "crank", "crate", "crave", "crawl", "crazy", "creme", +"crepe", "crept", "crib", "cried", "crisp", "crook", "crop", "cross", "crowd", "crown", "crumb", +"crush", "crust", "cub", "cult", "cupid", "cure", "curl", "curry", "curse", "curve", "curvy", +"cushy", "cut", "cycle", "dab", "dad", "daily", "dairy", "daisy", "dance", "dandy", "darn", +"dart", "dash", "data", "date", "dawn", "deaf", "deal", "dean", "debit", "debt", "debug", "decaf", +"decal", "decay", "deck", "decor", "decoy", "deed", "delay", "denim", "dense", "dent", "depth", +"derby", "desk", "dial", "diary", "dice", "dig", "dill", "dime", "dimly", "diner", "dingy", +"disco", "dish", "disk", "ditch", "ditzy", "dizzy", "dock", "dodge", "doing", "doll", "dome", +"donor", "donut", "dose", "dot", "dove", "down", "dowry", "doze", "drab", "drama", "drank", +"draw", "dress", "dried", "drift", "drill", "drive", "drone", "droop", "drove", "drown", "drum", +"dry", "duck", "duct", "dude", "dug", "duke", "duo", "dusk", "dust", "duty", "dwarf", "dwell", +"eagle", "early", "earth", "easel", "east", "eaten", "eats", "ebay", "ebony", "ebook", "echo", +"edge", "eel", "eject", "elbow", "elder", "elf", "elk", "elm", "elope", "elude", "elves", "email", +"emit", "empty", "emu", "enter", "entry", "envoy", "equal", "erase", "error", "erupt", "essay", +"etch", "evade", "even", "evict", "evil", "evoke", "exact", "exit", "fable", "faced", "fact", +"fade", "fall", "false", "fancy", "fang", "fax", "feast", "feed", "femur", "fence", "fend", +"ferry", "fetal", "fetch", "fever", "fiber", "fifth", "fifty", "film", "filth", "final", "finch", +"fit", "five", "flag", "flaky", "flame", "flap", "flask", "fled", "flick", "fling", "flint", +"flip", "flirt", "float", "flock", "flop", "floss", "flyer", "foam", "foe", "fog", "foil", "folic", +"folk", "food", "fool", "found", "fox", "foyer", "frail", "frame", "fray", "fresh", "fried", +"frill", "frisk", "from", "front", "frost", "froth", "frown", "froze", "fruit", "gag", "gains", +"gala", "game", "gap", "gas", "gave", "gear", "gecko", "geek", "gem", "genre", "gift", "gig", +"gills", "given", "giver", "glad", "glass", "glide", "gloss", "glove", "glow", "glue", "goal", +"going", "golf", "gong", "good", "gooey", "goofy", "gore", "gown", "grab", "grain", "grant", +"grape", "graph", "grasp", "grass", "grave", "gravy", "gray", "green", "greet", "grew", "grid", +"grief", "grill", "grip", "grit", "groom", "grope", "growl", "grub", "grunt", "guide", "gulf", +"gulp", "gummy", "guru", "gush", "gut", "guy", "habit", "half", "halo", "halt", "happy", "harm", +"hash", "hasty", "hatch", "hate", "haven", "hazel", "hazy", "heap", "heat", "heave", "hedge", +"hefty", "help", "herbs", "hers", "hub", "hug", "hula", "hull", "human", "humid", "hump", "hung", +"hunk", "hunt", "hurry", "hurt", "hush", "hut", "ice", "icing", "icon", "icy", "igloo", "image", +"ion", "iron", "islam", "issue", "item", "ivory", "ivy", "jab", "jam", "jaws", "jazz", "jeep", +"jelly", "jet", "jiffy", "job", "jog", "jolly", "jolt", "jot", "joy", "judge", "juice", "juicy", +"july", "jumbo", "jump", "junky", "juror", "jury", "keep", "keg", "kept", "kick", "kilt", "king", +"kite", "kitty", "kiwi", "knee", "knelt", "koala", "kung", "ladle", "lady", "lair", "lake", +"lance", "land", "lapel", "large", "lash", "lasso", "last", "latch", "late", "lazy", "left", +"legal", "lemon", "lend", "lens", "lent", "level", "lever", "lid", "life", "lift", "lilac", +"lily", "limb", "limes", "line", "lint", "lion", "lip", "list", "lived", "liver", "lunar", "lunch", +"lung", "lurch", "lure", "lurk", "lying", "lyric", "mace", "maker", "malt", "mama", "mango", +"manor", "many", "map", "march", "mardi", "marry", "mash", "match", "mate", "math", "moan", +"mocha", "moist", "mold", "mom", "moody", "mop", "morse", "most", "motor", "motto", "mount", +"mouse", "mousy", "mouth", "move", "movie", "mower", "mud", "mug", "mulch", "mule", "mull", +"mumbo", "mummy", "mural", "muse", "music", "musky", "mute", "nacho", "nag", "nail", "name", +"nanny", "nap", "navy", "near", "neat", "neon", "nerd", "nest", "net", "next", "niece", "ninth", +"nutty", "oak", "oasis", "oat", "ocean", "oil", "old", "olive", "omen", "onion", "only", "ooze", +"opal", "open", "opera", "opt", "otter", "ouch", "ounce", "outer", "oval", "oven", "owl", "ozone", +"pace", "pagan", "pager", "palm", "panda", "panic", "pants", "panty", "paper", "park", "party", +"pasta", "patch", "path", "patio", "payer", "pecan", "penny", "pep", "perch", "perky", "perm", +"pest", "petal", "petri", "petty", "photo", "plank", "plant", "plaza", "plead", "plot", "plow", +"pluck", "plug", "plus", "poach", "pod", "poem", "poet", "pogo", "point", "poise", "poker", +"polar", "polio", "polka", "polo", "pond", "pony", "poppy", "pork", "poser", "pouch", "pound", +"pout", "power", "prank", "press", "print", "prior", "prism", "prize", "probe", "prong", "proof", +"props", "prude", "prune", "pry", "pug", "pull", "pulp", "pulse", "puma", "punch", "punk", "pupil", +"puppy", "purr", "purse", "push", "putt", "quack", "quake", "query", "quiet", "quill", "quilt", +"quit", "quota", "quote", "rabid", "race", "rack", "radar", "radio", "raft", "rage", "raid", +"rail", "rake", "rally", "ramp", "ranch", "range", "rank", "rant", "rash", "raven", "reach", +"react", "ream", "rebel", "recap", "relax", "relay", "relic", "remix", "repay", "repel", "reply", +"rerun", "reset", "rhyme", "rice", "rich", "ride", "rigid", "rigor", "rinse", "riot", "ripen", +"rise", "risk", "ritzy", "rival", "river", "roast", "robe", "robin", "rock", "rogue", "roman", +"romp", "rope", "rover", "royal", "ruby", "rug", "ruin", "rule", "runny", "rush", "rust", "rut", +"sadly", "sage", "said", "saint", "salad", "salon", "salsa", "salt", "same", "sandy", "santa", +"satin", "sauna", "saved", "savor", "sax", "say", "scale", "scam", "scan", "scare", "scarf", +"scary", "scoff", "scold", "scoop", "scoot", "scope", "score", "scorn", "scout", "scowl", "scrap", +"scrub", "scuba", "scuff", "sect", "sedan", "self", "send", "sepia", "serve", "set", "seven", +"shack", "shade", "shady", "shaft", "shaky", "sham", "shape", "share", "sharp", "shed", "sheep", +"sheet", "shelf", "shell", "shine", "shiny", "ship", "shirt", "shock", "shop", "shore", "shout", +"shove", "shown", "showy", "shred", "shrug", "shun", "shush", "shut", "shy", "sift", "silk", +"silly", "silo", "sip", "siren", "sixth", "size", "skate", "skew", "skid", "skier", "skies", +"skip", "skirt", "skit", "sky", "slab", "slack", "slain", "slam", "slang", "slash", "slate", +"slaw", "sled", "sleek", "sleep", "sleet", "slept", "slice", "slick", "slimy", "sling", "slip", +"slit", "slob", "slot", "slug", "slum", "slurp", "slush", "small", "smash", "smell", "smile", +"smirk", "smog", "snack", "snap", "snare", "snarl", "sneak", "sneer", "sniff", "snore", "snort", +"snout", "snowy", "snub", "snuff", "speak", "speed", "spend", "spent", "spew", "spied", "spill", +"spiny", "spoil", "spoke", "spoof", "spool", "spoon", "sport", "spot", "spout", "spray", "spree", +"spur", "squad", "squat", "squid", "stack", "staff", "stage", "stain", "stall", "stamp", "stand", +"stank", "stark", "start", "stash", "state", "stays", "steam", "steep", "stem", "step", "stew", +"stick", "sting", "stir", "stock", "stole", "stomp", "stony", "stood", "stool", "stoop", "stop", +"storm", "stout", "stove", "straw", "stray", "strut", "stuck", "stud", "stuff", "stump", "stung", +"stunt", "suds", "sugar", "sulk", "surf", "sushi", "swab", "swan", "swarm", "sway", "swear", +"sweat", "sweep", "swell", "swept", "swim", "swing", "swipe", "swirl", "swoop", "swore", "syrup", +"tacky", "taco", "tag", "take", "tall", "talon", "tamer", "tank", "taper", "taps", "tarot", +"tart", "task", "taste", "tasty", "taunt", "thank", "thaw", "theft", "theme", "thigh", "thing", +"think", "thong", "thorn", "those", "throb", "thud", "thumb", "thump", "thus", "tiara", "tidal", +"tidy", "tiger", "tile", "tilt", "tint", "tiny", "trace", "track", "trade", "train", "trait", +"trap", "trash", "tray", "treat", "tree", "trek", "trend", "trial", "tribe", "trick", "trio", +"trout", "truce", "truck", "trump", "trunk", "try", "tug", "tulip", "tummy", "turf", "tusk", +"tutor", "tutu", "tux", "tweak", "tweet", "twice", "twine", "twins", "twirl", "twist", "uncle", +"uncut", "undo", "unify", "union", "unit", "untie", "upon", "upper", "urban", "used", "user", +"usher", "utter", "value", "vapor", "vegan", "venue", "verse", "vest", "veto", "vice", "video", +"view", "viral", "virus", "visa", "visor", "vixen", "vocal", "voice", "void", "volt", "voter", +"vowel", "wad", "wafer", "wager", "wages", "wagon", "wake", "walk", "wand", "wasp", "watch", +"water", "wavy", "wheat", "whiff", "whole", "whoop", "wick", "widen", "widow", "width", "wife", +"wifi", "wilt", "wimp", "wind", "wing", "wink", "wipe", "wired", "wiry", "wise", "wish", "wispy", +"wok", "wolf", "womb", "wool", "woozy", "word", "work", "worry", "wound", "woven", "wrath", +"wreck", "wrist", "xerox", "yahoo", "yam", "yard", "year", "yeast", "yelp", "yield", "yo-yo", +"yodel", "yoga", "yoyo", "yummy", "zebra", "zero", "zesty", "zippy", "zone", "zoom" +}; + + + CrashHandler& CrashHandler::getInstance() { #if HAS_SENTRY static CrashHandlerSentry sharedInstance; @@ -41,7 +167,11 @@ CrashHandler& CrashHandler::getInstance() { } CrashHandler::CrashHandler(QObject *parent) : QObject(parent) { + auto rng = QRandomGenerator::global(); + _supportTag = EFF_SHORT_WORDLIST[rng->bounded(EFF_SHORT_WORDLIST.size())] + "-" + + EFF_SHORT_WORDLIST[rng->bounded(EFF_SHORT_WORDLIST.size())] + "-" + + EFF_SHORT_WORDLIST[rng->bounded(EFF_SHORT_WORDLIST.size())]; } @@ -69,7 +199,7 @@ bool CrashHandler::start() { setStarted(started); if ( started ) { - qCInfo(crash_handler) << "Crash handler started"; + qCInfo(crash_handler) << "Crash handler started. Support tag:" << getSupportTag(); std::size_t countAdded = 0; { diff --git a/libraries/networking/src/crash-handler/CrashHandler.h b/libraries/networking/src/crash-handler/CrashHandler.h index b74ed9f8703..eafe0db3d69 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.h +++ b/libraries/networking/src/crash-handler/CrashHandler.h @@ -19,6 +19,9 @@ #include #include +#include + + Q_DECLARE_LOGGING_CATEGORY(crash_handler) @@ -284,6 +287,17 @@ public slots: */ void logMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg); + /** + * @brief Returns the support tag. + * + * This is a randomly generated per program launch tag that can be used + * to aid in user support and debugging. The user can be asked to give + * this tag to a developer. It will be recorded in the crash reports. + * + * @return QString + */ + QString getSupportTag() const { return _supportTag; } + signals: /** @@ -353,6 +367,8 @@ public slots: private: + + std::atomic _crashMonitorStarted {false}; std::atomic _crashReportingEnabled {false}; std::atomic _logStreamingEnabled {false}; @@ -368,6 +384,11 @@ public slots: QString _previousMessage{}; int _repeatCount { 0 }; + + QString _supportTag{}; + + static const QStringList EFF_SHORT_WORDLIST; + }; diff --git a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp index ac230cca7d1..cce3deb3e41 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp +++ b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp @@ -49,6 +49,7 @@ bool CrashHandlerSentry::startCrashHandler() { sentry_set_context("Release", release_info); sentry_set_context("Machine", machine_info); + sentry_set_tag("support_tag", getSupportTag().toStdString().c_str()); qCInfo(crash_handler) << "Sentry crash handler initialized. SDK version" << sentry_sdk_version() << ". Will send reports to " << crashURL.c_str() << " with token " << crashToken.c_str(); return true; From 7deb3bbcb3fb2d662d7082f1c8af8daeeb70c88b Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Sun, 9 Aug 2026 00:23:25 +0200 Subject: [PATCH 17/29] Improve Sentry consent handling Avoid discarding reports --- .../networking/src/crash-handler/CrashHandlerSentry.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp index cce3deb3e41..644bb55b04d 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp +++ b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp @@ -24,9 +24,12 @@ bool CrashHandlerSentry::startCrashHandler() { sentry_options_set_debug(options, 1); // Debug for Sentry Native SDK itself sentry_options_set_enable_logs(options, 1); sentry_options_set_enable_metrics(options, 1); - + sentry_options_set_cache_keep(options, SENTRY_CACHE_KEEP_OFFLINE); + sentry_options_set_http_retry(options, true); sentry_options_set_require_user_consent(options, 1); sentry_options_set_minidump_mode(options, SENTRY_MINIDUMP_MODE_FULL); // TODO: a way to choose SENTRY_MINIDUMP_MODE_SMART, which sends smaller reports + sentry_options_set_crashpad_wait_for_upload(options, 1); // Wait for crashpad to upload before exiting + sentry_options_set_logs_with_attributes(options, true); if (sentry_init(options) == 0) { qCInfo(crash_handler) << "Sentry init successful"; @@ -82,8 +85,10 @@ void CrashHandlerSentry::sendLogMessage(QtMsgType type, const QMessageLogContext void CrashHandlerSentry::setCrashReportingEnabled(bool value) { if (value) { + qCInfo(crash_handler) << "Sentry crash reporting consent given. Crash reports will be sent to the server."; sentry_user_consent_give(); } else { + qCInfo(crash_handler) << "Sentry crash reporting consent revoked."; sentry_user_consent_revoke(); } } From 16cfde9b1b1e75bd7a72436312263c11c562b1dd Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Sun, 9 Aug 2026 00:24:09 +0200 Subject: [PATCH 18/29] Add metadata to Sentry logs --- .../src/crash-handler/CrashHandlerSentry.cpp | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp index 644bb55b04d..6f19d2905b6 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp +++ b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp @@ -52,6 +52,7 @@ bool CrashHandlerSentry::startCrashHandler() { sentry_set_context("Release", release_info); sentry_set_context("Machine", machine_info); + sentry_set_tag("machine_id", uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()).toStdString().c_str()); sentry_set_tag("support_tag", getSupportTag().toStdString().c_str()); qCInfo(crash_handler) << "Sentry crash handler initialized. SDK version" << sentry_sdk_version() << ". Will send reports to " << crashURL.c_str() << " with token " << crashToken.c_str(); @@ -61,26 +62,37 @@ bool CrashHandlerSentry::startCrashHandler() { } void CrashHandlerSentry::sendLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + sentry_level_t level = SENTRY_LEVEL_INFO; + + sentry_value_t attributes = sentry_value_new_object(); + switch(type) { case QtMsgType::QtFatalMsg: - sentry_log_fatal(msg.toStdString().c_str()); + level = SENTRY_LEVEL_FATAL; break; case QtMsgType::QtCriticalMsg: - sentry_log_error(msg.toStdString().c_str()); + level = SENTRY_LEVEL_ERROR; break; case QtMsgType::QtWarningMsg: - sentry_log_warn(msg.toStdString().c_str()); + level = SENTRY_LEVEL_WARNING; break; case QtMsgType::QtInfoMsg: - sentry_log_info(msg.toStdString().c_str()); + level = SENTRY_LEVEL_INFO; break; case QtMsgType::QtDebugMsg: - sentry_log_debug(msg.toStdString().c_str()); + level = SENTRY_LEVEL_DEBUG; break; default: - sentry_log_warn(msg.toStdString().c_str()); - sentry_log_warn("Previous log message was of unknown type %i", (int)type); + sentry_log_warn("Log message of unknown type %i", (int)type); } + + sentry_value_set_by_key(attributes, "file", sentry_value_new_string(context.file)); + sentry_value_set_by_key(attributes, "line", sentry_value_new_int32(context.line)); + sentry_value_set_by_key(attributes, "function", sentry_value_new_string(context.function)); + sentry_value_set_by_key(attributes, "category", sentry_value_new_string(context.category)); + + sentry_log(level, msg.toStdString().c_str(), attributes); + } void CrashHandlerSentry::setCrashReportingEnabled(bool value) { From b3ca1861780c42a87bfee0cb5121bd5cbd82e371 Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Sun, 9 Aug 2026 01:15:17 +0200 Subject: [PATCH 19/29] Add more hardware info to crash reports --- interface/src/Application_Setup.cpp | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/interface/src/Application_Setup.cpp b/interface/src/Application_Setup.cpp index 37c9d959b89..9635b8a69bd 100644 --- a/interface/src/Application_Setup.cpp +++ b/interface/src/Application_Setup.cpp @@ -138,6 +138,7 @@ #include "LocationBookmarks.h" #include "LODManager.h" #include "Menu.h" +#include "CPUIdent.h" #include "ResourceRequestObserver.h" #ifndef USE_GL #include "vk/VKWindow.h" @@ -619,11 +620,19 @@ void Application::initialize(const QCommandLineParser &parser) { { // identify gpu as early as possible to help identify OpenGL initialization errors. auto gpuIdent = GPUIdent::getInstance(); + auto &ch = CrashHandler::getInstance(); ch.setAnnotation("sentry[contexts][gpu][name]", gpuIdent->getName().toStdString()); ch.setAnnotation("sentry[contexts][gpu][version]", gpuIdent->getDriver().toStdString()); - ch.setAnnotation("gpu_memory", std::to_string(gpuIdent->getMemory())); + + ch.setAnnotation("gpu.memory", std::to_string(gpuIdent->getMemory())); + + ch.setAnnotation("cpu.arch", QSysInfo::currentCpuArchitecture()); + ch.setAnnotation("cpu.ideal_thread_count", std::to_string(QThread::idealThreadCount())); + ch.setAnnotation("cpu.brand", CPUIdent::Brand()); + ch.setAnnotation("cpu.vendor", CPUIdent::Vendor()); + } // make sure the debug draw singleton is initialized on the main thread. @@ -848,13 +857,28 @@ void Application::initialize(const QCommandLineParser &parser) { isTester = check_tester_file.exists() && check_tester_file.isFile(); #endif + auto glContextData = gl::ContextInfo::get(); + ProcessorInfo procInfo; + + + ch.setAnnotation("gl.version_int", std::to_string(glVersionToInteger(glContextData.version.c_str()))); + ch.setAnnotation("gl.version", glContextData.version.c_str()); + ch.setAnnotation("gl.vendor", glContextData.vendor.c_str()); + ch.setAnnotation("gl.shading_language_version", glContextData.shadingLanguageVersion.c_str()); + ch.setAnnotation("gl.renderer", glContextData.renderer.c_str()); + + + // The below seems to return nonsense, disabling for now + //ch.setAnnotation("cpu.cores", std::to_string(procInfo.numProcessorCores)); + //ch.setAnnotation("cpu.logical_cores", std::to_string(procInfo.numLogicalProcessors)); + + auto& userActivityLogger = UserActivityLogger::getInstance(); if (userActivityLogger.isEnabled()) { // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. static const QString TESTER = "HIFI_TESTER"; auto gpuIdent = GPUIdent::getInstance(); - auto glContextData = gl::ContextInfo::get(); QJsonObject properties = { { "version", applicationVersion() }, { "tester", QProcessEnvironment::systemEnvironment().contains(TESTER) || isTester }, @@ -887,7 +911,6 @@ void Application::initialize(const QCommandLineParser &parser) { properties["os_win_version"] = QSysInfo::windowsVersion(); } - ProcessorInfo procInfo; if (getProcessorInfo(procInfo)) { properties["processor_core_count"] = procInfo.numProcessorCores; properties["logical_processor_count"] = procInfo.numLogicalProcessors; From 82c5960c6820d57943b09039baa3219deee2c9d5 Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Sun, 9 Aug 2026 10:15:29 +0200 Subject: [PATCH 20/29] Add display name to crash reports --- interface/src/Application.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0c656d209a9..2003d5997b6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1404,6 +1404,7 @@ void Application::updateWindowTitle() const { auto& ch = CrashHandler::getInstance(); ch.setAnnotation("sentry[user][username]", metaverseUsername.toStdString()); + ch.setAnnotation("user.display_name", getMyAvatar()->getDisplayName().toStdString()); QString currentPlaceName; if (isServerlessMode()) { From eb363e53084986830467652d2dd8d85baebb727f Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Sun, 9 Aug 2026 13:35:21 +0200 Subject: [PATCH 21/29] Add contexts support to crash handler Since the current version of Sentry Native is too old, contexts have to be manually merged. This could be removed after an upgrade --- interface/src/Application.cpp | 2 +- interface/src/Application_Setup.cpp | 4 +- interface/src/main.cpp | 9 ++++- .../src/crash-handler/CrashHandler.h | 37 ++++++++++++----- .../src/crash-handler/CrashHandlerNone.h | 11 +++-- .../src/crash-handler/CrashHandlerSentry.cpp | 40 +++++++++++++++++++ .../src/crash-handler/CrashHandlerSentry.h | 7 +++- 7 files changed, 92 insertions(+), 18 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2003d5997b6..64f3b60b4f6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1403,7 +1403,7 @@ void Application::updateWindowTitle() const { QString domainUsername = domainAccountManager->getUsername(); auto& ch = CrashHandler::getInstance(); - ch.setAnnotation("sentry[user][username]", metaverseUsername.toStdString()); + ch.setContext("User", "username", metaverseUsername); ch.setAnnotation("user.display_name", getMyAvatar()->getDisplayName().toStdString()); QString currentPlaceName; diff --git a/interface/src/Application_Setup.cpp b/interface/src/Application_Setup.cpp index 9635b8a69bd..b252a0d39a6 100644 --- a/interface/src/Application_Setup.cpp +++ b/interface/src/Application_Setup.cpp @@ -623,8 +623,8 @@ void Application::initialize(const QCommandLineParser &parser) { auto &ch = CrashHandler::getInstance(); - ch.setAnnotation("sentry[contexts][gpu][name]", gpuIdent->getName().toStdString()); - ch.setAnnotation("sentry[contexts][gpu][version]", gpuIdent->getDriver().toStdString()); + ch.setContext("GPU", "name", gpuIdent->getName()); + ch.setContext("GPU", "version", gpuIdent->getDriver()); ch.setAnnotation("gpu.memory", std::to_string(gpuIdent->getMemory())); diff --git a/interface/src/main.cpp b/interface/src/main.cpp index e669d35f65a..339d8a8d1f6 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -660,9 +660,14 @@ int main(int argc, const char* argv[]) { ch.start(); QObject::connect(&ch, &CrashHandler::enabledChanged, [](bool enabled) { + auto& crashHandler = CrashHandler::getInstance(); Settings s; + s.beginGroup("Crash"); - s.setValue("ReportingEnabled", enabled); + s.setValue("ReportingEnabled", crashHandler.isEnabled()); + s.setValue("LogStreamingEnabled", crashHandler.isLogStreamingEnabled()); + s.setValue("StatsStreamingEnabled", crashHandler.isStatsStreamingEnabled()); + s.endGroup(); }); @@ -686,6 +691,8 @@ int main(int argc, const char* argv[]) { if (crashSettings.value("ReportingEnabled").toBool()) { ch.setEnabled(true); } + ch.setLogStreamingEnabled(crashSettings.value("LogStreamingEnabled").toBool()); + ch.setStatsStreamingEnabled(crashSettings.value("StatsStreamingEnabled").toBool()); crashSettings.endGroup(); } diff --git a/libraries/networking/src/crash-handler/CrashHandler.h b/libraries/networking/src/crash-handler/CrashHandler.h index eafe0db3d69..de06495c921 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.h +++ b/libraries/networking/src/crash-handler/CrashHandler.h @@ -174,10 +174,13 @@ public slots: * * Logs are sent even in the absence of a crash if this is enabled. * + * Emits enabledChanged signal. + * * @param enabled */ void setLogStreamingEnabled(bool enabled) { _logStreamingEnabled = enabled; + emit enabledChanged(enabled); } /** @@ -191,10 +194,12 @@ public slots: /** * @brief Set whether stats are streamed to the server for debugging. * + * Emits enabledChanged signal. * @param enabled */ void setStatsStreamingEnabled(bool enabled) { _statsStreamingEnabled = enabled; + emit enabledChanged(enabled); } /** @@ -271,8 +276,29 @@ public slots: */ void setAnnotation(const std::string &key, const std::string &value); + /** + * @brief Attach a tag to the crash report. + * + * Tags are always string values, and are searchable. + * + * @param name Key + * @param value Value + */ + virtual void setTag(std::string name, std::string value) = 0; - void setContext(const QString §ionName, const QString &key, const QString &value); + /** + * @brief Add context to the crash report + * + * Contexts in Sentry are informative, and not normally searchable. This should be used + * to provide detailed information that won't normally be used for locating reports. + * + * Unlike tags, contexts can be other things than string types. + * + * @param sectionName + * @param key + * @param value + */ + virtual void setContext(const QString §ionName, const QString &key, const QVariant &value) = 0; /** @@ -341,15 +367,6 @@ public slots: virtual void sendLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) = 0; - /** - * @brief Attach a tag to the crash report - * - * @param name Key - * @param value Value - */ - virtual void setTag(std::string name, std::string value) = 0; - - /** * @brief Marks the crash monitor as started * diff --git a/libraries/networking/src/crash-handler/CrashHandlerNone.h b/libraries/networking/src/crash-handler/CrashHandlerNone.h index 5c2db6d1664..a2440b75fb7 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerNone.h +++ b/libraries/networking/src/crash-handler/CrashHandlerNone.h @@ -11,6 +11,14 @@ class CrashHandlerNone : public CrashHandler { public: + virtual void setTag(std::string name, std::string value) override { + // This space intentionally left blank. + } + + virtual void setContext(const QString §ionName, const QString &key, const QVariant &value) override { + // This space intentionally left blank. + } + protected: virtual bool startCrashHandler() override { @@ -26,9 +34,6 @@ class CrashHandlerNone : public CrashHandler { // This space intentionally left blank. } - virtual void setTag(std::string name, std::string value) override { - // This space intentionally left blank. - } }; #endif diff --git a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp index 6f19d2905b6..832410b0622 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp +++ b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp @@ -108,3 +108,43 @@ void CrashHandlerSentry::setCrashReportingEnabled(bool value) { void CrashHandlerSentry::setTag(std::string name, std::string value) { sentry_set_tag(name.c_str(), value.c_str()); } + +void CrashHandlerSentry::setContext(const QString §ionName, const QString &key, const QVariant &value) { + // Newer Sentry Native SDKs have sentry_update_context(), but we don't. Got to do things manually. + + QMutexLocker lock(&_contextMutex); + + QVariantMap section = _context.value(sectionName).toMap(); + section.insert(key, value); + _context.insert(sectionName, section); + + + foreach(const QString §ionName, _context.keys()) { + sentry_value_t section_info = sentry_value_new_object(); + QVariantMap sectionMap = _context.value(sectionName).toMap(); + + foreach(const QString &key, sectionMap.keys()) { + sentry_value_t value; + + if (sectionMap.value(key).type() == QVariant::String) { + value = sentry_value_new_string(sectionMap.value(key).toString().toStdString().c_str()); + } else if (sectionMap.value(key).type() == QVariant::Int) { + value = sentry_value_new_int32(sectionMap.value(key).toInt()); + } else if (sectionMap.value(key).type() == QVariant::LongLong) { + value = sentry_value_new_int64(sectionMap.value(key).toLongLong()); + } else if (sectionMap.value(key).type() == QVariant::Double) { + value = sentry_value_new_double(sectionMap.value(key).toDouble()); + } else if (sectionMap.value(key).type() == QVariant::Bool) { + value = sentry_value_new_bool(sectionMap.value(key).toBool()); + } else { + qCWarning(crash_handler) << "Unsupported context value type" << sectionMap.value(key).typeName() << "for key" << key << "in section" << sectionName << ", adding as a string."; + value = sentry_value_new_string(sectionMap.value(key).toString().toStdString().c_str()); + } + + sentry_value_set_by_key(section_info, key.toStdString().c_str(), value); + } + + sentry_set_context(sectionName.toStdString().c_str(), section_info); + } + +} diff --git a/libraries/networking/src/crash-handler/CrashHandlerSentry.h b/libraries/networking/src/crash-handler/CrashHandlerSentry.h index a675776262b..52f5ad1ec80 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerSentry.h +++ b/libraries/networking/src/crash-handler/CrashHandlerSentry.h @@ -4,6 +4,7 @@ #define hifi_CrashHandlerSentry_h #include "CrashHandler.h" +#include class CrashHandlerSentry : public CrashHandler { Q_OBJECT @@ -11,7 +12,9 @@ class CrashHandlerSentry : public CrashHandler { public: + virtual void setTag(std::string name, std::string value) override; + virtual void setContext(const QString §ionName, const QString &key, const QVariant &value) override; protected: @@ -21,7 +24,9 @@ class CrashHandlerSentry : public CrashHandler { virtual void sendLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) override; - virtual void setTag(std::string name, std::string value) override; + private: + QMutex _contextMutex; + QVariantMap _context; }; From 79cdc5db9897c69d7803e41f2e7b26f2fd9825d9 Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Mon, 10 Aug 2026 02:12:04 +0200 Subject: [PATCH 22/29] Fix crash handler setting storage Make it correctly persist. There was a bit of a snag between the menu system and the crash specific setting. Make it all use the same setting keys. --- interface/src/main.cpp | 40 ++++++++++++++----- .../src/crash-handler/CrashHandler.h | 21 +++++++++- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 339d8a8d1f6..d8b3c7ebcd6 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -660,17 +660,33 @@ int main(int argc, const char* argv[]) { ch.start(); QObject::connect(&ch, &CrashHandler::enabledChanged, [](bool enabled) { - auto& crashHandler = CrashHandler::getInstance(); Settings s; - s.beginGroup("Crash"); - s.setValue("ReportingEnabled", crashHandler.isEnabled()); - s.setValue("LogStreamingEnabled", crashHandler.isLogStreamingEnabled()); - s.setValue("StatsStreamingEnabled", crashHandler.isStatsStreamingEnabled()); + s.beginGroup("Developer/Network"); + s.setValue("Enable Crash Reporting", enabled); + qInfo() << "SET: Crash reporting enabled:" << enabled; + s.endGroup(); + }); + + QObject::connect(&ch, &CrashHandler::logStreamingChanged, [](bool enabled) { + Settings s; + + s.beginGroup("Developer/Network"); + s.setValue("Enable Log Streaming", enabled); + qInfo() << "SET: Crash log streaming enabled:" << enabled; + s.endGroup(); + }); + + QObject::connect(&ch, &CrashHandler::statsStreamingChanged, [](bool enabled) { + Settings s; + s.beginGroup("Developer/Network"); + s.setValue("Enable Stats Streaming", enabled); + qInfo() << "SET: Crash stats streaming enabled:" << enabled; s.endGroup(); }); + // once the settings have been loaded, check if we need to flip the default for UserActivityLogger if (!ual.isDisabledSettingSet()) { // the user activity logger is opt-out for Interface @@ -687,13 +703,15 @@ int main(int argc, const char* argv[]) { { Settings crashSettings; - crashSettings.beginGroup("Crash"); - if (crashSettings.value("ReportingEnabled").toBool()) { - ch.setEnabled(true); - } - ch.setLogStreamingEnabled(crashSettings.value("LogStreamingEnabled").toBool()); - ch.setStatsStreamingEnabled(crashSettings.value("StatsStreamingEnabled").toBool()); + crashSettings.beginGroup("Developer/Network"); + ch.setEnabled(crashSettings.value("Enable Crash Reporting").toBool()); + ch.setLogStreamingEnabled(crashSettings.value("Enable Log Streaming").toBool()); + ch.setStatsStreamingEnabled(crashSettings.value("Enable Stats Streaming").toBool()); crashSettings.endGroup(); + + qInfo() << "Crash reporting enabled:" << ch.isEnabled(); + qInfo() << "Crash log streaming enabled:" << ch.isLogStreamingEnabled(); + qInfo() << "Crash stats streaming enabled:" << ch.isStatsStreamingEnabled(); } ch.setAnnotation("program", "interface"); diff --git a/libraries/networking/src/crash-handler/CrashHandler.h b/libraries/networking/src/crash-handler/CrashHandler.h index de06495c921..89d62a1bfaa 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.h +++ b/libraries/networking/src/crash-handler/CrashHandler.h @@ -180,7 +180,7 @@ public slots: */ void setLogStreamingEnabled(bool enabled) { _logStreamingEnabled = enabled; - emit enabledChanged(enabled); + emit logStreamingChanged(enabled); } /** @@ -199,7 +199,7 @@ public slots: */ void setStatsStreamingEnabled(bool enabled) { _statsStreamingEnabled = enabled; - emit enabledChanged(enabled); + emit statsStreamingChanged(enabled); } /** @@ -335,6 +335,23 @@ public slots: */ void enabledChanged(bool enabled); + /** + * @brief Emitted when the enabled/disabled state of the log streaming changes + * + * This can be used to store it as a setting. + * + * @param enabled Whether the log streaming is now enabled + */ + void logStreamingChanged(bool enabled); + + /** + * @brief Emitted when the enabled/disabled state of the stats streaming changes + * + * This can be used to store it as a setting. + * + * @param enabled Whether the stats streaming is now enabled + */ + void statsStreamingChanged(bool enabled); protected: /** From 601de1d6588f489f3700c7b3bb06e68c14a4931a Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Mon, 10 Aug 2026 02:33:41 +0200 Subject: [PATCH 23/29] Add stats and log streaming to the menu --- interface/src/Menu.cpp | 15 +++++++++++++++ interface/src/Menu.h | 2 ++ 2 files changed, 17 insertions(+) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a92f1b3ffd0..27f9446ff4c 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -684,6 +684,21 @@ Menu::Menu() { &CrashHandler::getInstance(), SLOT(setEnabled(bool))); + addCheckableActionToQMenuAndActionHash(networkMenu, + MenuOption::EnableLogStreaming, + 0, + CrashHandler::getInstance().isLogStreamingEnabled(), + &CrashHandler::getInstance(), + SLOT(setLogStreamingEnabled(bool))); + + addCheckableActionToQMenuAndActionHash(networkMenu, + MenuOption::EnableStatsStreaming, + 0, + CrashHandler::getInstance().isStatsStreamingEnabled(), + &CrashHandler::getInstance(), + SLOT(setStatsStreamingEnabled(bool))); + + addActionToQMenuAndActionHash(networkMenu, MenuOption::ShowDSConnectTable, 0, qApp, SLOT(loadDomainConnectionDialog())); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 1587d793f8e..5d27d2a7d58 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -91,6 +91,8 @@ namespace MenuOption { const QString DeleteBookmark = "Delete Bookmark..."; const QString DisableActivityLogger = "Disable Activity Logger"; const QString EnableCrashReporting = "Enable Crash Reporting"; + const QString EnableLogStreaming = "Enable Log Streaming"; + const QString EnableStatsStreaming = "Enable Stats Streaming"; const QString DisableEyelidAdjustment = "Disable Eyelid Adjustment"; const QString DisableLightEntities = "Disable Light Entities"; const QString DisplayCrashOptions = "Display Crash Options"; From c4c8e739c12d39102d765dde47d044f18f9e8726 Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Mon, 10 Aug 2026 15:16:45 +0200 Subject: [PATCH 24/29] Hack around some bug in the menu checkbox code. We've discussed redoing the menu system, so this is probably better addressed at that point in time or at least as a separate PR. --- interface/src/main.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index d8b3c7ebcd6..360167c91f3 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -666,6 +666,23 @@ int main(int argc, const char* argv[]) { s.setValue("Enable Crash Reporting", enabled); qInfo() << "SET: Crash reporting enabled:" << enabled; s.endGroup(); + + // FIXME: Horrible hack. Something in the menu system is badly broken + // and sometimes not generating the full path to a setting, so the same + // setting can be found eg as both: + // Developer/Network/Enable Crash Reporting + // Network/Enable Crash Reporting + // + // This leads to inconsistency, with settings not keeping state as + // they should. + // + // Since we plan to revamp the menu system anyway, hack around it for + // the time being. + + + s.beginGroup("Network"); + s.setValue("Enable Crash Reporting", enabled); + s.endGroup(); }); QObject::connect(&ch, &CrashHandler::logStreamingChanged, [](bool enabled) { @@ -675,6 +692,11 @@ int main(int argc, const char* argv[]) { s.setValue("Enable Log Streaming", enabled); qInfo() << "SET: Crash log streaming enabled:" << enabled; s.endGroup(); + + // FIXME: See above + s.beginGroup("Network"); + s.setValue("Enable Log Streaming", enabled); + s.endGroup(); }); QObject::connect(&ch, &CrashHandler::statsStreamingChanged, [](bool enabled) { @@ -684,6 +706,12 @@ int main(int argc, const char* argv[]) { s.setValue("Enable Stats Streaming", enabled); qInfo() << "SET: Crash stats streaming enabled:" << enabled; s.endGroup(); + + // FIXME: See above + s.beginGroup("Network"); + s.setValue("Enable Stats Streaming", enabled); + s.endGroup(); + }); From 2ec8522abf521f100d0e8d97ea245ebaf0dc6bcc Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Mon, 10 Aug 2026 19:15:20 +0200 Subject: [PATCH 25/29] Attach log file to crash report --- libraries/networking/CMakeLists.txt | 2 ++ .../networking/src/crash-handler/CrashHandlerSentry.cpp | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/libraries/networking/CMakeLists.txt b/libraries/networking/CMakeLists.txt index 4bcf1b209cb..50282d69ae4 100644 --- a/libraries/networking/CMakeLists.txt +++ b/libraries/networking/CMakeLists.txt @@ -2,6 +2,8 @@ set(TARGET_NAME networking) setup_hifi_library(Network WebSockets) link_hifi_libraries(shared platform) +include_hifi_library_headers(shared) + target_openssl() target_tbb() add_sentry() diff --git a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp index 832410b0622..6a9706dfffc 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp +++ b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp @@ -4,6 +4,8 @@ #include #include "../FingerprintUtils.h" #include "../UserActivityLogger.h" +#include "shared/FileLogger.h" + #include @@ -31,6 +33,13 @@ bool CrashHandlerSentry::startCrashHandler() { sentry_options_set_crashpad_wait_for_upload(options, 1); // Wait for crashpad to upload before exiting sentry_options_set_logs_with_attributes(options, true); + + // The real logger isn't easily available to us, and to deal with that older versions of Sentry SDK may not + // allow adding attachments after initialization, this is an easier way to deal with it. + FileLogger dummyLogger; + qCInfo(crash_handler) << "Will attach log file to crash reports: " << dummyLogger.getFilename(); + sentry_options_add_attachment(options, dummyLogger.getFilename().toStdString().c_str()); + if (sentry_init(options) == 0) { qCInfo(crash_handler) << "Sentry init successful"; } else { From 603e1ac2e21271103d6fd1edddb2b870053bf218 Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Mon, 10 Aug 2026 20:42:45 +0200 Subject: [PATCH 26/29] Add crash handler scripting interface --- interface/src/Application.cpp | 4 + .../CrashHandlerScriptingInterface.h | 86 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 libraries/networking/src/crash-handler/CrashHandlerScriptingInterface.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 64f3b60b4f6..413cc1de8ff 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -127,6 +127,8 @@ #include "LODManager.h" #include "Menu.h" #include "ResourceRequestObserver.h" +#include "crash-handler/CrashHandlerScriptingInterface.h" + #if defined(Q_OS_MAC) || defined(Q_OS_WIN) #include "SpeechRecognizer.h" #endif @@ -699,6 +701,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptManagerPoint scriptEngine->registerGlobalObject(sgp, "HifiAbout", AboutUtil::getInstance()); // Deprecated. scriptEngine->registerGlobalObject(sgp, "ResourceRequestObserver", DependencyManager::get().data()); + scriptEngine->registerGlobalObject(sgp, "CrashHandler", CrashHandlerScriptingInterface::getInstance()); + // connect this script engines printedMessage signal to the global ScriptEngines these various messages auto scriptEngines = DependencyManager::get().data(); connect(scriptManager.get(), &ScriptManager::printedMessage, scriptEngines, &ScriptEngines::onPrintedMessage); diff --git a/libraries/networking/src/crash-handler/CrashHandlerScriptingInterface.h b/libraries/networking/src/crash-handler/CrashHandlerScriptingInterface.h new file mode 100644 index 00000000000..417163fd96c --- /dev/null +++ b/libraries/networking/src/crash-handler/CrashHandlerScriptingInterface.h @@ -0,0 +1,86 @@ + +#include +#include "CrashHandler.h" + +/*@jsdoc + *

The CrashHandler provides access to the Crash Handler's state.

+ * @namespace CrashHandler + */ + +class CrashHandlerScriptingInterface : public QObject { + Q_OBJECT + public: + + static CrashHandlerScriptingInterface* getInstance() { + static CrashHandlerScriptingInterface instance; + return &instance; + } + + + + /*@jsdoc + * Gets the support tag for the crash handler. + * + * The support tag is a randomly generated string that is used to identify crash reports. + * It is randomly generated on every application start. + * @function CrashHandler.getSupportTag + * @returns {string} The support tag. + */ + Q_INVOKABLE QString getSupportTag() { + auto& crashHandler = CrashHandler::getInstance(); + return crashHandler.getSupportTag(); + } + + /*@jsdoc + * Returns the name of the crash handler implementation. + * @function CrashHandler.getCrashHandlerName + * @returns {string} The name of the crash handler implementation. + */ + Q_INVOKABLE QString getCrashHandlerName() { + auto& crashHandler = CrashHandler::getInstance(); + return crashHandler.metaObject()->className(); + } + + + /*@jsdoc + * Whether crash reporting is enabled. + * @function CrashHandler.isCrashReportingEnabled + * @returns {boolean} true if crash reporting is enabled, false if it isn't. + */ + Q_INVOKABLE bool isCrashReportingEnabled() { + auto& crashHandler = CrashHandler::getInstance(); + return crashHandler.isEnabled(); + } + + /*@jsdoc + * Whether log streaming is enabled. + * @function CrashHandler.isLogStreamingEnabled + * @returns {boolean} true if log streaming is enabled, false if it isn't. + */ + Q_INVOKABLE bool isLogStreamingEnabled() { + auto& crashHandler = CrashHandler::getInstance(); + return crashHandler.isLogStreamingEnabled(); + } + + /*@jsdoc + * Whether stats streaming is enabled. + * @function CrashHandler.isStatsStreamingEnabled + * @returns {boolean} true if stats streaming is enabled, false if it isn't. + */ + Q_INVOKABLE bool isStatsStreamingEnabled() { + auto& crashHandler = CrashHandler::getInstance(); + return crashHandler.isStatsStreamingEnabled(); + } + + + virtual ~CrashHandlerScriptingInterface() = default; + + private: + + CrashHandlerScriptingInterface(QObject* parent = nullptr) : QObject(parent) { + + } + + + +}; From 5ee8f830e1eef39d93db18e5f634ae64f5afad0c Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Mon, 10 Aug 2026 20:45:51 +0200 Subject: [PATCH 27/29] Add crash handler include guards --- libraries/networking/src/crash-handler/CrashHandler.h | 7 +++++++ libraries/networking/src/crash-handler/CrashHandlerNone.h | 1 + .../src/crash-handler/CrashHandlerScriptingInterface.h | 6 ++++++ .../networking/src/crash-handler/CrashHandlerSentry.h | 4 ++-- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/crash-handler/CrashHandler.h b/libraries/networking/src/crash-handler/CrashHandler.h index 89d62a1bfaa..b5909d3bdf6 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.h +++ b/libraries/networking/src/crash-handler/CrashHandler.h @@ -22,6 +22,11 @@ #include + +#ifndef overte_CrashHandler_h +#define overte_CrashHandler_h + + Q_DECLARE_LOGGING_CATEGORY(crash_handler) @@ -425,4 +430,6 @@ public slots: }; +#endif + diff --git a/libraries/networking/src/crash-handler/CrashHandlerNone.h b/libraries/networking/src/crash-handler/CrashHandlerNone.h index a2440b75fb7..92e855980b8 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerNone.h +++ b/libraries/networking/src/crash-handler/CrashHandlerNone.h @@ -5,6 +5,7 @@ #include "CrashHandler.h" + class CrashHandlerNone : public CrashHandler { Q_OBJECT diff --git a/libraries/networking/src/crash-handler/CrashHandlerScriptingInterface.h b/libraries/networking/src/crash-handler/CrashHandlerScriptingInterface.h index 417163fd96c..2efcddd0e5d 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerScriptingInterface.h +++ b/libraries/networking/src/crash-handler/CrashHandlerScriptingInterface.h @@ -2,6 +2,10 @@ #include #include "CrashHandler.h" + +#ifndef overte_CrashHandlerScriptingInterface_h +#define overte_CrashHandlerScriptingInterface_h + /*@jsdoc *

The CrashHandler provides access to the Crash Handler's state.

* @namespace CrashHandler @@ -84,3 +88,5 @@ class CrashHandlerScriptingInterface : public QObject { }; + +#endif diff --git a/libraries/networking/src/crash-handler/CrashHandlerSentry.h b/libraries/networking/src/crash-handler/CrashHandlerSentry.h index 52f5ad1ec80..ba8f491277c 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerSentry.h +++ b/libraries/networking/src/crash-handler/CrashHandlerSentry.h @@ -1,7 +1,7 @@ -#ifndef hifi_CrashHandlerSentry_h -#define hifi_CrashHandlerSentry_h +#ifndef overte_CrashHandlerSentry_h +#define overte_CrashHandlerSentry_h #include "CrashHandler.h" #include From 23a11e5badb29732b1d660c507683304b902c9c7 Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Mon, 10 Aug 2026 21:06:14 +0200 Subject: [PATCH 28/29] Store crash reports in application specific directory Also remove old unused code --- .../src/crash-handler/CrashHandler.cpp | 30 ------------------- .../src/crash-handler/CrashHandler.h | 3 -- .../src/crash-handler/CrashHandlerSentry.cpp | 13 ++++++++ 3 files changed, 13 insertions(+), 33 deletions(-) diff --git a/libraries/networking/src/crash-handler/CrashHandler.cpp b/libraries/networking/src/crash-handler/CrashHandler.cpp index 30c106d0837..16174b9b35e 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.cpp +++ b/libraries/networking/src/crash-handler/CrashHandler.cpp @@ -304,36 +304,6 @@ void CrashHandler::logMessage(QtMsgType type, const QMessageLogContext &context, } } -// Locate the full path to the binary's directory -QString CrashHandler::findBinaryDir() { - // Normally we'd just use QCoreApplication::applicationDirPath(), but we can't. - // That function needs the QApplication to be created first, and Crashpad is initialized as early as possible, - // which is well before QApplication, so that function throws out a warning and returns ".". - // - // So we must do things the hard way here. In particular this is needed to correctly handle things in AppImage - // on Linux. On Windows and MacOS falling back to argv[0] should be fine. - -#ifdef Q_OS_LINUX - // Find outselves by looking at /proc//exe - pid_t ourPid = getpid(); - QString exeLink = QString("/proc/%1/exe").arg(ourPid); - qCDebug(crash_handler) << "Looking at" << exeLink; - - QFileInfo exeLinkInfo(exeLink); - if (exeLinkInfo.isSymLink()) { - QFileInfo exeInfo(exeLinkInfo.symLinkTarget()); - qCDebug(crash_handler) << "exe symlink points at" << exeInfo; - return exeInfo.absoluteDir().absolutePath(); - } else { - qCWarning(crash_handler) << exeLink << "isn't a symlink. /proc not mounted?"; - } - -#endif - - return QString(); -} - - static void crashHandlerLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) { CrashHandler::getInstance().logMessage(type, context, msg); diff --git a/libraries/networking/src/crash-handler/CrashHandler.h b/libraries/networking/src/crash-handler/CrashHandler.h index b5909d3bdf6..47492bb0ab5 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.h +++ b/libraries/networking/src/crash-handler/CrashHandler.h @@ -398,9 +398,6 @@ public slots: */ void setStarted(bool started) { _crashMonitorStarted = started; } - // Move this to PathUtils - static QString findBinaryDir(); - CrashHandler(QObject *parent = nullptr); diff --git a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp index 6a9706dfffc..76194acbf39 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp +++ b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp @@ -5,9 +5,12 @@ #include "../FingerprintUtils.h" #include "../UserActivityLogger.h" #include "shared/FileLogger.h" +#include "PathUtils.h" #include +const QString CRASH_REPORTS_DIR_NAME = "crash-reports"; + bool CrashHandlerSentry::startCrashHandler() { std::string appPath = path().toStdString(); @@ -33,11 +36,21 @@ bool CrashHandlerSentry::startCrashHandler() { sentry_options_set_crashpad_wait_for_upload(options, 1); // Wait for crashpad to upload before exiting sentry_options_set_logs_with_attributes(options, true); + QString crashReportPath = PathUtils::getAppLocalDataFilePath(CRASH_REPORTS_DIR_NAME); + if (!QDir(crashReportPath).exists()) { + QDir().mkpath(crashReportPath); + } + + sentry_options_set_database_path(options, crashReportPath.toStdString().c_str()); + qCInfo(crash_handler) << "Will store crash reports in: " << crashReportPath; + // The real logger isn't easily available to us, and to deal with that older versions of Sentry SDK may not // allow adding attachments after initialization, this is an easier way to deal with it. FileLogger dummyLogger; qCInfo(crash_handler) << "Will attach log file to crash reports: " << dummyLogger.getFilename(); + + sentry_options_add_attachment(options, dummyLogger.getFilename().toStdString().c_str()); if (sentry_init(options) == 0) { From 5d7ba401b43b24d83287b23a97def61adf89665e Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Mon, 10 Aug 2026 21:40:44 +0200 Subject: [PATCH 29/29] Remove some debug log entries Add support tag to about screen --- .../qml/hifi/dialogs/TabletAboutDialog.qml | 6 ++++++ interface/src/Application_UI.cpp | 4 ++++ interface/src/main.cpp | 7 ------- .../CrashHandlerScriptingInterface.h | 18 +++++++++++++----- .../src/crash-handler/CrashHandlerSentry.cpp | 2 +- 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml index 33865bced08..e99b2899fe8 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml @@ -51,6 +51,12 @@ Rectangle { } } Item { height: 10; width: 1 } + RalewayRegular { + text: "Support tag:" + CrashHandler.supportTag + size: 20 + color: "white" + } + Item { height: 10; width: 1 } RalewayRegular { text: "An open source virtual reality platform." size: 20 diff --git a/interface/src/Application_UI.cpp b/interface/src/Application_UI.cpp index 53e8600b717..a0a7de4636b 100644 --- a/interface/src/Application_UI.cpp +++ b/interface/src/Application_UI.cpp @@ -92,6 +92,8 @@ #include "LocationBookmarks.h" #include "LODManager.h" #include "ResourceRequestObserver.h" +#include "crash-handler/CrashHandlerScriptingInterface.h" + #if defined(Q_OS_MAC) || defined(Q_OS_WIN) #include "SpeechRecognizer.h" #endif @@ -265,6 +267,7 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get().data()); surfaceContext->setContextProperty("PlatformInfo", PlatformInfoScriptingInterface::getInstance()); surfaceContext->setContextProperty("ExternalResource", ExternalResource::getInstance()); + surfaceContext->setContextProperty("CrashHandler", CrashHandlerScriptingInterface::getInstance()); // This `module` context property is blank for the QML scripting interface so that we don't get log errors when importing // certain JS files from both scripts (in the JS context) and QML (in the QML context). @@ -952,6 +955,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance()); // Deprecated surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get().data()); surfaceContext->setContextProperty("ExternalResource", ExternalResource::getInstance()); + surfaceContext->setContextProperty("CrashHandler", CrashHandlerScriptingInterface::getInstance()); if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { surfaceContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get())); diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 360167c91f3..d205afd1f70 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -664,7 +664,6 @@ int main(int argc, const char* argv[]) { s.beginGroup("Developer/Network"); s.setValue("Enable Crash Reporting", enabled); - qInfo() << "SET: Crash reporting enabled:" << enabled; s.endGroup(); // FIXME: Horrible hack. Something in the menu system is badly broken @@ -690,7 +689,6 @@ int main(int argc, const char* argv[]) { s.beginGroup("Developer/Network"); s.setValue("Enable Log Streaming", enabled); - qInfo() << "SET: Crash log streaming enabled:" << enabled; s.endGroup(); // FIXME: See above @@ -704,7 +702,6 @@ int main(int argc, const char* argv[]) { s.beginGroup("Developer/Network"); s.setValue("Enable Stats Streaming", enabled); - qInfo() << "SET: Crash stats streaming enabled:" << enabled; s.endGroup(); // FIXME: See above @@ -736,10 +733,6 @@ int main(int argc, const char* argv[]) { ch.setLogStreamingEnabled(crashSettings.value("Enable Log Streaming").toBool()); ch.setStatsStreamingEnabled(crashSettings.value("Enable Stats Streaming").toBool()); crashSettings.endGroup(); - - qInfo() << "Crash reporting enabled:" << ch.isEnabled(); - qInfo() << "Crash log streaming enabled:" << ch.isLogStreamingEnabled(); - qInfo() << "Crash stats streaming enabled:" << ch.isStatsStreamingEnabled(); } ch.setAnnotation("program", "interface"); diff --git a/libraries/networking/src/crash-handler/CrashHandlerScriptingInterface.h b/libraries/networking/src/crash-handler/CrashHandlerScriptingInterface.h index 2efcddd0e5d..d9ecc3aaed7 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerScriptingInterface.h +++ b/libraries/networking/src/crash-handler/CrashHandlerScriptingInterface.h @@ -15,6 +15,14 @@ class CrashHandlerScriptingInterface : public QObject { Q_OBJECT public: + Q_PROPERTY(QString supportTag READ getSupportTag CONSTANT); + Q_PROPERTY(QString crashHandlerName READ getCrashHandlerName CONSTANT); + Q_PROPERTY(bool crashReportingEnabled READ isCrashReportingEnabled CONSTANT); + Q_PROPERTY(bool logStreamingEnabled READ isLogStreamingEnabled CONSTANT); + Q_PROPERTY(bool statsStreamingEnabled READ isStatsStreamingEnabled CONSTANT); + + + static CrashHandlerScriptingInterface* getInstance() { static CrashHandlerScriptingInterface instance; return &instance; @@ -30,7 +38,7 @@ class CrashHandlerScriptingInterface : public QObject { * @function CrashHandler.getSupportTag * @returns {string} The support tag. */ - Q_INVOKABLE QString getSupportTag() { + QString getSupportTag() { auto& crashHandler = CrashHandler::getInstance(); return crashHandler.getSupportTag(); } @@ -40,7 +48,7 @@ class CrashHandlerScriptingInterface : public QObject { * @function CrashHandler.getCrashHandlerName * @returns {string} The name of the crash handler implementation. */ - Q_INVOKABLE QString getCrashHandlerName() { + QString getCrashHandlerName() { auto& crashHandler = CrashHandler::getInstance(); return crashHandler.metaObject()->className(); } @@ -51,7 +59,7 @@ class CrashHandlerScriptingInterface : public QObject { * @function CrashHandler.isCrashReportingEnabled * @returns {boolean} true if crash reporting is enabled, false if it isn't. */ - Q_INVOKABLE bool isCrashReportingEnabled() { + bool isCrashReportingEnabled() { auto& crashHandler = CrashHandler::getInstance(); return crashHandler.isEnabled(); } @@ -61,7 +69,7 @@ class CrashHandlerScriptingInterface : public QObject { * @function CrashHandler.isLogStreamingEnabled * @returns {boolean} true if log streaming is enabled, false if it isn't. */ - Q_INVOKABLE bool isLogStreamingEnabled() { + bool isLogStreamingEnabled() { auto& crashHandler = CrashHandler::getInstance(); return crashHandler.isLogStreamingEnabled(); } @@ -71,7 +79,7 @@ class CrashHandlerScriptingInterface : public QObject { * @function CrashHandler.isStatsStreamingEnabled * @returns {boolean} true if stats streaming is enabled, false if it isn't. */ - Q_INVOKABLE bool isStatsStreamingEnabled() { + bool isStatsStreamingEnabled() { auto& crashHandler = CrashHandler::getInstance(); return crashHandler.isStatsStreamingEnabled(); } diff --git a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp index 76194acbf39..241fa5015a8 100644 --- a/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp +++ b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp @@ -77,7 +77,7 @@ bool CrashHandlerSentry::startCrashHandler() { sentry_set_tag("machine_id", uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()).toStdString().c_str()); sentry_set_tag("support_tag", getSupportTag().toStdString().c_str()); - qCInfo(crash_handler) << "Sentry crash handler initialized. SDK version" << sentry_sdk_version() << ". Will send reports to " << crashURL.c_str() << " with token " << crashToken.c_str(); + qCInfo(crash_handler) << "Sentry crash handler initialized. SDK version" << sentry_sdk_version(); return true; //qCInfo(crash_handler) << "Crashpad uploads " << (enabled ? QString("enabled") : QString("disabled"));