log4qt内存泄露问题,heob内存检测工具的使用

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: log4qt内存泄露问题,heob内存检测工具的使用

log4qt,是大名鼎鼎的阿帕奇的java日志库log4j的qt移植版。本是挺常用的开源库,然而在使用过程中发现了内存泄露的坑。为了验证下,这里单独写了个测试demo,并使用qtcreator集成的hoeb内存泄露检测工具分析下。


测试用例很简单,就是一个MainWindow界面上放置两个按钮。点下按钮分别启动一个线程,间隔10ms不断的向日志文件里写日志。


测试用例


测试用例如下:


void MainWindow::on_pushButton_clicked()
{
    ui->pushButton->setEnabled(false);
    QFuture<void> future = QtConcurrent::run([&]()
    {
        while(1)
        {
            QMutex mutex;
            QMutexLocker locker(&mutex);
            logger->info("&&&&&on_pushButton_clicked&&&&&&&",__FILE__,__FUNCTION__,QString::number(__LINE__));
            QThread::msleep(10);
        }
    });
}
void MainWindow::on_pushButton_2_clicked()
{
    ui->pushButton_2->setEnabled(false);
    QFuture<void> future = QtConcurrent::run([&]()
    {
        while(1)
        {
            QMutex mutex;
            QMutexLocker locker(&mutex);
            logger->info("&&&&&on_pushButton_2_clicked&&&&&&&",__FILE__,__FUNCTION__,QString::number(__LINE__));
            QMetaObject::invokeMethod(qApp, [this]{
                ui->tb->setText("count:");
            });
            QThread::msleep(10);
        }
    });
}


测试结果


测试运行了一个小时,内存竟占用到惊人的四百多兆,结果如下:



为此,我还在github上提交了一个isuse,期待作者和其他开源爱好者们的回复。



测试源码目录结构


我的测试代码目录结构如下,把log4qt的源码整个复制过来,单独的log4qt文件夹内。



先说下测试环境,使用qt5.10.0的32位msvc工具链 和qt5.12.11的64位msvc工具链测试,结果一样,同样存在泄露。


使用的log4qt的版本是1.5.0


log4qt的github地址:GitHub - MEONMedical/Log4Qt: Log4Qt - Logging for the Qt cross-platform application framework


以下是我的log4qt_test.pro文件内容:


其中的build.pri和g++.pri文件,在log4qt的master分支里有。


#-------------------------------------------------
#
# Project created by QtCreator 2022-07-26T16:57:19
#
#-------------------------------------------------
QT       += core gui concurrent
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
DESTDIR = $$PWD/./bin
TARGET = log4qt_test
#TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
DEFINES +=LOG4QT_STATIC
LOG4QTSRCPATH = $$PWD/log4qt
INCLUDEPATH += -L $$LOG4QTSRCPATH \
                $$LOG4QTSRCPATH/helpers \
                 $$LOG4QTSRCPATH/spi \
                 $$LOG4QTSRCPATH/varia
DEPENDPATH  +=  $$LOG4QTSRCPATH \
            $$LOG4QTSRCPATH/helpers \
            $$LOG4QTSRCPATH/spi \
            $$LOG4QTSRCPATH/varia
include($$PWD/log4qt/log4qt.pri)
include($$PWD/log4qt/build.pri)
include($$PWD/log4qt/g++.pri)
include(logger/logger.pri)
SOURCES += \
        main.cpp \
        mainwindow.cpp
HEADERS += \
        mainwindow.h
FORMS += \
        mainwindow.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
LIBS += -L$$PWD/./bin


其中的logger.pri,仅是对log4qt的一个简单封装:


HEADERS  += $$PWD/include/mylogger.h \
            $$PWD/include/logqt.h \
            $$PWD/include/operation_log.h
SOURCES += \
           $$PWD/src/logqt.cpp \
    $$PWD/src/mylogger.cpp
INCLUDEPATH += $$PWD


