一、简单重定向
cpp
#include <QApplication>
#include <QDebug>
#include <QFile>
#include <QTextStream>
#include <QLoggingCategory>
#include <iostream>
#include <QMutex>
#include <QDateTime>
void customMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
//#1 create directory that name is log
QDir dir("log");
if (!dir.exists())
{
QDir dir;
dir.mkdir("log");
}
//#2 create log file by current date ==> eg:log20170418.txt
QString currentDate = QDateTime::currentDateTime().toString("yyyyMMdd");
QString logName = "log" + currentDate + ".txt";
QString logFileName = "log/" + logName;
// 加锁
static QMutex mutex;
mutex.lock();
QFile file(logFileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Append))
{
file.close();
return ;
}
//#3 joint string
QString currentDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
QString logMsg;
switch (type)
{
case QtDebugMsg:
logMsg = QString("%1: [Debug]: %2 Function: %3 File: %4 Line: %5\n").arg(currentDateTime).arg(msg).arg(context.function).arg(context.file).arg(context.line);
break;
case QtInfoMsg:
logMsg = QString("%1: [Info]: %2 Function: %3 File: %4 Line: %5\n").arg(currentDateTime).arg(msg).arg(context.function).arg(context.file).arg(context.line);
break;
case QtWarningMsg:
logMsg = QString("%1: [Warning]: %2 Function: %3 Line: %4 File: %5\n").arg(currentDateTime).arg(msg).arg(context.function).arg(context.file).arg(context.line);
break;
case QtCriticalMsg:
logMsg = QString("%1: [Critical]: %2 Function: %3 Line: %4 File: %5\n").arg(currentDateTime).arg(msg).arg(context.function).arg(context.file).arg(context.line);
break;
case QtFatalMsg:
logMsg = QString("%1: [Fatal]: %2 Function: %3 Line: %4 File: %5\n").arg(currentDateTime).arg(msg).arg(context.function).arg(context.file).arg(context.line);
abort();
break;
default:
break;
}
//#4 log message out to file
QTextStream stream(&file);
stream << logMsg << "\r\n";;
file.flush();
file.close();
// 解锁
mutex.unlock();
}
int main(int argc, char *argv[])
{
qInstallMessageHandler(customMessageHandler);
QApplication a(argc, argv);
qDebug() << "this is Debug";
qInfo() << "this is info";
qWarning() << "this is warning";
qCritical()<< "this is critical";
qFatal("this is fatal");
return a.exec();
}
二、
***.h
cpp
#ifndef LOGGER_H
#define LOGGER_H
#include <QObject>
#include <QtMessageHandler>
#include <QDebug>
#include <QtDebug>
#include <QTextStream>
#include <QApplication>
#include <QDateTime>
#include <QMutex>
#include <QFile>
#include <QFileInfo>
#include <QMetaEnum>
#include <QSettings>
#include <QDir>
#define LOG_MAXSIZE 5 * 1024 //单个log文件最大值
class logger: public QObject
{
Q_OBJECT
public:
explicit logOutput(QObject *parent = nullptr);
~logOutput();
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
static void logThread::outPutMsg(QtMsgType type, const char *msg);
#else
//信息处理函数(重写的myMessageHandler)
/*功能说明:通过调试信息保存到日志文件
*
*参数说明:
* msgType: 调试信息类型或级别(qdebug, qwarning, qfatal 。。。。)
* context: 调试信息所处文本,可使用context.file和context.line获取文本所处行数及所处文件路径,以及使用context.function获取文本所处函数名
* msg: 调试信息内容,自定义
*/
static void outPutMsg(QtMsgType msgType, const QMessageLogContext &context, const QString &msg);
#endif
static void install(); //安装信息处理函数
static void uninstall(); //卸载信息处理函数
static void deleteLog(); //删除过期日志
// static void setLogMaxSize(qint64 maxsize); //设置单个日志文件最大值
private:
/*
* 函数功能:
* 1、根据调试信息以及日期,保存到相应的文件。
* 2、在保存文件前需要判断文件大小是否大于自定义值,如果大于,便按照序号从小到大新建一个。
*
*
*/
static void saveLog(QString message, QString type);
//参数为模块开关名
static bool judgeSwitch(QString switchName); //判断模块是否存在
static void write_ini_file(QString switchName); //写入ini信息
static QVariant read_ini_file(QString switchName); //读取ini信息
};
#endif // LOGOUTPUT_H
***.cpp
cpp
logger.cpp
#include "logger.h"
#include <QFile>
logger::logger(QObject *parent)
: QObject{parent}
{
}
logger::~logOutput()
{
qDebug("内存已释放");
}
//安装日志函数
void logger::install()
{
//安装消息处理函数
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
qInstallMsgHandler(outPutMsg);
#else
qInstallMessageHandler(outPutMsg);
#endif
//创建log文件夹
QString logPath = QApplication::applicationDirPath() + "/log";
QDir dir(logPath);
if(!dir.exists())
{
dir.mkdir(logPath);
qDebug()<<"文件夹创建成功";
}
deleteLog(); //删除过期日志
}
//卸载日志函数
void logger::uninstall()
{
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
qInstallMsgHandler(0);
#else
qInstallMessageHandler(0);
#endif
}
//日志信息处理函数
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
void logger::outPutMsg(QtMsgType type, const char *msg)
#else
void logger::outPutMsg(QtMsgType msgType, const QMessageLogContext &context, const QString &msg)
#endif
{
bool switchValue; //判断开关标志
static QMutex mutex; //设置互斥锁
//通过文件路径获取模块名比如widget.cpp
QString switchKey = QString::fromLocal8Bit(context.file);
int last = switchKey.lastIndexOf("\\");
int leng = switchKey.length();
switchKey = switchKey.right(leng-last-1);
if(judgeSwitch(switchKey) == true) //判断对应模块配置属性是否存在
{
switchValue = read_ini_file(switchKey).toBool(); //读取配置的值
}
else
{
write_ini_file(switchKey); //新写入不存在的配置
switchValue = read_ini_file(switchKey).toBool(); //读取配置的值
}
//判断信息类型
QString type;
switch (msgType) {
case QtDebugMsg:
type = QString("Debug");
break;
case QtWarningMsg:
type = QString("Warning");
switchValue = true; //警告信息不能通过开关关闭
break;
case QtCriticalMsg:
type = QString("Critical");
switchValue = true; //危险信息不能通过开关关闭
break;
case QtFatalMsg:
type = QString("Fatal");
switchValue = true; //致命信息不能通过开关关闭
break;
case QtInfoMsg:
type = QString("Info");
default:
break;
}
if(switchValue == true) //判断配置开关是否打开
{
mutex.lock(); //互斥关锁
//文件名和行数以及函数
QString contextInfo = QString("[File:(%1), Line:(%2), Funtion(%3)]:").arg(context.file).arg(context.line).arg(context.function);
//获取当前时间,精确到秒
QString currentTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
//拼接信息字符串
QString message = QString("[%1] %2: %3 %4").arg(currentTime).arg(type).arg(contextInfo).arg(msg);
//存入信息到日志文件
saveLog(message, type);
mutex.unlock(); //开锁
}
}
//判断配置是否存在
bool logger::judgeSwitch(QString switchName)
{
QSettings ini_config("logSetting.ini", QSettings::IniFormat);
ini_config.beginGroup("class_Switch");
bool isContains = ini_config.contains(switchName);
ini_config.endGroup();
return isContains;
}
//读取配置文件
QVariant logger::read_ini_file(QString switchName)
{
QSettings ini_config("logSetting.ini", QSettings::IniFormat);
ini_config.beginGroup("class_Switch");
QVariant switchValue = ini_config.value(switchName);
ini_config.endGroup();
return switchValue;
}
//写入配置文件
void logger::write_ini_file(QString switchName)
{
QSettings ini_config("logSetting.ini", QSettings::IniFormat);
ini_config.beginGroup("class_Switch");
ini_config.setValue(switchName, false);
ini_config.endGroup();
}
//删除过期日志
void logger::deleteLog()
{
//获取日志文件夹地址
QString dirName = QApplication::applicationDirPath() + "/log";
QDir dir(dirName);
//获取文件夹下所有文件信息列表
QFileInfoList infoList = dir.entryInfoList(QDir::Files);
//遍历日志文件
foreach (QFileInfo fileInfo, infoList) {
//将文件创建时间与过期时间作比较,如果创建时间小于过期时间,则删除(代码是一分钟期限,如果改天为单位可以使用adddays)
if(fileInfo.birthTime() <= QDateTime::currentDateTime().addSecs(-1))
{
QFile::setPermissions(dirName + "/" +fileInfo.fileName(), QFileDevice::ReadOther | QFileDevice::WriteOther);
if(QFile::remove(dirName + "/" +fileInfo.fileName()))
{
qDebug("日志删除成功!");
}
else
{
qDebug("日志删除失败!");
}
}
}
}
//保存日志到文件
void logger::saveLog(QString message, QString type)
{
int i = 1; //当文件大小超过最大值时,给新文件添加编号
//以天为单位给文件命名
QString fileName = QApplication::applicationDirPath() + "/log/" + QDateTime::currentDateTime().toString("yyyy-MM-dd")+ "_" + type + "_log";
//文件名右边(后缀)
QString fileNameRight;
//最终要写入的文件名
QString fileNameLast = fileName + ".txt";
//绑定文件对象
QFile file(fileNameLast);
//判断文件大小
while(file.size() >= LOG_MAXSIZE)
{
//给新文件加入序号后缀
fileNameRight = QString("%1.txt").arg(i);
//拼接最终文件名
fileNameLast = fileName + fileNameRight;
//修改file绑定的文件名
file.setFileName(fileNameLast);
i++;
}
//只写和拼接的方式打开文件
bool isopen = file.open(QIODevice::WriteOnly | QIODevice::Append);
if(isopen == true)
{
QTextStream write(&file);
qDebug() << message;
write << message << "\r\n";
file.flush();
file.close();
}
}
cpp
int main(int argc, char *argv[])
{
logger::install();
QApplication a(argc, argv);
qDebug() << "this is Debug";
qInfo() << "this is info";
qWarning() << "this is warning";
qCritical()<< "this is critical";
qFatal("this is fatal");
return a.exec();
}
三、参数在配置文件设置(推荐)
****.h
cpp
#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>
#include "configinfo.h"
struct LogHandlerPrivate {
LogHandlerPrivate();
~LogHandlerPrivate();
// 打开日志文件 log.txt,如果日志文件不是当天创建的,则使用创建日期把其重命名为 yyyy-MM-dd.log,并重新创建一个 log.txt
void openAndBackupLogFile();
void checkLogFiles(); // 检测当前日志文件大小
void autoDeleteLog(); // 自动删除30天前的日志
// 消息处理函数
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
QDir logDir; // 日志文件夹
QTimer renameLogFileTimer; // 重命名日志文件
QTimer flushLogFileTimer; // 刷新输出到日志文件的定时器
QDate logFileCreatedDate; // 日志文件创建的时间
static QFile *logFile; // 日志文件
static QTextStream *logOut; // 输出日志的 QTextStream,使用静态对象就是为了减少函数调用的开销
static QMutex logMutex; // 同步使用的 mutex
QString logPath; // 日志的路径
ConfigInfo m_ConfigInfo;
static bool bDebugOnOff;
int Days;
int logLimitSize;
};
class LogHandler {
public:
void installMessageHandler(); // 给Qt安装消息处理函数
void uninstallMessageHandler(); // 取消安装消息处理函数并释放资源
static LogHandler& Get() {
static LogHandler m_logHandler;
return m_logHandler;
}
private:
LogHandler();
LogHandlerPrivate *d;
};
#endif // LOGHANDLER_H
****.cpp
cpp
#include "loghandler.h"
// 初始化 static 变量
QMutex LogHandlerPrivate::logMutex;
QFile* LogHandlerPrivate::logFile = nullptr;
QTextStream* LogHandlerPrivate::logOut = nullptr;
bool LogHandlerPrivate::bDebugOnOff;
LogHandlerPrivate::LogHandlerPrivate()
{
int renameTimer = m_ConfigInfo.find("LOG","RENAME_TIMER").toInt();
int flushTimer = m_ConfigInfo.find("LOG","FLUSH_TIMER").toInt();
bDebugOnOff = m_ConfigInfo.find("LOG","DEBUG").toString().contains("ON",Qt::CaseInsensitive);
Days = m_ConfigInfo.find("LOG","DAYS").toInt();
logLimitSize = m_ConfigInfo.find("LOG","LIMIT_SIZE").toInt();
logDir.setPath("log"); // TODO: 日志文件夹的路径,为 exe 所在目录下的 log 文件夹,可从配置文件读取
// 如果日志所在目录不存在,则创建
if (!logDir.exists()) {
logDir.mkpath("."); // 可以递归的创建文件夹
}
logPath = logDir.absoluteFilePath("today.log"); // 获取日志的路径
// ========获取日志文件创建的时间========
// 所以不能运行时使用这个函数检查创建时间,因为会在运行时变化,于是在程序启动时保存下日志文件的最后修改时间,
logFileCreatedDate = QFileInfo(logPath).lastModified().date(); // 若日志文件不存在,返回nullptr
// 打开日志文件,如果不是当天创建的,备份已有日志文件
openAndBackupLogFile();
// 十分钟检查一次日志文件创建时间
renameLogFileTimer.setInterval(1000 * renameTimer); // TODO: 从配置文件读取
renameLogFileTimer.start();
QObject::connect(&renameLogFileTimer, &QTimer::timeout, [this] {
QMutexLocker locker(&LogHandlerPrivate::logMutex);
openAndBackupLogFile(); // 打开日志文件
checkLogFiles(); // 检测当前日志文件大小
autoDeleteLog(); // 自动删除30天前的日志
});
// 定时刷新日志输出到文件,尽快的能在日志文件里看到最新的日志
flushLogFileTimer.setInterval(1000*flushTimer); // TODO: 从配置文件读取
flushLogFileTimer.start();
QObject::connect(&flushLogFileTimer, &QTimer::timeout, [] {
// qDebug() << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); // 测试不停的写入内容到日志文件
QMutexLocker locker(&LogHandlerPrivate::logMutex);
if (nullptr != logOut) {
logOut->flush();
}
});
}
LogHandlerPrivate::~LogHandlerPrivate() {
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 LogHandlerPrivate::openAndBackupLogFile() {
// 总体逻辑:
// 1. 程序启动时 logFile 为 nullptr,初始化 logFile,有可能是同一天打开已经存在的 logFile,所以使用 Append 模式
// 2. logFileCreatedDate is nullptr, 说明日志文件在程序开始时不存在,所以记录下创建时间
// 3. 程序运行时检查如果 logFile 的创建日期和当前日期不相等,则使用它的创建日期重命名,然后再生成一个新的 log.txt 文件
// 4. 检查日志文件超过 LOGLIMIT_NUM 个,删除最早的
// 备注:log.txt 始终为当天的日志文件,当第二天,会执行第3步,将使用 log.txt 的创建日期重命名它
// 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]] 如果文件是第一次创建,则创建日期是无效的,把其设置为当前日期
if (logFileCreatedDate.isNull()) {
logFileCreatedDate = QDate::currentDate();
}
}
// [[3]] 程序运行时如果创建日期不是当前日期,则使用创建日期重命名,并生成一个新的 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 后才出现
QFile::remove(logPath); // 删除重新创建,改变创建时间
// 重新创建 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");
}
}
// 检测当前日志文件大小
void LogHandlerPrivate::checkLogFiles() {
// 如果 protocal.log 文件大小超过5M,重新创建一个日志文件,原文件存档为yyyy-MM-dd_hhmmss.log
if (logFile->size() > 1024*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 后才出现
QFile::remove(logPath); // 删除重新创建,改变创建时间
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(-Days);
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());
}
}
}
// 消息处理函数
void LogHandlerPrivate::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
QMutexLocker locker(&LogHandlerPrivate::logMutex);
bool switchValue =true; //判断开关标志
QString level;
switch (type) {
case QtDebugMsg:
level = "DEBUG";
switchValue = LogHandlerPrivate::bDebugOnOff;
break;
case QtInfoMsg:
level = "INFO ";
break;
case QtWarningMsg:
level = "WARN ";
break;
case QtCriticalMsg:
level = "ERROR";
break;
case QtFatalMsg:
level = "FATAL";
break;
default:
break;
}
// 输出到标准输出: 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
if (nullptr == LogHandlerPrivate::logOut)
return;
// 输出到日志文件
/* QString fileName = context.file;
int index = fileName.lastIndexOf(QDir::separator());
fileName = fileName.mid(index + 1);
// 格式: 时间 - [Level] (文件名:行数, 函数): 消息
QString message = 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);*/
// 格式: 时间 - [Level] : 消息
QString message = QString("[%1]-[%2] : %3\n").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")).arg(level).arg(msg);
if (switchValue) {
std::cout << std::string(localMsg) << std::endl; //控制台输出
(*LogHandlerPrivate::logOut) << message;
}
}
/************************************************************************************************************
* *
* LogHandler *
* *
***********************************************************************************************************/
LogHandler::LogHandler() : d(nullptr) {
}
// 给Qt安装消息处理函数
void LogHandler::installMessageHandler() {
QMutexLocker locker(&LogHandlerPrivate::logMutex); // 类似C++11的lock_guard,析构时自动解锁
if (nullptr == d) {
d = new LogHandlerPrivate();
qInstallMessageHandler(LogHandlerPrivate::messageHandler); // 给 Qt 安装自定义消息处理函数
}
}
// 取消安装消息处理函数并释放资源
void LogHandler::uninstallMessageHandler() {
QMutexLocker locker(&LogHandlerPrivate::logMutex);
qInstallMessageHandler(nullptr);
delete d;
d = nullptr;
}
****.h
cpp
#ifndef CONFIGINFO_H
#define CONFIGINFO_H
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QSettings>
class ConfigInfo
{
public:
ConfigInfo();
~ConfigInfo();
QVariant find(const QString strGroup, const QString value);
private:
QSettings *m_config;
};
#endif // CONFIGINFO_H
****.cpp
cpp
#include "configinfo.h"
ConfigInfo::ConfigInfo()
{
QDir ConfigDir;
ConfigDir.setPath("config");
if (!ConfigDir.exists()) {
ConfigDir.mkpath("."); // 可以递归的创建文件夹
}
QString ConfigPath = ConfigDir.absoluteFilePath("config.ini"); // 获取路径
m_config = new QSettings(ConfigPath, QSettings::IniFormat);
}
ConfigInfo::~ConfigInfo()
{
if(m_config)
delete m_config;
}
QVariant ConfigInfo::find(const QString strGroup, const QString value)
{
m_config->beginGroup(strGroup);
QVariant switchValue = m_config->value(value);
m_config->endGroup();
return switchValue;
}
*****.ini
cpp
[LOG]
RENAME_TIMER = 2
FLUSH_TIMER = 1
DEBUG = on
# 自动删除30天前的日志
DAYS = 30
#文件大小5M
LIMIT_SIZE = 5
main.cpp
cpp
#include "mainwindow.h"
#include "loghandler.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// [[1]] 安装消息处理函数
LogHandler::Get().installMessageHandler();
MainWindow w;
w.show();
// [[2]] 输出测试,查看是否写入到文件
qDebug() << "Hello";
qInfo() << QString("God bless you!");
/*
// [[3]] 取消安装自定义消息处理,然后启用
LogHandler::Get().uninstallMessageHandler();
qDebug() << "........"; // 不写入日志
LogHandler::Get().installMessageHandler();
*/
int ret = app.exec(); // 事件循环结束
// [[4]] 程序结束时释放 LogHandler 的资源,例如刷新并关闭日志文件
LogHandler::Get().uninstallMessageHandler();
return ret;
}
https://doc.qt.io/qt-6/zh/qtlogging.html
https://blog.csdn.net/weixin_42661333/article/details/129127694