Merge pull request #482 from daleglass-overte/crash-handler-on-server

Crash handler on server
This commit is contained in:
Dale Glass 2023-07-09 21:50:43 +02:00 committed by GitHub
commit c05fde3db4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 776 additions and 178 deletions

View file

@ -23,6 +23,9 @@ link_hifi_libraries(
)
include_hifi_library_headers(procedural)
add_crashpad()
target_breakpad()
if (BUILD_TOOLS)
add_dependencies(${TARGET_NAME} oven)

View file

@ -24,6 +24,8 @@
#include <SharedUtil.h>
#include <ShutdownEventListener.h>
#include <shared/ScriptInitializerMixin.h>
#include <crash-handler/CrashHandler.h>
#include "Assignment.h"
#include "AssignmentClient.h"
@ -106,6 +108,9 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
const QCommandLineOption logOption("logOptions", "Logging options, comma separated: color,nocolor,process_id,thread_id,milliseconds,keep_repeats,journald,nojournald", "options");
parser.addOption(logOption);
const QCommandLineOption forceCrashReportingOption("forceCrashReporting", "Force crash reporting to initialize.");
parser.addOption(forceCrashReportingOption);
if (!parser.parse(QCoreApplication::arguments())) {
std::cout << parser.errorText().toStdString() << std::endl; // Avoid Qt log spam
parser.showHelp();
@ -174,6 +179,7 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
disableDomainPortAutoDiscovery = true;
}
Assignment::Type requestAssignmentType = Assignment::AllTypes;
if (argumentVariantMap.contains(ASSIGNMENT_TYPE_OVERRIDE_OPTION)) {
requestAssignmentType = (Assignment::Type) argumentVariantMap.value(ASSIGNMENT_TYPE_OVERRIDE_OPTION).toInt();
@ -182,6 +188,9 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
requestAssignmentType = (Assignment::Type) parser.value(clientTypeOption).toInt();
}
auto &ch = CrashHandler::getInstance();
ch.setAnnotation("type", QString::number(requestAssignmentType));
QString assignmentPool;
// check for an assignment pool passed on the command line or in the config
if (argumentVariantMap.contains(ASSIGNMENT_POOL_OPTION)) {
@ -247,6 +256,10 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
}
}
if (parser.isSet(forceCrashReportingOption)) {
ch.setEnabled(true);
}
QThread::currentThread()->setObjectName("main thread");
LogHandler::getInstance().moveToThread(thread());

View file