#include "../include/mylogger.h"
#include "../include/logqt.h"
#include <QJsonObject>
#include <QJsonValue>
#include <QJsonParseError>
#include <QJsonDocument>
#include <QDir>
#include <QFile>
MyLogger*  MyLogger::logger_ = new MyLogger;
MyLogger *MyLogger::getInstance()
{
    return logger_;
}
bool MyLogger::setConfPath(const QString &path)
{
    return log4qt_->setConfPath(path);
}
MyLogger::MyLogger(QObject *parent):QObject(parent),log4qt_(new LogQt),machine_id("Undefined")
{
    QString conf_path = QDir::currentPath()+"/../config/machine_info.json";
    QFile loadFile(conf_path);
    if(!loadFile.open(QIODevice::ReadOnly))
    {
//        log4qt_->error("load id config file failed");
        return;
    }
    else
    {
//        log4qt_->info("load id config file success");
    }
    QByteArray allData = loadFile.readAll();//将文件内容放入allData对象
    loadFile.close();//使用(读取)完成,结束占用
    QJsonParseError json_error;//解析期间报告错误
    QJsonDocument jsonDoc(QJsonDocument::fromJson(allData, &json_error));//新建一个json文档对象
    if(json_error.error != QJsonParseError::NoError)//如果解析成功,则该分支不会进入
    {
//        log4qt_->error("json format error: "+json_error.errorString());
        return;
    }
    QJsonObject obj = jsonDoc.object();
    if(obj.contains("machine_id")&&obj["machine_id"].isString())
        machine_id=obj["machine_id"].toString();
}
MyLogger::~MyLogger()
{
    if (log4qt_)
    {
        log4qt_->deleteLater();
        log4qt_ = NULL;
    }
}
void MyLogger::info(const QString& data,const QString& file,const QString& func,const QString& line)
{
    log4qt_->info(QString("%1  file: %2  func: %3  line: %4").arg(data).arg(file).arg(func).arg(line));
}
void MyLogger::debug(const QString& data,const QString& file,const QString& func,const QString& line)
{
    log4qt_->debug(QString("%1  file: %2  func: %3  line: %4").arg(data).arg(file).arg(func).arg(line));
}
void MyLogger::warn(const QString& data,const QString& file,const QString& func,const QString& line)
{
    log4qt_->warn(QString("%1  file: %2  func: %3  line: %4").arg(data).arg(file).arg(func).arg(line));
}
void MyLogger::error(const QString& data,const QString& file,const QString& func,const QString& line)
{
    log4qt_->error(QString("%1  file: %2  func: %3  line: %4").arg(data).arg(file).arg(func).arg(line));
}
QString get_data_time();
void MyLogger::operation_log(QString &business, QString &data, QString &user, business_state state, QString &level)
{
    QJsonObject data_obj;
    data_obj.insert("type","operation");
    data_obj.insert("time",get_data_time());
    data_obj.insert("level",level);
    data_obj.insert("user",user);
    data_obj.insert("business",business);
    QString str_business_state;
    if(state==business_state::start)
        str_business_state="start";
    else if(state==business_state::finsish)
        str_business_state="finish";
    else
        str_business_state="running";
    data_obj.insert("state",str_business_state);
    data_obj.insert("data",data);
    QJsonObject root_obj;
    root_obj.insert("type","operationlog");
    root_obj.insert("data",QJsonValue(data_obj));
    root_obj.insert("id",machine_id);
    log4qt_->info("*#*#"+QString(QJsonDocument(root_obj).toJson(QJsonDocument::Compact))+"#*#*");
}
void MyLogger::operation_log(QJsonObject &operation_obj)
{
    operation_obj.insert("type","operation");
    operation_obj.insert("time",get_data_time());
    QJsonObject root_obj;
    root_obj.insert("type","operationlog");
    root_obj.insert("data",QJsonValue(operation_obj));
    root_obj.insert("id",machine_id);
    log4qt_->info("*#*#"+QString(QJsonDocument(root_obj).toJson(QJsonDocument::Compact))+"#*#*");
}
void MyLogger::operation_log(QString &operation_str)
{
}
void MyLogger::data_change_log(QString &key, QString &value)
{
    QJsonObject data_obj;
    data_obj.insert("type","data");
    data_obj.insert("time",get_data_time());
    data_obj.insert("key",key);
    data_obj.insert("value",value);
    QJsonObject root_obj;
    root_obj.insert("type","operationlog");
    root_obj.insert("data",QJsonValue(data_obj));
    root_obj.insert("id",machine_id);
    log4qt_->info("*#*#"+QString(QJsonDocument(root_obj).toJson(QJsonDocument::Compact))+"#*#*");
}
void MyLogger::exception_log(QString &code, QString &desc)
{
    QJsonObject data_obj;
    data_obj.insert("type","exception");
    data_obj.insert("code",code);
    data_obj.insert("resion",desc);
    QJsonObject root_obj;
    root_obj.insert("type","operationlog");
    root_obj.insert("data",QJsonValue(data_obj));
    root_obj.insert("id",machine_id);
    log4qt_->info("*#*#"+QString(QJsonDocument(root_obj).toJson(QJsonDocument::Compact))+"#*#*");
}
void MyLogger::shutdown()
{
    log4qt_->shutdown();
}
#include <QDateTime>
QString get_data_time()
{
    return QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz");
}


