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


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

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

    测试用例

    测试用例如下:

    1. void MainWindow::on_pushButton_clicked()
    2. {
    3. ui->pushButton->setEnabled(false);
    4. QFuture<void> future = QtConcurrent::run([&]()
    5. {
    6. while(1)
    7. {
    8. QMutex mutex;
    9. QMutexLocker locker(&mutex);
    10. logger->info("&&&&&on_pushButton_clicked&&&&&&&",__FILE__,__FUNCTION__,QString::number(__LINE__));
    11. QThread::msleep(10);
    12. }
    13. });
    14. }
    15. void MainWindow::on_pushButton_2_clicked()
    16. {
    17. ui->pushButton_2->setEnabled(false);
    18. QFuture<void> future = QtConcurrent::run([&]()
    19. {
    20. while(1)
    21. {
    22. QMutex mutex;
    23. QMutexLocker locker(&mutex);
    24. logger->info("&&&&&on_pushButton_2_clicked&&&&&&&",__FILE__,__FUNCTION__,QString::number(__LINE__));
    25. QThread::msleep(10);
    26. }
    27. });
    28. }

    测试结果

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

     为此,我还在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分支里有。

    1. #-------------------------------------------------
    2. #
    3. # Project created by QtCreator 2022-07-26T16:57:19
    4. #
    5. #-------------------------------------------------
    6. QT += core gui concurrent
    7. greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
    8. CONFIG += c++11
    9. DESTDIR = $$PWD/./bin
    10. TARGET = log4qt_test
    11. #TEMPLATE = app
    12. # The following define makes your compiler emit warnings if you use
    13. # any feature of Qt which has been marked as deprecated (the exact warnings
    14. # depend on your compiler). Please consult the documentation of the
    15. # deprecated API in order to know how to port your code away from it.
    16. DEFINES += QT_DEPRECATED_WARNINGS
    17. # You can also make your code fail to compile if you use deprecated APIs.
    18. # In order to do so, uncomment the following line.
    19. # You can also select to disable deprecated APIs only up to a certain version of Qt.
    20. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
    21. DEFINES +=LOG4QT_STATIC
    22. LOG4QTSRCPATH = $$PWD/log4qt
    23. INCLUDEPATH += -L $$LOG4QTSRCPATH \
    24. $$LOG4QTSRCPATH/helpers \
    25. $$LOG4QTSRCPATH/spi \
    26. $$LOG4QTSRCPATH/varia
    27. DEPENDPATH += $$LOG4QTSRCPATH \
    28. $$LOG4QTSRCPATH/helpers \
    29. $$LOG4QTSRCPATH/spi \
    30. $$LOG4QTSRCPATH/varia
    31. include($$PWD/log4qt/log4qt.pri)
    32. include($$PWD/log4qt/build.pri)
    33. include($$PWD/log4qt/g++.pri)
    34. include(logger/logger.pri)
    35. SOURCES += \
    36. main.cpp \
    37. mainwindow.cpp
    38. HEADERS += \
    39. mainwindow.h
    40. FORMS += \
    41. mainwindow.ui
    42. # Default rules for deployment.
    43. qnx: target.path = /tmp/$${TARGET}/bin
    44. else: unix:!android: target.path = /opt/$${TARGET}/bin
    45. !isEmpty(target.path): INSTALLS += target
    46. LIBS += -L$$PWD/./bin

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

    1. HEADERS += $$PWD/include/mylogger.h \
    2. $$PWD/include/logqt.h \
    3. $$PWD/include/operation_log.h
    4. SOURCES += \
    5. $$PWD/src/logqt.cpp \
    6. $$PWD/src/mylogger.cpp
    7. INCLUDEPATH += $$PWD
    1. #include "../include/mylogger.h"
    2. #include "../include/logqt.h"
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. MyLogger* MyLogger::logger_ = new MyLogger;
    10. MyLogger *MyLogger::getInstance()
    11. {
    12. return logger_;
    13. }
    14. bool MyLogger::setConfPath(const QString &path)
    15. {
    16. return log4qt_->setConfPath(path);
    17. }
    18. MyLogger::MyLogger(QObject *parent):QObject(parent),log4qt_(new LogQt),machine_id("Undefined")
    19. {
    20. QString conf_path = QDir::currentPath()+"/../config/machine_info.json";
    21. QFile loadFile(conf_path);
    22. if(!loadFile.open(QIODevice::ReadOnly))
    23. {
    24. // log4qt_->error("load id config file failed");
    25. return;
    26. }
    27. else
    28. {
    29. // log4qt_->info("load id config file success");
    30. }
    31. QByteArray allData = loadFile.readAll();//将文件内容放入allData对象
    32. loadFile.close();//使用(读取)完成,结束占用
    33. QJsonParseError json_error;//解析期间报告错误
    34. QJsonDocument jsonDoc(QJsonDocument::fromJson(allData, &json_error));//新建一个json文档对象
    35. if(json_error.error != QJsonParseError::NoError)//如果解析成功,则该分支不会进入
    36. {
    37. // log4qt_->error("json format error: "+json_error.errorString());
    38. return;
    39. }
    40. QJsonObject obj = jsonDoc.object();
    41. if(obj.contains("machine_id")&&obj["machine_id"].isString())
    42. machine_id=obj["machine_id"].toString();
    43. }
    44. MyLogger::~MyLogger()
    45. {
    46. if (log4qt_)
    47. {
    48. log4qt_->deleteLater();
    49. log4qt_ = NULL;
    50. }
    51. }
    52. void MyLogger::info(const QString& data,const QString& file,const QString& func,const QString& line)
    53. {
    54. log4qt_->info(QString("%1 file: %2 func: %3 line: %4").arg(data).arg(file).arg(func).arg(line));
    55. }
    56. void MyLogger::debug(const QString& data,const QString& file,const QString& func,const QString& line)
    57. {
    58. log4qt_->debug(QString("%1 file: %2 func: %3 line: %4").arg(data).arg(file).arg(func).arg(line));
    59. }
    60. void MyLogger::warn(const QString& data,const QString& file,const QString& func,const QString& line)
    61. {
    62. log4qt_->warn(QString("%1 file: %2 func: %3 line: %4").arg(data).arg(file).arg(func).arg(line));
    63. }
    64. void MyLogger::error(const QString& data,const QString& file,const QString& func,const QString& line)
    65. {
    66. log4qt_->error(QString("%1 file: %2 func: %3 line: %4").arg(data).arg(file).arg(func).arg(line));
    67. }
    68. QString get_data_time();
    69. void MyLogger::operation_log(QString &business, QString &data, QString &user, business_state state, QString &level)
    70. {
    71. QJsonObject data_obj;
    72. data_obj.insert("type","operation");
    73. data_obj.insert("time",get_data_time());
    74. data_obj.insert("level",level);
    75. data_obj.insert("user",user);
    76. data_obj.insert("business",business);
    77. QString str_business_state;
    78. if(state==business_state::start)
    79. str_business_state="start";
    80. else if(state==business_state::finsish)
    81. str_business_state="finish";
    82. else
    83. str_business_state="running";
    84. data_obj.insert("state",str_business_state);
    85. data_obj.insert("data",data);
    86. QJsonObject root_obj;
    87. root_obj.insert("type","operationlog");
    88. root_obj.insert("data",QJsonValue(data_obj));
    89. root_obj.insert("id",machine_id);
    90. log4qt_->info("*#*#"+QString(QJsonDocument(root_obj).toJson(QJsonDocument::Compact))+"#*#*");
    91. }
    92. void MyLogger::operation_log(QJsonObject &operation_obj)
    93. {
    94. operation_obj.insert("type","operation");
    95. operation_obj.insert("time",get_data_time());
    96. QJsonObject root_obj;
    97. root_obj.insert("type","operationlog");
    98. root_obj.insert("data",QJsonValue(operation_obj));
    99. root_obj.insert("id",machine_id);
    100. log4qt_->info("*#*#"+QString(QJsonDocument(root_obj).toJson(QJsonDocument::Compact))+"#*#*");
    101. }
    102. void MyLogger::operation_log(QString &operation_str)
    103. {
    104. }
    105. void MyLogger::data_change_log(QString &key, QString &value)
    106. {
    107. QJsonObject data_obj;
    108. data_obj.insert("type","data");
    109. data_obj.insert("time",get_data_time());
    110. data_obj.insert("key",key);
    111. data_obj.insert("value",value);
    112. QJsonObject root_obj;
    113. root_obj.insert("type","operationlog");
    114. root_obj.insert("data",QJsonValue(data_obj));
    115. root_obj.insert("id",machine_id);
    116. log4qt_->info("*#*#"+QString(QJsonDocument(root_obj).toJson(QJsonDocument::Compact))+"#*#*");
    117. }
    118. void MyLogger::exception_log(QString &code, QString &desc)
    119. {
    120. QJsonObject data_obj;
    121. data_obj.insert("type","exception");
    122. data_obj.insert("code",code);
    123. data_obj.insert("resion",desc);
    124. QJsonObject root_obj;
    125. root_obj.insert("type","operationlog");
    126. root_obj.insert("data",QJsonValue(data_obj));
    127. root_obj.insert("id",machine_id);
    128. log4qt_->info("*#*#"+QString(QJsonDocument(root_obj).toJson(QJsonDocument::Compact))+"#*#*");
    129. }
    130. void MyLogger::shutdown()
    131. {
    132. log4qt_->shutdown();
    133. }
    134. #include
    135. QString get_data_time()
    136. {
    137. return QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz");
    138. }
    1. #ifndef LOGGER_H
    2. #define LOGGER_H
    3. #include
    4. #include "logger_global.h"
    5. enum business_state
    6. {
    7. start,
    8. running,
    9. finsish
    10. };
    11. class QJsonObject;
    12. class LogQt;
    13. /**
    14. * @class Logger logger.h
    15. * @brief 日志客户端/服务端
    16. * @note 根据日志配置文件中配置启动为客户端或服务端
    17. */
    18. class LOGGERSHARED_EXPORT MyLogger:public QObject
    19. {
    20. Q_OBJECT
    21. private:
    22. MyLogger(QObject *parent = NULL);
    23. ~MyLogger();
    24. public:
    25. Q_DISABLE_COPY(MyLogger)
    26. static MyLogger* getInstance();
    27. /**
    28. * @brief 设置配置文件路径
    29. * @param[in] path 配置文件路径
    30. * @return
    31. * -true 成功
    32. * -false 失败
    33. * @retval bool
    34. */
    35. bool setConfPath(const QString& path);
    36. public:
    37. /**
    38. * @brief
    39. * @param[in] data 日志
    40. * @param[in] table 表名称
    41. * @param[in] id
    42. * @note
    43. * -systemlog 系统运行表
    44. * -devlog 用户操作表
    45. */
    46. Q_INVOKABLE void info(const QString& data,const QString& file = QString(),const QString& func = QString(),const QString& line = QString());
    47. /**
    48. * @brief
    49. * @param[in] data 日志
    50. * @param[in] table 表名称
    51. * @param[in] id
    52. * @note
    53. * -systemlog 系统运行表
    54. * -devlog 用户操作表
    55. */
    56. Q_INVOKABLE void debug(const QString& data,const QString& file = QString(),const QString& func = QString(),const QString& line = QString());
    57. /**
    58. * @brief
    59. * @param[in] data 日志
    60. * @param[in] table 表名称
    61. * @param[in] id
    62. * @note
    63. * -systemlog 系统运行表
    64. * -devlog 用户操作表
    65. */
    66. Q_INVOKABLE void warn(const QString& data,const QString& file = QString(),const QString& func = QString(),const QString& line = QString());
    67. /**
    68. * @brief
    69. * @param[in] data 日志
    70. * @param[in] table 表名称
    71. * @param[in] id
    72. * @note
    73. * -systemlog 系统运行表
    74. * -devlog 用户操作表
    75. */
    76. Q_INVOKABLE void error(const QString& data,const QString& file = QString(),const QString& func = QString(),const QString& line = QString());
    77. /**
    78. * @brief 记录操作日志的接口
    79. * @param business 业务名称
    80. * @param data 最终显示的字符串
    81. * @param user 当前机器的操作员
    82. * @param level 日志的等级,默认是1
    83. * @param state 只有存在多次交互的业务需要这个字段,不需要多次交互的可以不填
    84. */
    85. Q_INVOKABLE void operation_log(QString& business,QString& data,QString& user,business_state state=running,QString& level=QString("1"));
    86. /**
    87. * @brief 依旧是操作日志,供c++直接调用。
    88. * @param operation_obj 日志的json对象
    89. */
    90. void operation_log(QJsonObject& operation_obj);
    91. /**
    92. * @brief 操作日志,供界面调用。传递json的字符串
    93. * @param operation_str 日志的json对象的字符串
    94. */
    95. Q_INVOKABLE void operation_log(QString& operation_str);
    96. /**
    97. * @brief 数据变更操作的接口,这里的数据仅限于用户需要知道的参数,不是每一个key的变更都要追踪
    98. * @param key 数据库中定义的有的变量key的名字使用与数据库一致的,数据库未定义的,写文档里面
    99. * @param value 变更后的值
    100. */
    101. Q_INVOKABLE void data_change_log(QString& key,QString& value);
    102. /**
    103. * @brief 异常的日志,这个异常的日志时给用户看的,不能随便打
    104. * @param code 异常代码
    105. * @param desc 异常发生时的代码
    106. */
    107. Q_INVOKABLE void exception_log(QString& code,QString& desc);
    108. /**
    109. * @brief 关闭日志,刷新buffer
    110. * @note 调用此方法后日志关闭,必须重新初始化
    111. */
    112. Q_INVOKABLE void shutdown();
    113. private:
    114. static MyLogger* logger_;
    115. LogQt* log4qt_;
    116. QString machine_id;
    117. };
    118. #define logger_debug(msg) MyLogger::getInstance()->debug(msg,__FILE__,__FUNCTION__,QString::number(__LINE__))
    119. #define logger_info(msg) MyLogger::getInstance()->info(msg,__FILE__,__FUNCTION__,QString::number(__LINE__))
    120. #define logger_warn(msg) MyLogger::getInstance()->warn(msg,__FILE__,__FUNCTION__,QString::number(__LINE__))
    121. #define logger_error(msg) MyLogger::getInstance()->error(msg,__FILE__,__FUNCTION__,QString::number(__LINE__))
    122. #endif // LOGGER_H
    1. #include "../include/Logqt.h"
    2. #include "log4qt/loggerrepository.h"
    3. #define LINE_MAX 1024
    4. LogQt::LogQt(QObject *parent) : QObject(parent)
    5. {
    6. }
    7. bool LogQt::setConfPath(const QString &path)
    8. {
    9. return Log4Qt::PropertyConfigurator::configure(path);
    10. }
    11. LogQt::~LogQt()
    12. {
    13. }
    14. void LogQt::info(const QString& data)
    15. {
    16. if(data.size()
    17. {
    18. logger()->info(data);
    19. return;
    20. }
    21. int size = data.size();
    22. logger()->info(QString("**********line start***%1**********").arg(size));
    23. int i=0;
    24. for(;i
    25. {
    26. logger()->info(data.mid(i,LINE_MAX).append("\\"));
    27. }
    28. logger()->info(data.mid(i,LINE_MAX));
    29. logger()->info("**********line finish*************");
    30. }
    31. void LogQt::debug(const QString& data)
    32. {
    33. if(data.size()
    34. {
    35. logger()->debug(data);
    36. return;
    37. }
    38. int size = data.size();
    39. logger()->debug(QString("**********line start***%1**********").arg(size));
    40. int i=0;
    41. for(;i
    42. {
    43. logger()->debug(data.mid(i,LINE_MAX).append("\\"));
    44. }
    45. logger()->debug(data.mid(i,LINE_MAX));
    46. logger()->debug("**********line finish*************");
    47. }
    48. void LogQt::warn(const QString& data)
    49. {
    50. if(data.size()
    51. {
    52. logger()->warn(data);
    53. return;
    54. }
    55. int size = data.size();
    56. logger()->warn(QString("**********line start***%1**********").arg(size));
    57. int i=0;
    58. for(;i
    59. {
    60. logger()->warn(data.mid(i,LINE_MAX).append("\\"));
    61. }
    62. logger()->warn(data.mid(i,LINE_MAX));
    63. logger()->warn("**********line finish*************");
    64. }
    65. void LogQt::error(const QString& data)
    66. {
    67. if(data.size()
    68. {
    69. logger()->error(data);
    70. return;
    71. }
    72. int size = data.size();
    73. logger()->error(QString("**********line start***%1**********").arg(size));
    74. int i=0;
    75. for(;i < size - LINE_MAX;i +=LINE_MAX)
    76. {
    77. logger()->error(data.mid(i,LINE_MAX).append("\\"));
    78. }
    79. logger()->error(data.mid(i,LINE_MAX));
    80. logger()->error("**********line finish*************");
    81. }
    82. void LogQt::shutdown()
    83. {
    84. logger()->loggerRepository()->shutdown();
    85. }
    1. #ifndef LOGQT_H
    2. #define LOGQT_H
    3. #include
    4. #include "log4qt/logger.h"
    5. #include "log4qt/propertyconfigurator.h"
    6. class LogQt : public QObject
    7. {
    8. Q_OBJECT
    9. LOG4QT_DECLARE_QCLASS_LOGGER
    10. public:
    11. explicit LogQt(QObject *parent = nullptr);
    12. ~LogQt();
    13. /**
    14. * @brief 设置配置文件路径
    15. * @param[in] path 配置文件路径
    16. * @return
    17. * -true 成功
    18. * -false 失败
    19. * @retval bool
    20. */
    21. bool setConfPath(const QString& path);
    22. /**
    23. * @brief
    24. * @param[in] data 日志
    25. * @param[in] table 表名称
    26. * @param[in] id
    27. * @note
    28. * -systemlog 系统运行表
    29. * -devlog 用户操作表
    30. */
    31. void info(const QString& data);
    32. /**
    33. * @brief
    34. * @param[in] data 日志
    35. * @param[in] table 表名称
    36. * @param[in] id
    37. * @note
    38. * -systemlog 系统运行表
    39. * -devlog 用户操作表
    40. */
    41. void debug(const QString& data);
    42. /**
    43. * @brief
    44. * @param[in] data 日志
    45. * @param[in] table 表名称
    46. * @param[in] id
    47. * @note
    48. * -systemlog 系统运行表
    49. * -devlog 用户操作表
    50. */
    51. void warn(const QString& data);
    52. /**
    53. * @brief
    54. * @param[in] data 日志
    55. * @param[in] table 表名称
    56. * @param[in] id
    57. * @note
    58. * -systemlog 系统运行表
    59. * -devlog 用户操作表
    60. */
    61. void error(const QString& data);
    62. void shutdown();
    63. signals:
    64. public slots:
    65. };
    66. #endif // LOG4QT_H

    日志配置

    1. log4j.rootLogger=DEBUG,daily,console
    2. log4j.appender.logfile=org.apache.log4j.FileAppender
    3. log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
    4. log4j.appender.logfile.layout.ConversionPattern=%-d [%t] %-5p: %m%n
    5. log4j.appender.logfile.File=./log/uiTest.log
    6. log4j.appender.logfile.ImmediateFlush=FALSE
    7. log4j.appender.logfile.Threshold=DEBUG
    8. log4j.appender.logfile.AppendFile=TRUE
    9. log4j.appender.console=org.apache.log4j.ConsoleAppender
    10. log4j.appender.console.layout=org.apache.log4j.PatternLayout
    11. log4j.appender.console.layout.ConversionPattern=%-d [%t] %-5p: %m%n
    12. #设置一个每日储存一个log文件的记录器
    13. log4j.appender.daily=org.apache.log4j.DailyFileAppender
    14. log4j.appender.daily.file=./log/uiTest.log
    15. log4j.appender.daily.appendFile=true
    16. log4j.appender.daily.datePattern=_yyyy_MM_dd
    17. #log4j.appender.daily.keepDays=90
    18. log4j.appender.daily.layout=${log4j.appender.console.layout}
    19. #log4j.appender.daily.layout.dateFormat=${log4j.appender.console.layout.dateFormat}
    20. log4j.appender.daily.layout.ConversionPattern=%-d [%t] %-5p: %m%n
    21. #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内存泄露怎么查

  • 相关阅读:
    【BitMap】千万卡数据查询优化
    7-38 最小生成树的唯一性
    【力扣算法简单五十题】07.二进制求和
    NSS [NISACTF 2022]middlerce
    将博客搬至CSDN
    内网渗透之内网信息收集(五)
    jupyter环境配置
    进阶JAVA篇- LocalDate 类与 LocalTime 类、LocalDateTime 类的常用API(六)
    说说对React refs 的理解?应用场景?
    升级包版本之后Reflections反射包在springboot jar环境下扫描不到class排查过程记录
  • 原文地址:https://blog.csdn.net/qq8864/article/details/126017464