Search code examples
c++qtspdlog

Get log from spdlog in qtextedit object from qt


I am trying to redirect my log in a qtextedit windows. I use this post : Redirecting std::cout from DLL in a separate thread to QTextEdit.

Here is my test program :

q_debugstream.h

#ifndef ThreadLogStream_H
#define ThreadLogStream_H
#include <iostream>
#include <streambuf>
#include <string>
#include <QScrollBar>
#include "QTextEdit"
#include "QDateTime"

// MessageHandler
 /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
class MessageHandler : public QObject
{
    Q_OBJECT
    public :
        MessageHandler(QTextEdit *textEdit, QObject * parent = Q_NULLPTR) : QObject(parent), m_textEdit(textEdit){}

    public slots:
        void catchMessage(QString msg)
        {
           this->m_textEdit->append(msg);
        }
        /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
    private:
        QTextEdit * m_textEdit;
};
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/


class ThreadLogStream : public QObject, public std::basic_streambuf<char>
{
Q_OBJECT
public:
    ThreadLogStream(std::ostream &stream, QObject * parent = Q_NULLPTR) :QObject(parent), m_stream(stream)
    {
        m_old_buf = stream.rdbuf();
        stream.rdbuf(this);
    }

    ~ThreadLogStream()
    {
        // output anything that is left
        if (!m_string.empty())
        {
            emit sendLogString(QString::fromStdString(m_string));
        }
        m_stream.rdbuf(m_old_buf);
    }


protected:
    virtual int_type overflow(int_type v)
    {
        if (v == '\n')
        {
            emit sendLogString(QString::fromStdString(m_string));
            m_string.erase(m_string.begin(), m_string.end());
        }
        else
            m_string += v;
        return v;
    }

    virtual std::streamsize xsputn(const char *p, std::streamsize n)
    {
        m_string.append(p, p + n);
        long pos = 0;
        while (pos != static_cast<long>(std::string::npos))
        {
            pos = static_cast<long>(m_string.find('\n'));
            if (pos != static_cast<long>(std::string::npos))
            {
                std::string tmp(m_string.begin(), m_string.begin() + pos);
                emit sendLogString(QString::fromStdString(tmp));
                m_string.erase(m_string.begin(), m_string.begin() + pos + 1);
            }
        }
        return n;
    }

private:
    std::ostream &m_stream;
    std::streambuf *m_old_buf;
    std::string m_string;
signals:
    void sendLogString(const QString& str);
};
#endif // ThreadLogStream_H

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QDebug>
#include <QTextEdit>

#include "q_debugstream.h"
#include "spdlog/spdlog.h"
namespace Ui {
class MainWindow;
}

class ThreadWorker : public QObject
{
    Q_OBJECT
public:
    ThreadWorker()
    {
    }
private:

signals:

    void finished();

public slots:
    void doWork()
    {
        std::cout<< "Hello World2" <<std::endl;
        qDebug() << "Hello World2q" ;
        SPDLOG_INFO("Hello world2 spdlog");
        emit finished();
    }
};



class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

    // QMessage
     /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
   static  void QMessageOutput(QtMsgType , const QMessageLogContext &, const QString &msg);
   /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/

public slots:
    void SingleThreadTest();
    void MultiThreadTest();



private:
    Ui::MainWindow *ui;

    // MessageHandler for display and ThreadLogStream for redirecting cout
     /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
    MessageHandler *msgHandler = Q_NULLPTR;
      ThreadLogStream* m_qd;
        /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QObject>
#include <QThread>

#include "spdlog/spdlog.h"

// Catch QMessage, redirect to cout
 /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
void MainWindow::QMessageOutput(QtMsgType , const QMessageLogContext &, const QString &msg)
{
   std::cout<<msg.toStdString().c_str()<<std::endl;
}
 /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/




MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
     ui->setupUi(this);

     // Set up ThreadLogStream, which redirect cout to signal sendLogString
     // Set up  MessageHandler,  wgucg catch message from sendLogString and Display
     /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
    m_qd = new ThreadLogStream(std::cout); //Redirect Console output to QTextEdit
    this->msgHandler = new MessageHandler(this->ui->textEdit, this);
    connect(m_qd, &ThreadLogStream::sendLogString, msgHandler, &MessageHandler::catchMessage);
    /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/


    connect(ui->pushButtonSingleThreadTest, SIGNAL(clicked()),this,SLOT(SingleThreadTest()));
    connect(ui->pushButtonMultiThreadTest, SIGNAL(clicked()),this,SLOT(MultiThreadTest()));
}



