基于Qt实现日志打印系统

 更新时间:2023年12月13日 08:28:17   作者:但行技术  
这篇文章主要为大家详细介绍了如何利用Qt开发一个日志打印系统,可以实现打印日志按日期、大小保存,过期删除,窗口实时显示日志,网络传输日志远程调试,需要的可以参考下

Qt 打印日志系统,实现打印日志保存,窗口显示日志,网络传输日志

一套成熟的系统往往都有相应的日志系统,以便调试查看

Qt的打印信息默认处理程序将消息打印到X11下的标准输出或Windows下的调试器,其实我们可以自己处理相关打印信息,可以选择保存下来、或者界面显示,网络传输等等,qInstallMessageHandler(QtMessageHandler handler)可以帮助我们快速实现我们的日志系统

实现方法也简单,下面是帮助文档里面为我们提供的日志

  #include <qapplication.h>
  #include <stdio.h>
  #include <stdlib.h>

  void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
  {
      QByteArray localMsg = msg.toLocal8Bit();
      switch (type) {
      case QtDebugMsg:
          fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
          break;
      case QtInfoMsg:
          fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
          break;
      case QtWarningMsg:
          fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
          break;
      case QtCriticalMsg:
          fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
          break;
      case QtFatalMsg:
          fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
          abort();
      }
  }

  int main(int argc, char **argv)
  {
      qInstallMessageHandler(myMessageOutput);
      QApplication app(argc, argv);
      ...
      return app.exec();
  }

下面稍微改造,符合我们实际使用的三种日志形式,有使用到c++11 lambda表达式方式安装,看不惯可以按例子编写,都一样的

 enum HandlerFlag
    {
        eSave   = 0x0001,
        eNet    = 0x0002,
        eNewLog = 0x0004,
    };
    Q_DECLARE_FLAGS(HandlerFlags, HandlerFlag)

列出主要实现代码

Q_DECLARE_OPERATORS_FOR_FLAGS(QLog::HandlerFlags)

void QLog::handler(HandlerFlags flags)
{
    {
        if(d->m_flags == flags){
            return;
        }
        QMutexLocker locker(&(d->m_handlermutex));
        qInstallMessageHandler(nullptr);
        d->m_flags = flags;
        if(!d->m_flags.testFlag(eNet)){
            if(d->mp_toNet){
                d->mp_toNet->deleteLater();
                d->mp_toNet = nullptr;
            }
            if(d->mp_netThread){
                d->mp_netThread->quit();
                d->mp_netThread->wait();
                d->mp_netThread->deleteLater();
                d->mp_netThread = nullptr;
            }
        }
        if(!d->m_flags.testFlag(eSave)){
            if (d->m_file.isOpen()) {
                d->m_file.close();
            }
            d->m_fileName.clear();
        }
        if(!d->m_flags){
            return;
        }
    }

    qInstallMessageHandler(
                [](QtMsgType type, const QMessageLogContext &context, const QString &msg)
    {
        Q_UNUSED(context)
        QLogPrivate* d = QLog::instance()->d;
        QMutexLocker locker(&(d->m_handlermutex));
        if(!d->m_flags){
            return;
        }
        QString content;
        switch (type) {
        case QtDebugMsg:
            content = QString("%1").arg(msg);
            break;
        case QtWarningMsg:
            content = QString("%1").arg(msg);
            break;
        case QtCriticalMsg:
            content = QString("%1").arg(msg);
            break;
        case QtFatalMsg:
            content = QString("%1").arg(msg);
            break;
        case QtInfoMsg:
            content = QString("%1").arg(msg);
            break;
        }

        if(d->m_flags.testFlag(eSave)){

            QString fileName = QString("%1/%2_log_%3.txt").arg(d->m_path).arg(d->m_appname).arg(QDate::currentDate().toString("yyyy-MM-dd"));
            if (d->m_fileName != fileName) {
                d->m_fileName = fileName;
                if (d->m_file.isOpen()) {
                    d->m_file.close();
                }
                QLog::instance()->autoDeleteLog();
                d->m_file.setFileName(fileName);
                d->m_file.open(QIODevice::WriteOnly | QIODevice::Append | QFile::Text);
            }

            QTextStream logStream(&(d->m_file));
            logStream << content << "\n";
            if(d->m_file.size() > d->m_maxLogsize){
                QString fileName = QString("%1/%2_log_%3.txt").arg(d->m_path).arg(d->m_appname).arg(QDateTime::currentDateTime().toString("yyyy-MM-dd-hhmmss"));
                d->m_file.rename(fileName);
                d->m_file.close();
                d->m_fileName.clear();
            }
        }

        if(d->m_flags.testFlag(eNet)){
            if(nullptr == d->mp_toNet){
                QLog::instance()->createLogNet();
            }
            QLog::instance()->toNet(content);
        }

        if(d->m_flags.testFlag(eNewLog)){
            QLog::instance()->newLog(content);
        }

    });
}

