找回密码
 立即注册
收起左侧

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

0
回复
154
查看
[复制链接]
累计签到:7 天
连续签到:1 天
来源: 2019-3-10 21:02:25 显示全部楼层 |阅读模式
用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即可。
完整代码下载:网络接收工具效果图:

完整代码:
  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. }
复制代码


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册