• 9953阅读
  • 16回复

Qt编写调试日志输出类带网络转发(开源) [复制链接]

上一主题 下一主题
离线liudianwu
 

只看楼主 倒序阅读 楼主  发表于: 2019-03-10
— 本帖被 20091001753 从 Qt 作品展 移动到本区(2020-01-14) —
用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即可。
完整代码下载: savelog.zip (6 K) 下载次数:886
网络转发效果图:


完整代码:
  1. #ifndef SAVELOG_H
  2. #define SAVELOG_H
  3. #include <QObject>
  4. class QFile;
  5. class QTcpSocket;
  6. class QTcpServer;
  7. #ifdef quc
  8. #if (QT_VERSION < QT_VERSION_CHECK(5,7,0))
  9. #include <QtDesigner/QDesignerExportWidget>
  10. #else
  11. #include <QtUiPlugin/QDesignerExportWidget>
  12. #endif
  13. class QDESIGNER_WIDGET_EXPORT SaveLog : public QObject
  14. #else
  15. class SaveLog : public QObject
  16. #endif
  17. {
  18.     Q_OBJECT
  19. public:
  20.     static SaveLog *Instance();
  21.     explicit SaveLog(QObject *parent = 0);
  22.     ~SaveLog();
  23. private:
  24.     static QScopedPointer<SaveLog> self;
  25.     //文件对象
  26.     QFile *file;
  27.     //是否重定向到网络
  28.     bool toNet;
  29.     //日志文件路径
  30.     QString path;
  31.     //日志文件名称
  32.     QString name;
  33.     //日志文件完整名称
  34.     QString fileName;
  35. signals:
  36.     void send(const QString &content);
  37. public slots:
  38.     //启动日志服务
  39.     void start();
  40.     //暂停日志服务
  41.     void stop();
  42.     //保存日志
  43.     void save(const QString &content);
  44.     //设置是否重定向到网络
  45.     void setToNet(bool toNet);
  46.     //设置日志文件存放路径
  47.     void setPath(const QString &path);
  48.     //设置日志文件名称
  49.     void setName(const QString &name);
  50. };
  51. class SendLog : public QObject
  52. {
  53.     Q_OBJECT
  54. public:
  55.     static SendLog *Instance();
  56.     explicit SendLog(QObject *parent = 0);
  57.     ~SendLog();
  58. private:
  59.     static QScopedPointer<SendLog> self;
  60.     QTcpSocket *socket;
  61.     QTcpServer *server;
  62. private slots:
  63.     void newConnection();
  64. public slots:
  65.     //发送日志
  66.     void send(const QString &content);
  67. };
  68. #endif // SAVELOG_H
  69. #include "savelog.h"
  70. #include "qmutex.h"
  71. #include "qfile.h"
  72. #include "qtcpsocket.h"
  73. #include "qtcpserver.h"
  74. #include "qdatetime.h"
  75. #include "qapplication.h"
  76. #include "qtimer.h"
  77. #include "qstringlist.h"
  78. #define QDATE qPrintable(QDate::currentDate().toString("yyyy-MM-dd"))
  79. //日志重定向
  80. #if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
  81. void Log(QtMsgType type, const char *msg)
  82. #else
  83. void Log(QtMsgType type, const QMessageLogContext &, const QString &msg)
  84. #endif
  85. {
  86.     //加锁,防止多线程中qdebug太频繁导致崩溃
  87.     QMutex mutex;
  88.     QMutexLocker locker(&mutex);
  89.     QString content;
  90.     //这里可以根据不同的类型加上不同的头部用于区分
  91.     switch (type) {
  92.     case QtDebugMsg:
  93.         content = QString("%1").arg(msg);
  94.         break;
  95.     case QtWarningMsg:
  96.         content = QString("%1").arg(msg);
  97.         break;
  98.     case QtCriticalMsg:
  99.         content = QString("%1").arg(msg);
  100.         break;
  101.     case QtFatalMsg:
  102.         content = QString("%1").arg(msg);
  103.         break;
  104.     }
  105.     SaveLog::Instance()->save(content);
  106. }
  107. QScopedPointer<SaveLog> SaveLog::self;
  108. SaveLog *SaveLog::Instance()
  109. {
  110.     if (self.isNull()) {
  111.         QMutex mutex;
  112.         QMutexLocker locker(&mutex);
  113.         if (self.isNull()) {
  114.             self.reset(new SaveLog);
  115.         }
  116.     }
  117.     return self.data();
  118. }
  119. SaveLog::SaveLog(QObject *parent) : QObject(parent)
  120. {
  121.     //必须用信号槽形式,不然提示 QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread
  122.     //估计日志钩子可能单独开了线程
  123.     connect(this, SIGNAL(send(QString)), SendLog::Instance(), SLOT(send(QString)));
  124.     file = new QFile(this);
  125.     toNet = false;
  126.     //默认取应用程序根目录
  127.     path = qApp->applicationDirPath();
  128.     //默认取应用程序可执行文件名称
  129.     QString str = qApp->applicationFilePath();
  130.     QStringList list = str.split("/");
  131.     name = list.at(list.count() - 1).split(".").at(0);
  132.     fileName = "";
  133. }
  134. SaveLog::~SaveLog()
  135. {
  136.     file->close();    
  137. }
  138. //安装日志钩子,输出调试信息到文件,便于调试
  139. void SaveLog::start()
  140. {
  141. #if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
  142.     qInstallMsgHandler(Log);
  143. #else
  144.     qInstallMessageHandler(Log);
  145. #endif
  146. }
  147. //卸载日志钩子
  148. void SaveLog::stop()
  149. {
  150. #if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
  151.     qInstallMsgHandler(0);
  152. #else
  153.     qInstallMessageHandler(0);
  154. #endif
  155. }
  156. void SaveLog::save(const QString &content)
  157. {
  158.     //如果重定向输出到网络则通过网络发出去,否则输出到日志文件
  159.     if (toNet) {
  160.         emit send(content);
  161.     } else {
  162.         //方法改进:之前每次输出日志都打开文件,改成只有当日期改变时才新建和打开文件
  163.         QString fileName = QString("%1/%2_log_%3.txt").arg(path).arg(name).arg(QDATE);
  164.         if (this->fileName != fileName) {
  165.             this->fileName = fileName;
  166.             if (file->isOpen()) {
  167.                 file->close();
  168.             }
  169.             file->setFileName(fileName);
  170.             file->open(QIODevice::WriteOnly | QIODevice::Append | QFile::Text);
  171.         }
  172.         QTextStream logStream(file);
  173.         logStream << content << "\n";
  174.     }
  175. }
  176. void SaveLog::setToNet(bool toNet)
  177. {
  178.     this->toNet = toNet;
  179. }
  180. void SaveLog::setPath(const QString &path)
  181. {
  182.     this->path = path;
  183. }
  184. void SaveLog::setName(const QString &name)
  185. {
  186.     this->name = name;
  187. }
  188. //网络发送日志数据类
  189. QScopedPointer<SendLog> SendLog::self;
  190. SendLog *SendLog::Instance()
  191. {
  192.     if (self.isNull()) {
  193.         QMutex mutex;
  194.         QMutexLocker locker(&mutex);
  195.         if (self.isNull()) {
  196.             self.reset(new SendLog);
  197.         }
  198.     }
  199.     return self.data();
  200. }
  201. SendLog::SendLog(QObject *parent)
  202. {
  203.     socket = NULL;
  204.     server = new QTcpServer(this);
  205.     connect(server, SIGNAL(newConnection()), this, SLOT(newConnection()));
  206.     int listenPort = 6000;
  207. #if (QT_VERSION > QT_VERSION_CHECK(5,0,0))
  208.     server->listen(QHostAddress::AnyIPv4, listenPort);
  209. #else
  210.     server->listen(QHostAddress::Any, listenPort);
  211. #endif
  212. }
  213. SendLog::~SendLog()
  214. {
  215.     if (socket != NULL) {
  216.         socket->disconnectFromHost();
  217.     }
  218.     server->close();
  219. }
  220. void SendLog::newConnection()
  221. {
  222.     while (server->hasPendingConnections()) {
  223.         socket = server->nextPendingConnection();
  224.     }
  225. }
  226. void SendLog::send(const QString &content)
  227. {
  228.     if (socket != NULL && socket->isOpen()) {
  229.         socket->write(content.toUtf8());
  230.         socket->flush();
  231.     }
  232. }



