From b5187208ea8cd8da433f2ec09e45f127550a80b1 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Thu, 6 Mar 2025 15:34:25 +0100 Subject: [PATCH] Support building with Sentry Native SDK As a replacement for Crashpad directly. Enables us to report the version number in a way that is useful in Sentry. This might also open the option for informing the user with the option to not send the report (with sentry_on_crash_handler, which on macOS is only supported with Breakpad). To use Sentry Native SDK set SENTRY_DSN to the API entry point, SENTRY_PROJECT to the project in Sentry, and point the CMAKE_PREFIX_PATH to the Sentry Native SDK installation path. Both the Crashpad and the Breakpad backends are supported. Dependencies (sentry dynamic library and crashpad_handler if used) are installed with the `Dependencies` install component. Change-Id: I3eb96cbee3ed3910b3f0dfe4c127bd1346b96fc6 Reviewed-by: Marcus Tillmanns Reviewed-by: Christian Stenger --- CMakeLists.txt | 26 +++++++ src/app/CMakeLists.txt | 14 ++++ src/app/main.cpp | 50 ++++++++++++- src/plugins/coreplugin/CMakeLists.txt | 9 ++- src/plugins/coreplugin/coreplugin.cpp | 38 +++++++--- src/plugins/coreplugin/systemsettings.cpp | 85 +++++++++++++---------- src/plugins/coreplugin/systemsettings.h | 2 +- 7 files changed, 173 insertions(+), 51 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c9fc6b5e458..f3c740df275 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,6 +136,32 @@ if(CRASHPAD_BACKEND_URL AND (WIN32 OR APPLE)) # Linux is not supported for now endif() add_feature_info("Build with Crashpad" ${BUILD_WITH_CRASHPAD} "") +set(SENTRY_DSN "$ENV{QTC_SENTRY_DSN}" CACHE STRING "Data Source Name to use for sending Sentry events.") +set(SENTRY_PROJECT "" CACHE STRING "Project ID to use for sending Sentry events.") +set(BUILD_WITH_SENTRY OFF) +if(SENTRY_DSN AND SENTRY_PROJECT) + find_package(sentry QUIET) + if(TARGET sentry::sentry) + set(BUILD_WITH_SENTRY ON) + get_target_property(__sentry_configs sentry::sentry IMPORTED_CONFIGURATIONS) + get_target_property(__sentry_lib sentry::sentry IMPORTED_LOCATION_${__sentry_configs}) + install(FILES ${__sentry_lib} + DESTINATION "${IDE_LIBRARY_ARCHIVE_PATH}" + COMPONENT Dependencies + EXCLUDE_FROM_ALL + ) + if (TARGET sentry_crashpad::crashpad_handler) + get_target_property(SENTRY_CRASHPAD_PATH sentry_crashpad::crashpad_handler IMPORTED_LOCATION_${__sentry_configs}) + install(PROGRAMS ${SENTRY_CRASHPAD_PATH} + DESTINATION "${IDE_LIBEXEC_PATH}" + COMPONENT Dependencies + EXCLUDE_FROM_ALL + ) + endif() + endif() +endif() +add_feature_info("Build with Sentry" ${BUILD_WITH_SENTRY} "") + function (set_if_target var target) if (TARGET "${target}") set(_result ON) diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 728aa24492a..d73fd88226c 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -157,6 +157,20 @@ if(BUILD_WITH_CRASHPAD) ) endif() +extend_qtc_executable(qtcreator + CONDITION BUILD_WITH_SENTRY + DEFINES + SENTRY_DSN="${SENTRY_DSN}" + SENTRY_PROJECT="${SENTRY_PROJECT}" + ENABLE_SENTRY + DEPENDS sentry::sentry +) +extend_qtc_executable(qtcreator + CONDITION DEFINED SENTRY_CRASHPAD_PATH + DEFINES + SENTRY_CRASHPAD_PATH="${SENTRY_CRASHPAD_PATH}" +) + if ((NOT WIN32) AND (NOT APPLE)) # install logo foreach(size 16 24 32 48 64 128 256 512) diff --git a/src/app/main.cpp b/src/app/main.cpp index 8feef335736..120d56bbb31 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -54,6 +54,12 @@ #include "client/settings.h" #endif +#ifdef ENABLE_SENTRY +#include + +Q_LOGGING_CATEGORY(sentryLog, "qtc.sentry", QtWarningMsg) +#endif + using namespace ExtensionSystem; using namespace Utils; @@ -496,6 +502,35 @@ void startCrashpad(const AppInfo &appInfo, bool crashReportingEnabled) } #endif +#ifdef ENABLE_SENTRY +void configureSentry(const AppInfo &appInfo, bool crashReportingEnabled) +{ + if (!crashReportingEnabled) + return; + + sentry_options_t *options = sentry_options_new(); + sentry_options_set_dsn(options, SENTRY_DSN); +#ifdef Q_OS_WIN + sentry_options_set_database_pathw(options, appInfo.crashReports.nativePath().toStdWString().c_str()); +#else + sentry_options_set_database_path(options, appInfo.crashReports.nativePath().toUtf8().constData()); +#endif +#ifdef SENTRY_CRASHPAD_PATH + if (const FilePath handlerpath = appInfo.libexec / "crashpad_handler"; handlerpath.exists()) { + sentry_options_set_handler_path(options, handlerpath.nativePath().toUtf8().constData()); + } else if (const auto fallback = FilePath::fromString(SENTRY_CRASHPAD_PATH); fallback.exists()) { + sentry_options_set_handler_path(options, fallback.nativePath().toUtf8().constData()); + } else { + qCWarning(sentryLog) << "Failed to find crashpad_handler for Sentry crash reports."; + } +#endif + const QString release = QString(SENTRY_PROJECT) + "@" + QCoreApplication::applicationVersion(); + sentry_options_set_release(options, release.toUtf8().constData()); + sentry_options_set_debug(options, sentryLog().isDebugEnabled() ? 1 : 0); + sentry_init(options); +} +#endif + class ShowInGuiHandler { public: @@ -792,10 +827,15 @@ int main(int argc, char **argv) CrashHandlerSetup setupCrashHandler( Core::Constants::IDE_DISPLAY_NAME, CrashHandlerSetup::EnableRestart, info.libexec.path()); -#ifdef ENABLE_CRASHPAD // depends on AppInfo and QApplication being created - bool crashReportingEnabled = settings->value("CrashReportingEnabled", false).toBool(); + const bool crashReportingEnabled = settings->value("CrashReportingEnabled", false).toBool(); + +#if defined(ENABLE_CRASHPAD) startCrashpad(info, crashReportingEnabled); +#elif defined(ENABLE_SENTRY) + configureSentry(info, crashReportingEnabled); +#else + Q_UNUSED(crashReportingEnabled) #endif PluginManager pluginManager; @@ -983,5 +1023,9 @@ int main(int argc, char **argv) QApplication::setEffectEnabled(Qt::UI_AnimateMenu, false); } - return restarter.restartOrExit(app.exec()); + const int exitCode = restarter.restartOrExit(app.exec()); +#ifdef ENABLE_SENTRY + sentry_close(); +#endif + return exitCode; } diff --git a/src/plugins/coreplugin/CMakeLists.txt b/src/plugins/coreplugin/CMakeLists.txt index 7ae5182cd6c..3a0f0006cf5 100644 --- a/src/plugins/coreplugin/CMakeLists.txt +++ b/src/plugins/coreplugin/CMakeLists.txt @@ -322,8 +322,13 @@ extend_qtc_plugin(Core ) extend_qtc_plugin(Core - CONDITION BUILD_WITH_CRASHPAD - DEFINES ENABLE_CRASHPAD + CONDITION BUILD_WITH_CRASHPAD OR BUILD_WITH_SENTRY + DEFINES ENABLE_CRASHREPORTING +) + +extend_qtc_plugin(Core + CONDITION BUILD_WITH_CRASHPAD OR (BUILD_WITH_SENTRY AND SENTRY_CRASHPAD_PATH) + DEFINES CRASHREPORTING_USES_CRASHPAD ) set(FONTS_BASE "${QtCreator_SOURCE_DIR}/src/share/3rdparty/studiofonts/") diff --git a/src/plugins/coreplugin/coreplugin.cpp b/src/plugins/coreplugin/coreplugin.cpp index 1aef8c04557..85dbc20db0f 100644 --- a/src/plugins/coreplugin/coreplugin.cpp +++ b/src/plugins/coreplugin/coreplugin.cpp @@ -359,9 +359,13 @@ bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage) Utils::PathChooser::setAboutToShowContextMenuHandler(&addToPathChooserContextMenu); -#ifdef ENABLE_CRASHPAD - connect(ICore::instance(), &ICore::coreOpened, this, &CorePlugin::warnAboutCrashReporing, - Qt::QueuedConnection); +#ifdef ENABLE_CRASHREPORTING + connect( + ICore::instance(), + &ICore::coreOpened, + this, + &CorePlugin::warnAboutCrashReporing, + Qt::QueuedConnection); #endif #ifdef WITH_TESTS @@ -542,19 +546,33 @@ void CorePlugin::warnAboutCrashReporing() // static QString CorePlugin::msgCrashpadInformation() { - return Tr::tr("%1 uses Google Crashpad for collecting crashes and sending them to Sentry " - "for processing. Crashpad may capture arbitrary contents from crashed process’ " +#if ENABLE_CRASHREPORTING +#if CRASHREPORTING_USES_CRASHPAD + const QString backend = "Google Crashpad"; + const QString url + = "https://chromium.googlesource.com/crashpad/crashpad/+/master/doc/overview_design.md"; +#else + const QString backend = "Google Breakpad"; + const QString url + = "https://chromium.googlesource.com/breakpad/breakpad/+/HEAD/docs/client_design.md"; +#endif + //: %1 = application name, %2 crash backend name (Google Crashpad or Google Breakpad) + return Tr::tr("%1 uses %2 for collecting crashes and sending them to Sentry " + "for processing. %2 may capture arbitrary contents from crashed process’ " "memory, including user sensitive information, URLs, and whatever other content " "users have trusted %1 with. The collected crash reports are however only used " "for the sole purpose of fixing bugs.") - .arg(QGuiApplication::applicationDisplayName()) - + "

