Skip to content

Commit 8fe543c

Browse files
committed
frontend: Update crash handling and log upload functionality
Updates include: * Use of CrashHandler to provide automatic uploads of the most recent crash log if an unclean shutdown was detected and it has not been uploaded yet. * Detection and handling of unclean shutdowns is delegated entirely to the CrashHandler class * Use of OBSLogReply has been replaced with the LogUploadDialog, which asks for confirmation before new uploads of log files (confirmation is skipped for files with available upload URLs already - only available for crash logs with this change) Architectural changes: * OBSApp is the layer responsible for application launch and shutdown states, as well as crash logs and application logs * The actual handling is delegated to purpose-made classes which OBSApp owns instances of * OBSBasic in turn refers to OBSApp for all this functionality, and can subscribe/connect to appropriate events exposed by OBSApp to this purpose * Implementation details (like the existence of the CrashHandler class) are not exposed to OBSBasic or the LogUploadDialog The amount of changes for normal log file upload have been purposefully limited. A proper refactoring of the application log file handling will move this code out of OBSBasic as well.
1 parent e84a608 commit 8fe543c

File tree

12 files changed

+235
-360
lines changed

12 files changed

+235
-360
lines changed

frontend/OBSApp.cpp

Lines changed: 160 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include "OBSApp.hpp"
1919

2020
#include <components/Multiview.hpp>
21+
#include <dialogs/LogUploadDialog.hpp>
22+
#include <utility/CrashHandler.hpp>
2123
#include <utility/OBSEventFilter.hpp>
2224
#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER)
2325
#include <utility/models/branches.hpp>
@@ -29,6 +31,8 @@
2931
#endif
3032
#include <qt-wrappers.hpp>
3133

34+
#include <QCheckBox>
35+
#include <QDesktopServices>
3236
#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER)
3337
#include <QFile>
3438
#endif
@@ -42,6 +46,8 @@
4246
#include <qpa/qplatformnativeinterface.h>
4347
#endif
4448

49+
#include <chrono>
50+
4551
#ifdef _WIN32
4652
#include <sstream>
4753
#define WIN32_LEAN_AND_MEAN
@@ -61,6 +67,7 @@ string lastCrashLogFile;
6167

6268
extern bool portable_mode;
6369
extern bool safe_mode;
70+
extern bool multi;
6471
extern bool disable_3p_plugins;
6572
extern bool opt_disable_updater;
6673
extern bool opt_disable_missing_files_check;
@@ -79,6 +86,67 @@ extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 1;
7986
extern "C" __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
8087
#endif
8188

