使用C++和QT實現Log自定義紀錄檔系統

2023-12-12 21:00:47

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

#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);
}