前言

提前祝大家过个好年
最近忙于项目,今天抽出点时间写写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 ,这样导致 loggerHandler 越来越多,重复写入了,解决这个问题需要防止重复走 __init__

利用元类继承实现单例

该方法利用元类 Type__call__ 实例化的对象调用不会走 __init__ 的特性来规避问题

如果你觉得本方法需要覆盖父类不太好,那么还有第三种方法

自写初始化方法

方法1中,每次都会走 __init__,而我们在 __init__ 中又进行了 add Handler 等操作,那么我们将所有初始化及 add 操作放在自写方法中即可

如上图所示,这对请求实例化的用户是无感知的,它只需要和之前一样调,但其实内部在实例化时调用了 start, 同时重复实例化时走 __init__ 没有任何代码逻辑(走的Object)

小彩蛋

我们在实际测试过程中发现,在配置了log持久化存储搭配多线程使用的时候,写入log文件的日志会丢失数据,测试发现应该是实例化后立刻写入会出现一些延迟,再加上测试代码
写入一条后立刻结束,导致的丢失问题,当然,在实际使用中,一般是初始化时实例化,也不可能在log后直接停止
但是问题还是要解决,实例化我们在每次请求实例化时等待一下即可 time.sleep(1) ,等待时间与机器性能有关,好的机器不会出现问题,
1s是保险的
然后最终代码为

02-11 20:35