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

news2024/9/22 10:26:43

背景:

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

日志输出格式:

在这里插入图片描述
我们需要的格式包括以下内容:
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);
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java - 框架 - Mybatis】(01) 普通Java项目使用Mybatis操作Mysql - 快速上手

普通Java项目使用Mybatis操作Mysql - 快速上手 说明 通过软件"IntelliJ IDEA"创建"Maven"项目完成&#xff1b;通过"Mybatis"框架操纵"MySQL"数据库完成操作&#xff1b; 环境 Java版本"1.8.0_202"&#xff1b;Windows …

根据QQ号获取暗恋的人的全部歌单

文章目录 前言一、成果展示二、后端开发流程三、前后端障碍与难点解决四、待扩展内容五、总结 前言 本人喜欢使用QQ音乐听歌&#xff0c;并且喜欢点击好友栏目观看最近在听&#xff0c;了解暗恋的人最近在听什么歌曲&#xff0c;知己知彼&#xff0c;百战不殆。但是每次都需要…

Python数据分析实验一:Python数据采集与存储

目录 一、实验目的与要求二、实验过程三、主要程序清单和运行结果1、爬取 “中国南海网” 站点上的相关信息2、爬取天气网站上的北京的历史天气信息 四、程序运行结果五、实验体会 一、实验目的与要求 1、目的&#xff1a; 理解抓取网页数据的一般处理过程&#xff1b;熟悉应用…

JAVA实战开源项目:智能停车场管理系统(Vue+SpringBoot)

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容A. 车主端功能B. 停车工作人员功能C. 系统管理员功能1. 停车位模块2. 车辆模块3. 停车记录模块4. IC卡模块5. IC卡挂失模块 三、界面展示3.1 登录注册3.2 车辆模块3.3 停车位模块3.4 停车数据模块3.5 IC卡档案模块3.6 IC卡挂…

【python】异常处理

前言 省略各种废话&#xff0c;直接快速整理知识点 try-except 基础 作用 程序不可能永远都是对的&#xff0c;当7除a&#xff0c;a由用户输入时&#xff0c;用户输入0就会报错。try-except就是解决这些问题。 结构 多分支自定义错误类型 上方的exception是一个错误类型…

Unity性能优化篇(七) UI优化注意事项以及使用Sprite Atlas打包精灵图集

UI优化注意事项 1.尽量避免使用IMGUI(OnGUI)来做游戏时的UI&#xff0c;因为IMGUI的开销比较大。 2.如果一个UGUI的控件不需要进行射线检测&#xff0c;则可以取消勾选Raycast Target 3.尽量避免使用完全透明的图片和UI控件。因为即使完全透明&#xff0c;我们看不见它&#xf…

【牛客】HJ87 密码强度等级 CM62 井字棋

题目一:密码强度等级 题目链接&#xff1a;密码强度等级_牛客题霸_牛客网 (nowcoder.com) 本题主要考察C语言中逻辑分支语句&#xff0c;基本语句以及对各种特殊字符 &#xff0c;ASCII值以及条件表达中的逻辑运算符关系运算符各自功能的理解&#xff0c;以及基本使用&#x…

【linuxC语言】dup、dup2函数

文章目录 前言一、dup函数二、dup2函数三、将标准输出重定向到文件总结 前言 在Linux环境下&#xff0c;dup、dup2以及原子操作都是用于文件描述符管理和处理的重要工具。这些功能提供了对文件描述符进行复制和原子操作的能力&#xff0c;使得在多线程或多进程环境中更加安全和…

qt一个项目有且只有有一个maindow,其他小窗口用QWidget,QDialog是带有yes和no的QWidget

QMaindow QWidget QDialog区别很大 我想要在生成一个小窗口&#xff0c;结果选择基类为maindow&#xff0c;应该是QWidget 然后就出现奇奇怪怪的问题 QMaindow和QWidget不能乱选择&#xff0c;而且各自QPaintEvent也有很多区别 以下就是错误&#xff1a; 我继承maindow的基类…

C#,排列组合的堆生成法(Heap’s Algorithm for generating permutations)算法与源代码

