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 handler LogManager::RedirectToStdout and LogManager::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
})
}
}
}