Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Best approach for viewing C++ logging in QML

Best approach for viewing C++ logging in QML

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
2 Posts 1 Posters 1.7k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • W Offline
    W Offline
    wooseokyourself
    wrote on 24 Nov 2021, 02:54 last edited by wooseokyourself
    #1

    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.

    1 Reply Last reply
    0
    • W Offline
      W Offline
      wooseokyourself
      wrote on 24 Nov 2021, 08:46 last edited by
      #2

      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
                                 })
              }
          }
      }
      
      1 Reply Last reply
      1

      1/2

      24 Nov 2021, 02:54

      • Login

      • Login or register to search.
      1 out of 2
      • First post
        1/2
        Last post
      0
      • Categories
      • Recent
      • Tags
      • Popular
      • Users
      • Groups
      • Search
      • Get Qt Extensions
      • Unsolved