89+
namespace {
90+
91+
typedef struct UncleanLaunchAction {
92+
bool useSafeMode = false;
93+
bool sendCrashReport = false;
94+
95+
} UncleanLaunchAction;
96+
97+
UncleanLaunchAction handleUncleanShutdown(bool enableCrashUpload)
98+
{
99+
UncleanLaunchAction launchAction;
100+
101+
blog(LOG_WARNING, "Crash or unclean shutdown detected");
102+
103+
QMessageBox crashWarning;
104+
105+
crashWarning.setIcon(QMessageBox::Warning);
106+
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
107+
crashWarning.setOption(QMessageBox::Option::DontUseNativeDialog);
108+
#endif
109+
crashWarning.setWindowTitle(QTStr("CrashHandling.Dialog.Title"));
110+
crashWarning.setText(QTStr("CrashHandling.Dialog.Text"));
111+
112+
if (enableCrashUpload) {
113+
crashWarning.setInformativeText(QTStr("CrashHandling.Dialog.PrivacyNotice"));
114+
115+
QCheckBox *sendCrashReportCheckbox = new QCheckBox(QTStr("CrashHandling.Dialog.SendReport"));
116+
crashWarning.setCheckBox(sendCrashReportCheckbox);
117+
}
118+
119+
QPushButton *launchSafeButton =
120+
crashWarning.addButton(QTStr("CrashHandling.Dialog.LaunchSafe"), QMessageBox::AcceptRole);
121+
QPushButton *launchNormalButton =
122+
crashWarning.addButton(QTStr("CrashHandling.Dialog.LaunchNormal"), QMessageBox::RejectRole);
123+
124+
crashWarning.setDefaultButton(launchNormalButton);
125+
126+
crashWarning.exec();
127+
128+
bool useSafeMode = crashWarning.clickedButton() == launchSafeButton;
129+
130+
if (useSafeMode) {
131+
launchAction.useSafeMode = true;
132+
133+
blog(LOG_INFO, "[Safe Mode] Safe mode launch selected, loading 3rd party plugins is disabled");
134+
} else {
135+
blog(LOG_WARNING, "[Safe Mode] Normal launch selected, loading 3rd party plugins is enabled");
136+
}
137+
138+
bool sendCrashReport = (enableCrashUpload) ? crashWarning.checkBox()->isChecked() : false;
139+
140+
if (sendCrashReport) {
141+
launchAction.sendCrashReport = true;
142+
143+
blog(LOG_INFO, "User selected to send crash report");
144+
}
145+
146+
return launchAction;
147+
}
148+
} // namespace
149+
82150
QObject *CreateShortcutFilter()
83151
{
84152
return new OBSEventFilter([](QObject *obj, QEvent *event) {
@@ -751,8 +819,11 @@ std::vector<UpdateBranch> OBSApp::GetBranches()
751819

752820
OBSApp::OBSApp(int &argc, char **argv, profiler_name_store_t *store)
753821
: QApplication(argc, argv),
754-
profilerNameStore(store)
822+
profilerNameStore(store),
823+
appLaunchUUID_(QUuid::createUuid())
755824
{
825+
connect(this, &QCoreApplication::aboutToQuit, this, &OBSApp::applicationShutdown);
826+
756827
/* fix float handling */
757828
#if defined(Q_OS_UNIX)
758829
if (!setlocale(LC_NUMERIC, "C"))
@@ -767,6 +838,11 @@ OBSApp::OBSApp(int &argc, char **argv, profiler_name_store_t *store)
767838
#else
768839
connect(qApp, &QGuiApplication::commitDataRequest, this, &OBSApp::commitData);
769840
#endif
841+
if (multi) {
842+
crashHandler_ = std::make_unique<OBS::CrashHandler>();
843+
} else {
844+
crashHandler_ = std::make_unique<OBS::CrashHandler>(appLaunchUUID_);
845+
}
770846

771847
sleepInhibitor = os_inhibit_sleep_create("OBS Video/audio");
772848

@@ -777,31 +853,7 @@ OBSApp::OBSApp(int &argc, char **argv, profiler_name_store_t *store)
777853
setDesktopFileName("com.obsproject.Studio");
778854
}
779855

780-
OBSApp::~OBSApp()
781-
{
782-
#ifdef _WIN32
783-
bool disableAudioDucking = config_get_bool(appConfig, "Audio", "DisableAudioDucking");
784-
if (disableAudioDucking)
785-
DisableAudioDucking(false);
786-
#else
787-
delete snInt;
788-
close(sigintFd[0]);
789-
close(sigintFd[1]);
790-
#endif
791-
792-
#ifdef __APPLE__
793-
bool vsyncDisabled = config_get_bool(appConfig, "Video", "DisableOSXVSync");
794-
bool resetVSync = config_get_bool(appConfig, "Video", "ResetOSXVSyncOnExit");
795-
if (vsyncDisabled && resetVSync)
796-
EnableOSXVSync(true);
797-
#endif
798-
799-
os_inhibit_sleep_set_active(sleepInhibitor, false);
800-
os_inhibit_sleep_destroy(sleepInhibitor);
801-
802-
if (libobs_initialized)
803-
obs_shutdown();
804-
}
856+
OBSApp::~OBSApp(){};
805857

806858
static void move_basic_to_profiles(void)
807859
{
@@ -958,6 +1010,22 @@ void OBSApp::AppInit()
9581010
throw "Failed to create profile directories";
9591011
}
9601012

1013+
void OBSApp::checkForUncleanShutdown()
1014+
{
1015+
bool hasUncleanShutdown = crashHandler_->hasUncleanShutdown();
1016+
bool hasNewCrashLog = crashHandler_->hasNewCrashLog();
1017+
1018+
if (hasUncleanShutdown) {
1019+
UncleanLaunchAction launchAction = handleUncleanShutdown(hasNewCrashLog);
1020+
1021+
safe_mode = launchAction.useSafeMode;
1022+
1023+
if (launchAction.sendCrashReport) {
1024+
crashHandler_->uploadLastCrashLog();
1025+
}
1026+
}
1027+
}
1028+
9611029
const char *OBSApp::GetRenderModule() const
9621030
{
9631031
const char *renderer = config_get_string(appConfig, "Video", "Renderer");
@@ -1090,6 +1158,15 @@ bool OBSApp::OBSInit()
10901158
connect(this, &QGuiApplication::applicationStateChanged,
10911159
[this](Qt::ApplicationState state) { ResetHotkeyState(state == Qt::ApplicationActive); });
10921160
ResetHotkeyState(applicationState() == Qt::ApplicationActive);
1161+
1162+
connect(crashHandler_.get(), &OBS::CrashHandler::crashLogUploadFailed, this,
1163+
[this](const QString &errorMessage) {
1164+
emit this->logUploadFailed(OBS::LogFileType::CrashLog, errorMessage);
1165+
});
1166+
1167+
connect(crashHandler_.get(), &OBS::CrashHandler::crashLogUploadFinished, this,
1168+
[this](const QString &fileUrl) { emit this->logUploadFinished(OBS::LogFileType::CrashLog, fileUrl); });
1169+
10931170
return true;
10941171
}
10951172

@@ -1173,9 +1250,37 @@ const char *OBSApp::GetCurrentLog() const
11731250
return currentLogFile.c_str();
11741251
}
11751252

1176-
const char *OBSApp::GetLastCrashLog() const
1253+
void OBSApp::openCrashLogDirectory() const
1254+
{
1255+
std::filesystem::path crashLogDirectory = crashHandler_->getCrashLogDirectory();
1256+
1257+
if (crashLogDirectory.empty()) {
1258+
return;
1259+
}
1260+
1261+
QString crashLogDirectoryString = QString::fromStdString(crashLogDirectory.u8string());
1262+
1263+
QUrl crashLogDirectoryURL = QUrl::fromLocalFile(crashLogDirectoryString);
1264+
QDesktopServices::openUrl(crashLogDirectoryURL);
1265+
}
1266+
1267+
void OBSApp::uploadLastAppLog() const
1268+
{
1269+
OBSBasic *basicWindow = static_cast<OBSBasic *>(GetMainWindow());
1270+
1271+
basicWindow->UploadLog("obs-studio/logs", GetLastLog(), OBS::LogFileType::LastAppLog);
1272+
}
1273+
1274+
void OBSApp::uploadCurrentAppLog() const
1275+
{
1276+
OBSBasic *basicWindow = static_cast<OBSBasic *>(GetMainWindow());
1277+
1278+
basicWindow->UploadLog("obs-studio/logs", GetCurrentLog(), OBS::LogFileType::CurrentAppLog);
1279+
}
1280+
1281+
void OBSApp::uploadLastCrashLog()
11771282
{
1178-
return lastCrashLogFile.c_str();
1283+
crashHandler_->uploadLastCrashLog();
11791284
}
11801285

11811286
OBS::LogFileState OBSApp::getLogFileState(OBS::LogFileType type) const
@@ -1589,3 +1694,30 @@ void OBSApp::commitData(QSessionManager &manager)
15891694
}
15901695
}
15911696
#endif
1697+
1698+
void OBSApp::applicationShutdown() noexcept
1699+
{
1700+
#ifdef _WIN32
1701+
bool disableAudioDucking = config_get_bool(appConfig, "Audio", "DisableAudioDucking");
1702+
if (disableAudioDucking)
1703+
DisableAudioDucking(false);
1704+
#else
1705+
delete snInt;
1706+
close(sigintFd[0]);
1707+
close(sigintFd[1]);
1708+
#endif
1709+
1710+
#ifdef __APPLE__
1711+
bool vsyncDisabled = config_get_bool(appConfig, "Video", "DisableOSXVSync");
1712+
bool resetVSync = config_get_bool(appConfig, "Video", "ResetOSXVSyncOnExit");
1713+
if (vsyncDisabled && resetVSync)
1714+
EnableOSXVSync(true);
1715+
#endif
1716+
1717+
os_inhibit_sleep_set_active(sleepInhibitor, false);
1718+
os_inhibit_sleep_destroy(sleepInhibitor);
1719+
1720+
if (libobs_initialized) {
1721+
obs_shutdown();
1722+
}
1723+
}