简要说明下其中的部分代码

日志安装,使用到c++11 lambda表达式方式安装,看不惯可以按例子编写,都一样的

 qInstallMessageHandler(
                [](QtMsgType type, const QMessageLogContext &context, const QString &msg)
    {
    });

文件保存:

1.每次又新消息都会进入我们大安装函数中来,我们可以选择是否保存到文件当中去

2.在每次保存之前先判断下当前的文件名是否为当前日期,不是的话关闭旧的文件句柄,在按当前日期打开新的文件

3.如果文件大小大于最大值,比如1000M的大小(file.size() > d->m_maxLogsize),则重名当前文件,再打开一个新文件,防止日志文件太大

QString fileName = QString("%1/%2_log_%3.txt").arg(d->m_path).arg(d->m_appname).arg(QDate::currentDate().toString("yyyy-MM-dd"));
            if (d->m_fileName != fileName) {
                d->m_fileName = fileName;
                if (d->m_file.isOpen()) {
                    d->m_file.close();
                }
                QLog::instance()->autoDeleteLog();
                d->m_file.setFileName(fileName);
                d->m_file.open(QIODevice::WriteOnly | QIODevice::Append | QFile::Text);
            }

            QTextStream logStream(&(d->m_file));
            logStream << content << "\n";
            if(d->m_file.size() > d->m_maxLogsize){
                QString fileName = QString("%1/%2_log_%3.txt").arg(d->m_path).arg(d->m_appname).arg(QDateTime::currentDateTime().toString("yyyy-MM-dd-hhmmss"));
                d->m_file.rename(fileName);
                d->m_file.close();
                d->m_fileName.clear();
            }

过期删除,比如大于d->m_saveday (30天)的就给他删掉

void QLog::autoDeleteLog()
{
    qint64 var = d->m_saveday * (-1);
    QDateTime deleteday = QDateTime::currentDateTime().addDays(var);
    QDir dir(d->m_path);
    QStringList filters;
    filters<<"*.txt";
    dir.setNameFilters(filters);

    QFileInfoList  filelst = dir.entryInfoList();
    if( filelst.count()< d->m_saveday){
        return;
    }

    foreach (QFileInfo mItem, filelst)
    {
        if(mItem.fileName().contains(d->m_appname) && mItem.created() <= deleteday ){
            QFile::remove(mItem.absoluteFilePath());
        }
    }
}

网络传输,首先定义日志服务器

class QLogToNet:public QObject
{
    Q_OBJECT
private:
    QLogToNet(quint16 port,QObject* parent = nullptr);
    ~QLogToNet();
public slots:
    void slot_toNet(QString log);
private:
    class QLogToNetPrivate;
    QLogToNetPrivate* const d;
    friend class QLog;
};
class QLogToNet::QLogToNetPrivate
{
public:
    QMutex             m_mutex;
    QTcpServer*        mp_server;
    QList<QTcpSocket*> m_lstTcpSocket;
};

