查看完整版本: [-- Qt编写调试日志输出类带网络转发(开源) --]

QTCN开发网 -> Qt代码秀 -> Qt编写调试日志输出类带网络转发(开源) [打印本页] 登录 -> 注册 -> 回复主题 -> 发表主题

liudianwu 2019-03-10 20:48

Qt编写调试日志输出类带网络转发(开源)

用qt开发商业程序已经九年了,陆陆续续开发过至少几十个程序,除了一些算不算项目的小工具外,大部分的程序都需要有个日志的输出功能,希望可以将程序的运行状态存储到文本文件或者数据库或者做其他处理等,qt对这个日志输出也做了很好的封装,在Qt4是qInstallMsgHandler,Qt5里边是qInstallMessageHandler,有了这个神器,只要在你的项目中所有qdebug qinfo等输出的日志信息,都会重定向接收到,网上大部分人写的demo都是接收到输出打印日志存储到文本文件,其实这就带给很多人误解,容易产生以为日志只能输出到文本文件,其实安装了日志钩子以后,拿到了所有调试打印信息,你完全可以用来存储到数据库+html有颜色区分格式的文件+网络转发输出(尤其适用于嵌入式linux无界面程序,现场不方便外接调试打印的设备)。
做过的这么多项目中,Qt4和Qt5的都有,我一般保留四个版本,4.8.7,为了兼容qt4, 5.7.0,最后的支持XP的版本, 最新的长期支持版本5.9.7 最高的新版本5.12。毫无疑问,我要封装的这个日志类,也要支持4+5的,而且提供友好的接口。
1:支持动态启动和停止。
2:支持日志存储的目录。
3:支持网络发出打印日志。
4:支持Qt4+Qt5。开箱即用。
5:支持多线程。
6:使用做到最简单,start即可。
完整代码下载:[attachment=19986]
网络转发效果图:
[attachment=19987]

完整代码:
  1. #ifndef SAVELOG_H
    #define SAVELOG_H

    #include <QObject>

    class QFile;
    class QTcpSocket;
    class QTcpServer;

    #ifdef quc
    #if (QT_VERSION < QT_VERSION_CHECK(5,7,0))
    #include <QtDesigner/QDesignerExportWidget>
    #else
    #include <QtUiPlugin/QDesignerExportWidget>
    #endif

    class QDESIGNER_WIDGET_EXPORT SaveLog : public QObject
    #else
    class SaveLog : public QObject
    #endif

    {
        Q_OBJECT
    public:
        static SaveLog *Instance();
        explicit SaveLog(QObject *parent = 0);
        ~SaveLog();

    private:
        static QScopedPointer<SaveLog> self;

        //文件对象
        QFile *file;
        //是否重定向到网络
        bool toNet;
        //日志文件路径
        QString path;
        //日志文件名称
        QString name;
        //日志文件完整名称
        QString fileName;

    signals:
        void send(const QString &content);

    public slots:
        //启动日志服务
        void start();
        //暂停日志服务
        void stop();
        //保存日志
        void save(const QString &content);

        //设置是否重定向到网络
        void setToNet(bool toNet);
        //设置日志文件存放路径
        void setPath(const QString &path);
        //设置日志文件名称
        void setName(const QString &name);

    };

    class SendLog : public QObject
    {
        Q_OBJECT
    public:
        static SendLog *Instance();
        explicit SendLog(QObject *parent = 0);
        ~SendLog();

    private:
        static QScopedPointer<SendLog> self;
        QTcpSocket *socket;
        QTcpServer *server;

    private slots:
        void newConnection();

    public slots:
        //发送日志
        void send(const QString &content);
    };

    #endif // SAVELOG_H

    #include "savelog.h"
    #include "qmutex.h"
    #include "qfile.h"
    #include "qtcpsocket.h"
    #include "qtcpserver.h"
    #include "qdatetime.h"
    #include "qapplication.h"
    #include "qtimer.h"
    #include "qstringlist.h"

    #define QDATE qPrintable(QDate::currentDate().toString("yyyy-MM-dd"))

    //日志重定向
    #if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
    void Log(QtMsgType type, const char *msg)
    #else
    void Log(QtMsgType type, const QMessageLogContext &, const QString &msg)
    #endif
    {
        //加锁,防止多线程中qdebug太频繁导致崩溃
        QMutex mutex;
        QMutexLocker locker(&mutex);
        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;
        }

        SaveLog::Instance()->save(content);
    }

    QScopedPointer<SaveLog> SaveLog::self;
    SaveLog *SaveLog::Instance()
    {
        if (self.isNull()) {
            QMutex mutex;
            QMutexLocker locker(&mutex);
            if (self.isNull()) {
                self.reset(new SaveLog);
            }
        }

        return self.data();
    }

    SaveLog::SaveLog(QObject *parent) : QObject(parent)
    {
        //必须用信号槽形式,不然提示 QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread
        //估计日志钩子可能单独开了线程
        connect(this, SIGNAL(send(QString)), SendLog::Instance(), SLOT(send(QString)));

        file = new QFile(this);
        toNet = false;
        //默认取应用程序根目录
        path = qApp->applicationDirPath();
        //默认取应用程序可执行文件名称
        QString str = qApp->applicationFilePath();
        QStringList list = str.split("/");
        name = list.at(list.count() - 1).split(".").at(0);
        fileName = "";
    }

    SaveLog::~SaveLog()
    {
        file->close();    
    }

    //安装日志钩子,输出调试信息到文件,便于调试
    void SaveLog::start()
    {
    #if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
        qInstallMsgHandler(Log);
    #else
        qInstallMessageHandler(Log);
    #endif
    }

    //卸载日志钩子
    void SaveLog::stop()
    {
    #if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
        qInstallMsgHandler(0);
    #else
        qInstallMessageHandler(0);
    #endif
    }

    void SaveLog::save(const QString &content)
    {
        //如果重定向输出到网络则通过网络发出去,否则输出到日志文件
        if (toNet) {
            emit send(content);
        } else {
            //方法改进:之前每次输出日志都打开文件,改成只有当日期改变时才新建和打开文件
            QString fileName = QString("%1/%2_log_%3.txt").arg(path).arg(name).arg(QDATE);
            if (this->fileName != fileName) {
                this->fileName = fileName;
                if (file->isOpen()) {
                    file->close();
                }

                file->setFileName(fileName);
                file->open(QIODevice::WriteOnly | QIODevice::Append | QFile::Text);
            }

            QTextStream logStream(file);
            logStream << content << "\n";
        }
    }

    void SaveLog::setToNet(bool toNet)
    {
        this->toNet = toNet;
    }

    void SaveLog::setPath(const QString &path)
    {
        this->path = path;
    }

    void SaveLog::setName(const QString &name)
    {
        this->name = name;
    }


    //网络发送日志数据类
    QScopedPointer<SendLog> SendLog::self;
    SendLog *SendLog::Instance()
    {
        if (self.isNull()) {
            QMutex mutex;
            QMutexLocker locker(&mutex);
            if (self.isNull()) {
                self.reset(new SendLog);
            }
        }

        return self.data();
    }

    SendLog::SendLog(QObject *parent)
    {
        socket = NULL;
        server = new QTcpServer(this);
        connect(server, SIGNAL(newConnection()), this, SLOT(newConnection()));

        int listenPort = 6000;
    #if (QT_VERSION > QT_VERSION_CHECK(5,0,0))
        server->listen(QHostAddress::AnyIPv4, listenPort);
    #else
        server->listen(QHostAddress::Any, listenPort);
    #endif
    }

    SendLog::~SendLog()
    {
        if (socket != NULL) {
            socket->disconnectFromHost();
        }

        server->close();
    }

    void SendLog::newConnection()
    {
        while (server->hasPendingConnections()) {
            socket = server->nextPendingConnection();
        }
    }

    void SendLog::send(const QString &content)
    {
        if (socket != NULL && socket->isOpen()) {
            socket->write(content.toUtf8());
            socket->flush();
        }
    }




