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 <marcus.tillmanns@qt.io>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Eike Ziller
2025-03-06 15:34:25 +01:00
parent 97dd597a56
commit b5187208ea
7 changed files with 173 additions and 51 deletions

View File

@@ -136,6 +136,32 @@ if(CRASHPAD_BACKEND_URL AND (WIN32 OR APPLE)) # Linux is not supported for now
endif() endif()
add_feature_info("Build with Crashpad" ${BUILD_WITH_CRASHPAD} "") 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) function (set_if_target var target)
if (TARGET "${target}") if (TARGET "${target}")
set(_result ON) set(_result ON)

View File

@@ -157,6 +157,20 @@ if(BUILD_WITH_CRASHPAD)
) )
endif() 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)) if ((NOT WIN32) AND (NOT APPLE))
# install logo # install logo
foreach(size 16 24 32 48 64 128 256 512) foreach(size 16 24 32 48 64 128 256 512)

View File

@@ -54,6 +54,12 @@
#include "client/settings.h" #include "client/settings.h"
#endif #endif
#ifdef ENABLE_SENTRY
#include <sentry.h>
Q_LOGGING_CATEGORY(sentryLog, "qtc.sentry", QtWarningMsg)
#endif
using namespace ExtensionSystem; using namespace ExtensionSystem;
using namespace Utils; using namespace Utils;
@@ -496,6 +502,35 @@ void startCrashpad(const AppInfo &appInfo, bool crashReportingEnabled)
} }
#endif #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 class ShowInGuiHandler
{ {
public: public:
@@ -792,10 +827,15 @@ int main(int argc, char **argv)
CrashHandlerSetup setupCrashHandler( CrashHandlerSetup setupCrashHandler(
Core::Constants::IDE_DISPLAY_NAME, CrashHandlerSetup::EnableRestart, info.libexec.path()); Core::Constants::IDE_DISPLAY_NAME, CrashHandlerSetup::EnableRestart, info.libexec.path());
#ifdef ENABLE_CRASHPAD
// depends on AppInfo and QApplication being created // 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); startCrashpad(info, crashReportingEnabled);
#elif defined(ENABLE_SENTRY)
configureSentry(info, crashReportingEnabled);
#else
Q_UNUSED(crashReportingEnabled)
#endif #endif
PluginManager pluginManager; PluginManager pluginManager;
@@ -983,5 +1023,9 @@ int main(int argc, char **argv)
QApplication::setEffectEnabled(Qt::UI_AnimateMenu, false); 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;
} }

View File

@@ -322,8 +322,13 @@ extend_qtc_plugin(Core
) )
extend_qtc_plugin(Core extend_qtc_plugin(Core
CONDITION BUILD_WITH_CRASHPAD CONDITION BUILD_WITH_CRASHPAD OR BUILD_WITH_SENTRY
DEFINES ENABLE_CRASHPAD 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/") set(FONTS_BASE "${QtCreator_SOURCE_DIR}/src/share/3rdparty/studiofonts/")

View File

@@ -359,9 +359,13 @@ bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage)
Utils::PathChooser::setAboutToShowContextMenuHandler(&addToPathChooserContextMenu); Utils::PathChooser::setAboutToShowContextMenuHandler(&addToPathChooserContextMenu);
#ifdef ENABLE_CRASHPAD #ifdef ENABLE_CRASHREPORTING
connect(ICore::instance(), &ICore::coreOpened, this, &CorePlugin::warnAboutCrashReporing, connect(
Qt::QueuedConnection); ICore::instance(),
&ICore::coreOpened,
this,
&CorePlugin::warnAboutCrashReporing,
Qt::QueuedConnection);
#endif #endif
#ifdef WITH_TESTS #ifdef WITH_TESTS
@@ -542,19 +546,33 @@ void CorePlugin::warnAboutCrashReporing()
// static // static
QString CorePlugin::msgCrashpadInformation() QString CorePlugin::msgCrashpadInformation()
{ {
return Tr::tr("%1 uses Google Crashpad for collecting crashes and sending them to Sentry " #if ENABLE_CRASHREPORTING
"for processing. Crashpad may capture arbitrary contents from crashed process " #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 " "memory, including user sensitive information, URLs, and whatever other content "
"users have trusted %1 with. The collected crash reports are however only used " "users have trusted %1 with. The collected crash reports are however only used "
"for the sole purpose of fixing bugs.") "for the sole purpose of fixing bugs.")
.arg(QGuiApplication::applicationDisplayName()) .arg(QGuiApplication::applicationDisplayName(), backend)
+ "<br><br>" + Tr::tr("More information:") + "<br><br>" + Tr::tr("More information:") + "<br><a href='" + url
+ "<br><a href='https://chromium.googlesource.com/crashpad/crashpad/+/master/doc/" + "'>"
"overview_design.md'>" //: %1 = crash backend name (Google Crashpad or Google Breakpad)
+ Tr::tr("Crashpad Overview") + Tr::tr("%1 Overview").arg(backend)
+ "</a>" + "</a>"
"<br><a href='https://sentry.io/security/'>" "<br><a href='https://sentry.io/security/'>"
+ Tr::tr("%1 security policy").arg("Sentry.io") + "</a>"; + Tr::tr("%1 security policy").arg("Sentry.io") + "</a>";
#else
return {};
#endif
} }
ExtensionSystem::IPlugin::ShutdownFlag CorePlugin::aboutToShutdown() ExtensionSystem::IPlugin::ShutdownFlag CorePlugin::aboutToShutdown()

