欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Qt實(shí)現(xiàn)自定義日志類的示例代碼

 更新時間:2023年12月13日 08:32:53   作者:fengMisaka  
這篇文章主要為大家詳細(xì)介紹了使用 qInstallMessageHandler() 實(shí)現(xiàn)一個簡單的日志工具,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以學(xué)習(xí)一下

一、前言

C++ 中比較不錯的日志工具有 log4cxx,log4qt 等,但是它們都不能和 qDebug()qInfo() 等有機(jī)的結(jié)合在一起,所以在 Qt 中使用總覺得不夠舒服,感謝 Qt 提供了 qInstallMessageHandler() 這個函數(shù),使用這個函數(shù)可以安裝自定義的日志輸出處理函數(shù),把日志輸出到文件,控制臺等,具體的使用可以查看 Qt 的幫助文檔。

本文主要是介紹使用 qInstallMessageHandler() 實(shí)現(xiàn)一個簡單的日志工具,例如調(diào)用 qDebug() << "Hi",輸出的內(nèi)容會同時輸出到日志文件和控制臺,并且日志文件如果不是當(dāng)天創(chuàng)建的,會使用它的創(chuàng)建日期備份起來,涉及到的文件有:

  • main.cpp: 使用示例
  • LogHandler.h: 自定義日志相關(guān)類的頭文件
  • LogHandler.cpp: 自定義日志相關(guān)類的實(shí)現(xiàn)文件

另外實(shí)現(xiàn)功能:

  • 單個日志文件例如大于 5M 后重新創(chuàng)建一個新的日志文件;
  • 刪除超過 30 天的日志;
  • 使用鎖確保多線程安全。

后期考慮實(shí)現(xiàn)功能:

  • 日志的相關(guān)配置數(shù)據(jù)例如輸出目錄寫到配置文件;
  • 日志可以選擇存放在服務(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();

    // 打開日志文件 log.txt,如果日志文件不是當(dāng)天創(chuàng)建的,則使用創(chuàng)建日期把其重命名為 yyyy-MM-dd.log,并重新創(chuàng)建一個 log.txt
    void openAndBackupLogFile();
    void checkLogFiles(); // 檢測當(dāng)前日志文件大小
    void autoDeleteLog(); // 自動刪除30天前的日志

    // 消息處理函數(shù)
    static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);

    QDir   logDir;              // 日志文件夾
    QTimer renameLogFileTimer;  // 重命名日志文件使用的定時器
    QTimer flushLogFileTimer;   // 刷新輸出到日志文件的定時器
    QDate  logFileCreatedDate;  // 日志文件創(chuàng)建的時間

    static QFile *logFile;      // 日志文件
    static QTextStream *logOut; // 輸出日志的 QTextStream,使用靜態(tài)對象就是為了減少函數(shù)調(dià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)建的時間========
    // QFileInfo::created(): On most Unix systems, this function returns the time of the last status change.
    // 所以不能運(yùn)行時使用這個函數(shù)檢查創(chuàng)建時間,因?yàn)闀谶\(yùn)行時變化,于是在程序啟動時保存下日志文件的最后修改時間,
    logFileCreatedDate = QFileInfo(logPath).lastModified().date(); // 若日志文件不存在,返回nullptr

    // 打開日志文件,如果不是當(dāng)天創(chuàng)建的,備份已有日志文件
    openAndBackupLogFile();

    // 十分鐘檢查一次日志文件創(chuàng)建時間
    renameLogFileTimer.setInterval(1000 *  2); // TODO: 可從配置文件讀取
    renameLogFileTimer.start();
    QObject::connect(&renameLogFileTimer, &QTimer::timeout, [this] {
        QMutexLocker locker(&LogHandlerPrivate::logMutex);
        openAndBackupLogFile(); // 打開日志文件
        checkLogFiles(); // 檢測當(dāng)前日志文件大小
        autoDeleteLog(); // 自動刪除30天前的日志
    });

    // 定時刷新日志輸出到文件,盡快的能在日志文件里看到最新的日志
    flushLogFileTimer.setInterval(1000); // TODO: 可從配置文件讀取
    flushLogFileTimer.start();
    QObject::connect(&flushLogFileTimer, &QTimer::timeout, [] {
        // qDebug() << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); // 測試不停的寫入內(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;
    }
}