" + Tr::tr("More information:") - + "
" - + Tr::tr("Crashpad Overview") + .arg(QGuiApplication::applicationDisplayName(), backend) + + "

" + Tr::tr("More information:") + "
" + //: %1 = crash backend name (Google Crashpad or Google Breakpad) + + Tr::tr("%1 Overview").arg(backend) + "" "
" + Tr::tr("%1 security policy").arg("Sentry.io") + ""; +#else + return {}; +#endif } ExtensionSystem::IPlugin::ShutdownFlag CorePlugin::aboutToShutdown() diff --git a/src/plugins/coreplugin/systemsettings.cpp b/src/plugins/coreplugin/systemsettings.cpp index 47ededcde1e..2a5111877b4 100644 --- a/src/plugins/coreplugin/systemsettings.cpp +++ b/src/plugins/coreplugin/systemsettings.cpp @@ -12,7 +12,7 @@ #include "iversioncontrol.h" // sic! #include "vcsmanager.h" -#ifdef ENABLE_CRASHPAD +#ifdef ENABLE_CRASHREPORTING #include "coreplugin.h" #endif @@ -40,7 +40,7 @@ using namespace Layouting; namespace Core::Internal { -#ifdef ENABLE_CRASHPAD +#ifdef CRASHREPORTING_USES_CRASHPAD // TODO: move to somewhere in Utils static QString formatSize(qint64 size) { @@ -55,7 +55,7 @@ static QString formatSize(qint64 size) return i == 0 ? QString("%0 %1").arg(outputSize).arg(units[i]) // Bytes : QString("%0 %1").arg(outputSize, 0, 'f', 2).arg(units[i]); // KB, MB, GB, TB } -#endif // ENABLE_CRASHPAD +#endif // CRASHREPORTING_USES_CRASHPAD SystemSettings &systemSettings() { @@ -145,15 +145,17 @@ SystemSettings::SystemSettings() askBeforeExit.setLabelText(Tr::tr("Ask for confirmation before exiting")); askBeforeExit.setLabelPlacement(BoolAspect::LabelPlacement::Compact); -#ifdef ENABLE_CRASHPAD +#ifdef ENABLE_CRASHREPORTING enableCrashReporting.setSettingsKey("CrashReportingEnabled"); enableCrashReporting.setLabelPlacement(BoolAspect::LabelPlacement::Compact); enableCrashReporting.setLabelText(Tr::tr("Enable crash reporting")); enableCrashReporting.setToolTip( - Tr::tr("Allow crashes to be automatically reported. Collected reports are " - "used for the sole purpose of fixing bugs.")); - -#endif + "

