From 48274ee17801a35812a59b5827cdcd5bd9f26323 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Mon, 13 Mar 2023 14:55:59 +0100 Subject: [PATCH] feat(GODT-2482): more attachment to relevant exceptions. --- .../bridge-gui/bridge-gui/LogUtils.cpp | 48 +------------ .../frontend/bridge-gui/bridge-gui/LogUtils.h | 1 - .../bridge-gui/bridge-gui/QMLBackend.cpp | 2 +- .../frontend/bridge-gui/bridge-gui/main.cpp | 18 +++-- .../bridge-gui/bridgepp/CMakeLists.txt | 1 + .../bridgepp/bridgepp/Exception/Exception.h | 4 ++ .../bridgepp/bridgepp/GRPC/GRPCClient.cpp | 27 +++++--- .../bridgepp/bridgepp/Log/LogUtils.cpp | 68 +++++++++++++++++++ .../bridgepp/bridgepp/Log/LogUtils.h | 33 +++++++++ 9 files changed, 135 insertions(+), 67 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridgepp/bridgepp/Log/LogUtils.cpp create mode 100644 internal/frontend/bridge-gui/bridgepp/bridgepp/Log/LogUtils.h diff --git a/internal/frontend/bridge-gui/bridge-gui/LogUtils.cpp b/internal/frontend/bridge-gui/bridge-gui/LogUtils.cpp index 4964039b..556699a6 100644 --- a/internal/frontend/bridge-gui/bridge-gui/LogUtils.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/LogUtils.cpp @@ -18,26 +18,13 @@ #include "LogUtils.h" #include "BuildConfig.h" +#include #include using namespace bridgepp; -namespace { -qsizetype const logFileTailMaxLength = 25 * 1024; ///< The maximum length of the portion of log returned by tailOfLatestBridgeLog() -} - - -//**************************************************************************************************************************************************** -/// \return user logs directory used by bridge. -//**************************************************************************************************************************************************** -QString userLogsDir() { - QString const path = QDir(bridgepp::userDataDir()).absoluteFilePath("logs"); - QDir().mkpath(path); - return path; -} - //**************************************************************************************************************************************************** /// \return A reference to the log. //**************************************************************************************************************************************************** @@ -69,36 +56,3 @@ Log &initLog() { return log; } - - -//**************************************************************************************************************************************************** -/// \brief Return the path of the latest bridge log. -/// \return The path of the latest bridge log file. -/// \return An empty string if no bridge log file was found. -//**************************************************************************************************************************************************** -QString latestBridgeLogPath() { - QDir const logsDir(userLogsDir()); - if (logsDir.isEmpty()) { - return QString(); - } - QFileInfoList files = logsDir.entryInfoList({ "v*.log" }, QDir::Files); // could do sorting, but only by last modification time. we want to sort by creation time. - std::sort(files.begin(), files.end(), [](QFileInfo const &lhs, QFileInfo const &rhs) -> bool { - return lhs.birthTime() < rhs.birthTime(); - }); - return files.back().absoluteFilePath(); -} - - -//**************************************************************************************************************************************************** -/// Return the maxSize last bytes of the latest bridge log. -//**************************************************************************************************************************************************** -QByteArray tailOfLatestBridgeLog() { - QString path = latestBridgeLogPath(); - if (path.isEmpty()) { - return QByteArray(); - } - - QFile file(path); - return file.open(QIODevice::Text | QIODevice::ReadOnly) ? file.readAll().right(logFileTailMaxLength) : QByteArray(); -} - diff --git a/internal/frontend/bridge-gui/bridge-gui/LogUtils.h b/internal/frontend/bridge-gui/bridge-gui/LogUtils.h index 60e1896d..b324301e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/LogUtils.h +++ b/internal/frontend/bridge-gui/bridge-gui/LogUtils.h @@ -24,7 +24,6 @@ bridgepp::Log &initLog(); ///< Initialize the application log. -QByteArray tailOfLatestBridgeLog(); ///< Return the last bytes of the last bridge log. #endif //BRIDGE_GUI_LOG_UTILS_H diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp index b0cbad7e..5671245a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp @@ -19,9 +19,9 @@ #include "QMLBackend.h" #include "BuildConfig.h" #include "EventStreamWorker.h" -#include "LogUtils.h" #include #include +#include #include #include #include diff --git a/internal/frontend/bridge-gui/bridge-gui/main.cpp b/internal/frontend/bridge-gui/bridge-gui/main.cpp index 3f7a6a14..b7f3225f 100644 --- a/internal/frontend/bridge-gui/bridge-gui/main.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/main.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -116,7 +117,7 @@ QQmlComponent *createRootQmlComponent(QQmlApplicationEngine &engine) { rootComponent->loadUrl(QUrl(qrcQmlDir + "/Bridge.qml")); if (rootComponent->status() != QQmlComponent::Status::Ready) { - QString const &err =rootComponent->errorString(); + QString const &err = rootComponent->errorString(); app().log().error(err); throw Exception("Could not load QML component", err); } @@ -211,7 +212,7 @@ void focusOtherInstance() { QString error; if (!client.connectToServer(5000, sc.port, &error)) { - throw Exception(QString("Could not connect to bridge focus service for a raise call: %1").arg(error)); + throw Exception("Could not connect to bridge focus service for a raise call.", error); } if (!client.raise().ok()) { throw Exception(QString("The raise call to the bridge focus service failed.")); @@ -220,7 +221,7 @@ void focusOtherInstance() { catch (Exception const &e) { app().log().error(e.qwhat()); auto uuid = reportSentryException("Exception occurred during focusOtherInstance()", e); - app().log().fatal(QString("reportID: %1 Captured exception: %2").arg(QByteArray(uuid.bytes, 16).toHex()).arg(e.qwhat())); + app().log().fatal(QString("reportID: %1 Captured exception: %2").arg(QByteArray(uuid.bytes, 16).toHex(), e.qwhat())); } } @@ -276,15 +277,12 @@ int main(int argc, char *argv[]) { } BridgeApp guiApp(argc, argv); + initSentry(); + auto sentryCloser = qScopeGuard([] { sentry_close(); }); try { QString const& configDir = bridgepp::userConfigDir(); - // Init sentry. - initSentry(); - - auto sentryClose = qScopeGuard([] { sentry_close(); }); - initQtApplication(); @@ -311,7 +309,7 @@ int main(int argc, char *argv[]) { if (!cliOptions.attach) { if (isBridgeRunning()) { throw Exception("An orphan instance of bridge is already running. Please terminate it and relaunch the application.", - QString(), QString(), tailOfLatestBridgeLog()); + QString(), __FUNCTION__, tailOfLatestBridgeLog()); } // before launching bridge, we remove any trailing service config file, because we need to make sure we get a newly generated one. @@ -345,7 +343,7 @@ int main(int argc, char *argv[]) { std::unique_ptr rootComponent(createRootQmlComponent(engine)); std::unique_ptr rootObject(rootComponent->create(engine.rootContext())); if (!rootObject) { - throw Exception("Could not create root object."); + throw Exception("Could not create QML root object."); } ProcessMonitor *bridgeMonitor = app().bridgeMonitor(); diff --git a/internal/frontend/bridge-gui/bridgepp/CMakeLists.txt b/internal/frontend/bridge-gui/bridgepp/CMakeLists.txt index 3b3ed991..af03519a 100644 --- a/internal/frontend/bridge-gui/bridgepp/CMakeLists.txt +++ b/internal/frontend/bridge-gui/bridgepp/CMakeLists.txt @@ -146,6 +146,7 @@ add_library(bridgepp ${FOCUS_PROTO_CPP_FILE} ${FOCUS_PROTO_H_FILE} ${FOCUS_GRPC_CPP_FILE} ${FOCUS_GRPC_H_FILE} bridgepp/FocusGRPC/FocusGRPCClient.cpp bridgepp/FocusGRPC/FocusGRPCClient.h bridgepp/Log/Log.h bridgepp/Log/Log.cpp + bridgepp/Log/LogUtils.h bridgepp/Log/LogUtils.cpp bridgepp/ProcessMonitor.cpp bridgepp/ProcessMonitor.h bridgepp/User/User.cpp bridgepp/User/User.h bridgepp/Worker/Worker.h bridgepp/Worker/Overseer.h bridgepp/Worker/Overseer.cpp) diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/Exception/Exception.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/Exception/Exception.h index dad9a867..21a1689d 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/Exception/Exception.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/Exception/Exception.h @@ -23,6 +23,7 @@ #include + namespace bridgepp { @@ -44,6 +45,9 @@ public: // member functions QByteArray attachment() const noexcept; ///< Return the attachment for the exception. QString detailedWhat() const; ///< Return the detailed description of the message (i.e. including the function name and the details). +public: // static data members + static qsizetype const attachmentMaxLength {25 * 1024}; ///< The maximum length text attachment sent in Sentry reports, in bytes. + private: // data members QString const qwhat_; ///< The description of the exception. QByteArray const what_; ///< The c-string version of the qwhat message. Stored as a QByteArray for automatic lifetime management. diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp index 22b4a376..350b78bd 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp @@ -22,6 +22,7 @@ #include "../BridgeUtils.h" #include "../Exception/Exception.h" #include "../ProcessMonitor.h" +#include "../Log/LogUtils.h" using namespace google::protobuf; @@ -70,7 +71,8 @@ GRPCConfig GRPCClient::waitAndRetrieveServiceConfig(QString const &configDir, qi bool found = false; while (true) { if (serverProcess && serverProcess->getStatus().ended) { - throw Exception("Bridge application exited before providing a gRPC service configuration file."); + throw Exception("Bridge application exited before providing a gRPC service configuration file.", QString(), __FUNCTION__, + tailOfLatestBridgeLog()); } if (file.exists()) { @@ -84,13 +86,20 @@ GRPCConfig GRPCClient::waitAndRetrieveServiceConfig(QString const &configDir, qi } if (!found) { - throw Exception("Server did not provide gRPC service configuration in time."); + throw Exception("Server did not provide gRPC service configuration in time.", QString(), __FUNCTION__, tailOfLatestBridgeLog()); } GRPCConfig sc; QString err; if (!sc.load(path, &err)) { - throw Exception("The gRPC service configuration file is invalid.", err); + // include the file content in the exception, if any + QByteArray array; + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + file.readAll(); + array = array.right(Exception::attachmentMaxLength); + } + + throw Exception("The gRPC service configuration file is invalid.", err, __FUNCTION__, array); } return sc; @@ -126,19 +135,20 @@ void GRPCClient::connectToServer(QString const &configDir, GRPCConfig const &con channel_ = CreateCustomChannel(address.toStdString(), grpc::SslCredentials(opts), chanArgs); if (!channel_) { - throw Exception("Channel creation failed."); + throw Exception("gRPC channel creation failed."); } stub_ = Bridge::NewStub(channel_); if (!stub_) { - throw Exception("Stub creation failed."); + throw Exception("gRPC stub creation failed."); } QDateTime const giveUpTime = QDateTime::currentDateTime().addMSecs(grpcConnectionWaitTimeoutMs); // if we reach giveUpTime without connecting, we give up int i = 0; while (true) { if (serverProcess && serverProcess->getStatus().ended) { - throw Exception("Bridge application ended before gRPC connexion could be established."); + throw Exception("Bridge application ended before gRPC connexion could be established.", QString(), __FUNCTION__, + tailOfLatestBridgeLog()); } this->logInfo(QString("Connection to gRPC server at %1. attempt #%2").arg(address).arg(++i)); @@ -148,7 +158,8 @@ void GRPCClient::connectToServer(QString const &configDir, GRPCConfig const &con } // connection established. if (QDateTime::currentDateTime() > giveUpTime) { - throw Exception("Connection to the RPC server failed."); + throw Exception("Connection to the gRPC server failed because of a timeout.", QString(), __FUNCTION__, + tailOfLatestBridgeLog()); } } @@ -180,7 +191,7 @@ void GRPCClient::connectToServer(QString const &configDir, GRPCConfig const &con log_->info("gRPC token was validated"); } catch (Exception const &e) { - throw Exception("Cannot connect to Go backend via gRPC: " + e.qwhat(), e.details()); + throw Exception("Cannot connect to Go backend via gRPC: " + e.qwhat(), e.details(), __FUNCTION__, e.attachment()); } } diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/Log/LogUtils.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/Log/LogUtils.cpp new file mode 100644 index 00000000..ee3780b2 --- /dev/null +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/Log/LogUtils.cpp @@ -0,0 +1,68 @@ +// Copyright (c) 2023 Proton AG +// +// This file is part of Proton Mail Bridge. +// +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . + + +#include "LogUtils.h" +#include +#include + + +namespace bridgepp { + + +//**************************************************************************************************************************************************** +/// \return user logs directory used by bridge. +//**************************************************************************************************************************************************** +QString userLogsDir() { + QString const path = QDir(bridgepp::userDataDir()).absoluteFilePath("logs"); + QDir().mkpath(path); + return path; +} + +//**************************************************************************************************************************************************** +/// \brief Return the path of the latest bridge log. +/// \return The path of the latest bridge log file. +/// \return An empty string if no bridge log file was found. +//**************************************************************************************************************************************************** +QString latestBridgeLogPath() { + QDir const logsDir(userLogsDir()); + if (logsDir.isEmpty()) { + return QString(); + } + QFileInfoList files = logsDir.entryInfoList({ "v*.log" }, QDir::Files); // could do sorting, but only by last modification time. we want to sort by creation time. + std::sort(files.begin(), files.end(), [](QFileInfo const &lhs, QFileInfo const &rhs) -> bool { + return lhs.birthTime() < rhs.birthTime(); + }); + return files.back().absoluteFilePath(); +} + + +//**************************************************************************************************************************************************** +/// Return the maxSize last bytes of the latest bridge log. +//**************************************************************************************************************************************************** +QByteArray tailOfLatestBridgeLog() { + QString path = latestBridgeLogPath(); + if (path.isEmpty()) { + return QByteArray(); + } + + QFile file(path); + return file.open(QIODevice::Text | QIODevice::ReadOnly) ? file.readAll().right(Exception::attachmentMaxLength) : QByteArray(); +} + + +} // namespace bridgepp diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/Log/LogUtils.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/Log/LogUtils.h new file mode 100644 index 00000000..2918cd15 --- /dev/null +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/Log/LogUtils.h @@ -0,0 +1,33 @@ +// Copyright (c) 2023 Proton AG +// +// This file is part of Proton Mail Bridge. +// +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . + + +#ifndef BRIDGE_PP_LOG_UTILS_H +#define BRIDGE_PP_LOG_UTILS_H + + +namespace bridgepp { + + +QString userLogsDir(); ///< Return the path of the user logs dir. +QByteArray tailOfLatestBridgeLog(); ///< Return the last bytes of the last bridge log. + + +} // namespace bridgepp + + +#endif //BRIDGE_PP_LOG_UTILS_H