diff --git a/internal/frontend/bridge-gui/bridge-gui/AppController.cpp b/internal/frontend/bridge-gui/bridge-gui/AppController.cpp index c745b784..4152d7d9 100644 --- a/internal/frontend/bridge-gui/bridge-gui/AppController.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/AppController.cpp @@ -73,13 +73,17 @@ ProcessMonitor *AppController::bridgeMonitor() const { //**************************************************************************************************************************************************** /// \param[in] function The function that caught the exception. /// \param[in] message The error message. +/// \param[in] details The details for the error. //**************************************************************************************************************************************************** -void AppController::onFatalError(QString const &function, QString const &message) { - QString const fullMessage = QString("%1(): %2").arg(function, message); - auto uuid = reportSentryException(SENTRY_LEVEL_ERROR, "AppController got notified of a fatal error", "Exception", fullMessage.toLocal8Bit()); +void AppController::onFatalError(QString const &function, QString const &message, QString const& details) { + QString fullMessage = QString("%1(): %2").arg(function, message); + if (!details.isEmpty()) + fullMessage += "\n\nDetails:\n" + details; + sentry_uuid_s const uuid = reportSentryException(SENTRY_LEVEL_ERROR, "AppController got notified of a fatal error", "Exception", + fullMessage.toLocal8Bit()); QMessageBox::critical(nullptr, tr("Error"), message); restart(true); - log().fatal(QString("reportID: %1 Captured exception: %2").arg(QByteArray(uuid.bytes, 16).toHex()).arg(fullMessage)); + log().fatal(QString("reportID: %1 Captured exception: %2").arg(QByteArray(uuid.bytes, 16).toHex(), fullMessage)); qApp->exit(EXIT_FAILURE); } diff --git a/internal/frontend/bridge-gui/bridge-gui/AppController.h b/internal/frontend/bridge-gui/bridge-gui/AppController.h index 224857c1..cba3bebd 100644 --- a/internal/frontend/bridge-gui/bridge-gui/AppController.h +++ b/internal/frontend/bridge-gui/bridge-gui/AppController.h @@ -58,7 +58,7 @@ public: // member functions. void setLauncherArgs(const QString& launcher, const QStringList& args); public slots: - void onFatalError(QString const &function, QString const &message); ///< Handle fatal errors. + void onFatalError(QString const &function, QString const &message, QString const& details); ///< Handle fatal errors. private: // member functions AppController(); ///< Default constructor. diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp index 6314bcc8..37a7e6c3 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp @@ -25,8 +25,8 @@ #define HANDLE_EXCEPTION(x) try { x } \ - catch (Exception const &e) { emit fatalError(__func__, e.qwhat()); } \ - catch (...) { emit fatalError(__func__, QString("An unknown exception occurred")); } + catch (Exception const &e) { emit fatalError(__func__, e.qwhat(), e.details()); } \ + catch (...) { emit fatalError(__func__, QString("An unknown exception occurred"), QString()); } #define HANDLE_EXCEPTION_RETURN_BOOL(x) HANDLE_EXCEPTION(x) return false; #define HANDLE_EXCEPTION_RETURN_QSTRING(x) HANDLE_EXCEPTION(x) return QString(); #define HANDLE_EXCEPTION_RETURN_ZERO(x) HANDLE_EXCEPTION(x) return 0; @@ -56,12 +56,8 @@ void QMLBackend::init(GRPCConfig const &serviceConfig) { app().grpc().setLog(&log); this->connectGrpcEvents(); - QString error; - if (app().grpc().connectToServer(serviceConfig, app().bridgeMonitor(), error)) { - app().log().info("Connected to backend via gRPC service."); - } else { - throw Exception(QString("Cannot connectToServer to go backend via gRPC: %1").arg(error)); - } + app().grpc().connectToServer(serviceConfig, app().bridgeMonitor()); + app().log().info("Connected to backend via gRPC service."); QString bridgeVer; app().grpc().version(bridgeVer); @@ -597,7 +593,8 @@ void QMLBackend::setDiskCachePath(QUrl const &path) const { void QMLBackend::login(QString const &username, QString const &password) const { HANDLE_EXCEPTION( if (username.compare("coco@bandicoot", Qt::CaseInsensitive) == 0) { - throw Exception("User requested bridge-gui to crash by trying to log as coco@bandicoot"); + throw Exception("User requested bridge-gui to crash by trying to log as coco@bandicoot", + "This error exists for test purposes and should be ignored."); } app().grpc().login(username, password); ) diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h index be39aab1..19b221a2 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h @@ -235,7 +235,7 @@ signals: // Signals received from the Go backend, to be forwarded to QML void selectUser(QString const); ///< Signal that request the given user account to be displayed. // This signal is emitted when an exception is intercepted is calls triggered by QML. QML engine would intercept the exception otherwise. - void fatalError(QString const &function, QString const &message) const; ///< Signal emitted when an fatal error occurs. + void fatalError(QString const &function, QString const &message, QString const &details) const; ///< Signal emitted when an fatal error occurs. private: // member functions void retrieveUserList(); ///< Retrieve the list of users via gRPC. diff --git a/internal/frontend/bridge-gui/bridge-gui/main.cpp b/internal/frontend/bridge-gui/bridge-gui/main.cpp index 7e2f89c7..56db1ea1 100644 --- a/internal/frontend/bridge-gui/bridge-gui/main.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/main.cpp @@ -428,9 +428,16 @@ int main(int argc, char *argv[]) { return result; } catch (Exception const &e) { - auto uuid = reportSentryException(SENTRY_LEVEL_ERROR, "Exception occurred during main", "Exception", e.what()); + QString fullMessage = e.qwhat(); + bool const hasDetails = !e.details().isEmpty(); + if (hasDetails) + fullMessage += "\n\nDetails:\n" + e.details(); + sentry_uuid_s const uuid = reportSentryException(SENTRY_LEVEL_ERROR, "Exception occurred during main", "Exception", fullMessage.toLocal8Bit()); QMessageBox::critical(nullptr, "Error", e.qwhat()); - QTextStream(stderr) << "reportID: " << QByteArray(uuid.bytes, 16).toHex() << "Captured exception :" << e.qwhat() << "\n"; + QTextStream errStream(stderr); + errStream << "reportID: " << QByteArray(uuid.bytes, 16).toHex() << " Captured exception :" << e.qwhat() << "\n"; + if (hasDetails) + errStream << "\nDetails:\n" << e.details() << "\n"; return EXIT_FAILURE; } } diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/Exception/Exception.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/Exception/Exception.cpp index 42c27355..a9179038 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/Exception/Exception.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/Exception/Exception.cpp @@ -23,11 +23,13 @@ namespace bridgepp { //**************************************************************************************************************************************************** -/// \param[in] what A description of the exception +/// \param[in] what A description of the exception. +/// \param[in] details The optional details for the exception. //**************************************************************************************************************************************************** -Exception::Exception(QString what) noexcept +Exception::Exception(QString what, QString details) noexcept : std::exception() - , what_(std::move(what)) { + , what_(std::move(what)) + , details_(std::move(details)) { } @@ -36,7 +38,8 @@ Exception::Exception(QString what) noexcept //**************************************************************************************************************************************************** Exception::Exception(Exception const &ref) noexcept : std::exception(ref) - , what_(ref.what_) { + , what_(ref.what_) + , details_(ref.details_) { } @@ -45,14 +48,15 @@ Exception::Exception(Exception const &ref) noexcept //**************************************************************************************************************************************************** Exception::Exception(Exception &&ref) noexcept : std::exception(ref) - , what_(ref.what_) { + , what_(ref.what_) + , details_(ref.details_) { } //**************************************************************************************************************************************************** /// \return a string describing the exception //**************************************************************************************************************************************************** -QString const &Exception::qwhat() const noexcept { +QString Exception::qwhat() const noexcept { return what_; } @@ -65,4 +69,12 @@ const char *Exception::what() const noexcept { } +//**************************************************************************************************************************************************** +/// \return The details for the exception. +//**************************************************************************************************************************************************** +QString Exception::details() const noexcept { + return details_; +} + + } // namespace bridgepp diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/Exception/Exception.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/Exception/Exception.h index 6f755ea4..9cde3e7d 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/Exception/Exception.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/Exception/Exception.h @@ -31,17 +31,19 @@ namespace bridgepp { //**************************************************************************************************************************************************** class Exception : public std::exception { public: // member functions - explicit Exception(QString what = QString()) noexcept; ///< Constructor + explicit Exception(QString what = QString(), QString details = QString()) noexcept; ///< Constructor Exception(Exception const &ref) noexcept; ///< copy constructor Exception(Exception &&ref) noexcept; ///< copy constructor Exception &operator=(Exception const &) = delete; ///< Disabled assignment operator Exception &operator=(Exception &&) = delete; ///< Disabled assignment operator ~Exception() noexcept override = default; ///< Destructor - QString const &qwhat() const noexcept; ///< Return the description of the exception as a QString + QString qwhat() const noexcept; ///< Return the description of the exception as a QString const char *what() const noexcept override; ///< Return the description of the exception as C style string + QString details() const noexcept; ///< Return the details for the exception private: // data members - QString const what_; ///< The description of the exception + QString const what_; ///< The description of the exception. + QString const details_; ///< The optional details for the exception. }; diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp index f2bf58b1..adcc145c 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp @@ -88,8 +88,9 @@ GRPCConfig GRPCClient::waitAndRetrieveServiceConfig(qint64 timeoutMs, ProcessMon } GRPCConfig sc; - if (!sc.load(path)) { - throw Exception("The gRPC service configuration file is invalid."); + QString err; + if (!sc.load(path, &err)) { + throw Exception("The gRPC service configuration file is invalid.", err); } return sc; @@ -105,11 +106,10 @@ void GRPCClient::setLog(Log *log) { //**************************************************************************************************************************************************** -/// \param[out] outError If the function returns false, this variable contains a description of the error. /// \param[in] serverProcess An optional server process to monitor. If the process it, no need and retry, as connexion cannot be established. Ignored if null. /// \return true iff the connection was successful. //**************************************************************************************************************************************************** -bool GRPCClient::connectToServer(GRPCConfig const &config, ProcessMonitor *serverProcess, QString &outError) { +void GRPCClient::connectToServer(GRPCConfig const &config, ProcessMonitor *serverProcess) { try { serverToken_ = config.token.toStdString(); QString address; @@ -158,9 +158,10 @@ bool GRPCClient::connectToServer(GRPCConfig const &config, ProcessMonitor *serve this->logInfo("Successfully connected to gRPC server."); QString const clientToken = QUuid::createUuid().toString(); - QString clientConfigPath = createClientConfigFile(clientToken); + QString error; + QString clientConfigPath = createClientConfigFile(clientToken, &error); if (clientConfigPath.isEmpty()) { - throw Exception("gRPC client config could not be saved."); + throw Exception("gRPC client config could not be saved.", error); } this->logInfo(QString("Client config file was saved to '%1'").arg(QDir::toNativeSeparators(clientConfigPath))); @@ -176,12 +177,9 @@ bool GRPCClient::connectToServer(GRPCConfig const &config, ProcessMonitor *serve } log_->info("gRPC token was validated"); - - return true; } catch (Exception const &e) { - outError = e.qwhat(); - return false; + throw Exception("Cannot connect to Go backend via gRPC: " + e.qwhat(), e.details()); } } diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h index c84ee2b5..0484ad25 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h @@ -59,7 +59,7 @@ public: // member functions. GRPCClient &operator=(GRPCClient const &) = delete; ///< Disabled assignment operator. GRPCClient &operator=(GRPCClient &&) = delete; ///< Disabled move assignment operator. void setLog(Log *log); ///< Set the log for the client. - bool connectToServer(GRPCConfig const &config, class ProcessMonitor *serverProcess, QString &outError); ///< Establish connection to the gRPC server. + void connectToServer(GRPCConfig const &config, class ProcessMonitor *serverProcess); ///< Establish connection to the gRPC server. grpc::Status checkTokens(QString const &clientConfigPath, QString &outReturnedClientToken); ///< Performs a token check. grpc::Status addLogEntry(Log::Level level, QString const &package, QString const &message); ///< Performs the "AddLogEntry" gRPC call. diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCConfig.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCConfig.cpp index 1fda5eff..7092d539 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCConfig.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCConfig.cpp @@ -25,8 +25,7 @@ using namespace bridgepp; namespace { -Exception const invalidFileException("The service configuration file is invalid"); // Exception for invalid config. -Exception const couldNotSaveException("The service configuration file could not be saved"); ///< Exception for write errors. +Exception const invalidFileException("The content of the service configuration file is invalid"); // Exception for invalid config. QString const keyPort = "port"; ///< The JSON key for the port. QString const keyCert = "cert"; ///< The JSON key for the TLS certificate. QString const keyToken = "token"; ///< The JSON key for the identification token. @@ -78,8 +77,11 @@ qint32 jsonIntValue(QJsonObject const &object, QString const &key) { bool GRPCConfig::load(QString const &path, QString *outError) { try { QFile file(path); + if (!file.exists()) + throw Exception("The file service configuration file does not exist."); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - throw Exception("Could not open gRPC service config file."); + throw Exception("The file exists but cannot be opened."); } QJsonDocument const doc = QJsonDocument::fromJson(file.readAll()); @@ -93,7 +95,7 @@ bool GRPCConfig::load(QString const &path, QString *outError) { } catch (Exception const &e) { if (outError) { - *outError = e.qwhat(); + *outError = QString("Error loading gRPC service configuration file '%1'.\n%2").arg(QFileInfo(path).absoluteFilePath(), e.qwhat()); } return false; } @@ -115,19 +117,19 @@ bool GRPCConfig::save(QString const &path, QString *outError) { QFile file(path); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { - throw couldNotSaveException; + throw Exception("The file could not be opened for writing."); } QByteArray const array = QJsonDocument(object).toJson(); if (array.size() != file.write(array)) { - throw couldNotSaveException; + throw Exception("An error occurred while writing to the file."); } return true; } catch (Exception const &e) { if (outError) { - *outError = e.qwhat(); + *outError = QString("Error saving gRPC service configuration file '%1'.\n%2").arg(QFileInfo(path).absoluteFilePath(), e.qwhat()); } return false; } diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.cpp index ebcce011..b651ae54 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.cpp @@ -76,10 +76,12 @@ QString grpcClientConfigBasePath() { //**************************************************************************************************************************************************** /// \param[in] token The token to put in the file. +/// \param[out] outError if the function returns an empty string and this pointer is not null, the pointer variable holds a description of the error +/// on exit. /// \return The path of the created file. -/// \return A null string if the file could not be saved.. +/// \return A null string if the file could not be saved. //**************************************************************************************************************************************************** -QString createClientConfigFile(QString const &token) { +QString createClientConfigFile(QString const &token, QString *outError) { QString const basePath = grpcClientConfigBasePath(); QString path, error; for (qint32 i = 0; i < 1000; ++i) // we try a decent amount of times @@ -88,13 +90,16 @@ QString createClientConfigFile(QString const &token) { if (!QFileInfo(path).exists()) { GRPCConfig config; config.token = token; - if (!config.save(path)) { + + if (!config.save(path, outError)) { return QString(); } return path; } } + if (outError) + *outError = "no usable client configuration file name could be found."; return QString(); } diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.h index 8cc9dcbc..844ec4c5 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.h @@ -36,7 +36,7 @@ typedef std::shared_ptr SPStreamEvent; ///< Type definition f QString grpcServerConfigPath(); ///< Return the path of the gRPC server config file. QString grpcClientConfigBasePath(); ///< Return the path of the gRPC client config file. -QString createClientConfigFile(QString const &token); ///< Create the client config file the server will retrieve and return its path. +QString createClientConfigFile(QString const &token, QString *outError); ///< Create the client config file the server will retrieve and return its path. grpc::LogLevel logLevelToGRPC(Log::Level level); ///< Convert a Log::Level to gRPC enum value. Log::Level logLevelFromGRPC(grpc::LogLevel level); ///< Convert a grpc::LogLevel to a Log::Level. grpc::UserState userStateToGRPC(UserState state); ///< Convert a bridgepp::UserState to a grpc::UserState.