Qt實(shí)現(xiàn)自定義日志類(lèi)的示例代碼
一、前言
C++ 中比較不錯(cuò)的日志工具有 log4cxx
,log4qt
等,但是它們都不能和 qDebug()
, qInfo()
等有機(jī)的結(jié)合在一起,所以在 Qt 中使用總覺(jué)得不夠舒服,感謝 Qt 提供了 qInstallMessageHandler()
這個(gè)函數(shù),使用這個(gè)函數(shù)可以安裝自定義的日志輸出處理函數(shù),把日志輸出到文件,控制臺(tái)等,具體的使用可以查看 Qt 的幫助文檔。
本文主要是介紹使用 qInstallMessageHandler()
實(shí)現(xiàn)一個(gè)簡(jiǎn)單的日志工具,例如調(diào)用 qDebug() << "Hi"
,輸出的內(nèi)容會(huì)同時(shí)輸出到日志文件和控制臺(tái),并且日志文件如果不是當(dāng)天創(chuàng)建的,會(huì)使用它的創(chuàng)建日期備份起來(lái),涉及到的文件有:
- main.cpp: 使用示例
- LogHandler.h: 自定義日志相關(guān)類(lèi)的頭文件
- LogHandler.cpp: 自定義日志相關(guān)類(lèi)的實(shí)現(xiàn)文件
另外實(shí)現(xiàn)功能:
- 單個(gè)日志文件例如大于 5M 后重新創(chuàng)建一個(gè)新的日志文件;
- 刪除超過(guò) 30 天的日志;
- 使用鎖確保多線(xiàn)程安全。
后期考慮實(shí)現(xiàn)功能:
- 日志的相關(guān)配置數(shù)據(jù)例如輸出目錄寫(xiě)到配置文件;
- 日志可以選擇存放在服務(wù)器。
二、代碼實(shí)現(xiàn)
2.1 LogHandler.h
#ifndef LOGHANDLER_H #define LOGHANDLER_H #include <iostream> #include <QDebug> #include <QDateTime> #include <QMutexLocker> #include <QDir> #include <QFile> #include <QFileInfo> #include <QTimer> #include <QTextStream> #include <QTextCodec> const int g_logLimitSize = 5; struct LogHandlerPrivate { LogHandlerPrivate(); ~LogHandlerPrivate(); // 打開(kāi)日志文件 log.txt,如果日志文件不是當(dāng)天創(chuàng)建的,則使用創(chuàng)建日期把其重命名為 yyyy-MM-dd.log,并重新創(chuàng)建一個(gè) log.txt void openAndBackupLogFile(); void checkLogFiles(); // 檢測(cè)當(dāng)前日志文件大小 void autoDeleteLog(); // 自動(dòng)刪除30天前的日志 // 消息處理函數(shù) static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg); QDir logDir; // 日志文件夾 QTimer renameLogFileTimer; // 重命名日志文件使用的定時(shí)器 QTimer flushLogFileTimer; // 刷新輸出到日志文件的定時(shí)器 QDate logFileCreatedDate; // 日志文件創(chuàng)建的時(shí)間 static QFile *logFile; // 日志文件 static QTextStream *logOut; // 輸出日志的 QTextStream,使用靜態(tài)對(duì)象就是為了減少函數(shù)調(diào)用的開(kāi)銷(xiāo) static QMutex logMutex; // 同步使用的 mutex }; class LogHandler { public: void installMessageHandler(); // 給Qt安裝消息處理函數(shù) void uninstallMessageHandler(); // 取消安裝消息處理函數(shù)并釋放資源 static LogHandler& Get() { static LogHandler m_logHandler; return m_logHandler; } private: LogHandler(); LogHandlerPrivate *d; }; #endif // LOGHANDLER_H
2.2 LogHandler.cpp
#include "LogHandler.h" /************************************************************************************************************ * * * LogHandlerPrivate * * * ***********************************************************************************************************/ // 初始化 static 變量 QMutex LogHandlerPrivate::logMutex; QFile* LogHandlerPrivate::logFile = nullptr; QTextStream* LogHandlerPrivate::logOut = nullptr; LogHandlerPrivate::LogHandlerPrivate() { logDir.setPath("log"); // TODO: 日志文件夾的路徑,為 exe 所在目錄下的 log 文件夾,可從配置文件讀取 QString logPath = logDir.absoluteFilePath("today.log"); // 獲取日志的路徑 // ========獲取日志文件創(chuàng)建的時(shí)間======== // QFileInfo::created(): On most Unix systems, this function returns the time of the last status change. // 所以不能運(yùn)行時(shí)使用這個(gè)函數(shù)檢查創(chuàng)建時(shí)間,因?yàn)闀?huì)在運(yùn)行時(shí)變化,于是在程序啟動(dòng)時(shí)保存下日志文件的最后修改時(shí)間, logFileCreatedDate = QFileInfo(logPath).lastModified().date(); // 若日志文件不存在,返回nullptr // 打開(kāi)日志文件,如果不是當(dāng)天創(chuàng)建的,備份已有日志文件 openAndBackupLogFile(); // 十分鐘檢查一次日志文件創(chuàng)建時(shí)間 renameLogFileTimer.setInterval(1000 * 2); // TODO: 可從配置文件讀取 renameLogFileTimer.start(); QObject::connect(&renameLogFileTimer, &QTimer::timeout, [this] { QMutexLocker locker(&LogHandlerPrivate::logMutex); openAndBackupLogFile(); // 打開(kāi)日志文件 checkLogFiles(); // 檢測(cè)當(dāng)前日志文件大小 autoDeleteLog(); // 自動(dòng)刪除30天前的日志 }); // 定時(shí)刷新日志輸出到文件,盡快的能在日志文件里看到最新的日志 flushLogFileTimer.setInterval(1000); // TODO: 可從配置文件讀取 flushLogFileTimer.start(); QObject::connect(&flushLogFileTimer, &QTimer::timeout, [] { // qDebug() << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); // 測(cè)試不停的寫(xiě)入內(nèi)容到日志文件 QMutexLocker locker(&LogHandlerPrivate::logMutex); if (nullptr != logOut) { logOut->flush(); } }); } LogHandlerPrivate::~LogHandlerPrivate() { if (nullptr != logFile) { logFile->flush(); logFile->close(); delete logOut; delete logFile; // 因?yàn)樗麄兪?static 變量 logOut = nullptr; logFile = nullptr; } } // 打開(kāi)日志文件 log.txt,如果不是當(dāng)天創(chuàng)建的,則使用創(chuàng)建日期把其重命名為 yyyy-MM-dd.log,并重新創(chuàng)建一個(gè) log.txt void LogHandlerPrivate::openAndBackupLogFile() { // 總體邏輯: // 1. 程序啟動(dòng)時(shí) logFile 為 nullptr,初始化 logFile,有可能是同一天打開(kāi)已經(jīng)存在的 logFile,所以使用 Append 模式 // 2. logFileCreatedDate is nullptr, 說(shuō)明日志文件在程序開(kāi)始時(shí)不存在,所以記錄下創(chuàng)建時(shí)間 // 3. 程序運(yùn)行時(shí)檢查如果 logFile 的創(chuàng)建日期和當(dāng)前日期不相等,則使用它的創(chuàng)建日期重命名,然后再生成一個(gè)新的 log.txt 文件 // 4. 檢查日志文件超過(guò) LOGLIMIT_NUM 個(gè),刪除最早的 // 備注:log.txt 始終為當(dāng)天的日志文件,當(dāng)?shù)诙?,?huì)執(zhí)行第3步,將使用 log.txt 的創(chuàng)建日期重命名它 // 如果日志所在目錄不存在,則創(chuàng)建 if (!logDir.exists()) { logDir.mkpath("."); // 可以遞歸的創(chuàng)建文件夾 } QString logPath = logDir.absoluteFilePath("today.log"); // log.txt的路徑 // [[1]] 程序每次啟動(dòng)時(shí) logFile 為 nullptr if (logFile == nullptr) { logFile = new QFile(logPath); logOut = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) ? new QTextStream(logFile) : nullptr; if (logOut != nullptr) logOut->setCodec("UTF-8"); // [[2]] 如果文件是第一次創(chuàng)建,則創(chuàng)建日期是無(wú)效的,把其設(shè)置為當(dāng)前日期 if (logFileCreatedDate.isNull()) { logFileCreatedDate = QDate::currentDate(); } } // [[3]] 程序運(yùn)行時(shí)如果創(chuàng)建日期不是當(dāng)前日期,則使用創(chuàng)建日期重命名,并生成一個(gè)新的 log.txt if (logFileCreatedDate != QDate::currentDate()) { logFile->flush(); logFile->close(); delete logOut; delete logFile; QString newLogPath = logDir.absoluteFilePath(logFileCreatedDate.toString("yyyy-MM-dd.log"));; QFile::copy(logPath, newLogPath); // Bug: 按理說(shuō) rename 會(huì)更合適,但是 rename 時(shí)最后一個(gè)文件總是顯示不出來(lái),需要 killall Finder 后才出現(xiàn) QFile::remove(logPath); // 刪除重新創(chuàng)建,改變創(chuàng)建時(shí)間 // 重新創(chuàng)建 log.txt logFile = new QFile(logPath); logOut = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) ? new QTextStream(logFile) : nullptr; logFileCreatedDate = QDate::currentDate(); if (logOut != nullptr) logOut->setCodec("UTF-8"); } } // 檢測(cè)當(dāng)前日志文件大小 void LogHandlerPrivate::checkLogFiles() { // 如果 protocal.log 文件大小超過(guò)5M,重新創(chuàng)建一個(gè)日志文件,原文件存檔為yyyy-MM-dd_hhmmss.log if (logFile->size() > 1024*g_logLimitSize) { logFile->flush(); logFile->close(); delete logOut; delete logFile; QString logPath = logDir.absoluteFilePath("today.log"); // 日志的路徑 QString newLogPath = logDir.absoluteFilePath(logFileCreatedDate.toString("yyyy-MM-dd.log")); QFile::copy(logPath, newLogPath); // Bug: 按理說(shuō) rename 會(huì)更合適,但是 rename 時(shí)最后一個(gè)文件總是顯示不出來(lái),需要 killall Finder 后才出現(xiàn) QFile::remove(logPath); // 刪除重新創(chuàng)建,改變創(chuàng)建時(shí)間 logFile = new QFile(logPath); logOut = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) ? new QTextStream(logFile) : NULL; logFileCreatedDate = QDate::currentDate(); if (logOut != nullptr) logOut->setCodec("UTF-8"); } } // 自動(dòng)刪除30天前的日志 void LogHandlerPrivate::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 ) { // "."和".."跳過(guò) if (f.baseName() == "") continue; dateTime2 = QDateTime::fromString(f.baseName(), "yyyy-MM-dd"); if (dateTime2 < dateTime1) { // 只要日志時(shí)間小于前30天的時(shí)間就刪除 dir.remove(f.absoluteFilePath()); } } } // 消息處理函數(shù) void LogHandlerPrivate::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { QMutexLocker locker(&LogHandlerPrivate::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; } // 輸出到標(biāo)準(zhǔn)輸出: Windows 下 std::cout 使用 GB2312,而 msg 使用 UTF-8,但是程序的 Local 也還是使用 UTF-8 #if defined(Q_OS_WIN) QByteArray localMsg = QTextCodec::codecForName("GB2312")->fromUnicode(msg); //msg.toLocal8Bit(); #else QByteArray localMsg = msg.toLocal8Bit(); #endif std::cout << std::string(localMsg) << std::endl; if (nullptr == LogHandlerPrivate::logOut) { return; } // 輸出到日志文件, 格式: 時(shí)間 - [Level] (文件名:行數(shù), 函數(shù)): 消息 QString fileName = context.file; int index = fileName.lastIndexOf(QDir::separator()); fileName = fileName.mid(index + 1); (*LogHandlerPrivate::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); } /************************************************************************************************************ * * * LogHandler * * * ***********************************************************************************************************/ LogHandler::LogHandler() : d(nullptr) { } // 給Qt安裝消息處理函數(shù) void LogHandler::installMessageHandler() { QMutexLocker locker(&LogHandlerPrivate::logMutex); // 類(lèi)似C++11的lock_guard,析構(gòu)時(shí)自動(dòng)解鎖 if (nullptr == d) { d = new LogHandlerPrivate(); qInstallMessageHandler(LogHandlerPrivate::messageHandler); // 給 Qt 安裝自定義消息處理函數(shù) } } // 取消安裝消息處理函數(shù)并釋放資源 void LogHandler::uninstallMessageHandler() { QMutexLocker locker(&LogHandlerPrivate::logMutex); qInstallMessageHandler(nullptr); delete d; d = nullptr; }
2.3 main.cpp
#include "LogHandler.h" #include <QApplication> #include <QDebug> #include <QTime> #include <QPushButton> int main(int argc, char *argv[]) { QApplication app(argc, argv); // [[1]] 安裝消息處理函數(shù) LogHandler::Get().installMessageHandler(); // [[2]] 輸出測(cè)試,查看是否寫(xiě)入到文件 qDebug() << "Hello"; qDebug() << "當(dāng)前時(shí)間是: " << QTime::currentTime().toString("hh:mm:ss"); qInfo() << QString("God bless you!"); QPushButton *button = new QPushButton("退出"); button->show(); QObject::connect(button, &QPushButton::clicked, [&app] { qDebug() << "退出"; app.quit(); }); // [[3]] 取消安裝自定義消息處理,然后啟用 LogHandler::Get().uninstallMessageHandler(); qDebug() << "........"; // 不寫(xiě)入日志 LogHandler::Get().installMessageHandler(); int ret = app.exec(); // 事件循環(huán)結(jié)束 // [[4]] 程序結(jié)束時(shí)釋放 LogHandler 的資源,例如刷新并關(guān)閉日志文件 LogHandler::Get().uninstallMessageHandler(); return ret; }
如果想實(shí)現(xiàn)日志存放在服務(wù)器,可以參考:Qt 打印日志系統(tǒng),實(shí)現(xiàn)打印日志按日期、大小保存,過(guò)期刪除,窗口實(shí)時(shí)顯示日志,網(wǎng)絡(luò)傳輸日志遠(yuǎn)程調(diào)試
2.4 運(yùn)行效果
控制臺(tái)輸出:
Hello
當(dāng)前時(shí)間是: "16:29:42"
"God bless you!"
........
退出
日志文件(exe 所在目錄的 log 目錄下的 log.txt):
16:29:42 - [Debug] (main.cpp:15, int main(int, char **)): Hello
16:29:42 - [Debug] (main.cpp:16, int main(int, char **)): 當(dāng)前時(shí)間是: "16:29:42"
16:29:42 - [Info ] (main.cpp:17, int main(int, char **)): "God bless you!"
16:29:46 - [Debug] (main.cpp:22, auto main(int, char **)::(anonymous class)::operator()() const): 退出
注意:
Release 版本默認(rèn)不包含文件名、函數(shù)名和行數(shù)信息,需要在 .pro 文件中加入一行代碼,重新 make 運(yùn)行后生效。
DEFINES += QT_MESSAGELOGCONTEXT
三、其他 C++ 日志框架
C++ 中的日志框架有很多,其中比較著名的有:
- log4cxx:Java 社區(qū)著名的 Log4j 的 C++ 移植版,用于為 C++ 程序提供日志功能,以便開(kāi)發(fā)者對(duì)目標(biāo)程序進(jìn)行調(diào)試和審計(jì)。
- log4cplus:一個(gè)簡(jiǎn)單易用的 C++ 日志記錄 API,它提供了對(duì)日志管理和配置的線(xiàn)程安全、靈活和任意粒度控制(也基于 Log4j)。
- Log4cpp:一個(gè) C++ 類(lèi)庫(kù),可以靈活地記錄到文件、syslog、IDSA 和其他目的地(也基于 Log4j)。
- google-glog:一個(gè) C++ 語(yǔ)言的應(yīng)用級(jí)日志記錄框架,提供了 C++ 風(fēng)格的流操作和各種輔助宏。
- Pantheios:一個(gè)類(lèi)型安全、高效、泛型和可擴(kuò)展性的 C++ 日志 API 庫(kù)(號(hào)稱(chēng) C++ 領(lǐng)域速度最快的日志庫(kù))。
- POCO:還提供了一個(gè) 好的日志支持文檔。
- ACE:ACE 也有日志支持。
- Boost.Log:設(shè)計(jì)的非常模塊化,并且可擴(kuò)展。
- Easylogging++:輕量級(jí)高性能 C++ 日志庫(kù)(只有一個(gè)頭文件)。
- G3log:一個(gè)開(kāi)源、支持跨平臺(tái)的異步 C++ 日志框架,支持自定義日志格式。基于 g2log 構(gòu)建,提升了性能,支持自定義格式。
- Plog:可移植、簡(jiǎn)單和可擴(kuò)展的 C++ 日志庫(kù)。
- spdlog:一個(gè)快速的 C++ 日志庫(kù),只包含頭文件,兼容 C++11。
- ……
其中 log4cplus、glog 較為流行,一個(gè)是著名的 Log4j 的衍生品,另一個(gè)則是 Google 的“親兒子”。
包括 log4qt,也是 Log4j 的衍生品,可以參考:DevBean豆子大神的github
以上就是Qt實(shí)現(xiàn)自定義日志類(lèi)的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Qt自定義日志類(lèi)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Qt地圖自適應(yīng)拉伸的實(shí)現(xiàn)示例
最近需要寫(xiě)一個(gè)程序,要是讓qt到程序自適應(yīng),本文主要介紹了Qt地圖自適應(yīng)拉伸的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12關(guān)于C++內(nèi)部類(lèi)的介紹與使用示例
今天小編就為大家分享一篇關(guān)于關(guān)于C++內(nèi)部類(lèi)的介紹與使用示例,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
這篇文章主要介紹了C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法,涉及C語(yǔ)言針對(duì)數(shù)組的遍歷與判斷技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07C++實(shí)現(xiàn)二叉樹(shù)遍歷序列的求解方法
這篇文章主要介紹了C++實(shí)現(xiàn)二叉樹(shù)遍歷序列的求解方法,需要的朋友可以參考下2014-08-08c++?創(chuàng)建型設(shè)計(jì)模式工廠方法Factory?Method示例詳解
這篇文章主要為大家介紹了c++?創(chuàng)建型設(shè)計(jì)模式工廠方法Factory?Method示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09C++進(jìn)程的創(chuàng)建和進(jìn)程ID標(biāo)識(shí)詳細(xì)介紹
傳統(tǒng)的C++(C++98)中并沒(méi)有引入線(xiàn)程這個(gè)概念。linux和unix操作系統(tǒng)的設(shè)計(jì)采用的是多進(jìn)程,進(jìn)程間的通信十分方便,同時(shí)進(jìn)程之間互相有著獨(dú)立的空間,不會(huì)污染其他進(jìn)程的數(shù)據(jù),天然的隔離性給程序的穩(wěn)定性帶來(lái)了很大的保障2022-08-08C++的std::transform()的實(shí)現(xiàn)
在 C++ 標(biāo)準(zhǔn)庫(kù)中,std::transform() 是一個(gè)非常有用的算法函數(shù),它能夠?qū)⒔o定范圍中的每個(gè)元素進(jìn)行變換,并將變換后的結(jié)果存儲(chǔ)到另一個(gè)范圍中,本文就詳細(xì)的介紹一下具體用法,感興趣的可以了解一下2023-08-08