这里介绍使用 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.
参考: