用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便可。html
完整代碼下載:https://download.csdn.net/download/feiyangqingyun/11010379linux
網絡接收日誌工具截圖:數據庫
完整代碼:網絡
#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(); } }