frontend/OBSApp.hpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <QApplication>
2929
#include <QPalette>
3030
#include <QPointer>
31+
#include <QUuid>
3132

3233
#include <deque>
3334
#include <functional>
@@ -42,11 +43,13 @@ class QFileSystemWatcher;
4243
class QSocketNotifier;
4344

4445
namespace OBS {
46+
class CrashHandler;
4547

4648
enum class LogFileType { NoType, CurrentAppLog, LastAppLog, CrashLog };
4749

4850
enum class LogFileState { NoState, New, Uploaded };
4951
} // namespace OBS
52+
5053
struct UpdateBranch {
5154
QString name;
5255
QString display_name;
@@ -59,6 +62,9 @@ class OBSApp : public QApplication {
5962
Q_OBJECT
6063

6164
private:
65+
QUuid appLaunchUUID_;
66+
std::unique_ptr<OBS::CrashHandler> crashHandler_;
67+
6268
std::string locale;
6369

6470
ConfigFile appConfig;
@@ -115,12 +121,14 @@ private slots:
115121

116122
private slots:
117123
void themeFileChanged(const QString &);
124+
void applicationShutdown() noexcept;
118125

119126
public:
120127
OBSApp(int &argc, char **argv, profiler_name_store_t *store);
121128
~OBSApp();
122129

123130
void AppInit();
131+
void checkForUncleanShutdown();
124132
bool OBSInit();
125133

126134
void UpdateHotkeyFocusSetting(bool reset = true);
@@ -158,7 +166,10 @@ private slots:
158166
const char *GetLastLog() const;
159167
const char *GetCurrentLog() const;
160168

161-
const char *GetLastCrashLog() const;
169+
void openCrashLogDirectory() const;
170+
void uploadLastAppLog() const;
171+
void uploadCurrentAppLog() const;
172+
void uploadLastCrashLog();
162173

163174
OBS::LogFileState getLogFileState(OBS::LogFileType type) const;
164175

frontend/cmake/ui-dialogs.cmake

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ target_sources(
2929
dialogs/OBSBasicTransform.hpp
3030
dialogs/OBSBasicVCamConfig.cpp
3131
dialogs/OBSBasicVCamConfig.hpp
32-
dialogs/OBSLogReply.cpp
33-
dialogs/OBSLogReply.hpp
3432
dialogs/OBSLogViewer.cpp
3533
dialogs/OBSLogViewer.hpp
3634
dialogs/OBSMissingFiles.cpp

frontend/cmake/ui-qt.cmake

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ target_sources(
4444
forms/OBSBasicVCamConfig.ui
4545
forms/OBSExtraBrowsers.ui
4646
forms/OBSImporter.ui
47-
forms/OBSLogReply.ui
48-
forms/OBSLogReply.ui
4947
forms/OBSMissingFiles.ui
5048
forms/OBSRemux.ui
5149
forms/source-toolbar/browser-source-toolbar.ui

frontend/data/locale/en-US.ini

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -453,12 +453,6 @@ Output.BroadcastStartFailed="Failed to start broadcast"
453453
Output.BroadcastStopFailed="Failed to stop broadcast"
454454

455455
# log upload dialog text and messages
456-
LogReturnDialog="Log Upload Successful"
457-
LogReturnDialog.Description="Your log file has been uploaded. You can now share the URL for debugging or support purposes."
458-
LogReturnDialog.Description.Crash="Your crash report has been uploaded. You can now share the URL for debugging purposes."
459-
LogReturnDialog.CopyURL="Copy URL"
460-
LogReturnDialog.AnalyzeURL="Analyze"
461-
LogReturnDialog.ErrorUploadingLog="Error uploading log file"
462456
LogUploadDialog.Title="OBS Studio Log File Upload"
463457
LogUploadDialog.Labels.PrivacyNotice="Please read the <a href='https://obsproject.com/privacy-policy'>Privacy Policy</a> and its section regarding file uploads before uploading any files."
464458
LogUploadDialog.Labels.Progress="Log upload in progress - please wait..."

0 commit comments

Comments
 (0)