当前位置: 首页 > 科技观察

神器日志,你真的了解吗?

时间:2023-03-19 12:56:50 科技观察

本文转载自微信公众号《Python技术》,作者:佩森酱。转载本文请联系Python技术公众号。logging是python标准模块,用于记录和处理程序中的日志。功能很强大,官方文档很详细,网上也有很多说明和教程,但是对于很多刚接触的同学来说,还是有些障碍。一是因为标准库文档过于繁琐,需要很高的理论基础。当您匆忙时,您经常会被文档弄糊涂。其次,大部分的说明资料要么是官方文档的罗列,要么是简单的应用,对实际应用帮助不大。今天,我们从应用中的一些问题入手,探讨一下日志神器背后的原理,让它真正帮助到我们。我应该使用logging.debug还是logger.debug?debug是一种日志生成方式,log模块中日志级别为DEBUG,还有info、warning、error、critical。这里以debug为代表进行说明。我们经常看到logging.debug是用来记录一会儿日志的,logger.debug是用来记录一会儿日志的。我们应该使用什么?先看代码:importlogginglogging.debug('debuginformation')logger=logging.getLogger()logger.debug('debuginfo')首先将logging作为一个模块引入。logging.debug使用日志模块的模块方法。logger由logging.getLogger()产生,是一个日志对象,logger.debug调用logger对象的方法。上面代码中logging.debug和logger.debug的效果是完全一样的。这是因为,为了方便开发者,logging模块提供了debug等一系列模块方法,导入模块后可以直接使用。这样开发者就不需要关心日志模块的细节,像打印一样输出日志。如果需要自定义日志输出,比如将日志输出到文件,过滤一定级别的日志,则需要创建或获取一个实际的日志对象进行处理,比如上面通过getLogger方法获取的日志对象代码。我们知道在程序设计中要避免重复设计。如果模块方法采用一种机制,日志对象上的方法采用另一种机制,就会出现重复造轮的问题。所以在使用module方法时,logging实际上是创建了一个日志对象——rootlogger。即logging.debug的调用本质上是调用rootlogger的log方法。相当于默认会使用rootlogger作为日志处理对象。如何获取根记录器对象?通过不带参数的logging.getLogger()方法获取。那么logging.debug和rootLogger.debug就是一回事。logging.debug是rootlogger.debug的快捷方式,可以理解(但不严谨)。如果您注意日志树,您会发现该程序具有层次结构。通过相互引用,调用形成树状结构。加载程序的地方是树的根,比如python中要运行的代码文件,我们称之为main。其他的枝叶从树的根部长出来。对于一个模块,形成了一棵自己的树。如何用日志清楚地记录层级结构?直接打印调用栈虽然可以看出调用结构,但是不够直观,缺乏业务逻辑描述。但是使用print打印出层级结构需要写很多代码来反映(一种通过运行状态获取代码状态的方式)调用环境。日志记录提供了一个完整的解决方案。上面提到的rootlogger是整个日志树的根,其他logger都是从rootlogger延伸出来的枝叶。只要通过getLogger(loggername)方法获取的logger对象是从rootlogger扩展出来的。如何向下延伸?很简单,就像引用模块的层级关系一样,使用.来分隔级别,例如:logger=logging.getLogger('mod1.mod2.mod3')logger.debug("debuginformation")语句logging.getLogger('mod1.mod2.mod3')实际上创建了三个记录器,名称是mod1,mod1.mod2和mod1.mod2.mod3mod1是根,mod1.mod2是孩子,mod1.mod2.mod3是孙子。如果在mod1上设置了一个日志处理程序,那么其他两个日志对象将使用这个处理程序。这样不仅记录的日志更加清晰,而且可以为同根的日志对象设置共享的日志处理方式。这感觉很不方便。它需要这么多层。怎样才能更方便?将在后面的实用参考中进行说明。logging.basicConfig的优缺点说完日志模块的树形结构,我们再来看一个很常见的设置方法basicConfig。方便设置日志处理和记录方式,如果不需要,不需要为每个日志对象单独设置。根据第一节的分析,我们知道直接使用module方法其实是使用了rootlogger,所以我们可以理解为basicConfig设置了rootlogger的日志处理方式。也就是说:一旦通过logging.basicConfig设置了日志处理方式,其他所有日志都会受到影响。另外basicConfig是一次性方法,即只有第一次设置有效,后面的设置无效,本来就是一劳永逸的方法。但是如果用错了地方,那就很麻烦了。看下面的例子:__all__=['Connection','ConnectionPool','logger']warnings.filterwarnings('error',category=pymysql.err.Warning)#useloggingmoduleforeasydebuglogging.basicConfig(format='%(asctime)s%(levelname)8s:%(message)s',datefmt='%m-%d%H:%M:%S')logger=logging.getLogger(__name__)logger.setLevel('WARNING')这段代码,使用logging.basicConfig设置日志,也就是说后面的日志都会这样输出。但它是一个低级模块——pymysqlpool[1]。pymysqlpool封装了pymysql[2]模块,提供了连接池特性,在多线程数据库场景中很有用。也就是说,pymysqlpool只会通过引用加载,不会作为main加载,这就尴尬了,因为main中的日志设置没有作用。作为一个服务模块(相对于业务的底层模块),不通过basicConfig设置日志方式,或者通过自己专属的日志对象设置,或者交给main来设置,例如:logger=logging.getLogger(__name__)fmt=logging.Formatter("%(asctime)s%(levelname)8s:%(message)s",datefmt='%m-%d%H:%M:%S')hdl=logging.StreamHandler()hdl.setFormatter(fmt)logger.addHandler(hdl)logger.setLevel('WARNING')进行测试,可以在测试的初始化方法中使用basicConfig设置,因为模块经常作为程序被加载到库中。实用参考在了解了日志模块的以下特点和背后的原理后,这里给出几个实用参考。不要使用logging.basicConfig在子模块中设置日志记录模式。强烈建议通过logger=logging.getLogger(__name__)在任何模块中创建一个日志对象,因为__name__代表加载模板的引用名称。例如,froma.b.cimportb模块c中的__name__值为a.b.c。而这个引用名称恰好适合logger定义的层次结构。通过命令行参数设置不同类型的日志,见代码:importloggingimportargparselogger=logging.getLogger(__name__)defcreate_args_parse():parser=argparse.ArgumentParser(description="argumentlist")parser.add_argument('-d','--debug',action='store_true',help='debugmode')#添加其他命令行参数returnparserdefset_logger(debug):formatter=logging.Formatter('%(asctime)s-%(levelname)8s-%(name)s-%(filename)s:%(lineno)d-%(thread)d-%(funcName)s:\t%(message)s')ifdebug:hd=logging.StreamHandler()logger.setLevel(logging.DEBUG)hd.setFormatter(formatter)else:hd=logging.FileHandler(f'{__name__}.log','a',encoding='utf-8')logger.setLevel(logging.INFO)hd.setFormatter(formatter)logger.addHandler(hd)if__name__=='__main__':parser=create_args_parse()args=parser.parse_args()debug=args.debugset_logger(debug)...代码有点长,但不难理解.create_args_parse方法用于解析命令行参数,其中定义了一个debug参数,表示开启debug模式。set_logger方法接收是否为调试模式的参数,根据是否为调试模式设置不同的日志模式。在main中,先调用create_args_parse获取命令行参数对象,然后从中解析参数,提取debug模式,发送给set_logger方法设置日志模式。这样只需要在运行程序的时候加上参数-d,就可以让日志打印到终端。如果不添加,日志会自动转到__main__.log日志文件。综上所述,python为我们提供了很多方便的功能,其中有一些是需要使用才能理解的,所以在遇到问题的时候,我们需要多研究,找出其特点和内部原理或机制,以便我们可以更好的应用。了解了日志的原理后,它在我的很多项目中发挥了巨大的作用,再也不用为如何使用它、如何让它更合理而发愁了。我希望这篇文章也能对你有所帮助。参考资料[1]pymysqlpool:https://pypi.org/project/pymysql-pool/[2]pymysql:https://pypi.org/project/PyMySQL/