解决Python中由于logging模块误用导致的内存泄露

作者:C Wong 时间:2021-08-24 08:04:46 

首先介绍下怎么发现的吧, 线上的项目日志是通过 logging 模块打到 syslog 里, 跑了一段时间后发现 syslog 的 UDP 连接超过了 8W, 没错是 8 W. 主要是 logging 模块用的不对

我们之前有这么一个需求, 就是针对每一个连接日志输出当前连接的信息, 所以每一个 连接就创建了一个日志实例, 并分配一个 Formatter, 创建日志实例为了区分其他连接 所以我就简单粗暴的用了当前对象的 id 来作为日志名称:


import logging

class Connection(object):
 def __init__(self):
   self._logger_name = "Connection.{}".format(id(self))
   self.logger = logging.getLogger(self._logger_name)

当然测试环境是开 DEBUG, 开 DEBUG 就不会往 syslog 里打, 所以不会出现 UDP 连接数 过多, 也就不会知道有内存泄露的, 我们来看看这样为什么会导致内存泄露, 首先看看 getLogger 的代码:


def getLogger(name=None):
 """
 Return a logger with the specified name, creating it if necessary.

If no name is specified, return the root logger.
 """
 if name:
   return Logger.manager.getLogger(name)
 else:
   return root

主要调用了 Logger.manager.getLogger, 这个函数有下面一段代码片段


     if name in self.loggerDict:
       rv = self.loggerDict[name]
       if isinstance(rv, PlaceHolder):
         ph = rv
         rv = (self.loggerClass or _loggerClass)(name)
         rv.manager = self
         self.loggerDict[name] = rv
         self._fixupChildren(ph, rv)
         self._fixupParents(rv)
     else:
       rv = (self.loggerClass or _loggerClass)(name)
       rv.manager = self
       self.loggerDict[name] = rv
       self._fixupParents(rv)

logging 模块为了保证同一个名称引用同一个日志实例,所以就把所有的日志实例全部存 在了一个 loggerDict 的字典里, 所以除非程序退出, 创建的日志实例引用是不会释放的, 所以日志实例里的 handlers 也不会释放. 之前我又用的对象的 id 来作为日志名称 的一部分, 所以 SyslogHandler 创建的 UDP 连接就一直被占用导致了过多的 UDP 连接.

为了解决这个问题我在连接关闭的时候加入了如下代码:


logging.Logger.manager.loggerDict.pop(self._logger_name)
self.logger.manager = None
self.logger.handlers = []

按说只加上上面第一行的代码就应该释放了, 但是没有, 所以又有了第三行代码, SyslogHandler 才最终释放, 这个问题暂时还不知道为什么, 还需要再查查.

2015-03-30 更新 如果日志名称是以 . 分隔, logging 模块则会将最后一部分作为日志名, 并往上去寻找 父 Logger, 如果找不到则创建 PlaceHolder 对象作为父, 并引用 Logger.

比如创建的 Logger 名称为 a.b.c, 那么实际的名称则为 c, 并将 b 作为 c 的父, a 作为 b 的 父, 如果没有该名称的 Logger 则创建 PlaceHolder 对象作为代替, PlaceHolder 会创建对当前 Logger 的引用. 所以需要被回收的日志对象名称里不应包含 .

标签:Python
0
投稿

猜你喜欢

  • python实现简单的购物程序代码实例

    2022-09-03 05:29:13
  • Win10用vscode打开anaconda环境中的python出错问题的解决

    2023-04-21 09:21:21
  • 拒绝盗图!教你怎么用python给图片加水印

    2022-05-29 21:42:51
  • 详解php用static方法的原因

    2023-11-20 19:20:45
  • 如何在ASP里面创建GUID

    2008-01-08 19:13:00
  • css学习笔记:div在IE6下无法遮盖select

    2009-04-30 13:21:00
  • Python pyecharts绘制词云图代码

    2021-03-18 03:17:08
  • Python中使用第三方库xlutils来追加写入Excel文件示例

    2022-05-23 10:04:11
  • 利用python爬取斗鱼app中照片方法实例

    2022-01-03 17:40:39
  • 解决pycharm中导入自己写的.py函数出错问题

    2023-07-09 12:12:05
  • SaaS中的用户体验设计

    2009-05-20 12:28:00
  • Python如何在windows环境安装pip及rarfile

    2021-01-12 18:00:21
  • jQuery实现同一点击,两个不同链接,指向两个不同的iframe

    2010-06-21 10:52:00
  • 重温Javascript继承机制

    2011-07-04 12:17:23
  • 如何编写python的daemon程序

    2023-07-27 05:13:28
  • 多级联动下拉选择框,动态获取下一级

    2008-09-04 10:34:00
  • 用Python读取几十万行文本数据

    2023-10-13 21:56:04
  • firefox与ie 的javascript区别

    2010-03-14 11:30:00
  • PHP xpath()函数讲解

    2023-06-04 07:03:38
  • asp如何在网上查找链接?

    2010-06-22 21:10:00
  • asp之家 网络编程 m.aspxhome.com