1 排列组合的堆生成法 堆生成算法用于生成n个对象的所有组合。其思想是通过选择一对要交换的元素&#xff0c;在不干扰其他n-2元素的情况下&#xff0c;从先前的组合生成每个组合。 下面是生成n个给定数的所有组合的示例。 示例&#xff1a; 输入&#xff1a;1 2 3 输出&a…

【Qt】四种绘图设备详细使用

绘图设备有4个: **绘图设备是指继承QPainterDevice的子类————**QPixmap QImage QPicture QBitmap(黑白图片) QBitmap——父类QPixmapQPixmap图片类&#xff0c;主要用来显示&#xff0c;它针对于显示器显示做了特殊优化&#xff0c;依赖于平台的&#xff0c;只能在主线程…

python编程从入门到实践

python编程从入门到实践 if语句1.条件测试&#xff1a;2.更多的条件测试&#xff1a;3.外星人颜色#1&#xff1a;4. 外星人颜色#2&#xff1a;5. 外星人颜色#3&#xff1a;6. 人生的不同阶段&#xff1a;7. 喜欢的水果&#xff1a;8. 以特殊方式跟管理员打招呼&#xff1a;9. 处…

程序员失业,被迫开启 PlanB——成为自由职业/独立开发者的第 0 天

程序员失业&#xff0c;被迫开启 PlanB——成为自由职业/独立开发者的第 0 天 今天在逛V2EX的时候看到的一个帖子&#xff0c;程序员中年被裁&#xff0c;被迫开启独立开发这条路。 原贴如下&#xff1a; lastday, 失业啦 公司年前通知我合同到期不续签&#xff0c;今天是我…

React改变数据【案例】

State传统方式 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>React Demo</title> <!--…

专业的项目管理系统,企智汇!帮助企业提高项目管理效率!

一款专业的项目管理系统&#xff0c;是企智汇项目管理系统&#xff01;企智汇专业做项目管理系统10年&#xff0c;经过10年的打磨&#xff0c;有成熟的项目管理系统功能&#xff0c;它面向各个企业的项目团队&#xff0c;提供数字化、智能化、信息化的项目管理功能&#xff0c;…

【亲测有效】解决三月八号ChatGPT 发消息无响应!

背景 今天忽然发现 ChatGPT 无法发送消息&#xff0c;能查看历史对话&#xff0c;但是无法发送消息。 可能的原因 出现这个问题的各位&#xff0c;应该都是点击登录后顶部弹窗邀请 [加入多语言 alapha 测试] 了&#xff0c;并且语言选择了中文&#xff0c;抓包看到 ab.chatg…

工作合同坑

1-不要给证件原件&#xff0c;给复印件&#xff0c;并写明用处 。 2-正式入职才填写详细的个人信息 3-入职的各种相关费用需要谨慎&#xff0c;注意是否合法正规 4-招转培是个坑 5-无薪试岗&#xff0c;滚 6-试用期工资不能低于转正之后的80%&#xff0c;一定要有纸质的正式工资…

LayerNorm的图是不是画错了

这是网上一张很流行的说明几个 Normalization 区别的图 这图出自Kaiming的文章 Group Norm 但是他这个 Layer Norm 的图是不是画错了? 我大四写毕设的时候就想问&#x1f923;&#x1f923;&#x1f923; 这都几年过去了 我觉得图应该是这样画的&#xff0c;相同颜色的区域…

使用docker-compose编排ruoyi项目

目录 一、开始部署 1.拉取ruoyi代码 2.拉取node镜像 3.拉取maven镜像 4.在/root/ruoyi/java下写一个Dockerfile用于后端Java环境 5.拉取MySQL&#xff0c;Redis&#xff0c;Nginx镜像 6.在/root/java目录下写一个nginx.conf 7.在/root/ruoyi目录下写docker-compose.yml文…

计算机网络——计算机网络的性能

计算机网络——计算机网络的性能 速率带宽吞吐量时延时延宽带积往返时间RTT利用率信道利用率网络利用率 我们今天来看看计算机网络的性能。 速率 速率这个很简单&#xff0c;就是数据的传送速率&#xff0c;也称为数据率&#xff0c;或者比特率&#xff0c;单位为bit/s&#…