View File

@@ -12,7 +12,7 @@
#include "iversioncontrol.h" // sic! #include "iversioncontrol.h" // sic!
#include "vcsmanager.h" #include "vcsmanager.h"
#ifdef ENABLE_CRASHPAD #ifdef ENABLE_CRASHREPORTING
#include "coreplugin.h" #include "coreplugin.h"
#endif #endif
@@ -40,7 +40,7 @@ using namespace Layouting;
namespace Core::Internal { namespace Core::Internal {
#ifdef ENABLE_CRASHPAD #ifdef CRASHREPORTING_USES_CRASHPAD
// TODO: move to somewhere in Utils // TODO: move to somewhere in Utils
static QString formatSize(qint64 size) 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 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 : QString("%0 %1").arg(outputSize, 0, 'f', 2).arg(units[i]); // KB, MB, GB, TB
} }
#endif // ENABLE_CRASHPAD #endif // CRASHREPORTING_USES_CRASHPAD
SystemSettings &systemSettings() SystemSettings &systemSettings()
{ {
@@ -145,15 +145,17 @@ SystemSettings::SystemSettings()
askBeforeExit.setLabelText(Tr::tr("Ask for confirmation before exiting")); askBeforeExit.setLabelText(Tr::tr("Ask for confirmation before exiting"));
askBeforeExit.setLabelPlacement(BoolAspect::LabelPlacement::Compact); askBeforeExit.setLabelPlacement(BoolAspect::LabelPlacement::Compact);
#ifdef ENABLE_CRASHPAD #ifdef ENABLE_CRASHREPORTING
enableCrashReporting.setSettingsKey("CrashReportingEnabled"); enableCrashReporting.setSettingsKey("CrashReportingEnabled");
enableCrashReporting.setLabelPlacement(BoolAspect::LabelPlacement::Compact); enableCrashReporting.setLabelPlacement(BoolAspect::LabelPlacement::Compact);
enableCrashReporting.setLabelText(Tr::tr("Enable crash reporting")); enableCrashReporting.setLabelText(Tr::tr("Enable crash reporting"));
enableCrashReporting.setToolTip( enableCrashReporting.setToolTip(
Tr::tr("Allow crashes to be automatically reported. Collected reports are " "<p>"
"used for the sole purpose of fixing bugs.")); + Tr::tr("Allow crashes to be automatically reported. Collected reports are "
"used for the sole purpose of fixing bugs.")
#endif + "</p><p>"
+ Tr::tr("Crash reports are saved in \"%1\".").arg(appInfo().crashReports.toUserOutput()));
#endif // ENABLE_CRASHREPORTING
readSettings(); readSettings();
autoSaveInterval.setEnabler(&autoSaveModifiedFiles); autoSaveInterval.setEnabler(&autoSaveModifiedFiles);
@@ -174,7 +176,7 @@ public:
, m_terminalOpenArgs(new QLineEdit) , m_terminalOpenArgs(new QLineEdit)
, m_terminalExecuteArgs(new QLineEdit) , m_terminalExecuteArgs(new QLineEdit)
, m_environmentChangesLabel(new Utils::ElidingLabel) , m_environmentChangesLabel(new Utils::ElidingLabel)
#ifdef ENABLE_CRASHPAD #ifdef CRASHREPORTING_USES_CRASHPAD
, m_clearCrashReportsButton(new QPushButton(Tr::tr("Clear Local Crash Reports"), this)) , m_clearCrashReportsButton(new QPushButton(Tr::tr("Clear Local Crash Reports"), this))
, m_crashReportsSizeText(new QLabel(this)) , m_crashReportsSizeText(new QLabel(this))
#endif #endif
@@ -199,9 +201,12 @@ public:
resetFileBrowserButton->setToolTip(Tr::tr("Reset to default.")); resetFileBrowserButton->setToolTip(Tr::tr("Reset to default."));
auto helpExternalFileBrowserButton = new QToolButton; auto helpExternalFileBrowserButton = new QToolButton;
helpExternalFileBrowserButton->setText(Tr::tr("?")); helpExternalFileBrowserButton->setText(Tr::tr("?"));
#ifdef ENABLE_CRASHPAD #ifdef ENABLE_CRASHREPORTING
auto helpCrashReportingButton = new QToolButton(this); auto helpCrashReportingButton = new QToolButton(this);
helpCrashReportingButton->setText(Tr::tr("?")); helpCrashReportingButton->setText(Tr::tr("?"));
connect(helpCrashReportingButton, &QAbstractButton::clicked, this, [this] {
showHelpDialog(Tr::tr("Crash Reporting"), CorePlugin::msgCrashpadInformation());
});
#endif #endif
auto resetTerminalButton = new QPushButton(Tr::tr("Reset")); auto resetTerminalButton = new QPushButton(Tr::tr("Reset"));
resetTerminalButton->setToolTip(Tr::tr("Reset to default.", "Terminal")); resetTerminalButton->setToolTip(Tr::tr("Reset to default.", "Terminal"));
@@ -241,13 +246,14 @@ public:
grid.addRow({Tr::tr("Maximum number of entries in \"Recent Files\":"), grid.addRow({Tr::tr("Maximum number of entries in \"Recent Files\":"),
Row{s.maxRecentFiles, st}}); Row{s.maxRecentFiles, st}});
grid.addRow({s.askBeforeExit}); grid.addRow({s.askBeforeExit});
#ifdef ENABLE_CRASHPAD #ifdef ENABLE_CRASHREPORTING
const QString toolTip = Tr::tr("Crash reports are saved in \"%1\".") Row crashDetails;
.arg(appInfo().crashReports.toUserOutput()); #ifdef CRASHREPORTING_USES_CRASHPAD
m_clearCrashReportsButton->setToolTip(toolTip); m_clearCrashReportsButton->setToolTip(s.enableCrashReporting.toolTip());
m_crashReportsSizeText->setToolTip(toolTip); m_crashReportsSizeText->setToolTip(s.enableCrashReporting.toolTip());
Row crashDetails crashDetails.addItems({m_clearCrashReportsButton, m_crashReportsSizeText});
= Row{m_clearCrashReportsButton, m_crashReportsSizeText, helpCrashReportingButton, st}; #endif // CRASHREPORTING_USES_CRASHPAD
crashDetails.addItem(helpCrashReportingButton);
if (qtcEnvironmentVariableIsSet("QTC_SHOW_CRASHBUTTON")) { if (qtcEnvironmentVariableIsSet("QTC_SHOW_CRASHBUTTON")) {
auto crashButton = new QPushButton("CRASH!!!"); auto crashButton = new QPushButton("CRASH!!!");
connect(crashButton, &QPushButton::clicked, [] { connect(crashButton, &QPushButton::clicked, [] {
@@ -257,6 +263,7 @@ public:
}); });
crashDetails.addItem(crashButton); crashDetails.addItem(crashButton);
} }
crashDetails.addItem(st);
grid.addRow({s.enableCrashReporting, crashDetails}); grid.addRow({s.enableCrashReporting, crashDetails});
#endif #endif
@@ -283,20 +290,24 @@ public:
m_externalFileBrowserEdit->setText(UnixUtils::fileBrowser(ICore::settings())); m_externalFileBrowserEdit->setText(UnixUtils::fileBrowser(ICore::settings()));
} }
#ifdef ENABLE_CRASHPAD #if defined(ENABLE_CRASHREPORTING) && defined(CRASHREPORTING_USES_CRASHPAD)
connect(helpCrashReportingButton, &QAbstractButton::clicked, this, [this] { const FilePaths reportsPaths
showHelpDialog(Tr::tr("Crash Reporting"), CorePlugin::msgCrashpadInformation()); = {ICore::crashReportsPath() / "completed",
}); ICore::crashReportsPath() / "reports",
ICore::crashReportsPath() / "attachments",
ICore::crashReportsPath() / "pending",
ICore::crashReportsPath() / "new"};
const FilePath reportsPath = ICore::crashReportsPath() const auto updateClearCrashWidgets = [this, reportsPaths] {
/ QLatin1String(
HostOsInfo::isMacHost() ? "completed" : "reports");
const auto updateClearCrashWidgets = [this, reportsPath] {
qint64 size = 0; qint64 size = 0;
const FilePaths crashFiles = reportsPath.dirEntries(QDir::Files); FilePath::iterateDirectories(
for (const FilePath &file : crashFiles) reportsPaths,
size += file.fileSize(); [&size](const FilePath &item) {
m_clearCrashReportsButton->setEnabled(!crashFiles.isEmpty()); size += item.fileSize();
return IterationPolicy::Continue;
},
FileFilter({}, QDir::Files, QDirIterator::Subdirectories));
m_clearCrashReportsButton->setEnabled(size > 0);
m_crashReportsSizeText->setText(formatSize(size)); m_crashReportsSizeText->setText(formatSize(size));
}; };
updateClearCrashWidgets(); updateClearCrashWidgets();
@@ -304,13 +315,17 @@ public:
m_clearCrashReportsButton, m_clearCrashReportsButton,
&QPushButton::clicked, &QPushButton::clicked,
this, this,
[updateClearCrashWidgets, reportsPath] { [updateClearCrashWidgets, reportsPaths] {
const FilePaths &crashFiles = reportsPath.dirEntries(QDir::Files); FilePath::iterateDirectories(
for (const FilePath &file : crashFiles) reportsPaths,
file.removeFile(); [](const FilePath &item) {
item.removeRecursively();
return IterationPolicy::Continue;
},
FileFilter({}, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot));
updateClearCrashWidgets(); updateClearCrashWidgets();
}); });
#endif #endif // ENABLE_CRASHREPORTING && CRASHREPORTING_USES_CRASHPAD
if (HostOsInfo::isAnyUnixHost()) { if (HostOsInfo::isAnyUnixHost()) {
connect(resetTerminalButton, connect(resetTerminalButton,
@@ -389,7 +404,7 @@ private:
QLineEdit *m_terminalOpenArgs; QLineEdit *m_terminalOpenArgs;
QLineEdit *m_terminalExecuteArgs; QLineEdit *m_terminalExecuteArgs;
Utils::ElidingLabel *m_environmentChangesLabel; Utils::ElidingLabel *m_environmentChangesLabel;
#ifdef ENABLE_CRASHPAD #ifdef CRASHREPORTING_USES_CRASHPAD
QPushButton *m_clearCrashReportsButton; QPushButton *m_clearCrashReportsButton;
QLabel *m_crashReportsSizeText; QLabel *m_crashReportsSizeText;
#endif #endif

View File

@@ -34,7 +34,7 @@ public:
Utils::SelectionAspect reloadSetting{this}; Utils::SelectionAspect reloadSetting{this};
#ifdef ENABLE_CRASHPAD #ifdef ENABLE_CRASHREPORTING
Utils::BoolAspect enableCrashReporting{this}; Utils::BoolAspect enableCrashReporting{this};
#endif #endif