#ifndef LOGGER_H
#define LOGGER_H
#include <QObject>
#include "logger_global.h"
enum business_state
{
    start,
    running,
    finsish
};
class QJsonObject;
class LogQt;
/**
 * @class Logger logger.h
 * @brief 日志客户端/服务端
 * @note 根据日志配置文件中配置启动为客户端或服务端
 */
class LOGGERSHARED_EXPORT MyLogger:public QObject
{
    Q_OBJECT
private:
    MyLogger(QObject *parent = NULL);
    ~MyLogger();
public:
    Q_DISABLE_COPY(MyLogger)
    static MyLogger* getInstance();
    /**
    * @brief 设置配置文件路径
    * @param[in] path 配置文件路径
    * @return
    * -true 成功
    * -false 失败
    * @retval bool
    */
    bool setConfPath(const QString& path);
public:
    /**
    * @brief
    * @param[in] data 日志
    * @param[in] table 表名称
    * @param[in] id
    * @note
    * -systemlog 系统运行表
    * -devlog 用户操作表
    */
    Q_INVOKABLE void info(const QString& data,const QString& file = QString(),const QString& func = QString(),const QString& line = QString());
    /**
    * @brief
    * @param[in] data 日志
    * @param[in] table 表名称
    * @param[in] id
    * @note
    * -systemlog 系统运行表
    * -devlog 用户操作表
    */
    Q_INVOKABLE void debug(const QString& data,const QString& file = QString(),const QString& func = QString(),const QString& line = QString());
    /**
    * @brief
    * @param[in] data 日志
    * @param[in] table 表名称
    * @param[in] id
    * @note
    * -systemlog 系统运行表
    * -devlog 用户操作表
    */
    Q_INVOKABLE void warn(const QString& data,const QString& file = QString(),const QString& func = QString(),const QString& line = QString());
    /**
    * @brief
    * @param[in] data 日志
    * @param[in] table 表名称
    * @param[in] id
    * @note
    * -systemlog 系统运行表
    * -devlog 用户操作表
    */
    Q_INVOKABLE void error(const QString& data,const QString& file = QString(),const QString& func = QString(),const QString& line = QString());
    /**
     * @brief           记录操作日志的接口
     * @param business  业务名称
     * @param data      最终显示的字符串
     * @param user      当前机器的操作员
     * @param level     日志的等级,默认是1
     * @param state     只有存在多次交互的业务需要这个字段,不需要多次交互的可以不填
     */
    Q_INVOKABLE void operation_log(QString& business,QString& data,QString& user,business_state state=running,QString& level=QString("1"));
    /**
     * @brief 依旧是操作日志,供c++直接调用。
     * @param operation_obj 日志的json对象
     */
    void operation_log(QJsonObject& operation_obj);
    /**
     * @brief 操作日志,供界面调用。传递json的字符串
     * @param operation_str 日志的json对象的字符串
     */
    Q_INVOKABLE void operation_log(QString& operation_str);
    /**
     * @brief           数据变更操作的接口,这里的数据仅限于用户需要知道的参数,不是每一个key的变更都要追踪
     * @param key       数据库中定义的有的变量key的名字使用与数据库一致的,数据库未定义的,写文档里面
     * @param value     变更后的值
     */
    Q_INVOKABLE void data_change_log(QString& key,QString& value);
    /**
     * @brief       异常的日志,这个异常的日志时给用户看的,不能随便打
     * @param code  异常代码
     * @param desc  异常发生时的代码
     */
    Q_INVOKABLE void exception_log(QString& code,QString& desc);
    /**
    * @brief 关闭日志,刷新buffer
    * @note 调用此方法后日志关闭,必须重新初始化
    */
    Q_INVOKABLE void shutdown();
private:
    static MyLogger* logger_;
    LogQt* log4qt_;
    QString machine_id;
};
#define logger_debug(msg)    MyLogger::getInstance()->debug(msg,__FILE__,__FUNCTION__,QString::number(__LINE__))
#define logger_info(msg)     MyLogger::getInstance()->info(msg,__FILE__,__FUNCTION__,QString::number(__LINE__))
#define logger_warn(msg)     MyLogger::getInstance()->warn(msg,__FILE__,__FUNCTION__,QString::number(__LINE__))
#define logger_error(msg)    MyLogger::getInstance()->error(msg,__FILE__,__FUNCTION__,QString::number(__LINE__))
#endif // LOGGER_H


