本文没有固定的主题,都是在开发过程中总结发现的一些tips,写在这里,一方面是做笔记,另一方面也希望能帮到他人(持续更新)。

1、logging模块

1.1 python的logging模块是线程安全的

python的logging模块是线程安全的,多个线程调用同一个Logger对象打印日志并不会造成混乱。他的线程安全是通过锁机制来维护的,在handler对象中定义了一个threading.RLock对象保证同一时刻只有一个线程写文件(所有的handler都是logging.Handler的子类,这个所对象是在logging.Handler中定义的)。需要注意的是,python的logging模块不是进程安全的,如果多个进程访问,则会出现打印到一起的现象。如果需要使用进程安全的日志,可以使用multiprocessing模块提供的get_logger()方法获取日志对象打印日志。

1.2 关于logger的继承关系

python中logger的继承关系是根据名字来确定的。例如,名为"a.b"的logger是名为"a"的logger的子logger,而所有的logger都有一个公共的祖先,名为root。默认情况下,logger默认会进行消息传递的,也就是说如果level允许,一个消息会传递给所有存在的父logger进行打印,root logger默认初始化为错误流打印。这有可能会引发一个问题:假如你定义的logger打印的消息等级在root中也允许,那么这个消息在打印你自己的logger的同时,也会打印到root logger。避免这个问题的方法就是设置logger的propagate属性为0,关闭递归。

1.3 关于logger的性能

这里只针对FileHandler进行说明,logging中的FileHandler是继承自StreamHandler的。在后者中,每写一条记录,就会调用flush()方法写入,也就是说对于FileHandler,每写一条记录都会访问一次磁盘,这对于大数据量开销是很大的。所以,在大量数据的时候,最好将多条记录拼接成一条进行写入。另外,由于handler是采用锁机制解决多线程问题,所以如果访问logger的线程特别多,也很有可能造成性能瓶颈,多线程写入不一定会比单线程要快(我当时处理的是大概每秒钟要写1w-2w多次,单线程测试的时候应该没啥问题,可后来系统上线之后,发现性能下降了很多,hold不住了,后来发现是多线程竞争锁导致性能的下降。最后改成了多进程,单个进程来写文件)。

1.4 关于logger的其他说明

logging模块有一个raiseExceptions属性来控制是否打印异常,默认情况下,raiseExceptions=1会将异常打印至stderr(logging.Handler中实现)。logger中最容易出现的错误就是编码错误,尤其是当数据来源不受控的时候。默认情况下,FileHandler使用utf-8编码,会尝试将输入的所有东西转化为utf-8编码,如果转码失败,就会将这个异常打印纸stderr(如果raiseExceptions=1)。当然,FileHandler的编码是可以设置的。但是,所以,在使用FileHandler或者其子类的时候,尽量将传入内容转化为想要的编码(编码问题下面会讲到,我当时就是因为编码问题,大量日志直接打入了错误流,相当郁闷)。