实现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()的重定向。

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

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 提取并返回文件名。该函数的主要作用是根据不同操作系统的路径格式,提取文件名,从而满足跨平台开发。
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 标识)。

二、处理日志的输出

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() 等)重定向到自定义的日志处理函数,以实现对日志的自定义记录和处理。

//重定向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 类时,我们需要确保相关资源的正确释放,包括关闭已打开的日志文件,并取消对消息的自定义处理。

LogManager::LogManager():
    mFileSize(5),
    mIsFileSplit(false)
{
    initManager();
}

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/441814.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鸡肋的Git

1.前言 对于大多数开发人员来说&#xff0c;我们大多数在学习或者工作过程中只关注核心部分&#xff0c;比如说学习Java&#xff0c;可能对于大多数人而言一开始都是从Java基础学起&#xff0c;然后408&#xff0c;Spring&#xff0c;中间件等&#xff0c;当你发现很多高深的技…

红队专题-开源漏扫-巡风xunfeng源码剖析与应用

开源漏扫-巡风xunfeng 介绍主体两部分:网络资产识别引擎,漏洞检测引擎。代码赏析插件编写JSON标示符Python脚本此外系统内嵌了辅助验证功能文件结构功能 模块添加IP三. 进行扫描在这里插入图片描述 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/de587a6f6f694…

html前端的几种加密/解密方式

HTML前端的加密解密方式有以下几种&#xff1a; 一、base64加密 Base64编码&#xff1a;Base64是一种将二进制数据转换为可打印字符的编码方式。在前端&#xff0c;可以使用JavaScript的btoa()函数进行Base64编码&#xff0c;使用atob()函数进行解码。 var str "hello…

二维码门楼牌管理系统在教育领域的应用及其优势

文章目录 前言一、二维码门楼牌管理系统概述二、教育领域的应用场景三、二维码门楼牌管理系统的优势四、结语 前言 随着信息技术的快速发展&#xff0c;二维码门楼牌管理系统在教育领域的应用越来越广泛。该系统不仅提高了地址信息的准确性&#xff0c;还为学校、家长和教育工…

Feign实现微服务间远程调用续;基于Redis实现消息队列用于延迟任务的处理,Redis分布式锁的实现;(黑马头条Day05)

目录 延迟任务和定时任务 使用Redis设计延迟队列原理 点评项目中选用list和zset两种数据结构进行实现 如何缓解Redis内存的压力同时保证Redis中任务能够被正确消费不丢失 系统流程设计 使用Feign实现微服务间的任务消费以及文章自动审核 系统微服务功能介绍 提交文章-&g…

C#,数值计算,解微分方程的龙格-库塔四阶方法与源代码

Carl Runge Martin Wilhelm Kutta 1 龙格-库塔四阶方法 数值分析中&#xff0c;龙格&#xff0d;库塔法&#xff08;Runge-Kutta&#xff09;是用于模拟常微分方程的解的重要的一类隐式或显式迭代法。这些技术由数学家卡尔龙格和马丁威尔海姆库塔于1900年左右发明。 对于一阶…

[Electron]中IPC进程间通信

Electron中IPC 进程间通信 (IPC) 是在 Electron 中构建功能丰富的桌面应用程序的关键部分之一。在 Electron 中&#xff0c;进程使用 ipcMain 和 ipcRenderer 模块&#xff0c;通过开发人员定义的“通道”传递消息来进行通信。 本文介绍以下几个方面&#xff1a; 1-渲染进程到…

【vue.js】文档解读【day 3】 | 列表渲染

如果阅读有疑问的话&#xff0c;欢迎评论或私信&#xff01;&#xff01; 文章目录 列表渲染v-forv-for 与对象在 v-for 里使用范围值template 上的 v-forv-for与v-if通过key管理状态组件上使用v-for数组变化侦测 列表渲染 v-for 在我们想要渲染出一个数组中的元素时&#xf…

【C语言】数据类型和变量

前言&#x1f49e;&#x1f49e; 啦啦啦~这里是土土数据结构学习笔记&#x1f973;&#x1f973; &#x1f4a5;个人主页&#xff1a;大耳朵土土垚的博客 &#x1f4a5; 所属专栏&#xff1a;C语言笔记 &#x1f4a5;欢迎大家&#x1f973;&#x1f973;点赞✨收藏&#x1f49…

linux网络编程(概念)

概念 通信四元组 IP&#xff08;主机&#xff09; 0号地址与1号地址 端口&#xff08;进程&#xff09; 四元组组成 各种体系结构 网络的封包和解包 ip地址向物理&#xff08;mac&#xff09;地址转换 mac转换ip-------->RARP协议 TCP协议 UDP协议 socket函数接口

瑞_23种设计模式_模板方法模式

文章目录 1 模板方法模式&#xff08;Template Pattern&#xff09; ★ 钩子函数1.1 介绍1.2 概述1.3 模板方法模式的结构1.4 模板方法模式的优缺点1.5 模板方法模式的使用场景 2 案例一2.1 需求2.2 代码实现 3 案例二3.1 需求3.2 代码实现 4 JDK源码解析&#xff08;InputStre…

java项目线上捉BUG经验记录

一 线上问题 昨晚上突然接到公司紧急来电电桩设备大面积离线&#xff0c;意味着某市的车无法充电……”&#xff0c;细想这个平台很久都没有更新&#xff0c;最近出现问题是刚好在一个月前也是出现这种情况&#xff0c;再上一次就是几年前更新的。平台平时是稳定&#xff0c;开…

php使用ElasticSearch

ElasticSearch简介 Elasticsearch 是一个分布式的、开源的搜索分析引擎&#xff0c;支持各种数据类型&#xff0c;包括文本、数字、地理、结构化、非结构化。 Lucene与ElasticSearch Apache Lucene是一款高性能的、可扩展的信息检索&#xff08;IR&#xff09;工具库&#xf…

【企业发展战略】某环境管理集团公司发展战略与规划项目纪实

在集团公司高速发展、业务范围不断扩大时&#xff0c;组织往往对公司未来的发展方向感到迷茫&#xff0c;不知道如何进行更好的规划&#xff0c;找到合适的发展战略&#xff0c;为企业提供更长远的发展空间&#xff0c;带来更多是利益。面对这个问题&#xff0c;华恒智信认为企…

StarRocks实战——欢聚集团极速的数据分析能力

目录 一、大数据平台架构 二、OLAP选型及改进 三、StarRocks 经验沉淀 3.1 资源隔离&#xff0c;助力业务推广 3.1.1 面临的挑战 3.1.2 整体效果 3.2 稳定优先&#xff0c;监控先行&#xff0c;优化运维 3.3降低门槛&#xff0c;不折腾用户 3.3.1 与现有的平台做打通 …

Springboot+vue的物业管理系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的物业管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的物业管理系统&#xff0c;采用M&#xff08;model&#xff09;V&#xff…

C++自创题目——几点了 very hard ver.

题目难度 普及 题目描述 一个老外用一口不流利的中文问你&#xff1a;“Xian zai ji dian le?”你看了一眼表&#xff0c;知道了现在是&#xff0c;你准备用这样的形式写在纸上&#xff1a; Now is m past/to h. 如果你看不懂&#xff0c;举个例子&#xff1a; 当h10&#…

运维知识点-Apache HTTP Server

Apache 介绍 介绍 Apache是一个开源的Web服务器软件&#xff0c;全称为Apache HTTP Server&#xff0c;由Apache软件基金会开发和维护。它是目前全球使用最广泛的Web服务器软件之一&#xff0c;占全球所有网络服务器的很大比例。Apache服务器具有跨平台的特性&#xff0c;可以…

【Hadoop大数据技术】——Hadoop概述与搭建环境(学习笔记)

&#x1f4d6; 前言&#xff1a;随着大数据时代的到来&#xff0c;大数据已经在金融、交通、物流等各个行业领域得到广泛应用。而Hadoop就是一个用于处理海量数据的框架&#xff0c;它既可以为海量数据提供可靠的存储&#xff1b;也可以为海量数据提供高效的处理。 目录 &#…

STM32 通过Modbus协议更改内部Flash(模仿EEPROM)的运行参数

main.c测试 uint8_t uart1RxBuf[64]{0};uint8_t Adc1ConvEnd0; uint8_t Adc2ConvEnd0;int main(void) {/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initial…