diff --git a/src/blackcore/application.cpp b/src/blackcore/application.cpp index 95f125236..7a28fb99d 100644 --- a/src/blackcore/application.cpp +++ b/src/blackcore/application.cpp @@ -21,6 +21,7 @@ #include "blackcore/inputmanager.h" #include "blackmisc/atomicfile.h" #include "blackmisc/applicationinfo.h" +#include "blackmisc/crashhandler.h" #include "blackmisc/datacache.h" #include "blackmisc/dbusserver.h" #include "blackmisc/directoryutils.h" @@ -68,16 +69,6 @@ #include #include -#ifdef BLACK_USE_CRASHPAD -#if defined(Q_OS_WIN) && !defined(NOMINMAX) -#define NOMINMAX -#endif -#include "crashpad/client/crashpad_client.h" -#include "crashpad/client/crash_report_database.h" -#include "crashpad/client/settings.h" -#include "crashpad/client/simulate_crash.h" -#endif - using namespace BlackConfig; using namespace BlackMisc; using namespace BlackMisc::Db; @@ -90,7 +81,6 @@ using namespace BlackCore::Context; using namespace BlackCore::Vatsim; using namespace BlackCore::Data; using namespace BlackCore::Db; -using namespace crashpad; BlackCore::CApplication *sApp = nullptr; // set by constructor @@ -382,9 +372,6 @@ namespace BlackCore // Settings if not already initialized msgs.push_back(this->initLocalSettings()); if (msgs.isFailure()) { break; } - - // we have settings, so enable crash handler. - msgs.push_back(this->initCrashHandler()); } while (false); @@ -959,7 +946,7 @@ namespace BlackCore CLogHandler::instance()->install(); // make sure we have a log handler! // File logger - m_fileLogger.reset(new CFileLogger(executable(), CDirectoryUtils::logDirectory())); + m_fileLogger.reset(new CFileLogger(this)); m_fileLogger->changeLogPattern(CLogPattern().withSeverityAtOrAbove(CStatusMessage::SeverityDebug)); } @@ -1636,164 +1623,20 @@ namespace BlackCore return CUrlList(); } -#ifdef BLACK_USE_CRASHPAD - //! Convert to file path - base::FilePath qstringToFilePath(const QString &str) - { -# ifdef Q_OS_WIN - return base::FilePath(str.toStdWString()); -# else - return base::FilePath(str.toStdString()); -# endif - } -#endif - - BlackMisc::CStatusMessageList CApplication::initCrashHandler() - { -#ifdef BLACK_USE_CRASHPAD - // No crash handling for unit tests - if (this->getApplicationInfo().isUnitTest()) { return CStatusMessage(this).info(u"No crash handler for unit tests"); } - - static const QString crashpadHandler(CBuildConfig::isRunningOnWindowsNtPlatform() ? "swift_crashpad_handler.exe" : "swift_crashpad_handler"); - static const QString handler = CFileUtils::appendFilePaths(CDirectoryUtils::binDirectory(), crashpadHandler); - // const QString crashpadPath = CDirectoryUtils::crashpadDirectory(); - const QString database = CDirectoryUtils::crashpadDatabaseDirectory(); - const QString metrics = CDirectoryUtils::crashpadMetricsDirectory(); - - if (!QFileInfo::exists(handler)) - { - return CStatusMessage(this).warning(u"Crashpad handler '%1' not found. Cannot init handler!") << handler; - } - - const CUrl serverUrl = this->getGlobalSetup().getCrashReportServerUrl(); - std::map annotations; - - // Backtrace annotations - annotations["token"] = "b15efd93e290be3cf5d39750cadc092b651327ff0c027b80abd75e0ee50df1da"; - annotations["format"] = "minidump"; - annotations["version"] = CBuildConfig::getVersionString().toStdString(); - annotations["platform"] = CBuildConfig::getPlatformString().toStdString(); - annotations["qtversion"] = QT_VERSION_STR; - - // add our logfile - const QString logFilePath = m_fileLogger->getLogFilePath(); // file and path - const QString logFileName = m_fileLogger->getLogFileName(); - const QString logAttachment = QStringLiteral("--attachment=attachment_%1=%2").arg(logFileName, logFilePath); - - std::vector arguments; - arguments.push_back(logAttachment.toStdString()); - - // and the simplified crash info if any - const QString crashInfoFileName("swiftcrashinfo.txt"); - const QString crashInfoFilePath(CFileUtils::appendFilePaths(CFileUtils::stripFileFromPath(logFilePath), crashInfoFileName)); - m_crashAndLogInfo.setLogPathAndFileName(crashInfoFilePath); - const QString crashAttachment = QStringLiteral("--attachment=attachment_%1=%2").arg(crashInfoFileName, crashInfoFilePath); - arguments.push_back(crashAttachment.toStdString()); - - // for testing purposes - if (CBuildConfig::isLocalDeveloperDebugBuild()) - { - arguments.push_back("--no-rate-limit"); - } - - QDir().mkpath(database); - m_crashReportDatabase = CrashReportDatabase::Initialize(qstringToFilePath(database)); - this->onCrashDumpUploadEnabledChanged(); // settings for crashpad uploads - m_crashpadClient = std::make_unique(); - m_crashpadClient->StartHandler(qstringToFilePath(handler), - qstringToFilePath(database), - qstringToFilePath(metrics), - serverUrl.getFullUrl().toStdString(), - annotations, - arguments, - false, true); - - this->crashAndLogAppendInfo(u"Init crash info at " % QDateTime::currentDateTimeUtc().toString()); - return CStatusMessage(this).info(u"Using crash handler"); -#else - return CStatusMessage(this).info(u"Not using crash handler"); -#endif - } - void CApplication::onCrashDumpUploadEnabledChanged() { - #ifdef BLACK_USE_CRASHPAD const bool enabled = CBuildConfig::isReleaseBuild() && m_crashDumpSettings.getThreadLocal().isEnabled(); this->enableCrashDumpUpload(enabled); - #endif - } - - void CApplication::triggerCrashInfoWrite() - { - m_crashAndLogInfo.triggerWritingFile(); - } - - void CApplication::setCrashInfo(const CCrashInfo &info) - { - m_crashAndLogInfo = info; - m_dsCrashAndLogInfo.inputSignal(); - } - - void CApplication::crashAndLogInfoUserName(const QString &name) - { - m_crashAndLogInfo.setUserName(name); - m_dsCrashAndLogInfo.inputSignal(); - } - - void CApplication::crashAndLogInfoSimulator(const QString &simulator) - { - m_crashAndLogInfo.setSimulatorString(simulator); - m_dsCrashAndLogInfo.inputSignal(); - } - - void CApplication::crashAndLogInfoFlightNetwork(const QString &flightNetwork) - { - m_crashAndLogInfo.setFlightNetworkString(flightNetwork); - m_dsCrashAndLogInfo.inputSignal(); - } - - void CApplication::crashAndLogAppendInfo(const QString &info) - { - m_crashAndLogInfo.appendInfo(info); - m_dsCrashAndLogInfo.inputSignal(); } void CApplication::simulateCrash() { -#ifdef BLACK_USE_CRASHPAD - CLogMessage(this).info(u"Simulated crash dump!"); - m_crashAndLogInfo.appendInfo("Simulated crash dump!"); - m_crashAndLogInfo.writeToFile(); - CRASHPAD_SIMULATE_CRASH(); - // real crash - // raise(SIGSEGV); #include -#else - CLogMessage(this).warning(u"This compiler or platform does not support crashpad. Cannot simulate crash dump!"); -#endif + CCrashHandler::instance()->simulateCrash(); } void CApplication::enableCrashDumpUpload(bool enable) { -#ifdef BLACK_USE_CRASHPAD - if (!m_crashReportDatabase) { return; } - crashpad::Settings *settings = m_crashReportDatabase->GetSettings(); - settings->SetUploadsEnabled(enable); -#else - Q_UNUSED(enable); -#endif - } - - bool CApplication::isCrashDumpUploadEnabled() const - { -#ifdef BLACK_USE_CRASHPAD - if (!m_crashReportDatabase) { return false; } - crashpad::Settings *settings = m_crashReportDatabase->GetSettings(); - bool enabled = false; - bool ok = settings->GetUploadsEnabled(&enabled); - return ok && enabled; -#else - return false; -#endif + CCrashHandler::instance()->setUploadsEnabled(enable); } bool CApplication::isSupportingCrashpad() const diff --git a/src/blackcore/application.h b/src/blackcore/application.h index 328e342fd..50a7da618 100644 --- a/src/blackcore/application.h +++ b/src/blackcore/application.h @@ -43,10 +43,6 @@ #include #include -#if !defined(Q_CC_MINGW) -#define BLACK_USE_CRASHPAD -#endif - class QHttpMultiPart; class QNetworkReply; class QNetworkRequest; @@ -57,12 +53,6 @@ namespace BlackMisc class CLogCategoryList; } -namespace crashpad -{ - class CrashpadClient; - class CrashReportDatabase; -} - namespace BlackCore { class CCoreFacade; @@ -303,26 +293,6 @@ namespace BlackCore virtual QString cmdLineArgumentsAsString(bool withExecutable = true); //! @} - // ----------------------- Crash info --------------------------------- - - //! Extra annotation for crash to easier identify annotation - void setCrashInfo(const BlackMisc::CCrashInfo &info); - - //! User name for crash info - void crashAndLogInfoUserName(const QString &name); - - //! Simulator string - void crashAndLogInfoSimulator(const QString &simulator); - - //! Flight network - void crashAndLogInfoFlightNetwork(const QString &flightNetwork); - - //! Append crash info - void crashAndLogAppendInfo(const QString &info); - - //! Get the crash info - const BlackMisc::CCrashInfo &getCrashInfo() const { return m_crashAndLogInfo; } - //! Simulate a crash //! \private only for testing purposes void simulateCrash(); @@ -331,9 +301,6 @@ namespace BlackCore //! \remark only change for testing void enableCrashDumpUpload(bool enable); - //! Is crash dump upload enabled - bool isCrashDumpUploadEnabled() const; - //! Has crashpad support? bool isSupportingCrashpad() const; @@ -727,22 +694,10 @@ namespace BlackCore bool m_localSettingsLoaded = false; //!< local settings loaded? // -------------- crashpad ----------------- - //! Init the crash handler - BlackMisc::CStatusMessageList initCrashHandler(); + BlackMisc::CSettingReadOnly m_crashDumpSettings { this, &CApplication::onCrashDumpUploadEnabledChanged }; //! Upload settings changed void onCrashDumpUploadEnabledChanged(); - -#ifdef BLACK_USE_CRASHPAD - std::unique_ptr m_crashpadClient; - std::unique_ptr m_crashReportDatabase; - BlackMisc::CSettingReadOnly m_crashDumpSettings { this, &CApplication::onCrashDumpUploadEnabledChanged }; -#endif - // crash info - void triggerCrashInfoWrite(); - - BlackMisc::CCrashInfo m_crashAndLogInfo; //!< info representing details - BlackMisc::CDigestSignal m_dsCrashAndLogInfo { this, &CApplication::triggerCrashInfoWrite, 10000, 5 }; }; } // namespace diff --git a/src/blackcore/blackcore.pro b/src/blackcore/blackcore.pro index 3811b6416..6adcdb7a3 100644 --- a/src/blackcore/blackcore.pro +++ b/src/blackcore/blackcore.pro @@ -20,9 +20,6 @@ INCLUDEPATH += pch DEFINES += LOG_IN_FILE BUILD_BLACKCORE_LIB -INCLUDEPATH *= $$EXTERNALSROOT/common/include/crashpad -INCLUDEPATH *= $$EXTERNALSROOT/common/include/crashpad/mini_chromium - HEADERS += *.h HEADERS += $$PWD/application/*.h HEADERS += $$PWD/audio/*.h @@ -41,13 +38,6 @@ LIBS *= -lvatlib DESTDIR = $$DestRoot/lib DLLDESTDIR = $$DestRoot/bin -msvc { - CONFIG(debug, debug|release): LIBS *= -lclientd -lutild -lbased -lRpcrt4 -lAdvapi32 - CONFIG(release, debug|release): LIBS *= -lclient -lutil -lbase -lRpcrt4 -lAdvapi32 -} -macx: LIBS *= -lclient -lutil -lbase -lbsm -framework Security -unix:!macx: LIBS *= -lclient -lutil -lbase - OTHER_FILES += readme.txt *.xml win32 { diff --git a/src/blackcore/data/globalsetup.cpp b/src/blackcore/data/globalsetup.cpp index afaed7dc9..27b593760 100644 --- a/src/blackcore/data/globalsetup.cpp +++ b/src/blackcore/data/globalsetup.cpp @@ -14,6 +14,7 @@ #include "blackmisc/network/server.h" #include "blackmisc/network/user.h" #include "blackmisc/stringutils.h" +#include "blackmisc/crashhandler.h" #include #include @@ -165,7 +166,7 @@ namespace BlackCore if (CBuildConfig::isLocalDeveloperDebugBuild()) { pingUrl.appendQuery("dev", "true"); } if (sApp) { - const CCrashInfo ci = sApp->getCrashInfo(); + const CCrashInfo ci = CCrashHandler::instance()->getCrashInfo(); pingUrl.appendQuery("application", sApp->getApplicationNameAndVersion()); if (!ci.getSimulatorString().isEmpty()) { pingUrl.appendQuery("fs", ci.getSimulatorString()); } if (!ci.getFlightNetworkString().isEmpty()) { pingUrl.appendQuery("network", ci.getFlightNetworkString()); } diff --git a/src/blackcore/simulator.cpp b/src/blackcore/simulator.cpp index dd90f4557..18f511ced 100644 --- a/src/blackcore/simulator.cpp +++ b/src/blackcore/simulator.cpp @@ -12,6 +12,7 @@ #include "blackcore/application.h" #include "blackmisc/simulation/data/modelcaches.h" #include "blackmisc/math/mathutils.h" +#include "blackmisc/crashhandler.h" #include "blackmisc/directoryutils.h" #include "blackmisc/threadutils.h" #include "blackmisc/logmessage.h" @@ -153,7 +154,7 @@ namespace BlackCore void ISimulator::reloadWeatherSettings() { // log crash info about weather - if (sApp && !sApp->isShuttingDown()) { sApp->crashAndLogAppendInfo(u"Simulator weather: " % boolToYesNo(m_isWeatherActivated)); } + if (sApp && !sApp->isShuttingDown()) { CCrashHandler::instance()->crashAndLogAppendInfo(u"Simulator weather: " % boolToYesNo(m_isWeatherActivated)); } if (!m_isWeatherActivated) { return; } const CWeatherScenario selectedWeatherScenario = m_weatherScenarioSettings.get(); if (!CWeatherScenario::isRealWeatherScenario(selectedWeatherScenario)) @@ -163,7 +164,7 @@ namespace BlackCore } // log crash info about weather - if (sApp && !sApp->isShuttingDown()) { sApp->crashAndLogAppendInfo(selectedWeatherScenario.toQString(true)); } + if (sApp && !sApp->isShuttingDown()) { CCrashHandler::instance()->crashAndLogAppendInfo(selectedWeatherScenario.toQString(true)); } } void ISimulator::clearAllRemoteAircraftData() @@ -872,7 +873,7 @@ namespace BlackCore const bool r = setup.isRenderingRestricted(); const bool e = setup.isRenderingEnabled(); - if (sApp && !sApp->isShuttingDown()) { sApp->crashAndLogAppendInfo(u"Rendering setup: " % setup.toQString(true)); } + if (sApp && !sApp->isShuttingDown()) { CCrashHandler::instance()->crashAndLogAppendInfo(u"Rendering setup: " % setup.toQString(true)); } emit this->renderRestrictionsChanged(r, e, setup.getMaxRenderedAircraft(), setup.getMaxRenderedDistance()); return true; } diff --git a/src/blackgui/components/dblogincomponent.cpp b/src/blackgui/components/dblogincomponent.cpp index a54f16917..621ecd976 100644 --- a/src/blackgui/components/dblogincomponent.cpp +++ b/src/blackgui/components/dblogincomponent.cpp @@ -18,6 +18,7 @@ #include "blackmisc/logmessage.h" #include "blackmisc/statusmessage.h" #include "blackmisc/verify.h" +#include "blackmisc/crashhandler.h" #include "blackconfig/buildconfig.h" #include @@ -164,8 +165,8 @@ namespace BlackGui } // crashpad info - sGui->crashAndLogInfoUserName(user.getRealNameAndId()); - sGui->crashAndLogAppendInfo(QStringLiteral("Login as user %1 %2").arg(user.getRealNameAndId(), user.getRolesAsString())); + CCrashHandler::instance()->crashAndLogInfoUserName(user.getRealNameAndId()); + CCrashHandler::instance()->crashAndLogAppendInfo(QStringLiteral("Login as user %1 %2").arg(user.getRealNameAndId(), user.getRolesAsString())); } else { diff --git a/src/blackgui/components/internalscomponent.cpp b/src/blackgui/components/internalscomponent.cpp index d0f5e5640..2c827977d 100644 --- a/src/blackgui/components/internalscomponent.cpp +++ b/src/blackgui/components/internalscomponent.cpp @@ -25,6 +25,7 @@ #include "blackmisc/math/mathutils.h" #include "blackmisc/logmessage.h" #include "blackmisc/statusmessage.h" +#include "blackmisc/crashhandler.h" #include "blackconfig/buildconfig.h" #include "ui_internalscomponent.h" @@ -93,7 +94,7 @@ namespace BlackGui if (sGui && sGui->isSupportingCrashpad()) { - ui->cb_CrashDumpUpload->setChecked(sGui->isCrashDumpUploadEnabled()); + ui->cb_CrashDumpUpload->setChecked(CCrashHandler::instance()->isCrashDumpUploadEnabled()); connect(ui->pb_SimulateCrash, &QPushButton::released, this, &CInternalsComponent::simulateCrash); connect(ui->cb_CrashDumpUpload, &QCheckBox::toggled, this, &CInternalsComponent::onCrashDumpUploadToggled); } @@ -348,7 +349,7 @@ namespace BlackGui { if (sGui && sGui->isSupportingCrashpad()) { - const bool current = sGui->isCrashDumpUploadEnabled(); + const bool current = CCrashHandler::instance()->isCrashDumpUploadEnabled(); if (current == checked) { return; } sGui->enableCrashDumpUpload(checked); } diff --git a/src/blackgui/components/logincomponent.cpp b/src/blackgui/components/logincomponent.cpp index 8878257b2..78c421e25 100644 --- a/src/blackgui/components/logincomponent.cpp +++ b/src/blackgui/components/logincomponent.cpp @@ -35,6 +35,7 @@ #include "blackmisc/simulation/aircraftmodel.h" #include "blackmisc/simulation/simulatedaircraft.h" #include "blackmisc/statusmessage.h" +#include "blackmisc/crashhandler.h" #include "blackconfig/buildconfig.h" #include @@ -324,9 +325,9 @@ namespace BlackGui { Q_ASSERT_X(currentServer.isValidForLogin(), Q_FUNC_INFO, "invalid server"); sGui->setExtraWindowTitle(QStringLiteral("[%1]").arg(ownAircraft.getCallsignAsString())); - sGui->crashAndLogInfoUserName(currentServer.getUser().getRealNameAndId()); - sGui->crashAndLogInfoFlightNetwork(currentServer.getEcosystem().toQString(true)); - sGui->crashAndLogAppendInfo(currentServer.getServerSessionId(false)); + CCrashHandler::instance()->crashAndLogInfoUserName(currentServer.getUser().getRealNameAndId()); + CCrashHandler::instance()->crashAndLogInfoFlightNetwork(currentServer.getEcosystem().toQString(true)); + CCrashHandler::instance()->crashAndLogAppendInfo(currentServer.getServerSessionId(false)); m_networkSetup.setLastServer(currentServer); m_lastAircraftModel.set(ownAircraft.getModel()); ui->le_LoginCallsign->setText(ownAircraft.getCallsignAsString()); @@ -550,7 +551,7 @@ namespace BlackGui const CSimulatorInfo sim = sGui->getIContextSimulator()->getSimulatorPluginInfo().getSimulator(); const CSimulatorInternals simulatorInternals = sGui->getIContextSimulator()->getSimulatorInternals(); const QString simStr = sim.toQString() + QStringLiteral(" ") + simulatorInternals.getSimulatorVersion(); - sGui->crashAndLogInfoSimulator(simStr); + CCrashHandler::instance()->crashAndLogInfoSimulator(simStr); } else { diff --git a/src/blackmisc/appstarttime.cpp b/src/blackmisc/appstarttime.cpp new file mode 100644 index 000000000..71eb9c361 --- /dev/null +++ b/src/blackmisc/appstarttime.cpp @@ -0,0 +1,18 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "appstarttime.h" + +namespace BlackMisc +{ + const QDateTime &getApplicationStartTimeUtc() + { + static const QDateTime gApplicationStartTimeUtc = QDateTime::currentDateTimeUtc(); + return gApplicationStartTimeUtc; + } +} diff --git a/src/blackmisc/appstarttime.h b/src/blackmisc/appstarttime.h new file mode 100644 index 000000000..acbb421f3 --- /dev/null +++ b/src/blackmisc/appstarttime.h @@ -0,0 +1,24 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKMISC_APPSTARTTIME_H +#define BLACKMISC_APPSTARTTIME_H + +#include "blackmisc/blackmiscexport.h" + +#include + +namespace BlackMisc +{ + //! Get the application start time in UTC. + BLACKMISC_EXPORT const QDateTime &getApplicationStartTimeUtc(); +} + +#endif diff --git a/src/blackmisc/blackmisc.pro b/src/blackmisc/blackmisc.pro index 5eafb883b..b2267df81 100644 --- a/src/blackmisc/blackmisc.pro +++ b/src/blackmisc/blackmisc.pro @@ -70,6 +70,10 @@ SOURCES += *.cpp \ $$PWD/test/*.cpp \ $$PWD/weather/*.cpp + +INCLUDEPATH *= $$EXTERNALSROOT/common/include/crashpad +INCLUDEPATH *= $$EXTERNALSROOT/common/include/crashpad/mini_chromium + win32 { LIBS *= -lShell32 -lDbghelp -lversion # Remove the one below once the Reg functions are removed again from CIdentifier @@ -79,6 +83,13 @@ win32-g++ { LIBS *= -lpsapi } +msvc { + CONFIG(debug, debug|release): LIBS *= -lclientd -lutild -lbased -lRpcrt4 -lAdvapi32 + CONFIG(release, debug|release): LIBS *= -lclient -lutil -lbase -lRpcrt4 -lAdvapi32 +} +macx: LIBS += -lclient -lutil -lbase -lbsm -framework Security -framework CoreFoundation -framework ApplicationServices -framework Foundation +unix:!macx: LIBS *= -lclient -lutil -lbase + DESTDIR = $$DestRoot/lib DLLDESTDIR = $$DestRoot/bin diff --git a/src/blackmisc/crashhandler.cpp b/src/blackmisc/crashhandler.cpp new file mode 100644 index 000000000..270c5ae94 --- /dev/null +++ b/src/blackmisc/crashhandler.cpp @@ -0,0 +1,189 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "crashhandler.h" + +#include "blackconfig/buildconfig.h" +#include "blackmisc/appstarttime.h" +#include "blackmisc/directoryutils.h" +#include "blackmisc/logmessage.h" +#include "blackmisc/filelogger.h" + +#include +#include +#include + +#ifdef BLACK_USE_CRASHPAD +#if defined(Q_OS_WIN) && !defined(NOMINMAX) +#define NOMINMAX +#endif +#include "crashpad/client/crashpad_client.h" +#include "crashpad/client/crash_report_database.h" +#include "crashpad/client/settings.h" +#include "crashpad/client/simulate_crash.h" +#endif + +using namespace BlackConfig; +using namespace BlackMisc; +using namespace crashpad; + +namespace BlackMisc +{ + CCrashHandler *CCrashHandler::instance() + { + static CCrashHandler crashHandler; + return &crashHandler; + } + + CCrashHandler::~CCrashHandler() + { } + +#ifdef BLACK_USE_CRASHPAD + //! Convert to file path + base::FilePath qstringToFilePath(const QString &str) + { +# ifdef Q_OS_WIN + return base::FilePath(str.toStdWString()); +# else + return base::FilePath(str.toStdString()); +# endif + } +#endif + + void CCrashHandler::init() + { +#ifdef BLACK_USE_CRASHPAD + static const QString crashpadHandler(CBuildConfig::isRunningOnWindowsNtPlatform() ? "swift_crashpad_handler.exe" : "swift_crashpad_handler"); + static const QString handler = CFileUtils::appendFilePaths(CDirectoryUtils::binDirectory(), crashpadHandler); + const QString database = CDirectoryUtils::crashpadDatabaseDirectory(); + const QString metrics = CDirectoryUtils::crashpadMetricsDirectory(); + + if (!QFileInfo::exists(handler)) { return; } + + const std::string serverUrl("http://swift-project.sp.backtrace.io:6097/"); + std::map annotations; + + // Backtrace annotations + annotations["token"] = "b15efd93e290be3cf5d39750cadc092b651327ff0c027b80abd75e0ee50df1da"; + annotations["format"] = "minidump"; + annotations["version"] = CBuildConfig::getVersionString().toStdString(); + annotations["platform"] = CBuildConfig::getPlatformString().toStdString(); + annotations["qtversion"] = QT_VERSION_STR; + + // add our logfile + const QString logAttachment = QStringLiteral("--attachment=attachment_%1=%2").arg(CFileLogger::getLogFileName(), CFileLogger::getLogFilePath()); + + std::vector arguments; + arguments.push_back(logAttachment.toStdString()); + + // and the simplified crash info if any + const QString crashInfoFileName("swiftcrashinfo.txt"); + const QString crashInfoFilePath(CFileUtils::appendFilePaths(CFileUtils::stripFileFromPath(CFileLogger::getLogFilePath()), crashInfoFileName)); + m_crashAndLogInfo.setLogPathAndFileName(crashInfoFilePath); + const QString crashAttachment = QStringLiteral("--attachment=attachment_%1=%2").arg(crashInfoFileName, crashInfoFilePath); + arguments.push_back(crashAttachment.toStdString()); + + // for testing purposes + if (CBuildConfig::isLocalDeveloperDebugBuild()) + { + arguments.push_back("--no-rate-limit"); + } + + QDir().mkpath(database); + + m_crashReportDatabase = CrashReportDatabase::Initialize(qstringToFilePath(database)); + m_crashpadClient = std::make_unique(); + m_crashpadClient->StartHandler(qstringToFilePath(handler), + qstringToFilePath(database), + qstringToFilePath(metrics), + serverUrl, + annotations, + arguments, + false, true); + + this->crashAndLogAppendInfo(u"Init crash info at " % QDateTime::currentDateTimeUtc().toString()); + #endif + } + + void CCrashHandler::setUploadsEnabled(bool enable) + { + #ifdef BLACK_USE_CRASHPAD + if (!m_crashReportDatabase) { return; } + crashpad::Settings *settings = m_crashReportDatabase->GetSettings(); + settings->SetUploadsEnabled(enable); + #else + Q_UNUSED(enable); + #endif + } + + bool CCrashHandler::isCrashDumpUploadEnabled() const + { + #ifdef BLACK_USE_CRASHPAD + if (!m_crashReportDatabase) { return false; } + crashpad::Settings *settings = m_crashReportDatabase->GetSettings(); + bool enabled = false; + bool ok = settings->GetUploadsEnabled(&enabled); + return ok && enabled; + #else + return false; +#endif + } + + void CCrashHandler::triggerCrashInfoWrite() + { + m_crashAndLogInfo.triggerWritingFile(); + } + + void CCrashHandler::setCrashInfo(const CCrashInfo &info) + { + m_crashAndLogInfo = info; + m_dsCrashAndLogInfo.inputSignal(); + } + + void CCrashHandler::crashAndLogInfoUserName(const QString &name) + { + m_crashAndLogInfo.setUserName(name); + m_dsCrashAndLogInfo.inputSignal(); + } + + void CCrashHandler::crashAndLogInfoSimulator(const QString &simulator) + { + m_crashAndLogInfo.setSimulatorString(simulator); + m_dsCrashAndLogInfo.inputSignal(); + } + + void CCrashHandler::crashAndLogInfoFlightNetwork(const QString &flightNetwork) + { + m_crashAndLogInfo.setFlightNetworkString(flightNetwork); + m_dsCrashAndLogInfo.inputSignal(); + } + + void CCrashHandler::crashAndLogAppendInfo(const QString &info) + { + m_crashAndLogInfo.appendInfo(info); + m_dsCrashAndLogInfo.inputSignal(); + } + + void CCrashHandler::simulateCrash() + { + #ifdef BLACK_USE_CRASHPAD + CLogMessage(this).info(u"Simulated crash dump!"); + m_crashAndLogInfo.appendInfo("Simulated crash dump!"); + m_crashAndLogInfo.writeToFile(); + CRASHPAD_SIMULATE_CRASH(); + // real crash + // raise(SIGSEGV); #include + #else + CLogMessage(this).warning(u"This compiler or platform does not support crashpad. Cannot simulate crash dump!"); +#endif + } + + CCrashHandler::CCrashHandler(QObject *parent) : + QObject(parent) + { } +} diff --git a/src/blackmisc/crashhandler.h b/src/blackmisc/crashhandler.h new file mode 100644 index 000000000..b65f97cc4 --- /dev/null +++ b/src/blackmisc/crashhandler.h @@ -0,0 +1,92 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKMISC_CRASHHANDLER_H +#define BLACKMISC_CRASHHANDLER_H + +#include "blackmisc/blackmiscexport.h" +#include "blackmisc/crashinfo.h" +#include "blackmisc/digestsignal.h" + +#include + +#if !defined(Q_CC_MINGW) +#define BLACK_USE_CRASHPAD +#endif + +namespace crashpad +{ + class CrashpadClient; + class CrashReportDatabase; +} + +namespace BlackMisc +{ + //! Crash handler + class BLACKMISC_EXPORT CCrashHandler : public QObject + { + Q_OBJECT + + public: + //! Get singleton instance + static CCrashHandler *instance(); + + //! Destructor + virtual ~CCrashHandler(); + + //! Initialize and start crashpad handler process + void init(); + + //! Enable/disable automatic uploading + void setUploadsEnabled(bool enable); + + //! Is automatic dump uploading enabled? + bool isCrashDumpUploadEnabled() const; + + // ----------------------- Crash info --------------------------------- + + //! Extra annotation for crash to easier identify annotation + void setCrashInfo(const BlackMisc::CCrashInfo &info); + + //! Set user name for crash info + void crashAndLogInfoUserName(const QString &name); + + //! Set simulator string in crash info + void crashAndLogInfoSimulator(const QString &simulator); + + //! Set flight network in crash info + void crashAndLogInfoFlightNetwork(const QString &flightNetwork); + + //! Append crash info + void crashAndLogAppendInfo(const QString &info); + + //! Get crash info + const BlackMisc::CCrashInfo &getCrashInfo() const { return m_crashAndLogInfo; } + + //! Simulate a crash + void simulateCrash(); + + private: + CCrashHandler(QObject *parent = nullptr); + + // crash info + void triggerCrashInfoWrite(); + + BlackMisc::CCrashInfo m_crashAndLogInfo; //!< info representing details + BlackMisc::CDigestSignal m_dsCrashAndLogInfo { this, &CCrashHandler::triggerCrashInfoWrite, 10000, 5 }; + +#ifdef BLACK_USE_CRASHPAD + std::unique_ptr m_crashpadClient; + std::unique_ptr m_crashReportDatabase; +#endif + }; +} + +#endif diff --git a/src/blackmisc/filelogger.cpp b/src/blackmisc/filelogger.cpp index 5f0ca9770..e6f88d87d 100644 --- a/src/blackmisc/filelogger.cpp +++ b/src/blackmisc/filelogger.cpp @@ -7,8 +7,10 @@ */ #include "blackconfig/buildconfig.h" +#include "blackmisc/appstarttime.h" #include "blackmisc/filelogger.h" #include "blackmisc/loghandler.h" +#include "blackmisc/directoryutils.h" #include #include @@ -26,22 +28,33 @@ using namespace BlackConfig; namespace BlackMisc { - CFileLogger::CFileLogger(QObject *parent) : - CFileLogger(QCoreApplication::applicationName(), QString(), parent) + //! Get application name + QString applicationName() { - // void + static const QString applicationName = QFileInfo(QCoreApplication::applicationFilePath()).completeBaseName(); + return applicationName; } - CFileLogger::CFileLogger(const QString &applicationName, const QString &logPath, QObject *parent) : - QObject(parent), - m_logFile(this), - m_applicationName(applicationName), - m_logPath(logPath) + //! Get log file name + QString logFileName() { - if (!m_logPath.isEmpty()) { QDir::root().mkpath(m_logPath); } + static const QString fileName = applicationName() % + QLatin1String("_") % + getApplicationStartTimeUtc().toString(QStringLiteral("yyMMddhhmmss")) % + QLatin1String("_") % + QString::number(QCoreApplication::applicationPid()) % + QLatin1String(".log"); + return fileName; + } + + CFileLogger::CFileLogger(QObject *parent) : + QObject(parent), + m_logFile(this) + { + Q_ASSERT(! applicationName().isEmpty()); + QDir::root().mkpath(CDirectoryUtils::logDirectory()); removeOldLogFiles(); - if (!m_logPath.isEmpty() && !m_logPath.endsWith('/')) { m_logPath += '/'; } - m_logFile.setFileName(getFullFileName()); + m_logFile.setFileName(getLogFilePath()); m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text); m_stream.setDevice(&m_logFile); m_stream.setCodec("UTF-8"); @@ -66,6 +79,11 @@ namespace BlackMisc } } + QString CFileLogger::getLogFileName() + { + return logFileName(); + } + void CFileLogger::ps_writeStatusMessageToFile(const BlackMisc::CStatusMessage &statusMessage) { if (statusMessage.isEmpty()) { return; } @@ -85,26 +103,17 @@ namespace BlackMisc writeContentToFile(finalContent); } - QString CFileLogger::getFullFileName() + QString CFileLogger::getLogFilePath() { - QString filePath; - Q_ASSERT(!m_applicationName.isEmpty()); - if (!m_logPath.isEmpty()) filePath += m_logPath; - - m_fileName += m_applicationName; - m_fileName += QLatin1String("_"); - m_fileName += QDateTime::currentDateTime().toString(QStringLiteral("yyMMddhhmmss")); - m_fileName += QLatin1String("_"); - m_fileName += QString::number(QCoreApplication::applicationPid()); - m_fileName += QLatin1String(".log"); - return filePath + m_fileName; + QString filePath = CDirectoryUtils::logDirectory() % '/' % logFileName(); + return filePath; } void CFileLogger::removeOldLogFiles() { - QString nameFilter(m_applicationName); + QString nameFilter(applicationName()); nameFilter += QLatin1String("*.log"); - QDir dir(m_logPath, nameFilter, QDir::Name, QDir::Files); + QDir dir(CDirectoryUtils::logDirectory(), nameFilter, QDir::Name, QDir::Files); QDateTime now = QDateTime::currentDateTime(); for (const auto &logFileInfo : dir.entryInfoList()) @@ -118,7 +127,7 @@ namespace BlackMisc void CFileLogger::writeHeaderToFile() { - m_stream << "This is " << m_applicationName; + m_stream << "This is " << applicationName(); m_stream << " version " << CBuildConfig::getVersionString(); m_stream << " running on " << QSysInfo::prettyProductName(); m_stream << " " << QSysInfo::currentCpuArchitecture() << endl; diff --git a/src/blackmisc/filelogger.h b/src/blackmisc/filelogger.h index 2ecfec289..ff295cecd 100644 --- a/src/blackmisc/filelogger.h +++ b/src/blackmisc/filelogger.h @@ -32,16 +32,6 @@ namespace BlackMisc //! Filename defaults to QCoreApplication::applicationName() and path to "." CFileLogger(QObject *parent = nullptr); - /*! - * Constructor - * \param applicationName Use the applications name without any extension. - * A timestamp and extension will be added automatically. - * \param logPath Path the log files is written to. If you leave this empty, the - * file will be written in the working directory of the binary. - * \param parent QObject parent - */ - CFileLogger(const QString &applicationName, const QString &logPath, QObject *parent = nullptr); - //! Destructor. virtual ~CFileLogger(); @@ -51,20 +41,18 @@ namespace BlackMisc //! Close file void close(); - //! Get the current log file path - QString getLogFilePath() const { return m_logFile.fileName(); } + //! Get the log file name + static QString getLogFileName(); - //! Get the current log file name - QString getLogFileName() const { return m_fileName; } + //! Get the log file path (including its name) + static QString getLogFilePath(); private slots: //! Write single status message to file void ps_writeStatusMessageToFile(const BlackMisc::CStatusMessage &statusMessage); private: - QString getFullFileName(); void removeOldLogFiles(); - void writeHeaderToFile(); void writeContentToFile(const QString &content); @@ -72,8 +60,6 @@ namespace BlackMisc QFile m_logFile; QString m_fileName; QTextStream m_stream; - QString m_applicationName; - QString m_logPath; //!< Empty by default. Hence the working directory "." is used QString m_previousCategories; }; } diff --git a/src/swiftcore/main.cpp b/src/swiftcore/main.cpp index 3ed085993..4486f8adf 100644 --- a/src/swiftcore/main.cpp +++ b/src/swiftcore/main.cpp @@ -10,6 +10,8 @@ #include "blackgui/guiapplication.h" #include "blackmisc/icons.h" #include "blackmisc/directoryutils.h" +#include "blackmisc/crashhandler.h" +#include "blackmisc/appstarttime.h" #include "swiftcore.h" #include @@ -28,6 +30,8 @@ int main(int argc, char *argv[]) CGuiApplication::highDpiScreenSupport(CGuiApplication::parseScaleFactor(argc, argv)); QApplication qa(argc, argv); Q_UNUSED(qa); // init of qa is required, but qa not used + + CCrashHandler::instance()->init(); CGuiApplication a(CApplicationInfo::swiftCore(), CApplicationInfo::PilotClientCore, CIcons::swiftCore24()); a.addWindowStateOption(); a.addDBusAddressOption(); diff --git a/src/swiftdata/main.cpp b/src/swiftdata/main.cpp index 31831b043..9174d80eb 100644 --- a/src/swiftdata/main.cpp +++ b/src/swiftdata/main.cpp @@ -10,6 +10,8 @@ #include "blackgui/guiapplication.h" #include "blackmisc/directoryutils.h" #include "blackmisc/icons.h" +#include "blackmisc/crashhandler.h" +#include "blackmisc/appstarttime.h" #include "swiftdata.h" #include @@ -26,6 +28,8 @@ int main(int argc, char *argv[]) CGuiApplication::highDpiScreenSupport(CGuiApplication::parseScaleFactor(argc, argv)); QApplication qa(argc, argv); Q_UNUSED(qa); + + CCrashHandler::instance()->init(); CGuiApplication a(CApplicationInfo::swiftMappingTool(), CApplicationInfo::MappingTool, CIcons::swiftDatabase48()); a.setSignalStartupAutomatically(false); // application will signal startup on its own a.splashScreen(CIcons::swiftDatabase256()); diff --git a/src/swiftguistandard/main.cpp b/src/swiftguistandard/main.cpp index 8d9b98ff7..fdf47f452 100644 --- a/src/swiftguistandard/main.cpp +++ b/src/swiftguistandard/main.cpp @@ -9,6 +9,8 @@ #include "blackgui/enableforframelesswindow.h" #include "blackgui/guiapplication.h" #include "blackmisc/directoryutils.h" +#include "blackmisc/crashhandler.h" +#include "blackmisc/appstarttime.h" #include "swiftguistd.h" #include "swiftguistdapplication.h" @@ -25,6 +27,8 @@ int main(int argc, char *argv[]) CGuiApplication::highDpiScreenSupport(CGuiApplication::parseScaleFactor(argc, argv)); QApplication qa(argc, argv); Q_UNUSED(qa); // application init needed + + CCrashHandler::instance()->init(); CSwiftGuiStdApplication a; // application with contexts a.setSignalStartupAutomatically(false); // application will signal startup on its own a.splashScreen(CIcons::swift256());