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 4a78138ce83..537d3dc09d4 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 @@ -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 7802c54e580..a095387692c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,44 +68,44 @@ 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 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: Release + cmake_build_type: RelWithDebInfo qt_source: source 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: Release + cmake_build_type: RelWithDebInfo qt_source: source 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: Release + cmake_build_type: RelWithDebInfo qt_source: source 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: Release + cmake_build_type: RelWithDebInfo qt_source: source 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 }}" >> $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. 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 }}" >> $GITHUB_ENV + echo "OVERTE_BACKTRACE_TOKEN=Nightly-$(date +%Y-%m-%d)_${GITHUB_SHA::7}" >> $GITHUB_ENV else echo "Unexpected Action event! Failing…" exit 1; @@ -243,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 @@ -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/.idea/cmake.xml b/.idea/cmake.xml index 28ac684d530..7ef1b58b772 100644 --- a/.idea/cmake.xml +++ b/.idea/cmake.xml @@ -3,21 +3,21 @@ - + - + - + - + - + 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/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/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..ac19cb01867 --- /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 "Sentry is enabled, but -DOVERTE_BACKTRACE_URL is empty!") + endif() + if (OVERTE_BACKTRACE_TOKEN STREQUAL "") + message(FATAL_ERROR "Sentry 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 46d5eecb627..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 @@ -65,7 +64,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") @@ -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/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.cpp b/interface/src/Application.cpp index 0c656d209a9..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); @@ -1403,7 +1407,8 @@ 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; if (isServerlessMode()) { diff --git a/interface/src/Application_Setup.cpp b/interface/src/Application_Setup.cpp index 37c9d959b89..b252a0d39a6 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.setContext("GPU", "name", gpuIdent->getName()); + ch.setContext("GPU", "version", gpuIdent->getDriver()); + + 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; 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/CrashRecoveryHandler.cpp b/interface/src/CrashRecoveryHandler.cpp index 179116c9ef2..8e492499161 100644 --- a/interface/src/CrashRecoveryHandler.cpp +++ b/interface/src/CrashRecoveryHandler.cpp @@ -174,19 +174,30 @@ 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."); } 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 +206,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 +229,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/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"; diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 65c3d828caf..d205afd1f70 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -657,13 +657,61 @@ 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"); - s.setValue("ReportingEnabled", enabled); + + s.beginGroup("Developer/Network"); + s.setValue("Enable Crash Reporting", 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) { + Settings s; + + s.beginGroup("Developer/Network"); + s.setValue("Enable Log Streaming", enabled); + s.endGroup(); + + // FIXME: See above + s.beginGroup("Network"); + s.setValue("Enable Log Streaming", enabled); s.endGroup(); }); + QObject::connect(&ch, &CrashHandler::statsStreamingChanged, [](bool enabled) { + Settings s; + + s.beginGroup("Developer/Network"); + s.setValue("Enable Stats Streaming", enabled); + s.endGroup(); + + // FIXME: See above + s.beginGroup("Network"); + s.setValue("Enable Stats Streaming", 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 @@ -680,10 +728,10 @@ int main(int argc, const char* argv[]) { { Settings crashSettings; - crashSettings.beginGroup("Crash"); - if (crashSettings.value("ReportingEnabled").toBool()) { - ch.setEnabled(true); - } + 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(); } 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)); } diff --git a/libraries/networking/CMakeLists.txt b/libraries/networking/CMakeLists.txt index 3550cf424e7..50282d69ae4 100644 --- a/libraries/networking/CMakeLists.txt +++ b/libraries/networking/CMakeLists.txt @@ -2,9 +2,11 @@ set(TARGET_NAME networking) setup_hifi_library(Network WebSockets) link_hifi_libraries(shared platform) +include_hifi_library_headers(shared) + 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/CrashHandler.cpp b/libraries/networking/src/crash-handler/CrashHandler.cpp index 4dc724fe1c6..16174b9b35e 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.cpp +++ b/libraries/networking/src/crash-handler/CrashHandler.cpp @@ -10,18 +10,168 @@ // #include "CrashHandler.h" -#include "CrashHandlerBackend.h" +#include "CrashHandlerNone.h" +#include "CrashHandlerSentry.h" + #include #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); + + +// 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() { - static CrashHandler sharedInstance; +#if HAS_SENTRY + static CrashHandlerSentry sharedInstance; +#else + static CrashHandlerNone sharedInstance; +#endif + return sharedInstance; } 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())]; } @@ -45,17 +195,17 @@ bool CrashHandler::start() { return false; } - auto started = startCrashHandler(_path.toStdString(), _crashUrl.toStdString(), _crashToken.toStdString()); + auto started = startCrashHandler(); setStarted(started); if ( started ) { - qCInfo(crash_handler) << "Crash handler started"; + qCInfo(crash_handler) << "Crash handler started. Support tag:" << getSupportTag(); std::size_t countAdded = 0; { std::lock_guard lock(_annotationsMutex); for(const auto &item : _annotations) { - setCrashAnnotation(item.first, item.second); + setTag(item.first, item.second); } countAdded = _annotations.size(); @@ -64,6 +214,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"; } @@ -72,7 +227,7 @@ bool CrashHandler::start() { } void CrashHandler::startMonitor(QCoreApplication *app) { - startCrashHookMonitor(app); + // startCrashHookMonitor(app); } void CrashHandler::setEnabled(bool enabled) { @@ -121,5 +276,37 @@ void CrashHandler::setAnnotation(const std::string &key, const std::string &valu return; } - setCrashAnnotation(key, value); -} \ No newline at end of file + setTag(key, value); +} + + +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; + } + + if (isLogStreamingEnabled()) { + sendLogMessage(type, context, msg); + } +} + +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..47492bb0ab5 100644 --- a/libraries/networking/src/crash-handler/CrashHandler.h +++ b/libraries/networking/src/crash-handler/CrashHandler.h @@ -14,10 +14,32 @@ #include #include #include +#include #include #include #include +#include + + + +#ifndef overte_CrashHandler_h +#define overte_CrashHandler_h + + +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 +79,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 +97,8 @@ public slots: */ void setPath(const QString &path); + + /** * @brief Start the crash handler * @@ -134,6 +162,51 @@ 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. + * + * Emits enabledChanged signal. + * + * @param enabled + */ + void setLogStreamingEnabled(bool enabled) { + _logStreamingEnabled = enabled; + emit logStreamingChanged(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. + * + * Emits enabledChanged signal. + * @param enabled + */ + void setStatsStreamingEnabled(bool enabled) { + _statsStreamingEnabled = enabled; + emit statsStreamingChanged(enabled); + } + /** * @brief Set the URL where to send crash reports to * @@ -165,6 +238,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. * @@ -179,6 +254,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. * @@ -193,6 +270,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. * @@ -202,6 +281,54 @@ 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; + + /** + * @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; + + + /** + * @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 + */ + 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: /** @@ -213,8 +340,53 @@ public slots: */ void enabledChanged(bool enabled); -private: - CrashHandler(QObject *parent = nullptr); + /** + * @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: + + /** + * @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; /** @@ -226,15 +398,35 @@ public slots: */ void setStarted(bool started) { _crashMonitorStarted = started; } + CrashHandler(QObject *parent = nullptr); + + +private: + + 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{}; QString _path; - QString _crashUrl; - QString _crashToken; + QString _crashUrl{OVERTE_BACKTRACE_URL}; + QString _crashToken{OVERTE_BACKTRACE_TOKEN}; + + QString _previousMessage{}; + int _repeatCount { 0 }; + + QString _supportTag{}; + + static const QStringList EFF_SHORT_WORDLIST; + }; +#endif + 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_Breakpad.cpp b/libraries/networking/src/crash-handler/CrashHandlerBackend_Breakpad.cpp deleted file mode 100644 index 984cabe5078..00000000000 --- a/libraries/networking/src/crash-handler/CrashHandlerBackend_Breakpad.cpp +++ /dev/null @@ -1,92 +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) { -} - -#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 cf893f267b4..00000000000 --- a/libraries/networking/src/crash-handler/CrashHandlerBackend_Crashpad.cpp +++ /dev/null @@ -1,522 +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 -} - -#endif // HAS_CRASHPAD 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 59441255756..00000000000 --- a/libraries/networking/src/crash-handler/CrashHandlerBackend_None.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// -// CrashHandler_None.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 !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) { - -} -#endif diff --git a/libraries/networking/src/crash-handler/CrashHandlerNone.h b/libraries/networking/src/crash-handler/CrashHandlerNone.h new file mode 100644 index 00000000000..92e855980b8 --- /dev/null +++ b/libraries/networking/src/crash-handler/CrashHandlerNone.h @@ -0,0 +1,40 @@ + + +#ifndef hifi_CrashHandlerNone_h +#define hifi_CrashHandlerNone_h + +#include "CrashHandler.h" + + +class CrashHandlerNone : public CrashHandler { + Q_OBJECT + + 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 { + 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. + } + +}; + +#endif diff --git a/libraries/networking/src/crash-handler/CrashHandlerScriptingInterface.h b/libraries/networking/src/crash-handler/CrashHandlerScriptingInterface.h new file mode 100644 index 00000000000..d9ecc3aaed7 --- /dev/null +++ b/libraries/networking/src/crash-handler/CrashHandlerScriptingInterface.h @@ -0,0 +1,100 @@ + +#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 + */ + +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; + } + + + + /*@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. + */ + 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. + */ + 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. + */ + 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. + */ + 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. + */ + bool isStatsStreamingEnabled() { + auto& crashHandler = CrashHandler::getInstance(); + return crashHandler.isStatsStreamingEnabled(); + } + + + virtual ~CrashHandlerScriptingInterface() = default; + + private: + + CrashHandlerScriptingInterface(QObject* parent = nullptr) : QObject(parent) { + + } + + + +}; + +#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..241fa5015a8 --- /dev/null +++ b/libraries/networking/src/crash-handler/CrashHandlerSentry.cpp @@ -0,0 +1,172 @@ +#include "CrashHandlerSentry.h" + +#include "sentry.h" +#include +#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(); + 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_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); + + 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) { + 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_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())) ; + + + 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(); + return true; + + //qCInfo(crash_handler) << "Crashpad uploads " << (enabled ? QString("enabled") : QString("disabled")); +} + +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: + level = SENTRY_LEVEL_FATAL; + break; + case QtMsgType::QtCriticalMsg: + level = SENTRY_LEVEL_ERROR; + break; + case QtMsgType::QtWarningMsg: + level = SENTRY_LEVEL_WARNING; + break; + case QtMsgType::QtInfoMsg: + level = SENTRY_LEVEL_INFO; + break; + case QtMsgType::QtDebugMsg: + level = SENTRY_LEVEL_DEBUG; + break; + default: + 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) { + 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(); + } +} + +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 new file mode 100644 index 00000000000..ba8f491277c --- /dev/null +++ b/libraries/networking/src/crash-handler/CrashHandlerSentry.h @@ -0,0 +1,33 @@ + + +#ifndef overte_CrashHandlerSentry_h +#define overte_CrashHandlerSentry_h + +#include "CrashHandler.h" +#include + +class CrashHandlerSentry : public CrashHandler { + Q_OBJECT + + 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: + + virtual bool startCrashHandler() override; + + virtual void setCrashReportingEnabled([[maybe_unused]] bool value) override; + + virtual void sendLogMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) override; + + private: + QMutex _contextMutex; + QVariantMap _context; +}; + + +#endif 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 # 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/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() 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 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