overte/libraries/networking/src/crash-handler/CrashHandler.h

240 lines
7.2 KiB
C++

//
// CrashHandler.cpp
//
//
// Created by Dale Glass on 25/06/2023.
// Copyright 2023-2025 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>
#include <atomic>
#include <unordered_map>
#include <mutex>
/**
* @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 (OVERTE_BACKTRACE_TOKEN and OVERTE_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 OVERTE_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 OVERTE_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 OVERTE_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 OVERTE_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.
*
* @note Annotations made before the crash handler are remembered, and sent to the
* crash handler as soon as it's initialized.
*
* @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.
*
* @note Annotations made before the crash handler are remembered, and sent to the
* crash handler as soon as it's initialized.
*
* @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.
*
* @note Annotations made before the crash handler are remembered, and sent to the
* crash handler as soon as it's initialized.
*
*
* @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; }
std::atomic<bool> _crashMonitorStarted {false};
std::atomic<bool> _crashReportingEnabled {false};
std::unordered_map<std::string, std::string> _annotations{};
std::mutex _annotationsMutex{};
QString _path;
QString _crashUrl;
QString _crashToken;
};