aiwen.zhang 2019-03-10 21:04
太强了。

jokerhuang 2019-03-11 11:06
我想说,真的开心死我了!从楼主的分享代码里学到了好多编程知识!

九重水 2019-03-11 13:43
九年?
哪年毕业的?

richards 2019-03-12 08:53
这个不错。我用过最好用的 就是 android的 monitor logcat

ccazqyy 2019-03-12 09:43
          

liuchangyin 2019-03-12 10:18
很实用

kaikai_king 2019-03-14 18:33
大佬  带带我

vaehate 2019-03-25 10:52
终于等到大佬发帖了

mengjin 2019-03-26 21:23
学习了

liuyuanan 2019-04-24 20:26
我们现在使用的日志库是qt4log开源库,唯一的遗憾是不能通过网络转发

greensky10 2019-05-14 18:07
感谢刘大佬分享!!!

0011411 2019-11-19 13:27
              

lizn520 2020-02-02 23:48
您好,我阅读完您这个代码有个疑问,
void Log(QtMsgType type, const QMessageLogContext &, const QString &msg)
#endif
{
    //加锁,防止多线程中qdebug太频繁导致崩溃
    QMutex mutex;
    QMutexLocker locker(&mutex);
    QString content;
...
请问这个mutex在这起作用了吗,按我的理解这里应该改成 static QMutex mutex才对呀,

104399 2020-05-05 14:03
多谢

toby520 2020-05-18 10:33
lizn520:您好,我阅读完您这个代码有个疑问,
void Log(QtMsgType type, const QMessageLogContext &, const QString &msg)
#endif
{
    //加锁,防止多线程中qdebug太频繁导致崩溃
....... (2020-02-02 23:48) 

理解的很对 需要优化

zipl1985 2022-07-23 12:30
学习了,太感谢了


查看完整版本: [-- Qt编写调试日志输出类带网络转发(开源) --] [-- top --]



Powered by phpwind v8.7 Code ©2003-2011 phpwind
Gzip disabled