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\n")
                                    .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.

 

参考:

 

posted @ 2020-03-30 15:59  Vzf  阅读(547)  评论(0编辑  收藏  举报