zoukankan      html  css  js  c++  java
  • 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.

    参考:

  • 相关阅读:
    第一阶段冲刺 第三天
    第一阶段冲刺 第二天
    第一阶段冲刺 第一天
    第十周进度表
    第九周进度表
    NABCD需求分析
    典型用户和场景分析
    第一个冲刺周期-第一天
    第十周进度表
    团队电梯演讲视频链接
  • 原文地址:https://www.cnblogs.com/vczf/p/12599139.html
Copyright © 2011-2022 走看看