@ -371,6 +371,8 @@ void AssetServer::completeSetup() {
auto& domainHandler = nodeList->getDomainHandler();
const QJsonObject& settingsObject = domainHandler.getSettingsObject();
commonParseSettingsObject(settingsObject);
static const QString ASSET_SERVER_SETTINGS_KEY = "asset_server";
if (!settingsObject.contains(ASSET_SERVER_SETTINGS_KEY)) {

View file

@ -38,6 +38,7 @@
#include "AudioMixerClientData.h"
#include "AvatarAudioStream.h"
#include "InjectedAudioStream.h"
#include "crash-handler/CrashHandler.h"
using namespace std;
@ -49,6 +50,7 @@ static const QString AUDIO_ENV_GROUP_KEY = "audio_env";
static const QString AUDIO_BUFFER_GROUP_KEY = "audio_buffer";
static const QString AUDIO_THREADING_GROUP_KEY = "audio_threading";
int AudioMixer::_numStaticJitterFrames{ DISABLE_STATIC_JITTER_FRAMES };
float AudioMixer::_noiseMutingThreshold{ DEFAULT_NOISE_MUTING_THRESHOLD };
float AudioMixer::_attenuationPerDoublingInDistance{ DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE };
@ -560,6 +562,8 @@ void AudioMixer::clearDomainSettings() {
void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
qCDebug(audio) << "AVX2 Support:" << (cpuSupportsAVX2() ? "enabled" : "disabled");
commonParseSettingsObject(settingsObject);
if (settingsObject.contains(AUDIO_THREADING_GROUP_KEY)) {
QJsonObject audioThreadingGroupObject = settingsObject[AUDIO_THREADING_GROUP_KEY].toObject();
const QString AUTO_THREADS = "auto_threads";

View file

@ -988,6 +988,8 @@ void AvatarMixer::handlePacketVersionMismatch(PacketType type, const SockAddr& s
}
void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
commonParseSettingsObject(domainSettings);
const QString AVATAR_MIXER_SETTINGS_KEY = "avatar_mixer";
QJsonObject avatarMixerGroupObject = domainSettings[AVATAR_MIXER_SETTINGS_KEY].toObject();

View file

@ -272,7 +272,7 @@ int EntityServer::sendSpecialPackets(const SharedNodePointer& node, OctreeQueryN
#ifdef EXTRA_ERASE_DEBUGGING
if (packetsSent > 0) {
qDebug() << "EntityServer::sendSpecialPackets() sent " << packetsSent << "special packets of "
qDebug() << "EntityServer::sendSpecialPackets() sent " << packetsSent << "special packets of "
<< totalBytes << " total bytes to node:" << node->getUUID();
}
#endif
@ -326,14 +326,14 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
} else {
tree->setEntityScriptSourceWhitelist("");
}
auto entityEditFilters = DependencyManager::get<EntityEditFilters>();
QString filterURL;
if (readOptionString("entityEditFilter", settingsSectionObject, filterURL) && !filterURL.isEmpty()) {
// connect the filterAdded signal, and block edits until you hear back
connect(entityEditFilters.data(), &EntityEditFilters::filterAdded, this, &EntityServer::entityFilterAdded);
entityEditFilters->addFilter(EntityItemID(), filterURL);
}
}
@ -367,7 +367,7 @@ void EntityServer::nodeKilled(SharedNodePointer node) {
// FIXME - this stats tracking is somewhat temporary to debug the Whiteboard issues. It's not a bad
// set of stats to have, but we'd probably want a different data structure if we keep it very long.
// Since this version uses a single shared QMap for all senders, there could be some lock contention
// Since this version uses a single shared QMap for all senders, there could be some lock contention
// on this QWriteLocker
void EntityServer::trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& sessionID) {
QWriteLocker locker(&_viewerSendingStatsLock);

View file

@ -13,12 +13,17 @@
#include <SharedUtil.h>
#include "AssignmentClientApp.h"
#include <crash-handler/CrashHandler.h>
int main(int argc, char* argv[]) {
setupHifiApplication(BuildInfo::ASSIGNMENT_CLIENT_NAME);
AssignmentClientApp app(argc, argv);
auto &ch = CrashHandler::getInstance();
ch.startMonitor(&app);
int acReturn = app.exec();
qDebug() << "assignment-client process" << app.applicationPid() << "exiting with status code" << acReturn;

View file

@ -121,6 +121,8 @@ void MessagesMixer::domainSettingsRequestComplete() {
}
void MessagesMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
commonParseSettingsObject(domainSettings);
const QString MESSAGES_MIXER_SETTINGS_KEY = "messages_mixer";
QJsonObject messagesMixerGroupObject = domainSettings[MESSAGES_MIXER_SETTINGS_KEY].toObject();

View file

@ -1023,6 +1023,8 @@ void OctreeServer::readConfiguration() {
const QJsonObject& settingsObject = DependencyManager::get<NodeList>()->getDomainHandler().getSettingsObject();
commonParseSettingsObject(settingsObject);
QString settingsKey = getMyDomainSettingsKey();
QJsonObject settingsSectionObject = settingsObject[settingsKey].toObject();
_settings = settingsSectionObject; // keep this for later

View file

@ -149,11 +149,14 @@ void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointer<Receiv
void EntityScriptServer::handleSettings() {
auto nodeList = DependencyManager::get<NodeList>();
auto& domainHandler = nodeList->getDomainHandler();
const QJsonObject& settingsObject = domainHandler.getSettingsObject();
commonParseSettingsObject(settingsObject);
static const QString ENTITY_SCRIPT_SERVER_SETTINGS_KEY = "entity_script_server";
if (!settingsObject.contains(ENTITY_SCRIPT_SERVER_SETTINGS_KEY)) {
@ -292,7 +295,7 @@ void EntityScriptServer::run() {
entityScriptingInterface->init();
_entityViewer.init();
// setup the JSON filter that asks for entities with a non-default serverScripts property
QJsonObject queryJSONParameters;
queryJSONParameters[EntityJSONQueryProperties::SERVER_SCRIPTS_PROPERTY] = EntityQueryFilterSymbol::NonDefault;
@ -303,7 +306,7 @@ void EntityScriptServer::run() {
queryFlags[EntityJSONQueryProperties::INCLUDE_DESCENDANTS_PROPERTY] = true;
queryJSONParameters[EntityJSONQueryProperties::FLAGS_PROPERTY] = queryFlags;
// setup the JSON parameters so that OctreeQuery does not use a frustum and uses our JSON filter
_entityViewer.getOctreeQuery().setJSONParameters(queryJSONParameters);
@ -380,7 +383,7 @@ void EntityScriptServer::nodeKilled(SharedNodePointer killedNode) {
if (!hasAnotherEntityServer) {
clear();
}
break;
}
case NodeType::Agent: {
@ -584,7 +587,7 @@ void EntityScriptServer::sendStatsPacket() {
}
scriptEngineStats["number_running_scripts"] = numberRunningScripts;
statsObject["script_engine_stats"] = scriptEngineStats;
auto nodeList = DependencyManager::get<NodeList>();
QJsonObject nodesObject;

View file

@ -36,6 +36,9 @@ target_zlib()
target_quazip()
target_openssl()
add_crashpad()
target_breakpad()
# libcrypto uses dlopen in libdl
if (UNIX)
target_link_libraries(${TARGET_NAME} ${CMAKE_DL_LIBS})

View file

@ -73,6 +73,38 @@
}
]
},
{
"label": "Networking / Crash Reporting",
"name": "crash_reporting",
"restart": true,
"assignment-types": [ 0, 1, 3, 4, 5, 6 ],
"settings": [
{
"name": "enable_crash_reporter",
"label": "Enable Crash Reporter",
"help": "Enable the automatic submission of crash reports to Overte e.V. This will help us find bugs and improve the code. Crash data will only be shared with developers trusted by Overte e.V., and will only be used to aid in development.",
"default": false,
"type": "checkbox",
"advanced": true
},
{
"name": "custom_crash_url",
"label": "Custom crash URL",
"help": "If this is set, it overrides the internal crash reporting URL. This can be used for instance to direct crash reports to Sentry.",
"default": "",
"type": "string",
"advanced": true
},
{
"name": "custom_crash_token",
"label": "Custom crash token",
"help": "This is a token that identifies the thing sending a crash report, such as a product name and version number. If not set, the compile time default will be used.",
"default": "",
"type": "string",
"advanced": true
}
]
},
{
"name": "webrtc",
"label": "Networking / WebRTC",

View file

@ -61,6 +61,8 @@
#include <OctreeDataUtils.h>
#include <ThreadHelpers.h>
#include <crash-handler/CrashHandler.h>
using namespace std::chrono;
@ -85,6 +87,8 @@ QUuid DomainServer::_overridingDomainID;
bool DomainServer::_getTempName { false };
QString DomainServer::_userConfigFilename;
int DomainServer::_parentPID { -1 };
bool DomainServer::_forceCrashReporting{false};
/// @brief The Domain server can proxy requests to the Directory Server, this function handles those forwarding requests.
/// @param connection The HTTP connection object.
@ -197,6 +201,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT,
QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this)
{
static const QString CRASH_REPORTER = "crash_reporting.enable_crash_reporter";
if (_parentPID != -1) {
watchParentProcess(_parentPID);
}
@ -237,12 +243,16 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_settingsManager.setupConfigMap(userConfigFilename);
// setup a shutdown event listener to handle SIGTERM or WM_CLOSE for us
#ifdef _WIN32
installNativeEventFilter(&ShutdownEventListener::getInstance());
#else
ShutdownEventListener::getInstance();
#endif
auto &ch = CrashHandler::getInstance();
ch.setEnabled(_settingsManager.valueOrDefaultValueForKeyPath(CRASH_REPORTER).toBool());
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
@ -417,6 +427,8 @@ void DomainServer::parseCommandLine(int argc, char* argv[]) {
const QCommandLineOption logOption("logOptions", "Logging options, comma separated: color,nocolor,process_id,thread_id,milliseconds,keep_repeats,journald,nojournald", "options");
parser.addOption(logOption);
const QCommandLineOption forceCrashReportingOption("forceCrashReporting", "Force crash reporting to initialize.");
parser.addOption(forceCrashReportingOption);
QStringList arguments;
for (int i = 0; i < argc; ++i) {
@ -488,6 +500,10 @@ void DomainServer::parseCommandLine(int argc, char* argv[]) {
qDebug() << "Parent process PID is" << _parentPID;
}
}
if (parser.isSet(forceCrashReportingOption)) {
_forceCrashReporting = true;
}
}
DomainServer::~DomainServer() {

View file

@ -83,6 +83,8 @@ public:
void screensharePresence(QString roomname, QUuid avatarID, int expiration_seconds = 0);
static bool forceCrashReporting() { return _forceCrashReporting; }
public slots:
/// Called by NodeList to inform us a node has been added
void nodeAdded(SharedNodePointer node);
@ -311,6 +313,8 @@ private:
static bool _getTempName;
static QString _userConfigFilename;
static int _parentPID;
static bool _forceCrashReporting;
bool _sendICEServerAddressToMetaverseAPIInProgress { false };
bool _sendICEServerAddressToMetaverseAPIRedo { false };

View file

@ -63,7 +63,9 @@ const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
DomainServerSettingsManager::DomainServerSettingsManager() {
// load the description object from the settings description
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
qDebug() << "Application dir: " << QCoreApplication::applicationDirPath();
QString descriptionFilePath = QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH;
QFile descriptionFile(descriptionFilePath);
descriptionFile.open(QIODevice::ReadOnly);
QJsonParseError parseError;
@ -89,7 +91,7 @@ DomainServerSettingsManager::DomainServerSettingsManager() {
static const QString MISSING_SETTINGS_DESC_MSG =
QString("Did not find settings description in JSON at %1 - Unable to continue. domain-server will quit.\n%2 at %3")
.arg(SETTINGS_DESCRIPTION_RELATIVE_PATH).arg(parseError.errorString()).arg(parseError.offset);
.arg(descriptionFilePath).arg(parseError.errorString()).arg(parseError.offset);
static const int MISSING_SETTINGS_DESC_ERROR_CODE = 6;
QMetaObject::invokeMethod(QCoreApplication::instance(), "queuedQuit", Qt::QueuedConnection,

View file

@ -50,12 +50,42 @@ enum SettingsType {
ContentSettings
};
/**
* @brief Manages the domain-wide settings
*
* The domain server doesn't use the interface's QSettings based system, but this one.
* The domain holds all the settings and distributes it to the connected assignment clients.
*
* Assignment clients request their settings by making a DomainSettingsRequest. The request
* is specific to the client's type.
*
* The response is generated in settingsResponseObjectForType and filtered according to what
* the client is allowed to see.
*
* To add a new setting, add it in resources/describe-settings.json
*
* To make a setting be sent to assignment clients, set the assignment-types value to an array
* of the desired assignment clients. The type is defined numerically.
*
* The canonical list of assignment types is in the Assignment::Type enum.
*
*
*
*/
class DomainServerSettingsManager : public QObject {
Q_OBJECT
public:
DomainServerSettingsManager();
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
/**
* @brief Loads the configuration from the specified file
*
* Performs version upgrades when we're loading an older version of the config.
*
* @param userConfigFilename
*/
void setupConfigMap(const QString& userConfigFilename);
// each of the three methods in this group takes a read lock of _settingsLock
@ -126,7 +156,21 @@ public:
enum DefaultSettingsInclusion { NoDefaultSettings, IncludeDefaultSettings };
enum SettingsBackupFlag { NotForBackup, ForBackup };
/// thread safe method to retrieve a JSON representation of settings
/**
* @brief Generates a JSON representation of settings
*
* This is what answers an assignment client's request for domain settings.
*
* @note thread safe
*
* @param typeValue Type of assignment client
* @param authentication
* @param domainSettingsInclusion
* @param contentSettingsInclusion
* @param defaultSettingsInclusion
* @param settingsBackupFlag
* @return QJsonObject
*/
QJsonObject settingsResponseObjectForType(const QString& typeValue,
SettingsRequestAuthentication authentication = NotAuthenticated,
DomainSettingsInclusion domainSettingsInclusion = IncludeDomainSettings,
@ -211,7 +255,7 @@ private:
// keep track of answers to api queries about which users are in which groups
QHash<QString, QHash<QUuid, QUuid>> _groupMembership; // QHash<user-name, QHash<group-id, rank-id>>
/// guard read/write access from multiple threads to settings
/// guard read/write access from multiple threads to settings
QReadWriteLock _settingsLock { QReadWriteLock::Recursive };
friend class DomainServer;

View file

@ -21,6 +21,8 @@
#include <SharedUtil.h>
#include "DomainServer.h"
#include <crash-handler/CrashHandler.h>
int main(int argc, char* argv[]) {
setupHifiApplication(BuildInfo::DOMAIN_SERVER_NAME);
@ -32,9 +34,21 @@ int main(int argc, char* argv[]) {
int currentExitCode = 0;
// use a do-while to handle domain-server restart
auto &ch = CrashHandler::getInstance();
ch.setPath(argv[0]);
if ( DomainServer::forceCrashReporting() ) {
ch.setEnabled(true);
}
ch.setAnnotation("program", "domain-server");
do {
crash::annotations::setShutdownState(false);
DomainServer domainServer(argc, argv);
ch.startMonitor(&domainServer);
currentExitCode = domainServer.exec();
} while (currentExitCode == DomainServer::EXIT_CODE_REBOOT);

View file

@ -179,7 +179,7 @@
#include "avatar/AvatarPackager.h"
#include "avatar/MyCharacterController.h"
#include "CrashRecoveryHandler.h"
#include "CrashHandler.h"
#include "crash-handler/CrashHandler.h"
#include "DiscoverabilityManager.h"
#include "GLCanvas.h"
#include "InterfaceDynamicFactory.h"
@ -407,8 +407,10 @@ public:
}
void deadlockDetectionCrash() {
setCrashAnnotation("_mod_faulting_tid", std::to_string((uint64_t)_mainThreadID));
setCrashAnnotation("deadlock", "1");
auto &ch = CrashHandler::getInstance();
ch.setAnnotation("_mod_faulting_tid", std::to_string((uint64_t)_mainThreadID));
ch.setAnnotation("deadlock", "1");
uint32_t* crashTrigger = nullptr;
*crashTrigger = 0xDEAD10CC;
}
@ -1054,9 +1056,11 @@ Application::Application(
{
// identify gpu as early as possible to help identify OpenGL initialization errors.
auto gpuIdent = GPUIdent::getInstance();
setCrashAnnotation("sentry[contexts][gpu][name]", gpuIdent->getName().toStdString());
setCrashAnnotation("sentry[contexts][gpu][version]", gpuIdent->getDriver().toStdString());
setCrashAnnotation("gpu_memory", std::to_string(gpuIdent->getMemory()));
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()));
}
// make sure the debug draw singleton is initialized on the main thread.
@ -1166,13 +1170,15 @@ Application::Application(
_logger->setSessionID(accountManager->getSessionID());
#endif
setCrashAnnotation("metaverse_session_id", accountManager->getSessionID().toString().toStdString());
setCrashAnnotation("main_thread_id", std::to_string((size_t)QThread::currentThreadId()));
auto &ch = CrashHandler::getInstance();
ch.setAnnotation("metaverse_session_id", accountManager->getSessionID().toString().toStdString());
ch.setAnnotation("main_thread_id", std::to_string((size_t)QThread::currentThreadId()));
if (steamClient) {
qCDebug(interfaceapp) << "[VERSION] SteamVR buildID:" << steamClient->getSteamVRBuildID();
}
setCrashAnnotation("steam", property(hifi::properties::STEAM).toBool() ? "1" : "0");
ch.setAnnotation("steam", property(hifi::properties::STEAM).toBool() ? "1" : "0");
qCDebug(interfaceapp) << "[VERSION] Build sequence:" << qPrintable(applicationVersion());
qCDebug(interfaceapp) << "[VERSION] MODIFIED_ORGANIZATION:" << BuildInfo::MODIFIED_ORGANIZATION;
@ -1236,7 +1242,8 @@ Application::Application(
connect(&domainHandler, SIGNAL(domainURLChanged(QUrl)), SLOT(domainURLChanged(QUrl)));
connect(&domainHandler, SIGNAL(redirectToErrorDomainURL(QUrl)), SLOT(goToErrorDomainURL(QUrl)));
connect(&domainHandler, &DomainHandler::domainURLChanged, [](QUrl domainURL){
setCrashAnnotation("domain", domainURL.toString().toStdString());
auto &ch = CrashHandler::getInstance();
ch.setAnnotation("domain", domainURL.toString().toStdString());
});
connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain()));
connect(&domainHandler, SIGNAL(connectedToDomain(QUrl)), SLOT(updateWindowTitle()));
@ -1348,8 +1355,9 @@ Application::Application(
setPreferredCursor(Cursor::Manager::getIconName(Cursor::Icon::SYSTEM));
}
setCrashAnnotation("display_plugin", displayPlugin->getName().toStdString());
setCrashAnnotation("hmd", displayPlugin->isHmd() ? "1" : "0");
auto &ch = CrashHandler::getInstance();
ch.setAnnotation("display_plugin", displayPlugin->getName().toStdString());
ch.setAnnotation("hmd", displayPlugin->isHmd() ? "1" : "0");
});
}
connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateSystemTabletMode);
@ -1388,7 +1396,8 @@ Application::Application(
connect(myAvatar.get(), &MyAvatar::skeletonModelURLChanged, [](){
QUrl avatarURL = qApp->getMyAvatar()->getSkeletonModelURL();
setCrashAnnotation("avatar", avatarURL.toString().toStdString());
auto &ch = CrashHandler::getInstance();
ch.setAnnotation("avatar", avatarURL.toString().toStdString());
});
// Inititalize sample before registering
@ -2698,7 +2707,8 @@ void Application::updateHeartbeat() const {
}
void Application::onAboutToQuit() {
setCrashAnnotation("shutdown", "1");
auto &ch = CrashHandler::getInstance();
ch.setAnnotation("shutdown", "1");
// quickly save AvatarEntityData before the EntityTree is dismantled
getMyAvatar()->saveAvatarEntityDataToSettings();
@ -7147,7 +7157,8 @@ void Application::updateWindowTitle() const {
QString metaverseUsername = accountManager->getAccountInfo().getUsername();
QString domainUsername = domainAccountManager->getUsername();
setCrashAnnotation("sentry[user][username]", metaverseUsername.toStdString());
auto &ch = CrashHandler::getInstance();
ch.setAnnotation("sentry[user][username]", metaverseUsername.toStdString());
QString currentPlaceName;
if (isServerlessMode()) {

View file

@ -34,6 +34,7 @@
#include <SettingManager.h>
#include <DependencyManager.h>
#include <UserActivityLogger.h>
#include <crash-handler/CrashHandler.h>
#include <BuildInfo.h>
bool CrashRecoveryHandler::checkForResetSettings(bool wasLikelyCrash, bool suppressPrompt) {
@ -91,7 +92,7 @@ bool CrashRecoveryHandler::suggestCrashReporting() {
QVBoxLayout* layout = new QVBoxLayout;
QString explainText;
auto &ual = UserActivityLogger::getInstance();
auto &ch = CrashHandler::getInstance();
@ -119,7 +120,7 @@ bool CrashRecoveryHandler::suggestCrashReporting() {
break;
}
if (!ual.isCrashMonitorStarted()) {
if (!ch.isStarted()) {
qWarning() << "Crash reporting not working, skipping suggestion to enable it.";
return false;
}
@ -131,8 +132,8 @@ bool CrashRecoveryHandler::suggestCrashReporting() {
QCheckBox* crashReportCheckbox = new QCheckBox("Enable automatic crash reporting");
crashReportCheckbox->setChecked(ual.isCrashReportingEnabled());
crashReportCheckbox->setEnabled(ual.isCrashMonitorStarted());
crashReportCheckbox->setChecked(ch.isEnabled());
crashReportCheckbox->setEnabled(ch.isStarted());
layout->addWidget(explainLabel);
layout->addSpacing(12);
@ -149,7 +150,7 @@ bool CrashRecoveryHandler::suggestCrashReporting() {
crashDialog.exec();
ual.setCrashReportingEnabled(crashReportCheckbox->isChecked());
ch.setEnabled(crashReportCheckbox->isChecked());
return true;
}
@ -157,7 +158,7 @@ bool CrashRecoveryHandler::suggestCrashReporting() {
CrashRecoveryHandler::Action CrashRecoveryHandler::promptUserForAction(bool showCrashMessage) {
QDialog crashDialog;
QLabel* label;
auto &ual = UserActivityLogger::getInstance();
auto &ch = CrashHandler::getInstance();
if (showCrashMessage) {
crashDialog.setWindowTitle("Interface Crashed Last Run");
@ -176,7 +177,7 @@ CrashRecoveryHandler::Action CrashRecoveryHandler::promptUserForAction(bool show
QRadioButton* option3 = new QRadioButton("Continue with my current settings");
QLabel* crashReportLabel = nullptr;
if (ual.isCrashMonitorStarted()) {
if (ch.isStarted()) {
crashReportLabel = new QLabel("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.");
@ -187,8 +188,8 @@ CrashRecoveryHandler::Action CrashRecoveryHandler::promptUserForAction(bool show
QCheckBox* crashReportCheckbox = new QCheckBox("Enable automatic crash reporting");
crashReportCheckbox->setChecked(ual.isCrashReportingEnabled());
crashReportCheckbox->setEnabled(ual.isCrashMonitorStarted());
crashReportCheckbox->setChecked(ch.isEnabled());
crashReportCheckbox->setEnabled(ch.isStarted());
option3->setChecked(true);
layout->addWidget(option1);
@ -218,7 +219,7 @@ CrashRecoveryHandler::Action CrashRecoveryHandler::promptUserForAction(bool show
}
}
ual.setCrashReportingEnabled(crashReportCheckbox->isChecked());
ch.setEnabled(crashReportCheckbox->isChecked());
// Dialog cancelled or "do nothing" option chosen
return CrashRecoveryHandler::DO_NOTHING;

View file

@ -22,8 +22,8 @@
#include <plugins/SteamClientPlugin.h>
#include <UserActivityLogger.h>
#include <UUID.h>
#include <crash-handler/CrashHandler.h>
#include "CrashHandler.h"
#include "Menu.h"
const Discoverability::Mode DEFAULT_DISCOVERABILITY_MODE = Discoverability::Connections;
@ -133,7 +133,8 @@ void DiscoverabilityManager::updateLocation() {
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
steamClient->updateLocation(domainHandler.getHostname(), currentAddress);
}
setCrashAnnotation("address", currentAddress.toString().toStdString());
CrashHandler::getInstance().setAnnotation("address", currentAddress.toString().toStdString());
}
void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply* requestReply) {

View file

@ -57,6 +57,7 @@
#include "LocationBookmarks.h"
#include "DeferredLightingEffect.h"
#include "PickManager.h"
#include "crash-handler/CrashHandler.h"
#include "scripting/SettingsScriptingInterface.h"
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
@ -636,12 +637,13 @@ Menu::Menu() {
true,
&UserActivityLogger::getInstance(),
SLOT(disable(bool)));
addCheckableActionToQMenuAndActionHash(networkMenu,
MenuOption::EnableCrashReporting,
0,
UserActivityLogger::getInstance().isCrashReportingEnabled(),
&UserActivityLogger::getInstance(),
SLOT(setCrashReportingEnabled(bool)));
CrashHandler::getInstance().isEnabled(),
&CrashHandler::getInstance(),
SLOT(setEnabled(bool)));
addActionToQMenuAndActionHash(networkMenu, MenuOption::ShowDSConnectTable, 0,
qApp, SLOT(loadDomainConnectionDialog()));

View file

@ -12,8 +12,8 @@
#include "Application.h"
#include <shared/GlobalAppProperties.h>
#include <shared/QtHelpers.h>
#include <crash-handler/CrashHandler.h>
#include "CrashHandler.h"
RenderEventHandler::RenderEventHandler(CheckCall checkCall, RenderCall renderCall) :
_checkCall(checkCall),
@ -29,7 +29,7 @@ RenderEventHandler::RenderEventHandler(CheckCall checkCall, RenderCall renderCal
void RenderEventHandler::initialize() {
setObjectName("Render");
PROFILE_SET_THREAD_NAME("Render");
setCrashAnnotation("render_thread_id", std::to_string((size_t)QThread::currentThreadId()));
CrashHandler::getInstance().setAnnotation("render_thread_id", std::to_string((size_t)QThread::currentThreadId()));
}
void RenderEventHandler::resumeThread() {

View file

@ -27,7 +27,7 @@
#include "AddressManager.h"
#include "Application.h"
#include "CrashHandler.h"
#include "crash-handler/CrashHandler.h"
#include "InterfaceLogging.h"
#include "UserActivityLogger.h"
#include "MainWindow.h"
@ -37,7 +37,7 @@
#ifdef Q_OS_WIN
#include <Windows.h>
extern "C" {
typedef int(__stdcall * CHECKMINSPECPROC) ();
typedef int(__stdcall* CHECKMINSPECPROC)();
}
#endif
@ -51,7 +51,7 @@ int main(int argc, const char* argv[]) {
// This appears to resolve the issues with corrupted fonts on OSX. No
// idea why.
qputenv("QT_ENABLE_GLYPH_CACHE_WORKAROUND", "true");
// https://i.kym-cdn.com/entries/icons/original/000/008/342/ihave.jpg
// https://i.kym-cdn.com/entries/icons/original/000/008/342/ihave.jpg
QSurfaceFormat::setDefaultFormat(format);
#endif
@ -251,6 +251,7 @@ int main(int argc, const char* argv[]) {
// --ignore-gpu-blacklist
// --suppress-settings-reset
parser.addOption(urlOption);
parser.addOption(protocolVersionOption);
parser.addOption(noUpdaterOption);
@ -296,7 +297,7 @@ int main(int argc, const char* argv[]) {
{
QCoreApplication tempApp(argc, const_cast<char**>(argv));
parser.process(QCoreApplication::arguments()); // Must be run after QCoreApplication is initalised.
parser.process(QCoreApplication::arguments()); // Must be run after QCoreApplication is initalised.
#ifdef Q_OS_OSX
if (QFileInfo::exists(QCoreApplication::applicationDirPath() + "/../../../config.json")) {
@ -310,10 +311,11 @@ int main(int argc, const char* argv[]) {
}
// We want to configure the logging system as early as possible
auto &logHandler = LogHandler::getInstance();
auto& logHandler = LogHandler::getInstance();
if (parser.isSet(logOption)) {
if (!logHandler.parseOptions(parser.value(logOption).toUtf8(), logOption.names().first())) {
QCoreApplication mockApp(argc, const_cast<char**>(argv)); // required for call to showHelp()
QCoreApplication mockApp(argc, const_cast<char**>(argv)); // required for call to showHelp()
parser.showHelp();
Q_UNREACHABLE();
}
@ -325,7 +327,7 @@ int main(int argc, const char* argv[]) {
Q_UNREACHABLE();
}
if (parser.isSet(helpOption)) {
QCoreApplication mockApp(argc, const_cast<char**>(argv)); // required for call to showHelp()
QCoreApplication mockApp(argc, const_cast<char**>(argv)); // required for call to showHelp()
parser.showHelp();
Q_UNREACHABLE();
}
@ -378,7 +380,7 @@ int main(int argc, const char* argv[]) {
// Early check for --traceFile argument
auto tracer = DependencyManager::set<tracing::Tracer>();
const char * traceFile = nullptr;
const char* traceFile = nullptr;
float traceDuration = 0.0f;
if (parser.isSet(traceFileOption)) {
traceFile = parser.value(traceFileOption).toStdString().c_str();
@ -412,6 +414,15 @@ int main(int argc, const char* argv[]) {
// Instance UserActivityLogger now that the settings are loaded
auto& ual = UserActivityLogger::getInstance();
auto& ch = CrashHandler::getInstance();
QObject::connect(&ch, &CrashHandler::enabledChanged, [](bool enabled) {
Settings s;
s.beginGroup("Crash");
s.setValue("ReportingEnabled", 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
@ -423,17 +434,20 @@ int main(int argc, const char* argv[]) {
if (parser.isSet(forceCrashReportingOption)) {
qInfo() << "Crash reporting enabled on the command-line";
ual.setCrashReportingEnabled(true);
ch.setEnabled(true);
}
auto crashHandlerStarted = startCrashHandler(argv[0]);
if (crashHandlerStarted) {
ual.setCrashMonitorStarted(true);
qDebug() << "Crash handler started:" << crashHandlerStarted;
} else {
qWarning() << "Crash handler failed to start";
{
Settings crashSettings;
crashSettings.beginGroup("Crash");
if (crashSettings.value("ReportingEnabled").toBool()) {
ch.setEnabled(true);
}
crashSettings.endGroup();
}
ch.setAnnotation("program", "interface");
const QString& applicationName = getInterfaceSharedMemoryName();
bool instanceMightBeRunning = true;
#ifdef Q_OS_WIN
@ -472,9 +486,9 @@ int main(int argc, const char* argv[]) {
if (socket.waitForConnected(LOCAL_SERVER_TIMEOUT_MS)) {
if (parser.isSet(urlOption)) {
QUrl url = QUrl(parser.value(urlOption));
if (url.isValid() && (url.scheme() == URL_SCHEME_OVERTE || url.scheme() == URL_SCHEME_OVERTEAPP
|| url.scheme() == HIFI_URL_SCHEME_HTTP || url.scheme() == HIFI_URL_SCHEME_HTTPS
|| url.scheme() == HIFI_URL_SCHEME_FILE)) {
if (url.isValid() && (url.scheme() == URL_SCHEME_OVERTE || url.scheme() == URL_SCHEME_OVERTEAPP ||
url.scheme() == HIFI_URL_SCHEME_HTTP || url.scheme() == HIFI_URL_SCHEME_HTTPS ||
url.scheme() == HIFI_URL_SCHEME_FILE)) {
qDebug() << "Writing URL to local socket";
socket.write(url.toString().toUtf8());
if (!socket.waitForBytesWritten(5000)) {
@ -495,7 +509,6 @@ int main(int argc, const char* argv[]) {
#endif
}
// FIXME this method of checking the OpenGL version screws up the `QOpenGLContext::globalShareContext()` value, which in turn
// leads to crashes when creating the real OpenGL instance. Disabling for now until we come up with a better way of checking
// the GL version on the system without resorting to creating a full Qt application
@ -522,7 +535,6 @@ int main(int argc, const char* argv[]) {
}
#endif
// Debug option to demonstrate that the client's local time does not
// need to be in sync with any other network node. This forces clock
// skew for the individual client
@ -590,7 +602,8 @@ int main(int argc, const char* argv[]) {
#if defined(Q_OS_LINUX)
app.setWindowIcon(QIcon(PathUtils::resourcesPath() + "images/brand-logo.svg"));
#endif
startCrashHookMonitor(&app);
ch.startMonitor(&app);
QTimer exitTimer;
if (traceDuration > 0.0f) {
@ -618,7 +631,7 @@ int main(int argc, const char* argv[]) {
#endif
// Setup local server
QLocalServer server { &app };
QLocalServer server{ &app };
// We failed to connect to a local server, so we remove any existing servers.
server.removeServer(applicationName);

View file

@ -4,6 +4,8 @@ link_hifi_libraries(shared platform)
target_openssl()
target_tbb()
add_crashpad()
target_breakpad()
if (WIN32 OR (UNIX AND NOT APPLE))
target_webrtc()

View file

@ -16,11 +16,17 @@
#include <BuildInfo.h>
#include <QtCore/QStandardPaths>
#include <QtCore/QDir>
#include <QJsonObject>
#include "crash-handler/CrashHandler.h"
#include "udt/PacketHeaders.h"
#include "SharedUtil.h"
#include "UUID.h"
static const QString CRASH_REPORTING_GROUP_KEY = "crash_reporting";
Assignment::Type Assignment::typeForNodeType(NodeType_t nodeType) {
switch (nodeType) {
case NodeType::AudioMixer:
@ -51,7 +57,7 @@ Assignment::Assignment() :
_payload(),
_isStatic(false)
{
}
Assignment::Assignment(Assignment::Command command, Assignment::Type type, const QString& pool, Assignment::Location location, QString dataDirectory) :
@ -84,9 +90,9 @@ Assignment::Assignment(ReceivedMessage& message) :
} else if (message.getType() == PacketType::CreateAssignment) {
_command = Assignment::CreateCommand;
}
QDataStream packetStream(message.getMessage());
packetStream >> *this;
}
@ -113,7 +119,7 @@ Assignment& Assignment::operator=(const Assignment& rhsAssignment) {
void Assignment::swap(Assignment& otherAssignment) {
using std::swap;
swap(_uuid, otherAssignment._uuid);
swap(_command, otherAssignment._command);
swap(_type, otherAssignment._type);
@ -148,10 +154,36 @@ const char* Assignment::typeToString(Assignment::Type type) {
}
}
void Assignment::commonParseSettingsObject(const QJsonObject &settingsObject) {
if (settingsObject.contains(CRASH_REPORTING_GROUP_KEY)) {
auto &ch = CrashHandler::getInstance();
QJsonObject crashGroupObject = settingsObject[CRASH_REPORTING_GROUP_KEY].toObject();
const QString CRASH_REPORTING_ENABLED = "enable_crash_reporter";
const QString CRASH_REPORTING_CUSTOM_URL = "custom_crash_url";
const QString CRASH_REPORTING_CUSTOM_TOKEN = "custom_crash_token";
bool enabled = crashGroupObject[CRASH_REPORTING_ENABLED].toBool();
QString url = crashGroupObject[CRASH_REPORTING_CUSTOM_URL].toString();
QString token = crashGroupObject[CRASH_REPORTING_CUSTOM_TOKEN].toString();
ch.setUrl(url);
ch.setToken(token);
ch.setEnabled(enabled);
ch.setAnnotation("program", "assignment-client");
ch.setAnnotation("assignment-client", "audio-mixer");
}
}
QDebug operator<<(QDebug debug, const Assignment &assignment) {
debug.nospace() << "UUID: " << qPrintable(assignment.getUUID().toString()) <<
", Type: " << assignment.getTypeName() << " (" << assignment.getType() << ")";
if (!assignment.getPool().isEmpty()) {
debug << ", Pool: " << assignment.getPool();
}
@ -161,11 +193,11 @@ QDebug operator<<(QDebug debug, const Assignment &assignment) {
QDataStream& operator<<(QDataStream &out, const Assignment& assignment) {
out << (quint8) assignment._type << assignment._uuid << assignment._pool << assignment._payload;
if (assignment._command == Assignment::RequestCommand) {
out << assignment._nodeVersion;
}
return out;
}
@ -173,11 +205,11 @@ QDataStream& operator>>(QDataStream &in, Assignment& assignment) {
quint8 packedType;
in >> packedType >> assignment._uuid >> assignment._pool >> assignment._payload;
assignment._type = (Assignment::Type) packedType;
if (assignment._command == Assignment::RequestCommand) {
in >> assignment._nodeVersion;
}
return in;
}
@ -187,3 +219,4 @@ uint qHash(const Assignment::Type& key, uint seed) {
// strongly typed enum for PacketType
return qHash((uint8_t) key, seed);
}

View file

@ -14,10 +14,12 @@
#include <QtCore/QUuid>
#include "ReceivedMessage.h"
#include "NodeList.h"
const int MAX_PAYLOAD_BYTES = 1024;
const QString emptyPool = QString();
@ -80,12 +82,12 @@ public:
void setPool(const QString& pool) { _pool = pool; };
const QString& getPool() const { return _pool; }
void setIsStatic(bool isStatic) { _isStatic = isStatic; }
bool isStatic() const { return _isStatic; }
const QString& getNodeVersion() const { return _nodeVersion; }
const char* getTypeName() const;
static const char* typeToString(Assignment::Type type);
@ -94,6 +96,16 @@ public:
friend QDataStream& operator>>(QDataStream &in, Assignment& assignment);
protected:
/**
* @brief Parse the part of the settings object common to all assignment clients
*
* Currently this is the crash reporting settings.
*
* @param settingsObject
*/
void commonParseSettingsObject(const QJsonObject &settingsObject);
QUuid _uuid; /// the 16 byte UUID for this assignment
Assignment::Command _command; /// the command for this assignment (Create, Deploy, Request)
Assignment::Type _type; /// the type of the assignment, defines what the assignee will do

View file

@ -82,15 +82,6 @@ void UserActivityLogger::requestError(QNetworkReply* errorReply) {
qCDebug(networking) << errorReply->error() << "-" << errorReply->errorString();
}
void UserActivityLogger::setCrashReportingEnabled(bool enabled) {
bool old = _crashReportingEnabled.get();
_crashReportingEnabled.set(enabled);
if (old != enabled) {
emit crashReportingEnabledChanged();
}
}
void UserActivityLogger::launch(QString applicationVersion, bool previousSessionCrashed, int previousSessionRuntime) {
const QString ACTION_NAME = "launch";
QJsonObject actionDetails;

View file

@ -36,51 +36,6 @@ public slots:
bool isDisabledSettingSet() const { return _disabled.isSet(); }
/**
* @brief Whether the crash monitor has been successfully started
*
* Reasons for it failing to start include:
*
* * Not having a crash reporter for the platform
* * Crash reporter not being configured with reporting URLs (CMAKE_BACKTRACE_TOKEN and CMAKE_BACKTRACE_URL)
* * Crash reporter is present and configured, but failed to initialize for some reason
*
* @return true Crash reporter is present, configured and working.
* @return false Crash reporter has not been started for one of the above reasons.
*/
bool isCrashMonitorStarted() const { return _crashMonitorStarted; }
/**
* @brief Whether the crash monitor will report crashes if they occur
*
* This setting is independent of isCrashMonitorStarted() -- crash reporting may be enabled but fail to work
* due to the crash reporting component being missing or failing to initialize.
*
* @return true Crashes will be reported to CMAKE_BACKTRACE_URL
* @return false Crashes will not be reported
*/
bool isCrashReportingEnabled() { return _crashReportingEnabled.get(); }
/**
* @brief Marks the crash monitor as started
*
* @warning Only to be used as part of the startup process
*
* @param started
*/
void setCrashMonitorStarted(bool started) { _crashMonitorStarted = started; }
/**
* @brief Set whether we want to submit crash reports to the report server
*
* The report server is configured with CMAKE_BACKTRACE_URL.
* Emits crashReportingEnabledChanged signal.
*
* @param enabled Whether it's enabled.
*/
void setCrashReportingEnabled(bool enabled);
void disable(bool disable);
void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters());
@ -111,9 +66,9 @@ private slots:
private:
UserActivityLogger();
Setting::Handle<bool> _disabled { "UserActivityLoggerDisabled", true };
Setting::Handle<bool> _crashReportingEnabled { "CrashReportingEnabled", false };
bool _crashMonitorStarted {false};
QElapsedTimer _timer;
};

View file

@ -0,0 +1,111 @@
//
// CrashHandler.cpp
//
//
// Created by Dale Glass on 25/06/2023.
// Copyright 2023 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
//
#include "CrashHandler.h"
#include "CrashHandlerBackend.h"
#include <QFileInfo>
#include <QCoreApplication>
CrashHandler& CrashHandler::getInstance() {
static CrashHandler sharedInstance;
return sharedInstance;
}
CrashHandler::CrashHandler(QObject *parent) : QObject(parent) {
}
void CrashHandler::setPath(const QString &path) {
QFileInfo fi(path);
if (isStarted()) {
qCWarning(crash_handler) << "Crash handler already started, too late to set the path.";
}
if (fi.isFile()) {
_path = fi.absolutePath();
} else {
_path = path;
}
}
bool CrashHandler::start() {
if (isStarted()) {
//qCWarning(crash_handler) << "Crash handler already started";
return false;
}
auto started = startCrashHandler(_path.toStdString(), _crashUrl.toStdString(), _crashToken.toStdString());
setStarted(started);
if ( started ) {
qCInfo(crash_handler) << "Crash handler started";
} else {
qCWarning(crash_handler) << "Crash handler failed to start";
}
return started;
}
void CrashHandler::startMonitor(QCoreApplication *app) {
startCrashHookMonitor(app);
}
void CrashHandler::setEnabled(bool enabled) {
start();
if (enabled != _crashReportingEnabled) {
_crashReportingEnabled = enabled;
setCrashReportingEnabled(enabled);
emit enabledChanged(enabled);
}
}
void CrashHandler::setUrl(const QString &url) {
// This can be called both from the settings system in an assignment client
// and from the commandline parser. We only emit a warning if the commandline
// argument causes the domain setting to be ignored.
if (isStarted() && url != _crashUrl) {
qCWarning(crash_handler) << "Setting crash reporting URL to " << url << "after the crash handler is already running has no effect";
} else {
_crashUrl = url;
}
}
void CrashHandler::setToken(const QString &token) {
if (isStarted() && token != _crashToken) {
qCWarning(crash_handler) << "Setting crash reporting token to " << token << "after the crash handler is already running has no effect";
} else {
_crashToken = token;
}
}
void CrashHandler::setAnnotation(const std::string &key, const char *value) {
setAnnotation(key, std::string(value));
setCrashAnnotation(key, std::string(value));
}
void CrashHandler::setAnnotation(const std::string &key, const QString &value) {
setAnnotation(key, value.toStdString());
}
void CrashHandler::setAnnotation(const std::string &key, const std::string &value) {
if (!isStarted()) {
qCWarning(crash_handler) << "Can't set annotation" << QString::fromStdString(key) << "to" << QString::fromStdString(value) << "crash handler not yet started";
return;
}
setCrashAnnotation(key, value);
}

View file

@ -0,0 +1,225 @@
//
// CrashHandler.cpp
//
//
// Created by Dale Glass on 25/06/2023.
// Copyright 2023 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
//
#pragma once
#include <QObject>
#include <QCoreApplication>
#include <SettingHandle.h>
/**
* @brief The global object in charge of setting up and controlling crash reporting.
*
* This object initializes and talks to crash reporting backends. For those, see
* CrashHandlerBackend.h and the .cpp files that implement that interface.
*
* The crash URL and token can only be passed to the underlying system on start, so
* things should be set up in such a way that startup is only done after those are set.
*
* start() will be automatically called when setEnabled() is called with true.
* setAnnotation() can only be called after start.
*
*
* To use, follow this general pattern in an application:
*
* @code {.cpp}
* auto &ch = CrashHandler::getInstance();
* ch.setPath(...);
* ch.setUrl("https://server.com/crash-reports");
* ch.setToken("1.2beta");
* ch.setEnabled(true);
* ch.setAnnotation("version", "1.3"); // Needs a started handler to work
* @endcode
*
* For an assignment client, there are two potential ways to start, through the command-line
* and through the settings system. Since the path, URL and token only apply on startup, the
* code must be written such that if command arguments are not given, setEnabled() or start()
* are not called until receiving the settings from the domain.
*
*/
class CrashHandler : public QObject {
Q_OBJECT
public:
static CrashHandler& getInstance();
public slots:
/**
* @brief Set the directory for the crash reports
*
* This sets the path for writing crash reports. This should be done on application startup.
*
* @param path Directory where to store crash reports. It's allowed to set this to argv[0],
* if the path is a filename, then the base directory will be automatically used.
*/
void setPath(const QString &path);
/**
* @brief Start the crash handler
*
* This is called automatically if it wasn't started yet when setEnabled() is called.
*
* @param path Path where to store the crash database
* @return true Started successfully
* @return false Failed to start
*/
bool start();
/**
* @brief Starts the unhandled exception monitor.
*
* On Windows, it's possible for the unhandled exception handler to be reset. This starts a timer
* to periodically set it back.
*
* On non-Windows systems this has no effect.
*
* @param app Main application
*/
void startMonitor(QCoreApplication *app);
/**
* @brief Whether the crash monitor has been successfully started
*
* Reasons for it failing to start include:
*
* * Not having a crash reporter for the platform
* * Crash reporter not being configured with reporting URLs (CMAKE_BACKTRACE_TOKEN and CMAKE_BACKTRACE_URL)
* * Crash reporter is present and configured, but failed to initialize for some reason
*
* @return true Crash reporter is present, configured and working.
* @return false Crash reporter has not been started for one of the above reasons.
*/
bool isStarted() const { return _crashMonitorStarted; }
/**
* @brief Whether the crash monitor will report crashes if they occur
*
* This setting is independent of isCrashMonitorStarted() -- crash reporting may be enabled but fail to work
* due to the crash reporting component being missing or failing to initialize.
*
* @return true Crashes will be reported to CMAKE_BACKTRACE_URL
* @return false Crashes will not be reported
*/
bool isEnabled() const { return _crashReportingEnabled; }
/**
* @brief Set whether we want to submit crash reports to the report server
*
* The report server is configured with CMAKE_BACKTRACE_URL.
* Emits crashReportingEnabledChanged signal.
*
* @note This automatically calls start(), so it should be called after setPath(), setUrl() and setToken()
* @param enabled Whether it's enabled.
*/
void setEnabled(bool enabled);
/**
* @brief Set the URL where to send crash reports to
*
* If not set, a predefined URL specified at compile time via CMAKE_BACKTRACE_URL
* will be used.
*
* @param url URL
*/
void setUrl(const QString &url);
/**
* @brief Set the token for the crash reporter
*
* This is an identifier in the crash collection service, such as Sentry, and may contain
* a branch name or a version number.
*
* If not set, a predefined token specified at compile time via CMAKE_BACKTRACE_TOKEN
* will be used.
*
* @param token Token
*/
void setToken(const QString &token);
/**
* @brief Set an annotation to be added to a crash
*
* Annotations add extra information, such as the application's version number,
* the current user, or any other information of interest.
*
* @param key Key
* @param value Value
*/
void setAnnotation(const std::string &key, const char *value);
/**
* @brief Set an annotation to be added to a crash
*
* Annotations add extra information, such as the application's version number,
* the current user, or any other information of interest.
*
* @param key Key
* @param value Value
*/
void setAnnotation(const std::string &key, const QString &value);
/**
* @brief Set an annotation to be added to a crash
*
* Annotations add extra information, such as the application's version number,
* the current user, or any other information of interest.
*
* @param key Key
* @param value Value
*/
void setAnnotation(const std::string &key, const std::string &value);
signals:
/**
* @brief Emitted when the enabled/disabled state of the crash handler changes
*
* This can be used to store it as a setting.
*
* @param enabled Whether the crash handler is now enabled
*/
void enabledChanged(bool enabled);
private:
CrashHandler(QObject *parent = nullptr);
/**
* @brief Marks the crash monitor as started
*
* @warning Only to be used as part of the startup process
*
* @param started
*/
void setStarted(bool started) { _crashMonitorStarted = started; }
bool _crashMonitorStarted {false};
bool _crashReportingEnabled {false};
QString _path;
QString _crashUrl;
QString _crashToken;
};

View file

@ -9,8 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_CrashHandler_h
#define hifi_CrashHandler_h
#ifndef hifi_CrashHandlerBackend_h
#define hifi_CrashHandlerBackend_h
#include <string>
#include <QCoreApplication>
@ -18,10 +18,10 @@
Q_DECLARE_LOGGING_CATEGORY(crash_handler)
bool startCrashHandler(std::string appPath);
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_CrashHandler_h
#endif // hifi_CrashHandlerBackend_h

View file

@ -57,7 +57,7 @@ void flushAnnotations() {
settings.sync();
}
bool startCrashHandler(std::string appPath) {
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;

View file

@ -41,14 +41,19 @@ Q_LOGGING_CATEGORY(crash_handler, "overte.crash_handler")
#endif
#include <BuildInfo.h>
#include <FingerprintUtils.h>
#include "../FingerprintUtils.h"
#include "../UserActivityLogger.h"
#include <UUID.h>
#include <UserActivityLogger.h>
static const std::string BACKTRACE_URL{ CMAKE_BACKTRACE_URL };
static const std::string BACKTRACE_TOKEN{ CMAKE_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
@ -351,8 +356,16 @@ static QString findBinaryDir() {
return QString();
}
bool startCrashHandler(std::string appPath) {
if (BACKTRACE_URL.empty() || BACKTRACE_TOKEN.empty()) {
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;
}
@ -362,7 +375,7 @@ bool startCrashHandler(std::string appPath) {
std::vector<std::string> arguments;
std::map<std::string, std::string> annotations;
annotations["sentry[release]"] = BACKTRACE_TOKEN;
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();
@ -389,10 +402,10 @@ bool startCrashHandler(std::string appPath) {
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));
// argv[0] gets us the path including the binary file
interfaceDir.cdUp();
}
if (!interfaceDir.exists(CRASHPAD_HANDLER_NAME)) {
@ -421,15 +434,15 @@ bool startCrashHandler(std::string appPath) {
}
// Enable automated uploads.
QObject::connect(&UserActivityLogger::getInstance(), &UserActivityLogger::crashReportingEnabledChanged, []() {
auto &ual = UserActivityLogger::getInstance();
setCrashReportingEnabled(ual.isCrashReportingEnabled());
});
// QObject::connect(&UserActivityLogger::getInstance(), &UserActivityLogger::crashReportingEnabledChanged, []() {
// auto &ual = UserActivityLogger::getInstance();
// setCrashReportingEnabled(ual.isCrashReportingEnabled());
// });
crashpadDatabase->GetSettings()->SetUploadsEnabled(UserActivityLogger::getInstance().isCrashReportingEnabled());
crashpadDatabase->GetSettings()->SetUploadsEnabled(CrashHandler::getInstance().isEnabled());
if (!client->StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true)) {
if (!client->StartHandler(handler, db, db, crashURL, annotations, arguments, true, true)) {
qCCritical(crash_handler) << "Failed to start crashpad handler";
return false;
}

View file

@ -20,7 +20,7 @@
Q_LOGGING_CATEGORY(crash_handler, "overte.crash_handler")
bool startCrashHandler(std::string appPath) {
bool startCrashHandler(std::string appPath, std::string crashURL, std::string crashToken) {
qCWarning(crash_handler) << "No crash handler available.";
return false;
}

View file

@ -11,6 +11,9 @@ include_hifi_library_headers(script-engine)
setup_memory_debugger()
setup_thread_debugger()
add_crashpad()
target_breakpad()
if (WIN32)
package_libraries_for_deployment()

View file

@ -18,7 +18,7 @@
#include <image/TextureProcessing.h>
#include <TextureBaker.h>
#include <crash-handler/CrashHandler.h>
#include "BakerCLI.h"
static const QString CLI_INPUT_PARAMETER = "i";
@ -38,7 +38,7 @@ OvenCLIApplication::OvenCLIApplication(int argc, char* argv[]) :
Q_ARG(QString, _outputUrlParameter.toString()), Q_ARG(QString, _typeParameter));
}
void OvenCLIApplication::parseCommandLine(int argc, char* argv[]) {
OvenCLIApplication::parseResult OvenCLIApplication::parseCommandLine(int argc, char* argv[], bool &enableCrashHandler) {
// parse the command line parameters
QCommandLineParser parser;
@ -50,6 +50,11 @@ void OvenCLIApplication::parseCommandLine(int argc, char* argv[]) {
{ CLI_DISABLE_TEXTURE_COMPRESSION_PARAMETER, "Disable texture compression." }
});
const QCommandLineOption forceCrashReportingOption("forceCrashReporting", "Force crash reporting to initialize.");
parser.addOption(forceCrashReportingOption);
auto versionOption = parser.addVersionOption();
auto helpOption = parser.addHelpOption();
@ -65,6 +70,10 @@ void OvenCLIApplication::parseCommandLine(int argc, char* argv[]) {
Q_UNREACHABLE();
}
if (parser.isSet(forceCrashReportingOption)) {
enableCrashHandler = true;
}
if (parser.isSet(versionOption)) {
parser.showVersion();
Q_UNREACHABLE();
@ -75,20 +84,27 @@ void OvenCLIApplication::parseCommandLine(int argc, char* argv[]) {
Q_UNREACHABLE();
}
if (!parser.isSet(CLI_INPUT_PARAMETER) || !parser.isSet(CLI_OUTPUT_PARAMETER)) {
// If one argument is given, so must be the other
if ((parser.isSet(CLI_INPUT_PARAMETER) != parser.isSet(CLI_OUTPUT_PARAMETER)) || !parser.positionalArguments().empty()) {
std::cout << "Error: Input and Output not set" << std::endl; // Avoid Qt log spam
QCoreApplication mockApp(argc, argv); // required for call to showHelp()
parser.showHelp();
Q_UNREACHABLE();
}
_inputUrlParameter = QDir::fromNativeSeparators(parser.value(CLI_INPUT_PARAMETER));
_outputUrlParameter = QDir::fromNativeSeparators(parser.value(CLI_OUTPUT_PARAMETER));
if (parser.isSet(CLI_INPUT_PARAMETER) && parser.isSet(CLI_OUTPUT_PARAMETER)) {
_inputUrlParameter = QDir::fromNativeSeparators(parser.value(CLI_INPUT_PARAMETER));
_outputUrlParameter = QDir::fromNativeSeparators(parser.value(CLI_OUTPUT_PARAMETER));
_typeParameter = parser.isSet(CLI_TYPE_PARAMETER) ? parser.value(CLI_TYPE_PARAMETER) : QString();
_typeParameter = parser.isSet(CLI_TYPE_PARAMETER) ? parser.value(CLI_TYPE_PARAMETER) : QString();
if (parser.isSet(CLI_DISABLE_TEXTURE_COMPRESSION_PARAMETER)) {
qDebug() << "Disabling texture compression";
TextureBaker::setCompressionEnabled(false);
if (parser.isSet(CLI_DISABLE_TEXTURE_COMPRESSION_PARAMETER)) {
qDebug() << "Disabling texture compression";
TextureBaker::setCompressionEnabled(false);
}
return OvenCLIApplication::CLIMode;
} else {
return OvenCLIApplication::GUIMode;
}
}

View file

@ -21,7 +21,25 @@ class OvenCLIApplication : public QCoreApplication, public Oven {
public:
OvenCLIApplication(int argc, char* argv[]);
static void parseCommandLine(int argc, char* argv[]);
enum parseResult {
GUIMode,
CLIMode
};
/**
* @brief Parses the command line arguments
*
* Oven can operate both in GUI and CLI mode. Depending on which arguments are passed,
* we pick the mode to use, and this function returns it.
*
* Both modes can have the crash handler enabled.
*
* @param argc argc
* @param argv argv
* @param enableCrashHandler Output parameter -- whether the crash handler should be enabled.
* @return parseResult Which application we should run
*/
static parseResult parseCommandLine(int argc, char* argv[], bool &enableCrashHandler);
static OvenCLIApplication* instance() { return dynamic_cast<OvenCLIApplication*>(QCoreApplication::instance()); }

View file

@ -14,24 +14,64 @@
#include <BuildInfo.h>
#include <SettingInterface.h>
#include <SharedUtil.h>
#include <SettingManager.h>
#include <DependencyManager.h>
#include <crash-handler/CrashHandler.h>
#include <iostream>
// This needs to be run after a QApplication has been created
void postAppInit(QCoreApplication *app, bool enableCrashHandler) {
Setting::init();
auto &ch = CrashHandler::getInstance();
QObject::connect(&ch, &CrashHandler::enabledChanged, [](bool enabled) {
Settings s;
s.beginGroup("Crash");
s.setValue("ReportingEnabled", enabled);
s.endGroup();
});
Settings crashSettings;
crashSettings.beginGroup("Crash");
ch.setEnabled(crashSettings.value("ReportingEnabled").toBool() || enableCrashHandler);
ch.startMonitor(app);
}
int main (int argc, char** argv) {
setupHifiApplication("Oven");
DependencyManager::set<Setting::Manager>();
auto &ch = CrashHandler::getInstance();
ch.setPath(argv[0]);
// figure out if we're launching our GUI application or just the simple command line interface
if (argc > 1) {
OvenCLIApplication::parseCommandLine(argc, argv);
bool enableCrashHandler = false;
OvenCLIApplication::parseResult res = OvenCLIApplication::parseCommandLine(argc, argv, enableCrashHandler);
// init the settings interface so we can save and load settings
Setting::init();
OvenCLIApplication app { argc, argv };
return app.exec();
} else {
// init the settings interface so we can save and load settings
Setting::init();
OvenGUIApplication app { argc, argv };
return app.exec();
switch(res) {
case OvenCLIApplication::CLIMode:
{
OvenCLIApplication app { argc, argv };
postAppInit(&app, enableCrashHandler);
return app.exec();
break;
}
case OvenCLIApplication::GUIMode:
{
OvenGUIApplication app { argc, argv };
postAppInit(&app, enableCrashHandler);
return app.exec();
break;
}
}
}