QLogToNet::QLogToNet(quint16 port, QObject *parent)
    :QObject(parent),d(new QLogToNetPrivate())
{
    d->mp_server = new QTcpServer(this);
    connect(d->mp_server,&QTcpServer::newConnection,this,[=](){
        QMutexLocker locker(&d->m_mutex);
        while (d->mp_server->hasPendingConnections()) {
            QTcpSocket* pTcpSocket = d->mp_server->nextPendingConnection();
            if(pTcpSocket){
                connect(pTcpSocket,&QTcpSocket::disconnected,this,[=]()
                {
                    QMutexLocker locker(&d->m_mutex);
                    QTcpSocket* pTcpSocket = qobject_cast<QTcpSocket*>(sender());
                    if(pTcpSocket){
                        d->m_lstTcpSocket.removeAll(pTcpSocket);
                        pTcpSocket->deleteLater();
                    }
                });
                d->m_lstTcpSocket<<pTcpSocket;
            }
        }
    });
    d->mp_server->listen(QHostAddress::AnyIPv4,port);
}

QLogToNet::~QLogToNet()
{
    if(d->mp_server->isListening()){
        d->mp_server->close();
    }
    delete d;
}

void QLogToNet::slot_toNet(QString log)
{
    QMutexLocker locker(&d->m_mutex);
    foreach (QTcpSocket* pTcpSocket ,d->m_lstTcpSocket) {
        pTcpSocket->write(log.toUtf8());
        pTcpSocket->flush();
    }
}

然后使用该日志服务器,为了性能,顺便移动到线程中去了

void QLog::createLogNet()
{
    if(nullptr == d->mp_toNet){
        d->mp_toNet = new QLogToNet(d->m_netPort,nullptr);
        connect(this,&QLog::sig_toNet,d->mp_toNet,&QLogToNet::slot_toNet);
        d->mp_netThread = new QThread(this);
        d->mp_toNet->moveToThread(d->mp_netThread);
        d->mp_netThread->start();
    }
}

则在有新消息时,我们就可以发射一个信号出去了

void QLog::toNet(QString log)
{
    emit sig_toNet(log);
}

日志服务器怎么用呢,你可以随便找个TCP工具连上你的日志服务器的IP和端口,这样你就可以远程调试查看了,如果系统在外地或者比较恶劣的环境,那调试就真是美滋滋方便多了,而且还可以多人查看呢,是不是很美好,愿意的话还可以稍微改下服务端接收一些调试命令,比如打印一些内存信息返回回来,方便调试。

窗口显示,我们定义一个显示控件QTextEdit,和newlog相连,每次有新消息直接append就可以了

connect(QLog::instance(),&QLog::sig_newLog,ui->textEdit,&QTextEdit::append);

在QTextEdit上模糊收索消息,主要用到QSyntaxHighlighter

#ifndef MARKDOWNHIGHLIGHTER_H
#define MARKDOWNHIGHLIGHTER_H

#include <QSyntaxHighlighter>
#include <QTextEdit>
#include <QVector>

class MarkdownHighlighter : public QSyntaxHighlighter
{
    Q_OBJECT
public:
    MarkdownHighlighter(QTextEdit *parent = 0);
    void highlightBlock(const QString &text);
    void SetColorText(const QString &str, const QColor &color);
    void clearRules();
    int markCount() const;
    void setMarkCount(int markCount);
private:
    struct HighlightingRule
    {
        QRegExp pattern;
        QTextCharFormat format;
    };
    QVector<HighlightingRule> m_highlightingRules;
};

#endif // MARKDOWNHIGHLIGHTER_H

实现

#include "markdownhighlighter.h"

MarkdownHighlighter::MarkdownHighlighter(QTextEdit *parent)
    :QSyntaxHighlighter(parent)
{
    this->setDocument(parent->document());
}

void MarkdownHighlighter::highlightBlock(const QString &text)
{
    foreach (HighlightingRule rule, m_highlightingRules)
    {
        QRegExp expression(rule.pattern);
        int index = text.indexOf(expression);
        while (index >= 0)
        {
            int length = expression.matchedLength();
            setFormat(index, length, rule.format);
            index = text.indexOf(expression, index + length);
        }
    }
}

void MarkdownHighlighter::SetColorText(const QString &str, const QColor &color)
{
    HighlightingRule rule;
    rule.pattern = QRegExp(str);
    QTextCharFormat format;
    format.setForeground(color);
    rule.format = format;
    m_highlightingRules.append(rule);
}

void MarkdownHighlighter::clearRules()
{
    m_highlightingRules.clear();
}

怎么用这个搜索类呢,我们可以在每次文字改变时,更新下搜索