欢迎关注微信公众号:Qt实战/Qt入门和进阶(各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发) QQ:517216493  WX:feiyangqingyun  QQ群:751439350
离线aiwen.zhang

只看该作者 1楼 发表于: 2019-03-10
太强了。
离线jokerhuang

只看该作者 2楼 发表于: 2019-03-11
我想说,真的开心死我了!从楼主的分享代码里学到了好多编程知识!
离线九重水

只看该作者 3楼 发表于: 2019-03-11
九年?
哪年毕业的?
离线richards

只看该作者 4楼 发表于: 2019-03-12
这个不错。我用过最好用的 就是 android的 monitor logcat
离线ccazqyy

只看该作者 5楼 发表于: 2019-03-12
          
离线liuchangyin

只看该作者 6楼 发表于: 2019-03-12
很实用
离线kaikai_king

只看该作者 7楼 发表于: 2019-03-14
大佬  带带我
离线vaehate

只看该作者 8楼 发表于: 2019-03-25
终于等到大佬发帖了
离线mengjin

只看该作者 9楼 发表于: 2019-03-26
学习了
离线liuyuanan

只看该作者 10楼 发表于: 2019-04-24
我们现在使用的日志库是qt4log开源库,唯一的遗憾是不能通过网络转发
离线greensky10

只看该作者 11楼 发表于: 2019-05-14
感谢刘大佬分享!!!
离线0011411

只看该作者 12楼 发表于: 2019-11-19
              
离线lizn520

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

只看该作者 14楼 发表于: 2020-05-05
多谢
离线toby520

只看该作者 15楼 发表于: 2020-05-18
回 lizn520 的帖子
lizn520:您好,我阅读完您这个代码有个疑问,
void Log(QtMsgType type, const QMessageLogContext &, const QString &msg)
#endif
{
    //加锁,防止多线程中qdebug太频繁导致崩溃
....... (2020-02-02 23:48) 

理解的很对 需要优化
QtQML多多指教开发社区 http://qtclub.heilqt.com
将QtCoding进行到底
关注移动互联网,关注金融
开发跨平台客户端,服务于金融行业
专业定制界面
群号:312125701   373955953(qml控件定做)
离线zipl1985

只看该作者 16楼 发表于: 2022-07-23
学习了,太感谢了
快速回复
限100 字节
 
上一个 下一个