实现QT中qDebug()的日志重定向

背景:

在项目开发过程中,为了方便分析和排查问题,我们需要将原本输出到控制台的调试信息写入日志文件,进行持久化存储,还可以实现日志分级等。

日志输出格式:

我们需要的格式包括以下内容:

1.2024-02-19 10:21:11:387: 时间戳,表示日志记录的时间,包括年、月、日、时、分、秒、毫秒。

2.thread:0x7f44aa137600: 线程信息,表示记录日志的线程ID。

3.Debug : 日志级别,这里是Debug级别,表示是调试信息。

4.sampleapply.cpp line:972 void SampleApply::on_applyBtn_clicked: 文件名、行号和函数名信息,指明日志记录所在的源文件、行号和函数。

5.{ 打印日志}: 具体的日志内容,这里是一个占位符,实际应该是打印的具体日志信息。

按照以上信息,接下来我会说明如何实现这些内容的打印输出以及qDebug()的重定向。

一、根据上下文提取文件名、函数名

cpp 复制代码
static QString getFileName(QString logContext)
{
    if (logContext.isEmpty())
        return QString("");

    //输入是相对路径名,只取其中的文件名
    /**
     * windows格式:"..\\UI\\src\\rpc\\uimessage.cpp"
     * ubuntu格式: "../UI/src/rpc/uimessage.cpp"
     */

    QString fileName(logContext);
    int start = 0;
#ifdef Q_OS_WIN32
    start = fileName.lastIndexOf("\\")+1;
#else
    start = fileName.lastIndexOf("/")+1;
#endif

    return fileName.mid(start);
}

说明:
根据给定的源文件路径 logContext 提取并返回文件名。该函数的主要作用是根据不同操作系统的路径格式,提取文件名,从而满足跨平台开发。
cpp 复制代码
static QString getFunName(QString logContext)
{
    if (logContext.isEmpty())
        return QString("");

    //有两个情况,只取其中的函数名,ubuntu下该格式不适用
    // void __cdecl MainWindow::onBootup()
    // int __cdecl main(int,char *[])
    QString funName(logContext);
    int start = 0;
#ifdef Q_OS_WIN32		//使用预处理器指令,判断当前编译环境是否为 Windows
    start = funName.lastIndexOf("decl")+4;
#endif
    int end   = funName.indexOf("(");

    return funName.mid(start, end-start);
}

说明:
根据给定的日志上下文 logContext 提取并返回函数名。该函数的主要作用是解析函数名的不同形式(在 Windows 环境下,一些函数可能带有 __cdecl 标识)。

二、处理日志的输出

