• QT高级01----自定义日志工具


    这里介绍使用 qInstallMessageHandler() 实现一个简单的日志工具

    • 自动拦截qDebug qInfo等消息
    • 自动删除30天日志
    • 格式化日志输出
    #ifndef LOG_MGR_H
    #define LOG_MGR_H
    
    #include "RestTypes.h"
    #include "Singleton.h"
    
    #include <QtGlobal>
    #include <memory>
    
    
    #define LogMgrInstance LogMgr::GetInstance()
    struct LogMgrPrivate;
    
    class LogMgr
    {
        SINGLETON(LogMgr)
    public:
        static LogMgr* GetInstance();
        ~LogMgr();
    
        void Uninstall(); // 释放资源
        void Install();   // 注册资源
    
    private:
        LogMgr();
        static LogMgr* instance;
    
    private:
        std::unique_ptr<LogMgrPrivate> d;
    };
    
    #endif // !LOG_MGR_H
    #include "LogMgr.h"
    #include <QDebug>
    #include <QDateTime>
    #include <QMutexLocker>
    #include <QtGlobal>
    #include <QDir>
    #include <QFile>
    #include <QFileInfo>
    #include <QTimer>
    #include <QTextStream>
    #include <iostream>
    #include <QTextCodec>
    
    #include <memory>
    #include <mutex>
    
    /************************************************************************************************************
     *                                                                                                          *
     *                                               LogMgrPrivate                                              *
     *                                                                                                          *
     ***********************************************************************************************************/
    struct LogMgrPrivate {
        LogMgrPrivate();
        ~LogMgrPrivate();
    
        // 打开日志文件 log.txt,如果日志文件不是当天创建的,则使用创建日期把其重命名为 yyyy-MM-dd.log,并重新创建一个 log.txt
        void openAndBackupLogFile();
    
        // 消息处理函数
        static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
    
        // 如果日志所在目录不存在,则创建
        void makeSureLogDirectory() const;
    
        QDir   logDir;              // 日志文件夹
        QTimer renameLogFileTimer;  // 重命名日志文件使用的定时器
        QTimer flushLogFileTimer;   // 刷新输出到日志文件的定时器
        QDate  logFileCreatedDate;  // 日志文件创建的时间
    
        static QFile *logFile;      // 日志文件
        static QTextStream *logOut; // 输出日志的 QTextStream,使用静态对象就是为了减少函数调用的开销
        static QMutex logMutex;     // 同步使用的 mutex
    };
    // 初始化 static 变量
    QMutex LogMgrPrivate::logMutex;
    QFile* LogMgrPrivate::logFile = nullptr;
    QTextStream* LogMgrPrivate::logOut = nullptr;
    
    LogMgrPrivate::LogMgrPrivate() {
        logDir.setPath("log"); // TODO: 日志文件夹的路径,为 exe 所在目录下的 log 文件夹,可从配置文件读取
        QString logPath = logDir.absoluteFilePath("dd.log"); // 日志的路径
        // 日志文件创建的时间
        // QFileInfo::created(): On most Unix systems, this function returns the time of the last status change.
        // 所以不能运行时使用这个函数检查创建时间,因为会在运行时变化,于是在程序启动时保存下日志文件的最后修改时间,
        // 在后面判断如果不是今天则用于重命名 log.txt
        // 如果是 Qt 5.10 后,lastModified() 可以使用 birthTime() 代替
        logFileCreatedDate = QFileInfo(logPath).birthTime().date();
    
        // 打开日志文件,如果不是当天创建的,备份已有日志文件
        openAndBackupLogFile();
    
        // 十分钟检查一次日志文件创建时间
        renameLogFileTimer.setInterval(1000 * 60 * 10); // TODO: 可从配置文件读取
        renameLogFileTimer.start();
        QObject::connect(&renameLogFileTimer, &QTimer::timeout, [this] {
            QMutexLocker locker(&LogMgrPrivate::logMutex);
            openAndBackupLogFile();
        });
    
        // 定时刷新日志输出到文件,尽快的能在日志文件里看到最新的日志
        flushLogFileTimer.setInterval(1000); // TODO: 可从配置文件读取
        flushLogFileTimer.start();
        QObject::connect(&flushLogFileTimer, &QTimer::timeout, [] {
            // qDebug() << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); // 测试不停的写入内容到日志文件
            QMutexLocker locker(&LogMgrPrivate::logMutex);
            if (nullptr != logOut) {
                logOut->flush();
            }
        });
    }
    
    LogMgrPrivate::~LogMgrPrivate() {
        if (nullptr != logFile) {
            logFile->flush();
            logFile->close();
            delete logOut;
            delete logFile;
    
            // 因为他们是 static 变量
            logOut  = nullptr;
            logFile = nullptr;
        }
    }
    
    // 打开日志文件 log.txt,如果不是当天创建的,则使用创建日期把其重命名为 yyyy-MM-dd.log,并重新创建一个 log.txt
    void LogMgrPrivate::openAndBackupLogFile() {
        // 总体逻辑:
        // 1. 程序启动时 logFile 为 nullptr,初始化 logFile,有可能是同一天打开已经存在的 logFile,所以使用 Append 模式
        // 2. logFileCreatedDate is nullptr, 说明日志文件在程序开始时不存在,所以记录下创建时间
        // 3. 程序运行时检查如果 logFile 的创建日期和当前日期不相等,则使用它的创建日期重命名,然后再生成一个新的 log.txt 文件
    
        makeSureLogDirectory(); // 如果日志所在目录不存在,则创建
        QString logPath = logDir.absoluteFilePath("dd.log"); // 日志的路径
    
        // [[1]] 程序启动时 logFile 为 nullptr
        if (nullptr == logFile) {
            logFile = new QFile(logPath);
            logOut  = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) ?  new QTextStream(logFile) : nullptr;
    
            if (nullptr != logOut) {
                logOut->setCodec("UTF-8");
            }
    
            // [[2]] 如果文件是第一次创建,则创建日期是无效的,把其设置为当前日期
            if (logFileCreatedDate.isNull()) {
                logFileCreatedDate = QDate::currentDate();
            }
    
            // 超过 30 个,删除 30 天前的日志文件
            logDir.sorting();
            QFileInfoList list = logDir.entryInfoList();//获取文件信息列表
            foreach (auto info, list) {
                qDebug() << info.filePath();
                if (info.birthTime().date().daysTo(QDate::currentDate()) > 30) {
                    QFile::remove(info.filePath());
                }
            }
    
        }
    
        // [[3]] 程序运行时如果创建日期不是当前日期,则使用创建日期重命名,并生成一个新的 log.txt
        if (logFileCreatedDate != QDate::currentDate()) {
            logFile->flush();
            logFile->close();
            delete logOut;
            delete logFile;
    
            QString newLogPath = logDir.absoluteFilePath(logFileCreatedDate.toString("dd-yyyy-MM-dd.log"));
            QFile::rename(logPath, newLogPath);
    
    
            logFile = new QFile(logPath);
            logOut  = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) ?  new QTextStream(logFile) : nullptr;
            logFileCreatedDate = QDate::currentDate();
    
            if (nullptr != logOut) {
                logOut->setCodec("UTF-8");
            }
        }
    }
    
    // 如果日志所在目录不存在,则创建
    void LogMgrPrivate::makeSureLogDirectory() const {
        if (!logDir.exists()) {
            logDir.mkpath("."); // 可以递归的创建文件夹
        }
    }
    
    // 消息处理函数
    void LogMgrPrivate::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
        QMutexLocker locker(&LogMgrPrivate::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;
        }
    
    
        QByteArray localMsg = msg.toLocal8Bit();
        std::cout << std::string(localMsg) << std::endl;
    
        if (nullptr == LogMgrPrivate::logOut) {
            return;
        }
    
        // 输出到日志文件, 格式: 时间 - [Level] (文件名:行数, 函数): 消息
        QString fileName = context.file;
        int index = fileName.lastIndexOf(QDir::separator());
        fileName = fileName.mid(index + 1);
    
        (*LogMgrPrivate::logOut) << QString("%1 - [%2] (%3:%4, %5): %6
    ")
                                        .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")).arg(level)
                                        .arg(fileName).arg(context.line).arg(context.function).arg(msg);
    }
    
    
    /************************************************************************************************************
     *                                                                                                          *
     *                                               LogMgr                                                     *
     *                                                                                                          *
     ***********************************************************************************************************/
    
    LogMgr* LogMgr::instance = nullptr;
    
    LogMgr* LogMgr::GetInstance()
    {
        static std::once_flag s_flag;
        std::call_once(s_flag, [&]() {
            instance = new LogMgr;
        });
        return instance;
    }
    
    LogMgr::LogMgr()
    {
        Uninstall();
    }
    
    LogMgr::~LogMgr()
    {
        LOG_TRACE()
        if (instance) {
            delete instance;
            instance = nullptr;
        }
    }
    
    void LogMgr::Uninstall()
    {
        QMutexLocker locker(&LogMgrPrivate::logMutex);
        qInstallMessageHandler(nullptr);
        d.release();
    }
    
    void LogMgr::Install()
    {
        QMutexLocker locker(&LogMgrPrivate::logMutex);
    
           if (nullptr == d) {
               d.reset(new LogMgrPrivate());
               qInstallMessageHandler(LogMgrPrivate::messageHandler); // 给 Qt 安装自定义消息处理函数
           }
    }

    生成:

    2020-05-30 15:34:43 - [DEBUG] (main.cpp:21, int __cdecl main(int,char *[])): This is a debug message.
    2020-05-30 15:34:43 - [INFO ] (main.cpp:22, int __cdecl main(int,char *[])): This is a info message.
    2020-05-30 15:34:43 - [WARN ] (main.cpp:23, int __cdecl main(int,char *[])): This is a warning message.
    2020-05-30 15:34:43 - [ERROR] (main.cpp:24, int __cdecl main(int,char *[])): This is a critical message.
    2020-03-30 15:37:16 - [DEBUG] (main.cpp:21, int __cdecl main(int,char *[])): This is a debug message.
    2020-03-30 15:37:16 - [INFO ] (main.cpp:22, int __cdecl main(int,char *[])): This is a info message.
    2020-03-30 15:37:16 - [WARN ] (main.cpp:23, int __cdecl main(int,char *[])): This is a warning message.
    2020-03-30 15:37:16 - [ERROR] (main.cpp:24, int __cdecl main(int,char *[])): This is a critical message.
    2020-03-30 15:38:15 - [DEBUG] (main.cpp:21, int __cdecl main(int,char *[])): This is a debug message.
    2020-03-30 15:38:15 - [INFO ] (main.cpp:22, int __cdecl main(int,char *[])): This is a info message.
    2020-03-30 15:38:15 - [WARN ] (main.cpp:23, int __cdecl main(int,char *[])): This is a warning message.
    2020-03-30 15:38:15 - [ERROR] (main.cpp:24, int __cdecl main(int,char *[])): This is a critical message.

    参考:

  • 相关阅读:
    1026 Table Tennis (30)
    1029 Median
    1025 PAT Ranking (25)
    1017 Queueing at Bank (25)
    1014 Waiting in Line (30)
    1057 Stack (30)
    1010 Radix (25)
    1008 Elevator (20)
    字母大小写转换
    Nmap的基础知识
  • 原文地址:https://www.cnblogs.com/vczf/p/12599139.html
Copyright © 2020-2023  润新知