// 打開日志文件 log.txt,如果不是當(dāng)天創(chuàng)建的,則使用創(chuàng)建日期把其重命名為 yyyy-MM-dd.log,并重新創(chuàng)建一個 log.txt
void LogHandlerPrivate::openAndBackupLogFile() {
    // 總體邏輯:
    // 1. 程序啟動時 logFile 為 nullptr,初始化 logFile,有可能是同一天打開已經(jīng)存在的 logFile,所以使用 Append 模式
    // 2. logFileCreatedDate is nullptr, 說明日志文件在程序開始時不存在,所以記錄下創(chuàng)建時間
    // 3. 程序運(yùn)行時檢查如果 logFile 的創(chuàng)建日期和當(dāng)前日期不相等,則使用它的創(chuàng)建日期重命名,然后再生成一個新的 log.txt 文件
    // 4. 檢查日志文件超過 LOGLIMIT_NUM 個,刪除最早的
    // 備注:log.txt 始終為當(dāng)天的日志文件,當(dāng)?shù)诙?,會?zhí)行第3步,將使用 log.txt 的創(chuàng)建日期重命名它

    // 如果日志所在目錄不存在,則創(chuàng)建
    if (!logDir.exists()) {
        logDir.mkpath("."); // 可以遞歸的創(chuàng)建文件夾
    }
    QString logPath = logDir.absoluteFilePath("today.log"); // log.txt的路徑

    // [[1]] 程序每次啟動時 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)建日期是無效的,把其設(shè)置為當(dāng)前日期
        if (logFileCreatedDate.isNull()) {
            logFileCreatedDate = QDate::currentDate();
        }
    }

    // [[3]] 程序運(yùn)行時如果創(chuàng)建日期不是當(dāng)前日期,則使用創(chuàng)建日期重命名,并生成一個新的 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: 按理說 rename 會更合適,但是 rename 時最后一個文件總是顯示不出來,需要 killall Finder 后才出現(xiàn)
        QFile::remove(logPath); // 刪除重新創(chuàng)建,改變創(chuàng)建時間

        // 重新創(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");
    }
}

// 檢測當(dāng)前日志文件大小
void LogHandlerPrivate::checkLogFiles() {
    // 如果 protocal.log 文件大小超過5M,重新創(chuàng)建一個日志文件,原文件存檔為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: 按理說 rename 會更合適,但是 rename 時最后一個文件總是顯示不出來,需要 killall Finder 后才出現(xiàn)
        QFile::remove(logPath); // 刪除重新創(chuàng)建,改變創(chuàng)建時間

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

// 自動刪除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 ) {
        // "."和".."跳過
        if (f.baseName() == "")
            continue;

        dateTime2 = QDateTime::fromString(f.baseName(), "yyyy-MM-dd");
        if (dateTime2 < dateTime1) { // 只要日志時間小于前30天的時間就刪除
            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;
    }

    // 輸出到日志文件, 格式: 時間 - [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); // 類似C++11的lock_guard,析構(gòu)時自動解鎖

    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]] 輸出測試,查看是否寫入到文件
    qDebug() << "Hello";
    qDebug() << "當(dāng)前時間是: " << 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() << "........"; // 不寫入日志
    LogHandler::Get().installMessageHandler();

    int ret = app.exec(); // 事件循環(huán)結(jié)束

    // [[4]] 程序結(jié)束時釋放 LogHandler 的資源,例如刷新并關(guān)閉日志文件
    LogHandler::Get().uninstallMessageHandler();

    return ret;
}

如果想實(shí)現(xiàn)日志存放在服務(wù)器,可以參考:Qt 打印日志系統(tǒng),實(shí)現(xiàn)打印日志按日期、大小保存,過期刪除,窗口實(shí)時顯示日志,網(wǎng)絡(luò)傳輸日志遠(yuǎn)程調(diào)試

2.4 運(yùn)行效果

控制臺輸出:

Hello
當(dāng)前時間是:  "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)前時間是:  "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++ 程序提供日志功能,以便開發(fā)者對目標(biāo)程序進(jìn)行調(diào)試和審計(jì)。
  • log4cplus:一個簡單易用的 C++ 日志記錄 API,它提供了對日志管理和配置的線程安全、靈活和任意粒度控制(也基于 Log4j)。
  • Log4cpp:一個 C++ 類庫,可以靈活地記錄到文件、syslog、IDSA 和其他目的地(也基于 Log4j)。
  • google-glog:一個 C++ 語言的應(yīng)用級日志記錄框架,提供了 C++ 風(fēng)格的流操作和各種輔助宏。
  • Pantheios:一個類型安全、高效、泛型和可擴(kuò)展性的 C++ 日志 API 庫(號稱 C++ 領(lǐng)域速度最快的日志庫)。
  • POCO:還提供了一個 好的日志支持文檔。
  • ACE:ACE 也有日志支持。
  • Boost.Log:設(shè)計(jì)的非常模塊化,并且可擴(kuò)展。
  • Easylogging++:輕量級高性能 C++ 日志庫(只有一個頭文件)。
  • G3log:一個開源、支持跨平臺的異步 C++ 日志框架,支持自定義日志格式?;?g2log 構(gòu)建,提升了性能,支持自定義格式。
  • Plog:可移植、簡單和可擴(kuò)展的 C++ 日志庫。
  • spdlog:一個快速的 C++ 日志庫,只包含頭文件,兼容 C++11。
  • ……