cpp 复制代码
void LogManager::outputLog(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    //如果要写文件需要加锁,以确保在多线程环境中正确处理日志输出
    QMutexLocker locker(&mLogMutex);
    QString out_text;
    QTextStream stream(&out_text);

    //打印时间和线程ID信息
    stream<<QDateTime::currentDateTime().toString("[yyyy-MM-dd hh:mm:ss:zzz] ") << "thread:" << QThread::currentThreadId() << " ";

    //根据日志类型在字符串头部加上相应的标识,如 "[Debug]", "[Info]", "[Warning]", "[Critical]", "[Fatal]"等
    switch (type) {
        case QtDebugMsg:        stream<<"[Debug   ]"; break;
        case QtInfoMsg:         stream<<"[Info    ]"; break;
        case QtWarningMsg:      stream<<"[Warning ]"; break;
        case QtCriticalMsg:     stream<<"[Critical]"; break;
        case QtFatalMsg:        stream<<"[Fatal   ]"; break;
        default:                stream<<"[Unknown ]"; break;
    }

    //调用 getFileName 和 getFunName 函数获取文件名和函数名,并添加到日志中
    QString fileName = getFileName(context.file);       ///<打印所在的源文件名称
    QString funName = getFunName(context.function);     ///<函数名称
    QString tmpStr = QString("[%1 line:%2 %3] { %4}").arg(fileName).arg(context.line).arg(funName).arg(msg);
    
    stream << tmpStr;

    //日志大小分割,分割大小默认为5MB,此功能默认不开启
    if(mIsFileSplit){

        QFileInfo info(mFile.fileName());

        //获取文件大小并进行比较
        if(info.size() >= mFileSize*1024*1024){
            //超过设定值后重命名该文件,关闭文件
            mFile.rename(mFilePath + QDate::currentDate().toString("yyyyMMdd")+"_ui_" + QString::number(LOG_INDEX) + ".txt");
            if(mFile.isOpen()){
                mFile.close();
            }

            //建立并打开新的文件准备进行写入
            mFile.setFileName(mFilePath + QDate::currentDate().toString("yyyyMMdd")+"_ui_" + QString::number(++LOG_INDEX) + ".txt");
            if(!mFile.open(QIODevice::WriteOnly|QIODevice::Append)){
                emit newLog(QtDebugMsg,"Open log file error:"+mFile.errorString()+mFile.fileName());        //打开失败发送错误消息
            }
        }

    }

    if(!mFile.isOpen()){
        //文件未打开则按照日期设定文件的名称
        mFile.setFileName(mFilePath + QDate::currentDate().toString("yyyyMMdd")+"_ui" + ".log");

        //打开文件,使用Append追加模式,避免同一文件被清除
        if(!mFile.open(QIODevice::WriteOnly|QIODevice::Append)){
            emit newLog(QtDebugMsg,"Open log file error:"+mFile.errorString()+mFile.fileName());        //打开失败发送错误消息
        }
    }
    //文件打开则直接进行写入
    if(mFile.isOpen()){
        //写入文件
        stream.setDevice(&mFile);
        stream<<out_text<<Qt::endl;
    }

    //发送信号给需要的对象,如ui上显示日志
    emit newLog(type, msg);

    //默认的输出,控制台
    //区分日志类型给文本加颜色
    //常见格式为:\e[显示方式;前景色;背景色m输出字符串\e[0m
    //其中\e=\033
    QString cmd_text;
    stream.setString(&cmd_text);
    switch (type) {
    //debug绿色
        case QtDebugMsg:        stream<<"\033[34m"; break;
    //info蓝色
        case QtInfoMsg:         stream<<"\033[34m"; break;
    //warning紫色
        case QtWarningMsg:      stream<<"\033[35m"; break;
    //critical加粗红色
        case QtCriticalMsg:     stream<<"\033[1;31m"; break;
    //fatal红底黑字
    //Fatal表示致命错误,默认处理会报异常的
        case QtFatalMsg:        stream<<"\033[1;30;41m"; break;
    //defualt默认颜色
        default:                stream<<"\033[0m"; break;
    }
    stream<<out_text<<"\033[0m";
    mDefaultOutput(type,context,cmd_text);
}

三、日志的重定向

在程序运行时将 Qt 的调试输出(qDebug() 等)重定向到自定义的日志处理函数,以实现对日志的自定义记录和处理。

cpp 复制代码
//重定向qdebug输出
void outputLogMessage(QtMsgType type, const QMessageLogContext& context, const QString& msg)
{
    //转发给单例的成员函数
    LogManager::getInstance()->outputLog(type,context,msg);
}

//在构造函数中调用:
void LogManager::initManager(const QString &path)
{
    //保存路径
    mFilePath=path;
    if(mFilePath.isEmpty())
    {
        //使用QDir直接获取当前路径
        mFilePath = QDir::currentPath()+"/log/ui/";
    }
    QDir dir(mFilePath);
    if(!dir.exists())
    {
        dir.mkpath(mFilePath);
    }
    //重定向qdebug到自定义函数
    mDefaultOutput=qInstallMessageHandler(outputLogMessage);
}

四、释放资源

在释放 LogManager 类时,我们需要确保相关资源的正确释放,包括关闭已打开的日志文件,并取消对消息的自定义处理。

cpp 复制代码
LogManager::LogManager():
    mFileSize(5),
    mIsFileSplit(false)
{
    initManager();
}

void LogManager::freeManager()
{
    mFile.close();
    qInstallMessageHandler(nullptr);
}

以上就是本文全部内容,欢迎一起讨论!

相关推荐
用户805533698034 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner4 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz9 天前
QML Hello World 入门示例
qt
xcyxiner12 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner13 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner13 天前
DicomViewer (添加模型类)3
qt
xcyxiner14 天前
DicomViewer (目录调整) 2
qt
xcyxiner14 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00616 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术16 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript