前言
提前祝大家过个好年
最近忙于项目,今天抽出点时间写写Blog谈谈昨天遇到的问题
项目最近要收尾了,想把Logger规整一下,因为很多地方都有用到
Python的Logger模块是Python自带的模块,可方便快捷的进行日志的记录
python doc
正文
线程安全
该模块本身就是线程安全的,下面的注释摘抄至 doc
也就是说你不需要关注多线程的问题,只要 getLogger()
时指定当前空间即可
意思是 logger.getLogger()
时传入相同的变量,会永远返回同一个对象,比如我在当前进程内的任何地方, 使用 log = logger.getLogger("work")
生成的log对象一直是同一个对象,这就是单例模式,官方推荐传入 __name__
因为他是Python包命名空间中模块的名称。
如果你是纠结 Logger 的单例怎么解决,你可以关闭网页了,因为他本身是单例的
手动写一个单例
手动写一个单例完全是为了记忆单例模式的使用,只是以 logger 模块举例
原始代码
不考虑 logger 的自带单例情况下的原始代码
代码精简过,大致意思不变
测试代码
测试是否可以使用的代码
利用__new__实现单例
我们知道,python实例化时其实是先走 __new__
再走 __init__
我们可以重写 __new__
方法,如果发现已生成对象直接返回该对象
同时为了防止多线程的资源竞争,我们使用线程锁来保证同一时间只有一个线程能访问 __new__
但是测试代码跑过之后发现每次会输出接近100条日志,这是为什么呢?
原来,每次请求实例化时,如有对象则直接返回之前生成的对象(MyLogger._instance),但是因为 Python3
默认继承新式类,
即 Object
,每次请求时返回了 object.__new__
然后会再执行一遍 MyLogger
的 __init__
方法,而我们在 __init__
中添加了两个 Handler
,
而上文提到, logger.getLogger
传入同一个参数则 logger 为一个, 导致每次请求时都会添加两个 Handler
到同一个 logger
,这样导致 logger
的 Handler
越来越多,重复写入了,解决这个问题需要防止重复走 __init__
利用元类继承实现单例
该方法利用元类 Type
的 __call__
实例化的对象调用不会走 __init__
的特性来规避问题
如果你觉得本方法需要覆盖父类不太好,那么还有第三种方法
自写初始化方法
方法1中,每次都会走 __init__
,而我们在 __init__
中又进行了 add Handler
等操作,那么我们将所有初始化及 add
操作放在自写方法中即可
如上图所示,这对请求实例化的用户是无感知的,它只需要和之前一样调,但其实内部在实例化时调用了 start
, 同时重复实例化时走 __init__
没有任何代码逻辑(走的Object)
小彩蛋
我们在实际测试过程中发现,在配置了log持久化存储搭配多线程使用的时候,写入log文件的日志会丢失数据,测试发现应该是实例化后立刻写入会出现一些延迟,再加上测试代码
写入一条后立刻结束,导致的丢失问题,当然,在实际使用中,一般是初始化时实例化,也不可能在log后直接停止
但是问题还是要解决,实例化我们在每次请求实例化时等待一下即可 time.sleep(1)
,等待时间与机器性能有关,好的机器不会出现问题,
1s是保险的
然后最终代码为