feat(GODT-2482): more attachment to relevant exceptions.

This commit is contained in:
Xavier Michelon
2023-03-13 14:55:59 +01:00
parent 4273405393
commit 48274ee178
9 changed files with 135 additions and 67 deletions

View File

@ -18,26 +18,13 @@
#include "LogUtils.h" #include "LogUtils.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include <bridgepp/Log/LogUtils.h>
#include <bridgepp/BridgeUtils.h> #include <bridgepp/BridgeUtils.h>
using namespace bridgepp; 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. /// \return A reference to the log.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
@ -69,36 +56,3 @@ Log &initLog() {
return log; 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();
}

View File

@ -24,7 +24,6 @@
bridgepp::Log &initLog(); ///< Initialize the application log. bridgepp::Log &initLog(); ///< Initialize the application log.
QByteArray tailOfLatestBridgeLog(); ///< Return the last bytes of the last bridge log.
#endif //BRIDGE_GUI_LOG_UTILS_H #endif //BRIDGE_GUI_LOG_UTILS_H

View File

@ -19,9 +19,9 @@
#include "QMLBackend.h" #include "QMLBackend.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "EventStreamWorker.h" #include "EventStreamWorker.h"
#include "LogUtils.h"
#include <bridgepp/BridgeUtils.h> #include <bridgepp/BridgeUtils.h>
#include <bridgepp/Exception/Exception.h> #include <bridgepp/Exception/Exception.h>
#include <bridgepp/Log/LogUtils.h>
#include <bridgepp/GRPC/GRPCClient.h> #include <bridgepp/GRPC/GRPCClient.h>
#include <bridgepp/Worker/Overseer.h> #include <bridgepp/Worker/Overseer.h>
#include <bridgepp/BridgeUtils.h> #include <bridgepp/BridgeUtils.h>

View File

