從0到1,手把手帶你使用QT的qInstallMessageHandler函數結合qDebug,qInfo實現自定義的日誌系統,支持輸出日誌到文件和控制台,支持自動檢測日誌文件大小,支持自動更新日誌文件修改日期,自動備份,自動刪除一個月前的日誌文件,支持多線程程式, 支持擴展,可輸出日誌到資料庫,網... ...
MyLog
說明
- 使用QT的qInstallMessageHandler函數結合qDebug,qInfo實現自定義的日誌系統
- 輸出日誌到文件和控制台
- 自動檢測日誌文件大小
- 自動更新日誌文件修改日期
- 自動備份
- 自動刪除一個月前的日誌文件
- 支持多線程程式
- 支持擴展,可輸出日誌到資料庫,網路,或伺服器
- 支持擴展,可使用config文件進行配置
警告
- 註:博主所有資源永久免費,若有幫助,請點贊轉發是對我莫大的幫助
- 註:博主本人學習過程的分享,引用他人的文章皆會標註原作者
- 註:本人文章非盈利性質,若有侵權請聯繫我刪除
- 註:獲取資源或者咨詢問題請聯繫Q:2950319782
- 註:博主本人很菜,文章基本是二次創作,大佬請忽略我的隨筆
- 註:我會一步步分享實現的細節,若仍有問題聯繫我
開發環境
- win10系統
- qtcreator4.11.1
- C++11
- QT5.14.2
GitHub
- GitHub下 的Log文件
- 若不能訪問GitHub,源碼的資源包會隨文章同步發佈,免費下載
- 資源包較GitHub更新不及時,請諒解
問題解決
需求
- 輸出日誌信息到日誌文件
- 更新日誌的修改日期
- 日誌文件超過一定大小備份老的創建新的
- 刪除一個月前的日誌文件
結構
思路
- 隨便創建一個widget程式,放個測試按鈕
- 主要思路是使用 qInstallMessageHandler()接管qDebug(), qWarning()等調試信息,然後將信息流存儲至本地日誌文件,並管理日誌文件
- 先創建一個MyLog的類,在這裡面我們實現自定義的日誌系統
- 這裡依然是使用單例實現,整個程式的日誌應該只能有一個
- 首先實現單例getInstance獲取MyLog的實例
- 下麵處理MyLog的構造函數,每一次啟動日誌系統,都要先設置日誌文件的路徑,然後更新修改日期,然後打開並備份老的日誌文件,打開之後,每10分鐘刷新日誌文件,每1秒都將信息輸出到日誌,
- 下麵實現這個打開並備份老的日誌文件的功能openAndBackupLogFile,這裡我們的日誌文件以天為單位,先處理一天內多次啟動日誌系統的情況,以追加的方式寫入到日誌文件里即可;如果程式運行的時候,日誌系統的日期和程式運行日期不統一,同步日誌日期為程式運行日期,生成新的日期日誌,並且備份老的日誌
- 然後實現處理日誌文件過大的問題,只要日誌文件超限,備份老的,創建新的即可
- 然後實現自動刪除超時的日誌文件,每次啟動日誌系統的時候,以當前時間為基準,計算出1個月前的時間,遍歷日誌目錄下的所有日誌文件,因為日誌文件都以時間命名,刪除超過1個月的日誌文件即可
- 最後,我們只需要處理信息函數即可,捕獲系統中的各種輸出信息,輸出到文件即可
關鍵代碼
MyLog.h
#ifndef MYLOG_H #define MYLOG_H #include <iostream> #include <QDateTime> #include <QMutexLocker> #include <QDir> #include <QTimer> #include <QTextStream> //最大保存文件大小 const int g_logLimitSize = 5; class MyLog { public: MyLog(); ~MyLog(); static MyLog* getInstance(); //消息處理函數 static void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg); public: //打開並備份之前的日誌文件 void openAndBackupLogFile(); void checkLogFiles(); void autoDeleteLog(); //安裝消息處理函數 void installMessageHandler(); //卸載消息處理函數,並釋放資源 void uninstallMessageHandler(); private: //日誌文件夾目錄 QDir logDir; //重命名日誌文件使用的定時器 QTimer renameLogFileTimer; //刷新輸出到日誌文件的定時器 QTimer flushLogFileTimer; //日誌文件的創建時間 QDate logFileCreateDate; //日誌文件 static QFile* logFile; //輸出日誌 static QTextStream* logOut; //日誌鎖 static QMutex logMutex; static QScopedPointer<MyLog> self; }; #endif // MYLOG_H
MyLog.cpp
If you need inspiring words,don't do it#include "mylog.h" #include<QDebug> #include<QTextCodec> #define LOG 1 //初始化靜態變數 QMutex MyLog::logMutex; QFile* MyLog::logFile = NULL; QTextStream* MyLog::logOut = NULL; QScopedPointer<MyLog> MyLog::self; //定義單例模式 MyLog* MyLog::getInstance() { //還沒有創建實例 if(self.isNull()) { //加把鎖,只能有一個線程訪問 static QMutex mutex; //自動加解鎖 QMutexLocker locker(&mutex); //再次判斷有沒有實例,防止等待的時間中有線程獲取到實例了 if(self.isNull()) { self.reset(new MyLog); } } return self.data(); } MyLog::MyLog() { //設置日誌文件夾的路徑,./exe logDir.setPath("log"); //獲取日誌的絕對路徑 QString logPath = logDir.absoluteFilePath("today.log"); //獲取日誌文件創建的時間 //保存日誌文件最後的修改時間 logFileCreateDate = QFileInfo(logPath).lastModified().date(); //打開並備份日誌文件 openAndBackupLogFile(); //每10分鐘檢查一次日誌文件創建的時間 renameLogFileTimer.setInterval(1000 * 60 *1000); renameLogFileTimer.start(); //處理超時事件,10分鐘重覆一次 QObject::connect(&renameLogFileTimer,&QTimer::timeout,[this](){ QMutexLocker locker(&MyLog::logMutex); openAndBackupLogFile(); checkLogFiles(); autoDeleteLog(); }); //定時刷新日誌輸出到日誌文件,1秒1刷新 flushLogFileTimer.setInterval(1000); flushLogFileTimer.start(); QObject::connect(&flushLogFileTimer,&QTimer::timeout,[](){ #if LOG // 測試不停地寫入當前時間到日誌文件 qDebug() << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); #endif //刷新 QMutexLocker locker(&MyLog::logMutex); if(NULL != logOut) { logOut->flush(); } }); } MyLog::~MyLog() { if(NULL != logFile) { logFile->flush(); logFile->close(); logOut = NULL; logFile = NULL; } } //打開並備份之前的日誌文件 void MyLog::openAndBackupLogFile() { //有可能一天多次打開日誌文件,使用追加的方式打開 //目錄不存在,創建目錄 if(!logDir.exists()) { logDir.mkpath("."); } //log.txt的路徑 QString logPath = logDir.absoluteFilePath("today.log"); //程式啟動的時候,logfile為空 if(logFile == NULL) { //創建新的 logFile = new QFile(logPath); //只寫,追加的方式打開日誌文件 //成功,創建文本流對象與日誌文件關聯,嚮日志文件寫內容 logOut = (logFile->open(QIODevice::WriteOnly | QIODevice::Text |QIODevice::Append)) ? new QTextStream(logFile) : NULL; if(logOut != NULL) { //設置編碼格式 logOut->setCodec("UTF-8"); } //日誌文件第一次創建,創建日期無效,設置為修改日期 if(logFileCreateDate.isNull()) { logFileCreateDate = QDate::currentDate(); } } //程式運行的時候,創建日期不是當前日期,更新日期,重命名,備份老的並生成新的log.txt if(logFileCreateDate != QDate::currentDate()) { //先刷新緩衝區,確保內容先輸出到文件里 logFile->flush(); logFile->close(); //更新日期到備份文件 QString backUpLogPath = logDir.absoluteFilePath(logFileCreateDate.toString("yyyy-MM-dd.log"));; //備份原來的日誌 QFile::copy(logPath,backUpLogPath); //刪除原來的日誌文件 QFile::remove(logPath); //創建新的log.txt,進行更新 //只寫,截斷的方式打開日誌 logFile = new QFile(logPath); logOut = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) ? new QTextStream(logFile) : NULL; //更新為修改時間 logFileCreateDate = QDate::currentDate(); if(logOut != NULL) { logOut->setCodec("UTF-8"); } } } //檢查文件大小 void MyLog::checkLogFiles() { //日誌文件大小超過5m,備份並重新創建日誌文件 if(logFile->size() > 1024* g_logLimitSize) { //清空緩衝 logFile->flush(); logFile->close(); QString logPath = logDir.absoluteFilePath("today.log"); //備份老的日誌文件 QString backUplogPath = logDir.absoluteFilePath(logFileCreateDate.toString("yyyy-MM-dd.log")); QFile::copy(logPath,backUplogPath); QFile::remove(logPath); //創建新的日誌文件 logFile = new QFile(logPath); logOut = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) ? new QTextStream(logFile) :NULL; logFileCreateDate = QDate::currentDate(); if(logOut != NULL) { logOut->setCodec("UTF-8"); } } } //自動刪除超過時間的日誌文件 void MyLog::autoDeleteLog() { //當前時間 QDateTime now = QDateTime::currentDateTime(); //基準,30天前 QDateTime dateTime1 = now.addDays(-30); QDateTime dateTime2; QString logPath = logDir.absoluteFilePath("today.log"); //打開日誌目錄 QDir dir(logPath); //獲取目錄下的所有文件信息列表 QFileInfoList fileList = dir.entryInfoList(); foreach(QFileInfo f, fileList) { //跳過文件名為空的文件 if(f.baseName() == "") { continue; } //將文件名解析為日期對象 dateTime2 = QDateTime::fromString(f.baseName(),"yyyy-MM-dd"); //大於30天,刪除 if(dateTime2 < dateTime1) { dir.remove(f.absoluteFilePath()); } } } //定義消息處理函數 void MyLog::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { QMutexLocker locker(&MyLog::logMutex); QString level; switch (type) { case QtDebugMsg: level = "DEBUG"; break; case QtInfoMsg: level = "INFO"; break; case QtWarningMsg: level = "WARN"; break; case QtCriticalMsg: level = "ERROR"; break; case QtFatalMsg: level = "FATAL"; break; default: break; } #if defined (Q_OS_WIN) QByteArray localMsg = QTextCodec::codecForName("GB2312")->fromUnicode(msg); #else QByteArray localMsg = msg.toLocal8Bit(); #endif //輸出到控制台 std::cout << std::string(localMsg) << std::endl; if(NULL == MyLog::logOut) { return; } //輸出到日誌文件 //獲取文件名,去掉路徑 QString fileName = context.file; int index = fileName.lastIndexOf(QDir::separator()); fileName = fileName.mid(index + 1); //寫入日誌信息 (*MyLog::logOut) << QString("%1 - [%2] (%3:%4, %5): %6\n") .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")) .arg(level) .arg(fileName) .arg(context.line) .arg(context.function) .arg(msg); } //安裝 void MyLog::installMessageHandler() { qInstallMessageHandler(MyLog::messageHandler); } //卸載 void MyLog::uninstallMessageHandler() { qInstallMessageHandler(NULL); }