Fix threading to run just after minimal initialization (like logger instanciation).

Rewrite Logger : out logs are run on core, and file logs on model.
Fix crash on removing logger listener.
This commit is contained in:
Julien Wadel 2023-10-17 17:30:22 +02:00
parent ca40833b43
commit bc2c51badd
8 changed files with 184 additions and 119 deletions

View file

@ -31,8 +31,6 @@ App::App(int &argc, char *argv[])
: SingleApplication(argc, argv, true, Mode::User | Mode::ExcludeAppPath | Mode::ExcludeAppVersion) {
mLinphoneThread = new Thread(this);
init();
qDebug() << "[App] Starting Thread";
mLinphoneThread->start();
}
App *App::getInstance() {
@ -58,6 +56,11 @@ void App::init() {
if (mParser->isSet("verbose")) QtLogger::enableVerbose(true);
if (mParser->isSet("qt-logs-only")) QtLogger::enableQtOnly(true);
if (!mLinphoneThread->isRunning()) {
qDebug() << "[App] Starting Thread";
mLinphoneThread->start();
}
// QML
mEngine = new QQmlApplicationEngine(this);
mEngine->addImportPath(":/");
@ -68,7 +71,10 @@ void App::init() {
QObject::connect(
mEngine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) QCoreApplication::exit(-1);
if (!obj && url == objUrl) {
qCritical() << "[App] Main.qml couldn't be load. The app will exit";
exit(-1);
}
},
Qt::QueuedConnection);
mEngine->load(url);
@ -83,6 +89,9 @@ void App::initCppInterfaces() {
//------------------------------------------------------------
void App::clean() {
// Wait 500ms to let time for log te be stored.
mLinphoneThread->wait(250);
qApp->processEvents(QEventLoop::AllEvents, 250);
mLinphoneThread->exit();
mLinphoneThread->wait();
delete mLinphoneThread;

View file

@ -19,21 +19,59 @@
*/
#include "QtLogger.hpp"
#include "tool/Utils.hpp"
#include <QApplication>
#include <QMessageBox>
#include <QMetaMethod>
#include <iostream>
#include <linphone++/linphone.hh>
// -----------------------------------------------------------------------------
static QtLogger gLogger;
// =============================================================================
#if defined(__linux__) || defined(__APPLE__)
#define BLUE "\x1B[1;34m"
#define YELLOW "\x1B[1;33m"
#define GREEN "\x1B[1;32m"
#define PURPLE "\x1B[1;35m"
#define RED "\x1B[1;31m"
#define RESET "\x1B[0m"
#else
#define BLUE ""
#define YELLOW ""
#define GREEN ""
#define PURPLE ""
#define RED ""
#define RESET ""
#endif // if defined(__linux__) || defined(__APPLE__)
// -----------------------------------------------------------------------------
static inline QByteArray getFormattedCurrentTime() {
return QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz").toLocal8Bit();
}
QtLogger::QtLogger(QObject *parent) : QObject(parent) {
qInstallMessageHandler(QtLogger::onQtLog);
}
QtLogger::~QtLogger() {
qInstallMessageHandler(0);
}
QtLogger *QtLogger::getInstance() {
return &gLogger;
}
void QtLogger::onQtLog(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
emit gLogger.logReceived(type, context.file, context.line, msg);
if (type == QtFatalMsg) {
QString out; // Qt force call abort() that kill the application. So it cannot be used to retrieve
// in other thread. Print the error in the hope that it can be catch somewhere.
gLogger.printLog(&out, Constants::AppDomain, linphone::LogLevel::Fatal, Utils::appStringToCoreString(msg));
}
emit gLogger.qtLogReceived(type, context.file, context.line, msg);
}
void QtLogger::enableVerbose(bool verbose) {
@ -43,3 +81,65 @@ void QtLogger::enableVerbose(bool verbose) {
void QtLogger::enableQtOnly(bool qtOnly) {
emit gLogger.requestQtOnlyEnabled(qtOnly);
}
void QtLogger::onLinphoneLog(const std::string &domain, linphone::LogLevel level, const std::string &message) {
QString qMessage;
printLog(&qMessage, domain, level, message);
if (level == linphone::LogLevel::Fatal) {
QMetaObject::invokeMethod(qApp, [qMessage]() {
QMessageBox::critical(nullptr, EXECUTABLE_NAME " will crash", qMessage);
std::terminate();
});
}
}
void QtLogger::printLog(QString *qMessage,
const std::string &domain,
linphone::LogLevel level,
const std::string &message) {
bool isAppLog = domain == Constants::AppDomain;
FILE *out = stdout;
// TypeColor Date SourceColor [Domain] TypeColor Type Reset Message
QString format = "%1 %2 %3[%4]%1 %5" RESET " %6\n";
QString colorType;
QString type;
*qMessage = Utils::coreStringToAppString(message);
switch (level) {
case linphone::LogLevel::Debug:
colorType = GREEN;
type = "DEBUG";
break;
case linphone::LogLevel::Trace:
colorType = BLUE;
type = "TRACE";
break;
case linphone::LogLevel::Message:
colorType = BLUE;
type = "MESSAGE";
break;
case linphone::LogLevel::Warning:
colorType = RED;
type = "WARNING";
out = stderr;
break;
case linphone::LogLevel::Error:
colorType = RED;
type = "ERROR";
out = stderr;
break;
case linphone::LogLevel::Fatal:
colorType = RED;
type = "FATAL";
out = stderr;
break;
}
QString messageToDisplay = format.arg(colorType)
.arg(getFormattedCurrentTime())
.arg(isAppLog ? PURPLE : YELLOW)
.arg(Utils::coreStringToAppString(domain))
.arg(type)
.arg(*qMessage);
fprintf(out, "%s", qPrintable(messageToDisplay));
fflush(out);
}

View file

@ -24,22 +24,38 @@
#include <QMetaMethod>
#include <QObject>
// =============================================================================
#include <linphone++/linphone.hh>
// =============================================================================
//
// Qt SDK
//fatal | |
// -- *----------> |
// | |
// | | <--------- *
// | | |
// -> | |
// V V
// OUT FILE
//
// Only one instance. Use getInstance() and logReceived() to bind logs coming from Qt.
class QtLogger : public QObject {
Q_OBJECT
public:
QtLogger(QObject *parent = nullptr);
~QtLogger();
static QtLogger *getInstance();
static void onQtLog(QtMsgType type, const QMessageLogContext &context, const QString &msg);
static void enableVerbose(bool verbose);
static void enableQtOnly(bool qtOnly);
void printLog(QString * qMessage, const std::string &domain, linphone::LogLevel level, const std::string &message);
// Log Sources
static void onQtLog(QtMsgType type, const QMessageLogContext &context, const QString &msg);
void onLinphoneLog(const std::string &domain, linphone::LogLevel level, const std::string &message);
signals:
void logReceived(QtMsgType type, QString contextFile, int contextLine, QString msg);
void qtLogReceived(QtMsgType type, QString contextFile, int contextLine, QString msg);
void requestVerboseEnabled(bool verbose);
void requestQtOnlyEnabled(bool qtOnly);
};

View file

@ -19,7 +19,10 @@ int main(int argc, char *argv[]) {
}
}
int result = app.exec();
int result = 0;
while (result >= 0) {
result = app.exec();
}
app.clean();
return result;
}

View file

@ -20,96 +20,14 @@
#include "LoggerListener.hpp"
#include <QCoreApplication>
#include <QDateTime>
#include <QMessageBox>
#include <QString>
#include "tool/Constants.hpp"
#include "tool/Utils.hpp"
// =============================================================================
#if defined(__linux__) || defined(__APPLE__)
#define BLUE "\x1B[1;34m"
#define YELLOW "\x1B[1;33m"
#define GREEN "\x1B[1;32m"
#define PURPLE "\x1B[1;35m"
#define RED "\x1B[1;31m"
#define RESET "\x1B[0m"
#else
#define BLUE ""
#define YELLOW ""
#define GREEN ""
#define PURPLE ""
#define RED ""
#define RESET ""
#endif // if defined(__linux__) || defined(__APPLE__)
// -----------------------------------------------------------------------------
static inline QByteArray getFormattedCurrentTime() {
return QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz").toLocal8Bit();
LoggerListener::LoggerListener() : QObject(nullptr) {
}
// -----------------------------------------------------------------------------
LoggerListener::LoggerListener() {
}
void LoggerListener::onLogMessageWritten(const std::shared_ptr<linphone::LoggingService> &,
void LoggerListener::onLogMessageWritten(const std::shared_ptr<linphone::LoggingService> &logService,
const std::string &domain,
linphone::LogLevel level,
const std::string &message) {
bool isAppLog = domain == Constants::AppDomain;
if (!mVerboseEnabled || (!isAppLog && mQtOnlyEnabled)) return;
FILE *out = stdout;
// TypeColor Date SourceColor [Domain] TypeColor Type Reset Message
QString format = "%1 %2 %3[%4]%1 %5" RESET " %6\n";
QString colorType;
QString type;
QString qMessage = Utils::coreStringToAppString(message);
switch (level) {
case linphone::LogLevel::Debug:
colorType = GREEN;
type = "DEBUG";
break;
case linphone::LogLevel::Trace:
colorType = BLUE;
type = "TRACE";
break;
case linphone::LogLevel::Message:
colorType = BLUE;
type = "MESSAGE";
break;
case linphone::LogLevel::Warning:
colorType = RED;
type = "WARNING";
out = stderr;
break;
case linphone::LogLevel::Error:
colorType = RED;
type = "ERROR";
out = stderr;
break;
case linphone::LogLevel::Fatal:
colorType = RED;
type = "FATAL";
out = stderr;
break;
}
QString messageToDisplay = format.arg(colorType)
.arg(getFormattedCurrentTime())
.arg(isAppLog ? PURPLE : YELLOW)
.arg(Utils::coreStringToAppString(domain))
.arg(type)
.arg(qMessage);
fprintf(out, "%s", qPrintable(messageToDisplay));
fflush(out);
if (level == linphone::LogLevel::Fatal) {
QMetaObject::invokeMethod(qApp, [qMessage]() {
QMessageBox::critical(nullptr, EXECUTABLE_NAME " will crash", qMessage);
std::terminate();
});
}
emit logReceived(logService, domain, level, message);
}

View file

@ -21,19 +21,23 @@
#ifndef LOGGER_LISTENER_H_
#define LOGGER_LISTENER_H_
#include <QMutex>
#include <QObject>
#include <memory>
#include <linphone++/linphone.hh>
// =============================================================================
class LoggerListener : public linphone::LoggingServiceListener {
class LoggerListener : public QObject, public linphone::LoggingServiceListener {
Q_OBJECT
public:
LoggerListener();
bool mVerboseEnabled = false;
bool mQtOnlyEnabled = false;
static QString printLog(bool isAppLog, const std::string &domain, linphone::LogLevel level, const std::string &message);
signals:
void logReceived(const std::shared_ptr<linphone::LoggingService> &logService,
const std::string &domain,
linphone::LogLevel level,
const std::string &message);
private:
virtual void onLogMessageWritten(const std::shared_ptr<linphone::LoggingService> &logService,
const std::string &domain,

View file

@ -19,9 +19,9 @@
*/
#include <QDateTime>
#include <QLoggingCategory>
#include <QMessageBox>
#include <QString>
#include <linphone++/linphone.hh>
#include "config.h"
@ -35,23 +35,26 @@
// -----------------------------------------------------------------------------
LoggerModel::LoggerModel(QObject *parent) : QObject(parent) {
connect(QtLogger::getInstance(), &QtLogger::logReceived, this, &LoggerModel::onLog, Qt::QueuedConnection);
connect(QtLogger::getInstance(), &QtLogger::qtLogReceived, this, &LoggerModel::onQtLog, Qt::QueuedConnection);
connect(QtLogger::getInstance(), &QtLogger::requestVerboseEnabled, this, &LoggerModel::enableVerbose,
Qt::QueuedConnection);
connect(QtLogger::getInstance(), &QtLogger::requestQtOnlyEnabled, this, &LoggerModel::enableQtOnly,
Qt::QueuedConnection);
connect(this, &LoggerModel::linphoneLogReceived, QtLogger::getInstance(), &QtLogger::onLinphoneLog,
Qt::QueuedConnection);
}
LoggerModel::~LoggerModel() {
linphone::LoggingService::get()->removeListener(mListener);
}
bool LoggerModel::isVerbose() const {
return mListener ? mListener->mVerboseEnabled : false;
return mVerboseEnabled;
}
void LoggerModel::enableVerbose(bool verbose) {
if (mListener && mListener->mVerboseEnabled != verbose) {
mListener->mVerboseEnabled = verbose;
if (mVerboseEnabled != verbose) {
mVerboseEnabled = verbose;
emit verboseEnabledChanged();
}
}
@ -62,24 +65,21 @@ void LoggerModel::enableFullLogs(const bool &full) {
}
bool LoggerModel::qtOnlyEnabled() const {
return mListener ? mListener->mQtOnlyEnabled : false;
return mQtOnlyEnabled;
}
void LoggerModel::enableQtOnly(const bool &enable) {
if (mListener) {
if (mListener->mQtOnlyEnabled != enable) {
mListener->mQtOnlyEnabled = enable;
auto service = linphone::LoggingService::get();
if (service) service->setDomain(enable ? Constants::AppDomain : "");
emit qtOnlyEnabledChanged();
}
if (mQtOnlyEnabled != enable) {
mQtOnlyEnabled = enable;
auto service = linphone::LoggingService::get();
if (service) service->setDomain(enable ? Constants::AppDomain : "");
emit qtOnlyEnabledChanged();
}
}
// -----------------------------------------------------------------------------
// Called from Qt
void LoggerModel::onLog(QtMsgType type, QString contextFile, int contextLine, QString msg) {
connect(this, &LoggerModel::logReceived, this, &LoggerModel::onLog, Qt::QueuedConnection);
void LoggerModel::onQtLog(QtMsgType type, QString contextFile, int contextLine, QString msg) {
auto service = linphone::LoggingService::get();
QString contextStr = "";
#ifdef QT_MESSAGELOGCONTEXT
@ -116,6 +116,16 @@ void LoggerModel::onLog(QtMsgType type, QString contextFile, int contextLine, QS
}
}
// Call from Linphone
void LoggerModel::onLinphoneLog(const std::shared_ptr<linphone::LoggingService> &,
const std::string &domain,
linphone::LogLevel level,
const std::string &message) {
bool isAppLog = domain == Constants::AppDomain;
if (!mVerboseEnabled || (!isAppLog && mQtOnlyEnabled)) return;
emit linphoneLogReceived(domain, level, message);
}
// -----------------------------------------------------------------------------
void LoggerModel::enable(bool status) {
@ -134,7 +144,7 @@ void LoggerModel::init(const std::shared_ptr<linphone::Config> &config) {
void LoggerModel::init() {
QLoggingCategory::setFilterRules("qt.qml.connections.warning=false");
mListener = std::make_shared<LoggerListener>();
connect(mListener.get(), &LoggerListener::logReceived, this, &LoggerModel::onLinphoneLog);
{
std::shared_ptr<linphone::LoggingService> loggingService = linphone::LoggingService::get();
loggingService->setDomain(Constants::AppDomain);

View file

@ -45,17 +45,22 @@ public:
void init();
void init(const std::shared_ptr<linphone::Config> &config);
static void onQtLog(QtMsgType type, const QMessageLogContext &context, const QString &msg);
public slots:
void onLog(QtMsgType type, QString file, int contextLine, QString msg);
void onQtLog(QtMsgType type, QString file, int contextLine, QString msg);// Received from Qt
void onLinphoneLog(const std::shared_ptr<linphone::LoggingService> &,
const std::string &domain,
linphone::LogLevel level,
const std::string &message);// Received from SDK
signals:
void logReceived(QtMsgType type, QString contextFile, int contextLine, QString msg);
void linphoneLogReceived(const std::string &domain, linphone::LogLevel level, const std::string &message); // Send to Qt
void verboseEnabledChanged();
void qtOnlyEnabledChanged();
private:
static void log(QtMsgType type, const QMessageLogContext &context, const QString &msg);
bool mVerboseEnabled = false;
bool mQtOnlyEnabled = false;
std::shared_ptr<LoggerListener> mListener;
};