Best approach for viewing C++ logging in QML
-
Hi! I'm on in developing desktop application, the UI part is constructed in QML and logical backend is C++.
In C++ backend, my apps write all kind of logs such as output of qDebug() or qCritial() to a file. I implemented it with qInstallMessageHandler. In short, all C++'s debugging messages are redirected to a file, as one line as a message.
Now I want to show the content of this log file into my QML windows, in real-time(like the output console of ordinary IDE). I'm beginner so it's hart to me to configure out what is the best approach for handling my situations.
Below are two approaches what I thoughts:Approach 1. My QML window reads the log file, and prints it's content in it's text editor or whatever which can show some text. When the C++ backend appends a log message to the file, the message also needs to be printed in QML window, so the QML window might be reading the file periodically. Right?
Approach 2. Make a string buffer in C++ side and bind it in QML as a property. When C++ backend append a log message to the file, it also push the message into the string buffer. Now my QML window only print the content of the string buffer, without accessing the log file.
Which approach is looks better? or any advice will appropriate. Thanks.
-
I did it with second approach, but it's not a string buffer but a log emitter, which just store latest log and alerts to QML by signal.
The message handlerLogManager::RedirectToStdout
andLogManager::RedirectToFile
change the log emitter when called./// LogManager.hpp #ifndef LOGMANAGER_HPP #define LOGMANAGER_HPP #include <QObject> #include <QString> #include <QDateTime> #include <QFile> #include <QMutex> class LogEmitter : public QObject { friend class LogManager; Q_OBJECT Q_PROPERTY(QString time MEMBER m_Time NOTIFY changed) Q_PROPERTY(QString type MEMBER m_Type NOTIFY changed) Q_PROPERTY(QString message MEMBER m_Message NOTIFY changed) public: explicit LogEmitter(QObject *parent = nullptr) {} void Set(const QString &Time, const QString &Type, const QString &Message) { m_Time = Time; m_Type = Type; m_Message = Message; m_Full = m_Time + " " + m_Type + ": " + m_Message; emit changed(); } QString GetString() { return m_Full; } signals: void changed(); private: QString m_Time; QString m_Type; QString m_Message; QString m_Full; }; class LogManager : public QObject { Q_OBJECT public: static LogManager &Initialize(); LogEmitter *GetEmitter(); private: static void RedirectToStdout(QtMsgType type, const QMessageLogContext &context, const QString &msg); static void RedirectToFile(QtMsgType type, const QMessageLogContext &context, const QString &msg); private: explicit LogManager(QObject *parent = nullptr); private: static LogEmitter *m_LogEmitter; }; #endif // LOGMANAGER_HPP
#include "LogManager.hpp" LogEmitter *LogManager::m_LogEmitter = new LogEmitter; namespace LogManagerVars { QDateTime DateTime; QFile File; }; LogManager &LogManager::Initialize() { LogManager *SingletonObject = new LogManager; if (qEnvironmentVariableIsEmpty(qgetenv("QTDIR"))) // If Qt creator then not write to file { qInstallMessageHandler(RedirectToStdout); } else { LogManagerVars::DateTime = QDateTime::currentDateTime(); LogManagerVars::File.setFileName(LogManagerVars::DateTime.toString("yyyy.MM.dd") + ".log"); qInstallMessageHandler(RedirectToFile); } return *SingletonObject; } LogEmitter *LogManager::GetEmitter() { return m_LogEmitter; } void LogManager::RedirectToStdout(QtMsgType type, const QMessageLogContext &context, const QString &msg) { QByteArray localMsg = msg.toLocal8Bit(); QString TypeStr; switch (type) { case QtDebugMsg: fprintf(stderr, "Debug: %s \n", localMsg.constData()); TypeStr = "DEBUG"; break; case QtInfoMsg: fprintf(stderr, "Info: %s \n", localMsg.constData()); TypeStr = "INFO"; break; case QtWarningMsg: fprintf(stderr, "Warning: %s (%s:%u)\n", localMsg.constData(), context.file, context.line); TypeStr = "WARNING"; break; case QtCriticalMsg: fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); TypeStr = "ERROR"; break; case QtFatalMsg: fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); TypeStr = "ERROR"; abort(); } m_LogEmitter->Set(QDateTime::currentDateTime().toString("[hh:mm:ss]"), TypeStr, msg); } void LogManager::RedirectToFile(QtMsgType type, const QMessageLogContext &context, const QString &msg) { if (type != QtInfoMsg && type != QtCriticalMsg) return; static QMutex Mutex; QMutexLocker lock(&Mutex); if (!LogManagerVars::File.isOpen()) if (!LogManagerVars::File.open(QIODevice::Append | QIODevice::Text)) return; m_LogEmitter->Set(QDateTime::currentDateTime().toString("[hh:mm:ss]"), (type == QtInfoMsg ? "INFO" : "ERROR"), msg); QString Output = m_LogEmitter->GetString() + "\n"; LogManagerVars::File.write(Output.toUtf8()); LogManagerVars::File.close(); } LogManager::LogManager(QObject *parent) { }
/// main.cpp #include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include "LogManager.hpp" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; const QUrl url(QStringLiteral("qrc:/src/qml/MainWindow.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); LogManager *logger = &LogManager::Initialize(); LogEmitter *log = logger->GetEmitter(); engine.rootContext()->setContextProperty("log", log); engine.load(url); return app.exec(); }
/// QML Popup { visible: false modal: true focus: false closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent padding: 10 ListModel { id: listModel function push(newElement) { if (listModel.count >= 50) listModel.remove(0); listModel.append(newElement); } } Component { id: delegate TextInput { selectByMouse: true text: timeStr + " " + typeStr + ": " + messageStr color: typeStr == "INFO" ? "black" : (typeStr == "ERROR" ? "red" : "blue") } } ListView { id:lst height: parent.height*0.8 width: parent.width/2 anchors.verticalCenter: parent.verticalCenter model: listModel delegate: delegate } Connections { target: log onChanged: { listModel.push({ timeStr: log.time, typeStr: log.type, messageStr: log.message }) } } }