#include "../include/Logqt.h"
#include "log4qt/loggerrepository.h"
#define LINE_MAX 1024
LogQt::LogQt(QObject *parent) : QObject(parent)
{
}
bool LogQt::setConfPath(const QString &path)
{
    return Log4Qt::PropertyConfigurator::configure(path);
}
LogQt::~LogQt()
{
}
void LogQt::info(const QString& data)
{    
    if(data.size()<LINE_MAX)
    {
        logger()->info(data);
        return;
    }
    int size = data.size();
    logger()->info(QString("**********line start***%1**********").arg(size));
    int i=0;
    for(;i<size - LINE_MAX;i +=LINE_MAX)
    {
        logger()->info(data.mid(i,LINE_MAX).append("\\"));
    }
    logger()->info(data.mid(i,LINE_MAX));
    logger()->info("**********line finish*************");
}
void LogQt::debug(const QString& data)
{    
    if(data.size()<LINE_MAX)
    {
        logger()->debug(data);
        return;
    }
    int size = data.size();
    logger()->debug(QString("**********line start***%1**********").arg(size));
    int i=0;
    for(;i<size - LINE_MAX;i +=LINE_MAX)
    {
        logger()->debug(data.mid(i,LINE_MAX).append("\\"));
    }
    logger()->debug(data.mid(i,LINE_MAX));
    logger()->debug("**********line finish*************");
}
void LogQt::warn(const QString& data)
{
    if(data.size()<LINE_MAX)
    {
        logger()->warn(data);
        return;
    }
    int size = data.size();
    logger()->warn(QString("**********line start***%1**********").arg(size));
    int i=0;
    for(;i<size - LINE_MAX;i +=LINE_MAX)
    {
        logger()->warn(data.mid(i,LINE_MAX).append("\\"));
    }
    logger()->warn(data.mid(i,LINE_MAX));
    logger()->warn("**********line finish*************");
}
void LogQt::error(const QString& data)
{
    if(data.size()<LINE_MAX)
    {
        logger()->error(data);
        return;
    }
    int size = data.size();
    logger()->error(QString("**********line start***%1**********").arg(size));
    int i=0;
    for(;i < size - LINE_MAX;i +=LINE_MAX)
    {
        logger()->error(data.mid(i,LINE_MAX).append("\\"));
    }
    logger()->error(data.mid(i,LINE_MAX));
    logger()->error("**********line finish*************");
}
void LogQt::shutdown()
{
    logger()->loggerRepository()->shutdown();
}


#ifndef LOGQT_H
#define LOGQT_H
#include <QObject>
#include "log4qt/logger.h"
#include "log4qt/propertyconfigurator.h"
class LogQt : public QObject
{
    Q_OBJECT
    LOG4QT_DECLARE_QCLASS_LOGGER
    public:
        explicit LogQt(QObject *parent = nullptr);
    ~LogQt();
    /**
    * @brief 设置配置文件路径
    * @param[in] path 配置文件路径
    * @return
    * -true 成功
    * -false 失败
    * @retval bool
    */
    bool setConfPath(const QString& path);
    /**
    * @brief
    * @param[in] data 日志
    * @param[in] table 表名称
    * @param[in] id
    * @note
    * -systemlog 系统运行表
    * -devlog 用户操作表
    */
    void info(const QString& data);
    /**
    * @brief
    * @param[in] data 日志
    * @param[in] table 表名称
    * @param[in] id
    * @note
    * -systemlog 系统运行表
    * -devlog 用户操作表
    */
    void debug(const QString& data);
    /**
    * @brief
    * @param[in] data 日志
    * @param[in] table 表名称
    * @param[in] id
    * @note
    * -systemlog 系统运行表
    * -devlog 用户操作表
    */
    void warn(const QString& data);
    /**
    * @brief
    * @param[in] data 日志
    * @param[in] table 表名称
    * @param[in] id
    * @note
    * -systemlog 系统运行表
    * -devlog 用户操作表
    */
    void error(const QString& data);
    void shutdown();
signals:
public slots:
};
#endif // LOG4QT_H


日志配置


log4j.rootLogger=DEBUG,daily,console
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%-d [%t] %-5p:  %m%n
log4j.appender.logfile.File=./log/uiTest.log
log4j.appender.logfile.ImmediateFlush=FALSE
log4j.appender.logfile.Threshold=DEBUG
log4j.appender.logfile.AppendFile=TRUE
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%-d [%t] %-5p:  %m%n
#设置一个每日储存一个log文件的记录器
log4j.appender.daily=org.apache.log4j.DailyFileAppender
log4j.appender.daily.file=./log/uiTest.log
log4j.appender.daily.appendFile=true
log4j.appender.daily.datePattern=_yyyy_MM_dd
#log4j.appender.daily.keepDays=90
log4j.appender.daily.layout=${log4j.appender.console.layout}
#log4j.appender.daily.layout.dateFormat=${log4j.appender.console.layout.dateFormat}
log4j.appender.daily.layout.ConversionPattern=%-d [%t] %-5p:  %m%n
#log4j.appender.daily.layout.contextPrinting=${log4j.appender.console.layout.contextPrinting}


