MyLog
說明
- 使用QT的qInstallMessageHandler函式結合qDebug,qInfo實現自定義的日誌系統
- 輸出日誌到檔案和控制檯
- 自動檢測日誌檔案大小
- 自動更新日誌檔案修改日期
- 自動備份
- 自動刪除一個月前的日誌檔案
- 支援多執行緒程式
- 支援擴充套件,可輸出日誌到資料庫,網路,或伺服器
- 支援擴充套件,可使用config檔案進行配置
警告
- 注:博主所有資源永久免費,若有幫助,請點贊轉發是對我莫大的幫助
- 注:博主本人學習過程的分享,引用他人的文章皆會標註原作者
- 注:本人文章非盈利性質,若有侵權請聯絡我刪除
- 注:獲取資源或者諮詢問題請聯絡Q:2950319782
- 注:博主本人很菜,文章基本是二次創作,大佬請忽略我的隨筆
- 注:我會一步步分享實現的細節,若仍有問題聯絡我
開發環境
- win10系統
- qtcreator4.11.1
- C++11
- QT5.14.2
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
#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);
}