diff --git a/internal/frontend/bridge-gui/bridge-gui/CMakeLists.txt b/internal/frontend/bridge-gui/bridge-gui/CMakeLists.txt index 745e11c2..981b06e9 100644 --- a/internal/frontend/bridge-gui/bridge-gui/CMakeLists.txt +++ b/internal/frontend/bridge-gui/bridge-gui/CMakeLists.txt @@ -80,6 +80,26 @@ qt_standard_project_setup() set(CMAKE_AUTORCC ON) message(STATUS "Using Qt ${Qt6_VERSION}") +#***************************************************************************************************************************************************** +# Sentry Native +#***************************************************************************************************************************************************** +find_package(sentry CONFIG REQUIRED) + +set(DSN_SENTRY "https://ea31dfe8574849108fb8ba044fec3620@api.protonmail.ch/core/v4/reports/sentry/7") +set(SENTRY_CONFIG_GENERATED_FILE_DIR ${CMAKE_CURRENT_BINARY_DIR}/sentry-generated) +set(SENTRY_CONFIG_FILE ${SENTRY_CONFIG_GENERATED_FILE_DIR}/project_sentry_config.h) +file(GENERATE OUTPUT ${SENTRY_CONFIG_FILE} CONTENT + "// AUTO GENERATED FILE, DO NOT MODIFY\n#pragma once\nconst char* SentryDNS=\"${DSN_SENTRY}\";\nconst char* SentryProductID=\"bridge-mail@${BRIDGE_APP_VERSION}\";\n" +) + +if (APPLE) + #TODO: Find a better way to extract this information + install(PROGRAMS "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/tools/sentry-native/crashpad_handler" DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/MacOS/") +endif() + +if (WIN32) + install(PROGRAMS "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/tools/sentry-native/crashpad_handler.exe" DESTINATION "${CMAKE_INSTALL_PREFIX}") +endif() #***************************************************************************************************************************************************** # Source files and output @@ -111,6 +131,7 @@ add_executable(bridge-gui Version.h QMLBackend.cpp QMLBackend.h UserList.cpp UserList.h + SentryUtils.cpp SentryUtils.h ${DOCK_ICON_SRC_FILE} DockIcon/DockIcon.h ) @@ -127,13 +148,14 @@ if (WIN32) # on Windows, we add a (non-Qt) resource file that contains the appli endif() target_precompile_headers(bridge-gui PRIVATE Pch.h) -target_include_directories(bridge-gui PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(bridge-gui PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${SENTRY_CONFIG_GENERATED_FILE_DIR}) target_link_libraries(bridge-gui Qt6::Widgets Qt6::Core Qt6::Quick Qt6::Qml Qt6::QuickControls2 + sentry::sentry bridgepp ) @@ -169,4 +191,4 @@ else() set(DEPLOY_OS Windows) endif() -include(Deploy${DEPLOY_OS}.cmake) \ No newline at end of file +include(Deploy${DEPLOY_OS}.cmake) diff --git a/internal/frontend/bridge-gui/bridge-gui/EventStreamWorker.cpp b/internal/frontend/bridge-gui/bridge-gui/EventStreamWorker.cpp index a0cddb68..0a471f02 100644 --- a/internal/frontend/bridge-gui/bridge-gui/EventStreamWorker.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/EventStreamWorker.cpp @@ -17,6 +17,7 @@ #include "EventStreamWorker.h" +#include "SentryUtils.h" #include #include #include @@ -54,6 +55,7 @@ void EventStreamReader::run() } catch (Exception const &e) { + reportSentryException(SENTRY_LEVEL_ERROR, "Error during event stream read", "Exception", e.what()); emit error(e.qwhat()); } } diff --git a/internal/frontend/bridge-gui/bridge-gui/SentryUtils.cpp b/internal/frontend/bridge-gui/bridge-gui/SentryUtils.cpp new file mode 100644 index 00000000..beed2c51 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/SentryUtils.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2022 Proton AG +// +// This file is part of Proton Mail Bridge. +// +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . + +#include "SentryUtils.h" + +static constexpr const char* LoggerName = "bridge-gui"; + +void reportSentryEvent(sentry_level_t level, const char* message) { + auto event = sentry_value_new_message_event(level, LoggerName, message); + sentry_capture_event(event); +} + +void reportSentryException(sentry_level_t level, const char* message, const char* exceptionType, const char* exception) { + auto event = sentry_value_new_message_event(level, LoggerName, message); + sentry_event_add_exception(event, sentry_value_new_exception(exceptionType, exception)); + sentry_capture_event(event); +} diff --git a/internal/frontend/bridge-gui/bridge-gui/SentryUtils.h b/internal/frontend/bridge-gui/bridge-gui/SentryUtils.h new file mode 100644 index 00000000..85d552a3 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/SentryUtils.h @@ -0,0 +1,26 @@ +// Copyright (c) 2022 Proton AG +// +// This file is part of Proton Mail Bridge. +// +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . + +#ifndef BRIDGE_GUI_SENTRYUTILS_H +#define BRIDGE_GUI_SENTRYUTILS_H + +#include + +void reportSentryEvent(sentry_level_t level, const char* message); +void reportSentryException(sentry_level_t level, const char* message, const char* exceptionType, const char* exception); + +#endif //BRIDGE_GUI_SENTRYUTILS_H diff --git a/internal/frontend/bridge-gui/bridge-gui/build.ps1 b/internal/frontend/bridge-gui/bridge-gui/build.ps1 index de55d576..16793360 100644 --- a/internal/frontend/bridge-gui/bridge-gui/build.ps1 +++ b/internal/frontend/bridge-gui/bridge-gui/build.ps1 @@ -77,7 +77,7 @@ Write-host "Running build for version $bridgeVersion - $buildConfig in $buildDir git submodule update --init --recursive $vcpkgRoot . $vcpkgBootstrap -disableMetrics -. $vcpkgExe install grpc:x64-windows --clean-after-build +. $vcpkgExe install sentry-native:x64-windows grpc:x64-windows --clean-after-build . $vcpkgExe upgrade --no-dry-run . $cmakeExe -G "Visual Studio 17 2022" -DCMAKE_BUILD_TYPE="$buildConfig" ` -DBRIDGE_APP_FULL_NAME="$bridgeFullName" ` diff --git a/internal/frontend/bridge-gui/bridge-gui/build.sh b/internal/frontend/bridge-gui/bridge-gui/build.sh index fd9417ef..25bcbf46 100755 --- a/internal/frontend/bridge-gui/bridge-gui/build.sh +++ b/internal/frontend/bridge-gui/bridge-gui/build.sh @@ -69,12 +69,12 @@ ${VCPKG_BOOTSTRAP} -disableMetrics check_exit "Failed to bootstrap vcpkg." if [[ "$OSTYPE" == "darwin"* ]]; then - ${VCPKG_EXE} install grpc:arm64-osx-min-11-0 --overlay-triplets=vcpkg/triplets --clean-after-build + ${VCPKG_EXE} install sentry-native:arm64-osx-min-11-0 grpc:arm64-osx-min-11-0 --overlay-triplets=vcpkg/triplets --clean-after-build check_exit "Failed installing gRPC for macOS / Apple Silicon" - ${VCPKG_EXE} install grpc:x64-osx-min-11-0 --overlay-triplets=vcpkg/triplets --clean-after-build + ${VCPKG_EXE} install sentry-native:x64-osx-min-11-0 grpc:x64-osx-min-11-0 --overlay-triplets=vcpkg/triplets --clean-after-build check_exit "Failed installing gRPC for macOS / Intel x64" elif [[ "$OSTYPE" == "linux"* ]]; then - ${VCPKG_EXE} install grpc:x64-linux --clean-after-build + ${VCPKG_EXE} install sentry-native:x64-linux grpc:x64-linux --clean-after-build check_exit "Failed installing gRPC for Linux / Intel x64" else echo "For Windows, use the build.ps1 Powershell script." diff --git a/internal/frontend/bridge-gui/bridge-gui/main.cpp b/internal/frontend/bridge-gui/bridge-gui/main.cpp index 93df1ab7..8b41dff9 100644 --- a/internal/frontend/bridge-gui/bridge-gui/main.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/main.cpp @@ -19,12 +19,16 @@ #include "Pch.h" #include "CommandLine.h" #include "QMLBackend.h" +#include "SentryUtils.h" #include "Version.h" #include #include #include #include #include +#include +#include + using namespace bridgepp; @@ -236,6 +240,7 @@ void focusOtherInstance() catch (Exception const& e) { app().log().error(e.qwhat()); + reportSentryException(SENTRY_LEVEL_ERROR, "Exception occurred during focusOtherInstance()", "Exception", e.what()); } } @@ -288,6 +293,25 @@ void closeBridgeApp() //**************************************************************************************************************************************************** int main(int argc, char *argv[]) { + // Init sentry. + sentry_options_t* options = sentry_options_new(); + sentry_options_set_dsn(options, SentryDNS); + { + const QString sentryCachePath = sentryCacheDir(); + sentry_options_set_database_path(options, sentryCachePath.toStdString().c_str()); + } + sentry_options_set_release(options, SentryProductID); + // Enable this for debugging sentry. + // sentry_options_set_debug(options, 1); + if (sentry_init(options) != 0) { + std::cerr << "Failed to initialize sentry" << std::endl; + } + + reportSentryException(SENTRY_LEVEL_ERROR, "Exception occurred during main", "Exception-Type", "mac os message"); + + + auto sentryClose = qScopeGuard([]{sentry_close();}); + // The application instance is needed to display system message boxes. As we may have to do it in the exception handler, // application instance is create outside the try/catch clause. if (QSysInfo::productType() != "windows") @@ -403,6 +427,7 @@ int main(int argc, char *argv[]) } catch (Exception const &e) { + reportSentryException(SENTRY_LEVEL_ERROR, "Exception occurred during main", "Exception", e.what()); QMessageBox::critical(nullptr, "Error", e.qwhat()); QTextStream(stderr) << e.qwhat() << "\n"; return EXIT_FAILURE; diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.cpp index 8c75e9be..94a63c6e 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.cpp @@ -167,6 +167,16 @@ QString userLogsDir() return path; } +//**************************************************************************************************************************************************** +/// \return sentry cache directory used by bridge. +//**************************************************************************************************************************************************** +QString sentryCacheDir() +{ + QString const path = QDir(userDataDir()).absoluteFilePath("sentry_cache"); + QDir().mkpath(path); + return path; +} + //**************************************************************************************************************************************************** /// \return The value GOOS would return for the current platform. diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.h index f28652fb..619a9555 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.h @@ -40,6 +40,7 @@ enum class OS { QString userConfigDir(); ///< Get the path of the user configuration folder. QString userCacheDir(); ///< Get the path of the user cache folder. QString userLogsDir(); ///< Get the path of the user logs folder. +QString sentryCacheDir(); ///< Get the path of the sentry cache folder. QString goos(); ///< return the value of Go's GOOS for the current platform ("darwin", "linux" and "windows" are supported). qint64 randN(qint64 n); ///< return a random integer in the half open range [0,n) QString randomFirstName(); ///< Get a random first name from a pre-determined list.