heob内存泄露工具分析


https://doc.qt.io/qtcreator/creator-heob.html


heob-堆观察器,qtcreator的4.6以后的版本集成了它的插件。 heob覆盖被调用进程的堆函数,以检测缓冲区溢出和内存泄漏。 在缓冲区溢出时,将引发访问冲突,并提供有问题的指令和缓冲区分配的堆栈跟踪。但heob.exe还是需要单独下载的。可以从github上下载生成好的heob.exe工具。


github:GitHub - ssbssa/heob: Detects buffer overruns and memory leaks.


下载地址:heob




转换QT为VisualStudio工程


有时候使用visualStudio工程打开项目,调试更方便好用些。


可以通过一个插件一键转换qt的pro工程为vs的工程。使用qt-vsaddin-msvc2015-2.2.0.vsix插件。在vs中打开qt项目报错,可能是需要执行以下转换:qmake -tp vc


插件镜像下载地址:


http://mirrors.tuna.tsinghua.edu.cn/qt/archive/vsaddin/2.2.0/qt-vsaddin-msvc2015-2.2.0.vsix


使用vs启动程,点击工具栏中的:调试,选择:“显示诊断工具”,profiler,选择memory usage.


结论


log4qt名声是挺大,开源的是个好东西,但是不代表它就没问题。还是要多做测试,尤其是多做压力情况下的测试,否则可能根本看不出来有问题。QT是好用,但是它的半自动化的内存托管方式是把双刃剑,平常你的new都很小心的对内存操作,记得释放。但是用了qt且习惯了它,它容易让你养成坏习惯。


引用


Visual Studio查看C++内存泄漏方法_wangshenqiang的博客-CSDN博客_vs内存泄露怎么查

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
6月前
|
JavaScript 前端开发 Java
内存管理和内存泄露(闭包、作用域链)(三)
内存管理和内存泄露(闭包、作用域链)
68 0
|
6月前
|
自然语言处理 JavaScript 前端开发
内存管理和内存泄露(闭包、作用域链)(二)
内存管理和内存泄露(闭包、作用域链)
47 0
|
6月前
|
IDE Linux 开发工具
内存泄漏检测工具Valgrind:C++代码问题检测的利器(一)
内存泄漏检测工具Valgrind:C++代码问题检测的利器
1362 0
|
2月前
|
C语言 Android开发 C++
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
本文介绍了使用MTuner软件进行Qt MinGW编译程序的内存泄漏检测的方法,提供了MTuner的下载链接和测试代码示例,并通过将Debug程序拖入MTuner来定位内存泄漏问题。
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
|
3月前
|
JavaScript 前端开发 Java
JavaScript内存泄露大揭秘!你的应用为何频频“爆内存”?点击解锁救星秘籍!
【8月更文挑战第23天】在Web前端开发中,JavaScript是构建动态网页的关键技术。然而,随着应用复杂度增加,内存管理变得至关重要。本文探讨了JavaScript中常见的内存泄露原因,包括意外的全局变量、不当使用的闭包、未清除的定时器、未清理的DOM元素引用及第三方库引发的内存泄露。通过了解这些问题并采取相应措施,开发者可以有效避免内存泄露,提高应用性能。
47 1
|
4月前
|
存储 算法 Java
(四)JVM成神路之深入理解虚拟机运行时数据区与内存溢出、内存泄露剖析
前面的文章中重点是对于JVM的子系统进行分析,在之前已经详细的阐述了虚拟机的类加载子系统以及执行引擎子系统,而本篇则准备对于JVM运行时的内存区域以及JVM运行时的内存溢出与内存泄露问题进行全面剖析。
|
5月前
|
缓存 Linux
centos内存检测工具
【6月更文挑战第1天】centos内存检测工具
161 3
|
5月前
|
监控 Linux 测试技术
edac是检测什么的,和centos内存条损害检测工具
【6月更文挑战第1天】edac是检测什么的,和centos内存条损害检测工具
173 2
|
5月前
|
机器学习/深度学习 分布式计算 JavaScript
心得经验总结:折腾几天,内存检测工具写出来了
心得经验总结:次奥,折腾几天,内存检测工具写出来了
25 0
|
6月前
|
C++ Windows
windows下内存检测工具
windows下内存检测工具
198 0