void LogWidget::on_le_find_textChanged(const QString &)
{
    QString str = ui->le_find->text();
    if(str.isEmpty()){
        mp_findMark->clearRules();
        mp_findMark->rehighlight();
        return;
    }
    mp_findMark->clearRules();
    mp_findMark->SetColorText(str,Qt::red);
    mp_findMark->rehighlight();
}

罗里吧嗦了一堆,主要时利用qInstallMessageHandler(QtMessageHandler handler)来处理qt的打印信息,下面分享下一些比较常用的打印格式

#define LOG_DEBUG qDebug()    << qPrintable(QString("[%1 %2 %3 %4]:").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss")).arg(__FILE__).arg(__FUNCTION__).arg(__LINE__))
#define LOG_INFO  qInfo()     << qPrintable(QString("[%1]:").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss")))
#define LOG_WARN  qWarning()  << qPrintable(QString("[%1 %2 %3 %4]:").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss")).arg(__FILE__).arg(__FUNCTION__).arg(__LINE__))
#define LOG_CRIT  qCritical() << qPrintable(QString("[%1 %2 %3 %4]:").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss")).arg(__FILE__).arg(__FUNCTION__).arg(__LINE__))

以上就是基于Qt实现日志打印系统的详细内容,更多关于Qt打印日志的资料请关注脚本之家其它相关文章!

相关文章

  • C++实现聊天程序

    C++实现聊天程序

    这篇文章主要为大家详细介绍了C++实现类似QQ聊天程序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • C语言实现全排列算法模板的方法

    C语言实现全排列算法模板的方法

    这篇文章主要介绍了C语言实现全排列算法模板的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • C语言跳转浏览器打开指定URL的操作代码

    C语言跳转浏览器打开指定URL的操作代码

    这篇文章主要介绍了C语言跳转浏览器打开指定URL,该代码使用sprintf()函数将要打开的URL添加到一个系统命令中,然后使用system()函数调用该命令以默认浏览器打开URL,需要的朋友可以参考下
    2023-04-04
  • C++学习笔记之初始化列表

    C++学习笔记之初始化列表

    初始化列表是类中构造函数的一部分,用于实例化类中变量时赋初值,下面这篇文章主要给大家介绍了关于C++学习笔记之初始化列表的相关资料,需要的朋友可以参考下
    2023-04-04
  • 深入了解C++中基于模板的类型擦除

    深入了解C++中基于模板的类型擦除

    在C\C++中主要有三种类型擦除的方式:基于void*的类型擦除、面向对象的类型擦除和基于模板的类型擦除,本文主要为大家详细介绍基于模板的类型擦除的相关知识,需要的可以了解下
    2023-12-12
  • C语言去除相邻重复字符函数的实现方法

    C语言去除相邻重复字符函数的实现方法

    这篇文章主要介绍了C语言去除相邻重复字符函数的实现方法的相关资料,实现去重字符串相邻重复的字符,不相邻的不用去重的功能,需要的朋友可以参考下
    2017-08-08
  • c++中的4种类型转化方式详细解析

    c++中的4种类型转化方式详细解析

    const_cast该函数用于去除指针变量的常量属性,将它转换为一个对应指针类型的普通变量。反过来,也可以将一个非常量的指针变量转换为一个常指针变量
    2013-10-10
  • C++设计模式之组合模式(Composite)

    C++设计模式之组合模式(Composite)

    这篇文章主要为大家详细介绍了C++设计模式之组合模式Composite,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-04-04
  • 详解c++中的trait与policy模板技术

    详解c++中的trait与policy模板技术

    trait模板和policy模板技术是把模板的trait和policy这两个针对不同具体类型有变化的方面抽离出来形成两个独立的模板。由于trait和policy本身是模板,它的行为是可配置的,在模板中通过组合或者以模板实参传进来的方式使用trait和policy,就可以配置出不同的具体实现
    2021-06-06
  • C++ Boost System超详细讲解

    C++ Boost System超详细讲解

    Boost是为C++语言标准库提供扩展的一些C++程序库的总称。Boost库是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一,是为C++语言标准库提供扩展的一些C++程序库的总称
    2022-11-11

最新评论