" + + Tr::tr("Allow crashes to be automatically reported. Collected reports are " + "used for the sole purpose of fixing bugs.") + + "

" + + Tr::tr("Crash reports are saved in \"%1\".").arg(appInfo().crashReports.toUserOutput())); +#endif // ENABLE_CRASHREPORTING readSettings(); autoSaveInterval.setEnabler(&autoSaveModifiedFiles); @@ -174,7 +176,7 @@ public: , m_terminalOpenArgs(new QLineEdit) , m_terminalExecuteArgs(new QLineEdit) , m_environmentChangesLabel(new Utils::ElidingLabel) -#ifdef ENABLE_CRASHPAD +#ifdef CRASHREPORTING_USES_CRASHPAD , m_clearCrashReportsButton(new QPushButton(Tr::tr("Clear Local Crash Reports"), this)) , m_crashReportsSizeText(new QLabel(this)) #endif @@ -199,9 +201,12 @@ public: resetFileBrowserButton->setToolTip(Tr::tr("Reset to default.")); auto helpExternalFileBrowserButton = new QToolButton; helpExternalFileBrowserButton->setText(Tr::tr("?")); -#ifdef ENABLE_CRASHPAD +#ifdef ENABLE_CRASHREPORTING auto helpCrashReportingButton = new QToolButton(this); helpCrashReportingButton->setText(Tr::tr("?")); + connect(helpCrashReportingButton, &QAbstractButton::clicked, this, [this] { + showHelpDialog(Tr::tr("Crash Reporting"), CorePlugin::msgCrashpadInformation()); + }); #endif auto resetTerminalButton = new QPushButton(Tr::tr("Reset")); resetTerminalButton->setToolTip(Tr::tr("Reset to default.", "Terminal")); @@ -241,13 +246,14 @@ public: grid.addRow({Tr::tr("Maximum number of entries in \"Recent Files\":"), Row{s.maxRecentFiles, st}}); grid.addRow({s.askBeforeExit}); -#ifdef ENABLE_CRASHPAD - const QString toolTip = Tr::tr("Crash reports are saved in \"%1\".") - .arg(appInfo().crashReports.toUserOutput()); - m_clearCrashReportsButton->setToolTip(toolTip); - m_crashReportsSizeText->setToolTip(toolTip); - Row crashDetails - = Row{m_clearCrashReportsButton, m_crashReportsSizeText, helpCrashReportingButton, st}; +#ifdef ENABLE_CRASHREPORTING + Row crashDetails; +#ifdef CRASHREPORTING_USES_CRASHPAD + m_clearCrashReportsButton->setToolTip(s.enableCrashReporting.toolTip()); + m_crashReportsSizeText->setToolTip(s.enableCrashReporting.toolTip()); + crashDetails.addItems({m_clearCrashReportsButton, m_crashReportsSizeText}); +#endif // CRASHREPORTING_USES_CRASHPAD + crashDetails.addItem(helpCrashReportingButton); if (qtcEnvironmentVariableIsSet("QTC_SHOW_CRASHBUTTON")) { auto crashButton = new QPushButton("CRASH!!!"); connect(crashButton, &QPushButton::clicked, [] { @@ -257,6 +263,7 @@ public: }); crashDetails.addItem(crashButton); } + crashDetails.addItem(st); grid.addRow({s.enableCrashReporting, crashDetails}); #endif @@ -283,20 +290,24 @@ public: m_externalFileBrowserEdit->setText(UnixUtils::fileBrowser(ICore::settings())); } -#ifdef ENABLE_CRASHPAD - connect(helpCrashReportingButton, &QAbstractButton::clicked, this, [this] { - showHelpDialog(Tr::tr("Crash Reporting"), CorePlugin::msgCrashpadInformation()); - }); +#if defined(ENABLE_CRASHREPORTING) && defined(CRASHREPORTING_USES_CRASHPAD) + const FilePaths reportsPaths + = {ICore::crashReportsPath() / "completed", + ICore::crashReportsPath() / "reports", + ICore::crashReportsPath() / "attachments", + ICore::crashReportsPath() / "pending", + ICore::crashReportsPath() / "new"}; - const FilePath reportsPath = ICore::crashReportsPath() - / QLatin1String( - HostOsInfo::isMacHost() ? "completed" : "reports"); - const auto updateClearCrashWidgets = [this, reportsPath] { + const auto updateClearCrashWidgets = [this, reportsPaths] { qint64 size = 0; - const FilePaths crashFiles = reportsPath.dirEntries(QDir::Files); - for (const FilePath &file : crashFiles) - size += file.fileSize(); - m_clearCrashReportsButton->setEnabled(!crashFiles.isEmpty()); + FilePath::iterateDirectories( + reportsPaths, + [&size](const FilePath &item) { + size += item.fileSize(); + return IterationPolicy::Continue; + }, + FileFilter({}, QDir::Files, QDirIterator::Subdirectories)); + m_clearCrashReportsButton->setEnabled(size > 0); m_crashReportsSizeText->setText(formatSize(size)); }; updateClearCrashWidgets(); @@ -304,13 +315,17 @@ public: m_clearCrashReportsButton, &QPushButton::clicked, this, - [updateClearCrashWidgets, reportsPath] { - const FilePaths &crashFiles = reportsPath.dirEntries(QDir::Files); - for (const FilePath &file : crashFiles) - file.removeFile(); + [updateClearCrashWidgets, reportsPaths] { + FilePath::iterateDirectories( + reportsPaths, + [](const FilePath &item) { + item.removeRecursively(); + return IterationPolicy::Continue; + }, + FileFilter({}, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot)); updateClearCrashWidgets(); }); -#endif +#endif // ENABLE_CRASHREPORTING && CRASHREPORTING_USES_CRASHPAD if (HostOsInfo::isAnyUnixHost()) { connect(resetTerminalButton, @@ -389,7 +404,7 @@ private: QLineEdit *m_terminalOpenArgs; QLineEdit *m_terminalExecuteArgs; Utils::ElidingLabel *m_environmentChangesLabel; -#ifdef ENABLE_CRASHPAD +#ifdef CRASHREPORTING_USES_CRASHPAD QPushButton *m_clearCrashReportsButton; QLabel *m_crashReportsSizeText; #endif diff --git a/src/plugins/coreplugin/systemsettings.h b/src/plugins/coreplugin/systemsettings.h index 36bc76129d9..0b9385d5daa 100644 --- a/src/plugins/coreplugin/systemsettings.h +++ b/src/plugins/coreplugin/systemsettings.h @@ -34,7 +34,7 @@ public: Utils::SelectionAspect reloadSetting{this}; -#ifdef ENABLE_CRASHPAD +#ifdef ENABLE_CRASHREPORTING Utils::BoolAspect enableCrashReporting{this}; #endif