其中 log4cplus、glog 較為流行,一個是著名的 Log4j 的衍生品,另一個則是 Google 的“親兒子”。

包括 log4qt,也是 Log4j 的衍生品,可以參考:DevBean豆子大神的github

以上就是Qt實(shí)現(xiàn)自定義日志類的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Qt自定義日志類的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Qt地圖自適應(yīng)拉伸的實(shí)現(xiàn)示例

    Qt地圖自適應(yīng)拉伸的實(shí)現(xiàn)示例

    最近需要寫一個程序,要是讓qt到程序自適應(yīng),本文主要介紹了Qt地圖自適應(yīng)拉伸的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • 關(guān)于C++內(nèi)部類的介紹與使用示例

    關(guān)于C++內(nèi)部類的介紹與使用示例

    今天小編就為大家分享一篇關(guān)于關(guān)于C++內(nèi)部類的介紹與使用示例,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • C語言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法

    C語言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法

    這篇文章主要介紹了C語言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法,涉及C語言針對數(shù)組的遍歷與判斷技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-07-07
  • C++實(shí)現(xiàn)二叉樹遍歷序列的求解方法

    C++實(shí)現(xiàn)二叉樹遍歷序列的求解方法

    這篇文章主要介紹了C++實(shí)現(xiàn)二叉樹遍歷序列的求解方法,需要的朋友可以參考下
    2014-08-08
  • C++使用回溯法解決黃金礦工問題

    C++使用回溯法解決黃金礦工問題

    在矩陣中考察回溯算法,分為任意起點(diǎn)、左上角開始等情況。從而有不同的模板,其實(shí)區(qū)別就是直接開始還是每個坐標(biāo)都去嘗試,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2022-10-10
  • c++?創(chuàng)建型設(shè)計(jì)模式工廠方法Factory?Method示例詳解

    c++?創(chuàng)建型設(shè)計(jì)模式工廠方法Factory?Method示例詳解

    這篇文章主要為大家介紹了c++?創(chuàng)建型設(shè)計(jì)模式工廠方法Factory?Method示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • C++進(jìn)程的創(chuàng)建和進(jìn)程ID標(biāo)識詳細(xì)介紹

    C++進(jìn)程的創(chuàng)建和進(jìn)程ID標(biāo)識詳細(xì)介紹

    傳統(tǒng)的C++(C++98)中并沒有引入線程這個概念。linux和unix操作系統(tǒng)的設(shè)計(jì)采用的是多進(jìn)程,進(jìn)程間的通信十分方便,同時進(jìn)程之間互相有著獨(dú)立的空間,不會污染其他進(jìn)程的數(shù)據(jù),天然的隔離性給程序的穩(wěn)定性帶來了很大的保障
    2022-08-08
  • C語言指針超詳細(xì)講解下篇

    C語言指針超詳細(xì)講解下篇

    指針提供了對地址操作的一種方法,因此,使用指針可使得?C?語言能夠更高效地實(shí)現(xiàn)對計(jì)算機(jī)底層硬件的操作。另外,通過指針可以更便捷地操作數(shù)組。在一定意義上可以說,指針是?C?語言的精髓
    2022-04-04
  • C語言進(jìn)階教程之預(yù)處理

    C語言進(jìn)階教程之預(yù)處理

    C語言提供了多種預(yù)處理功能,如宏定義、文件包含、條件編譯等,下面這篇文章主要給大家介紹了關(guān)于C語言進(jìn)階教程之預(yù)處理的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-02-02
  • C++的std::transform()的實(shí)現(xiàn)

    C++的std::transform()的實(shí)現(xiàn)

    在 C++ 標(biāo)準(zhǔn)庫中,std::transform() 是一個非常有用的算法函數(shù),它能夠?qū)⒔o定范圍中的每個元素進(jìn)行變換,并將變換后的結(jié)果存儲到另一個范圍中,本文就詳細(xì)的介紹一下具體用法,感興趣的可以了解一下
    2023-08-08

最新評論