void MainWindow::SingleThreadTest()
{

    std::cout<< "Hello World1" <<std::endl;
    qDebug() << "Hello World1q" ;
    SPDLOG_INFO("Hello world1 spdlog");
}

void MainWindow::MultiThreadTest()
{
    QThread     *workerThread= new QThread;
    ThreadWorker *worker  = new ThreadWorker();
    worker->moveToThread(workerThread);
    connect(workerThread, SIGNAL(started()), worker, SLOT(doWork()));
    connect(worker, SIGNAL(finished()), workerThread, SLOT(quit()));

    connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
    connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
    workerThread->start();
}

MainWindow::~MainWindow()
{
    delete ui;
}

main.cpp

#include "mainwindow.h"
#include <QApplication>

#include "spdlog/spdlog.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    SPDLOG_INFO("Starting main");

    MainWindow w;
    w.show();

    // Setup QMessageCatch
     /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
     qInstallMessageHandler(MainWindow::QMessageOutput);
     /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
    return a.exec();
}

My problem : logs from SPDLOG_INFO appear in my console, but not in my qtextedit window. Obviously, the logs from spdlog are not catch by the function QMessageOutput from qInstallMessageHandler.

My question : Is it possible to catch the log from spdlog on a qtextedit box ?


Solution

  • In spdlog last versions have introduced qt_sinks feature. To use this include the header spdlog/sinks/qt_sinks then call a factory function (spdlog::qt_logger_mt("logger_name", ui->plainTextEdit)) that constructs st or mt(thread-safe) loggers. Also you are not limited to QTextEdit or QPlainTextEdit widgets so you can sink logs into QML, QUdpSocket and any custom widget or QObject based classes with your provided a slot name with a QString parameter.

    Usage

    custom_class.hpp

    #ifndef CUSTOM_CLASS_HPP
    #define CUSTOM_CLASS_HPP
    
    #include <QObject>
    
    class custom_class : public QObject
    {
        Q_OBJECT
    public:
        explicit custom_class(QObject *parent = nullptr);
    
    public slots:
        void log2qml(const QString &str); // slot for using spdlog with custom objects
    
    signals:
        void send2qml(const QString& str);
    
    };
    
    #endif // CUSTOM_CLASS_HPP
    

    custom_class.cpp

    #include "custom_class.hpp"
    
    custom_class::custom_class(QObject *parent) : QObject(parent)
    {
        
    }
    
    void custom_class::log2qml(const QString &str)
    {
        emit send2qml(str);
    }
    

    mainwindow.hpp:

    #include "custom_class.hpp"
    #include <QMainWindow>
    #include <QThread>
    
    #include <spdlog/sinks/qt_sinks.h>
    
    QT_BEGIN_NAMESPACE
    namespace Ui { class MainWindow; }
    QT_END_NAMESPACE
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
    
        void log();
    
    private:
        Ui::MainWindow *ui;
        custom_class c;
        QThread *th = nullptr;
        std::shared_ptr<spdlog::logger> logger = nullptr, logger_custom = nullptr;
    };
    #endif // MAINWINDOW_HPP
    

    mainwindow.cpp:

    #include "mainwindow.hpp"
    #include "ui_mainwindow.h"
    
    #include "spdlog/spdlog.h"
    
    
    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
          , ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        // logger write into ui->plainTextEdit
        logger = spdlog::qt_logger_mt("plaintextedit_logger", ui->plainTextEdit);
        
        // custom_logger write into qml or any custom object. 3rd param is slot name that passes QString param in custom_object
        logger_custom = spdlog::qt_logger_mt("custom_qml_logger", &c, "log2qml");
        
        // set default logger
        spdlog::set_default_logger(logger);
        log();
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    void MainWindow::log()
    {
        th = QThread::create([this] {
       
    
     for (int i = 0; i < 1000; ++i) {
            SPDLOG_INFO(i); // default logger write into ui->plainTextEdit
            logger_custom->warn(i); // custom_logger write into qml or any custom object
            std::this_thread::sleep_for(std::chrono::microseconds(1));
        }
    });
    
     th->start();
    }
    

    output: enter image description here