Qt调试日志输出到文件(使用QtMessageHandler的消息处理函数)

日志类的介绍

/* 该类实现将调试信息打印输出到文件的功能,同时每次运行程序,会自动检查过期日志文件并删除,过期时间为一分钟,可在deleteLog方法中进行修改。

  • 可通过配置文件中的开关控制不同模块是否启动日志,但是只有普通的debug和info信息可以被关闭,其他几类信息不可被屏蔽。
  • 其中调试信息分为以下几类:
  • 1、debug
  • 2、warning
  • 3、critial
  • 4、fatal
  • 5、info
  • 调试方法:比如使用qDebug(“程序运行成功”)宏调试即可,其他几类以此类推,qWarning()、qCritial()、qInfo()、qFatal()。
  • 注意事项:
  • 1、日志文件类产生的log文件在QApplication的应用目录的log文件夹下
  • 2、其配置文件log_setting.ini在当前工作路径下,可在安装日志函数前使用QDir::setcurrent()进行更改
  • 3、使用方法:在main函数开始处调用静态函数logOutput::install()即可安装完毕。
  • 4、如果需要关闭某个模块的日志显示,可直接去新创建的配置文件中修改。
  • 5、配置文件中,true代表生成日志,false代表不生成日志。第一次创建配置文件默认全为false。
  • 6、日志文件格式:当天日期+信息种类
    */

思路:使用QtMessageHandler自带的回调消息处理函数myMessageHandler(可重写),获取调试信息所处文件位置及代码所处行数,并输出信息内容到文件,其中还要使用QSetting类对ini配置文件进行模块开关的读写。
头文件代码如下:

logOutput.h
#ifndef LOGOUTPUT_H
#define LOGOUTPUT_H

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define LOG_MAXSIZE  5 * 1024 //单个log文件最大值

class logOutput : 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

头文件的方法中,outPutmsg方法的作用是当其他模块调用qDebug等调试函数是,就会将当前语句的context内容、信息种类(qDebug、qWarning、qInfo。。。。)以及信息内容。其中context内容还可获取该语句的文件位置及行数。install是用来间接安装消息处理程序的。uninstall同理,一旦使用uninstall以后,该语句后面的调试信息都将得不到处理,从而输出到控制台上。deleteLog,是用来删除创建超过一定时间的过期日志的。saveLog是将消息处理函数得出的message保存到指定文件中。最后三个函数是对开关配置文件进行检查、读取和写入的操作。

logOutput.cpp
#include "logoutput.h"
#include 

logOutput::logOutput(QObject *parent)
    : QObject{parent}
{

}

logOutput::~logOutput()
{
    qDebug("内存已释放");
}

//安装日志函数
void logOutput::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 logOutput::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 logThread::outPutMsg(QtMsgType type, const char *msg)
#else
void logOutput::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 logOutput::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 logOutput::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 logOutput::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 logOutput::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 logOutput::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();
    }


}

代码运行流程:首先在main函数调用install静态方法安装消息处理函数后,然后创建一个log文件夹用以存放日志文件,同时在创建文件夹以后调用deleteLog函数,用以每次运行程序时,检查过期文件并删除。
以qDebug(“msg”)为例,然后等待主程序运行到该函数的位置。然后这时会调用outPutMsg函数,获取该语句的类型为QtDebug、文件内容context、以及信息内容“msg”。这时需要先对使用context.file获取文件地址,并提取其文件名,然后调用judgeSwitch方法,判断该内容的开关是否存在,如果是否,就调用写入配置函数,将该文件名写入开关配置,初始为false。接着通过switch语句判断信息类型,如果是qDebug和QInfo信息,则获取种类字符串type即可,其他信息种类必须把开关标志位设置为true。也就是不能通过开关关闭,防止错过致命消息。然后判断开关标志位,如果是true,则进行下一步,如果是false则停止。进入下一步以后,获取当前时间,信息所处文件名、行数、函数名以及信息内容本身。然后将他们拼接成一个message字符串(自定义输出到文件的内容,代码仅供参考),然后调用saveLog,传入信息内容和类型以保存文件。
首先,需要获取当天,当前类型的log文件夹大小,超过文件最大值则再创建一个同名文件,编号依次递增,这里设置的是最大5KB,然后使用QdataStream写入mesaage,具体实现请阅读代码,注释的很清楚。

展示:
配置文件内容如下:
Qt调试日志输出到文件(使用QtMessageHandler的消息处理函数)_第1张图片
log文件夹内容:
Qt调试日志输出到文件(使用QtMessageHandler的消息处理函数)_第2张图片
代码调试语句:
Qt调试日志输出到文件(使用QtMessageHandler的消息处理函数)_第3张图片log文件内容:
Qt调试日志输出到文件(使用QtMessageHandler的消息处理函数)_第4张图片
message = 当前时间 + 信息类型 + 文件位置 + 代码行数 + 函数名 + 信息内容。

ini文件的读写请阅读:https://www.cnblogs.com/QingYiShouJiuRen/p/16639547.html

后续扩展:可以将日志存储地址写入配置文件。也可使用代码直接设置地址。

你可能感兴趣的:(qt,开发语言,c++)