@ -27,6 +27,7 @@
#include <bridgepp/Exception/Exception.h> #include <bridgepp/Exception/Exception.h>
#include <bridgepp/FocusGRPC/FocusGRPCClient.h> #include <bridgepp/FocusGRPC/FocusGRPCClient.h>
#include <bridgepp/Log/Log.h> #include <bridgepp/Log/Log.h>
#include <bridgepp/Log/LogUtils.h>
#include <bridgepp/ProcessMonitor.h> #include <bridgepp/ProcessMonitor.h>
@ -116,7 +117,7 @@ QQmlComponent *createRootQmlComponent(QQmlApplicationEngine &engine) {
rootComponent->loadUrl(QUrl(qrcQmlDir + "/Bridge.qml")); rootComponent->loadUrl(QUrl(qrcQmlDir + "/Bridge.qml"));
if (rootComponent->status() != QQmlComponent::Status::Ready) { if (rootComponent->status() != QQmlComponent::Status::Ready) {
QString const &err =rootComponent->errorString(); QString const &err = rootComponent->errorString();
app().log().error(err); app().log().error(err);
throw Exception("Could not load QML component", err); throw Exception("Could not load QML component", err);
} }
@ -211,7 +212,7 @@ void focusOtherInstance() {
QString error; QString error;
if (!client.connectToServer(5000, sc.port, &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()) { if (!client.raise().ok()) {
throw Exception(QString("The raise call to the bridge focus service failed.")); throw Exception(QString("The raise call to the bridge focus service failed."));
@ -220,7 +221,7 @@ void focusOtherInstance() {
catch (Exception const &e) { catch (Exception const &e) {
app().log().error(e.qwhat()); app().log().error(e.qwhat());
auto uuid = reportSentryException("Exception occurred during focusOtherInstance()", e); 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); BridgeApp guiApp(argc, argv);
initSentry();
auto sentryCloser = qScopeGuard([] { sentry_close(); });
try { try {
QString const& configDir = bridgepp::userConfigDir(); QString const& configDir = bridgepp::userConfigDir();
// Init sentry.
initSentry();
auto sentryClose = qScopeGuard([] { sentry_close(); });
initQtApplication(); initQtApplication();
@ -311,7 +309,7 @@ int main(int argc, char *argv[]) {
if (!cliOptions.attach) { if (!cliOptions.attach) {
if (isBridgeRunning()) { if (isBridgeRunning()) {
throw Exception("An orphan instance of bridge is already running. Please terminate it and relaunch the application.", 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. // 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<QQmlComponent> rootComponent(createRootQmlComponent(engine)); std::unique_ptr<QQmlComponent> rootComponent(createRootQmlComponent(engine));
std::unique_ptr<QObject> rootObject(rootComponent->create(engine.rootContext())); std::unique_ptr<QObject> rootObject(rootComponent->create(engine.rootContext()));
if (!rootObject) { if (!rootObject) {
throw Exception("Could not create root object."); throw Exception("Could not create QML root object.");
} }
ProcessMonitor *bridgeMonitor = app().bridgeMonitor(); ProcessMonitor *bridgeMonitor = app().bridgeMonitor();

View File

@ -146,6 +146,7 @@ add_library(bridgepp
${FOCUS_PROTO_CPP_FILE} ${FOCUS_PROTO_H_FILE} ${FOCUS_GRPC_CPP_FILE} ${FOCUS_GRPC_H_FILE} ${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/FocusGRPC/FocusGRPCClient.cpp bridgepp/FocusGRPC/FocusGRPCClient.h
bridgepp/Log/Log.h bridgepp/Log/Log.cpp bridgepp/Log/Log.h bridgepp/Log/Log.cpp
bridgepp/Log/LogUtils.h bridgepp/Log/LogUtils.cpp
bridgepp/ProcessMonitor.cpp bridgepp/ProcessMonitor.h bridgepp/ProcessMonitor.cpp bridgepp/ProcessMonitor.h
bridgepp/User/User.cpp bridgepp/User/User.h bridgepp/User/User.cpp bridgepp/User/User.h
bridgepp/Worker/Worker.h bridgepp/Worker/Overseer.h bridgepp/Worker/Overseer.cpp) bridgepp/Worker/Worker.h bridgepp/Worker/Overseer.h bridgepp/Worker/Overseer.cpp)

View File

@ -23,6 +23,7 @@
#include <stdexcept> #include <stdexcept>
namespace bridgepp { namespace bridgepp {
@ -44,6 +45,9 @@ public: // member functions
QByteArray attachment() const noexcept; ///< Return the attachment for the exception. 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). 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 private: // data members
QString const qwhat_; ///< The description of the exception. 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. QByteArray const what_; ///< The c-string version of the qwhat message. Stored as a QByteArray for automatic lifetime management.

View File

@ -22,6 +22,7 @@
#include "../BridgeUtils.h" #include "../BridgeUtils.h"
#include "../Exception/Exception.h" #include "../Exception/Exception.h"
#include "../ProcessMonitor.h" #include "../ProcessMonitor.h"
#include "../Log/LogUtils.h"
using namespace google::protobuf; using namespace google::protobuf;
@ -70,7 +71,8 @@ GRPCConfig GRPCClient::waitAndRetrieveServiceConfig(QString const &configDir, qi
bool found = false; bool found = false;
while (true) { while (true) {
if (serverProcess && serverProcess->getStatus().ended) { 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()) { if (file.exists()) {
@ -84,13 +86,20 @@ GRPCConfig GRPCClient::waitAndRetrieveServiceConfig(QString const &configDir, qi
} }
if (!found) { 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; GRPCConfig sc;
QString err; QString err;
if (!sc.load(path, &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; return sc;
@ -126,19 +135,20 @@ void GRPCClient::connectToServer(QString const &configDir, GRPCConfig const &con
channel_ = CreateCustomChannel(address.toStdString(), grpc::SslCredentials(opts), chanArgs); channel_ = CreateCustomChannel(address.toStdString(), grpc::SslCredentials(opts), chanArgs);
if (!channel_) { if (!channel_) {
throw Exception("Channel creation failed."); throw Exception("gRPC channel creation failed.");
} }
stub_ = Bridge::NewStub(channel_); stub_ = Bridge::NewStub(channel_);
if (!stub_) { 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 QDateTime const giveUpTime = QDateTime::currentDateTime().addMSecs(grpcConnectionWaitTimeoutMs); // if we reach giveUpTime without connecting, we give up
int i = 0; int i = 0;
while (true) { while (true) {
if (serverProcess && serverProcess->getStatus().ended) { 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)); 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. } // connection established.
if (QDateTime::currentDateTime() > giveUpTime) { 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"); log_->info("gRPC token was validated");
} }
catch (Exception const &e) { 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());
} }
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
#include "LogUtils.h"
#include <bridgepp/BridgeUtils.h>
#include <bridgepp/Exception/Exception.h>
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

View File

@ -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 <https://www.gnu.org/licenses/>.
#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