多个python文件调用logging模块报错误


Posted in Python onFebruary 12, 2020

python logging模块主要是python提供的通用日志系统,使用的方法其实挺简单的,这块就不多介绍。下面主要会讲到在使用python logging模块的时候,涉及到多个python文件的调用,而每个文件设置了对应的logging方式不同,可能会产生的令人困惑的现象。

下面以自己在开发的时候遇到的问题作为叙述的背景:

有三个python模块A、B、C。主模块A会import B和C模块,主模块有对应的logging方式,

A使用logging的模块的方式为:

import logging
import logging.handlers
def CreateLogger(logFile = 'batch'):
  handler = logging.handlers.RotatingFileHandler(str(logFile) + '.LOG', maxBytes = 1024 * 1024 * 500, backupCount = 5)
  fmt = '%(asctime)s - %(filename)s:%(lineno)s - %(name)s - %(message)s'
  formatter = logging.Formatter(fmt)
  handler.setFormatter(formatter)
  logger = logging.getLogger(str(logFile))
  logger.addHandler(handler)
  logger.setLevel(logging.INFO)
  return logger
sLogger = CreateLogger()

其实A模块使用logging的方式很简单,创建一个RotatingFileHandler,通过RotatingFileHandler回滚logging的方式来控制LOG文件的个数和每个LOG文件的上限大小。并创建一个Formatter对象来设置LOG文件的格式。在程序中使用这种方式产生的logging对象来打LOG,很显然使用这种方式的话,LOG都会打印到对应的LOG文件中去。

B使用logging模块的方式为

def GetLogger(testName):
  logger = logging.getLogger(testName)
  logger.setLevel(logging.INFO)
  hdlr = logging.FileHandler(testName + '.LOG')
  hdlr.setLevel(logging.INFO)
  formatter = logging.Formatter("[%(asctime)s]\t[%(levelname)s]\t[%(thread)d]\t[%(pathname)s:%(lineno)d]\t%(message)s")
  hdlr.setFormatter(formatter)
  logger.addHandler(hdlr)
  return logger
logger = GetLogger('OK')
 
def SetLogger(log):
  global logger
  logger = log

B模块默认logging的方式跟A差不多,只是B选择logging的方式是往一个LOG文件中打LOG。A其实在实际使用B模块对应的函数和类的时候并没有直接用B的logging方式,而是对B logging进行了一个重定向,这个可以从SetLogger函数的作用可以函数。A直接会把已经logging对象传给B,这样B也可以和A共享同一个logging对象,并把LOG打到A设定的文件中。这对于一个主模块调用多个子模块的逻辑、而且每个子模块都有对应的logging使用方式、打到不同文件中进行统一还是挺有好处的,这样可以有效的控制总的LOG文件大小和数量。

但是没有注意C模块,然后发现的情况是,A程序在运行过程中会把A、B模块的LOG信息直接打到屏幕上,而且LOG文件中也有对应的LOG。这些挺让人困惑的,把对B模块的调用注释掉,依然会发现有A的LOG直接打到屏幕上。但是把A程序中设置logging对象的那段代码单独拿出来,一切都正常。

根据当时的情景,只能怀疑是C模块中有什么设置,会导致A、B模块打LOG的方式有些转变。后来意识到,C模块中并没有设置logging的对象,而是直接使用logging.info去打LOG。把这部分的逻辑注释掉,发现A、B打LOG的方式又恢复正常,再也不会往屏幕上打LOG。

通过参阅python logging模块的代码,发现一些有趣的现象:

1. logging对象其实是一个树形结构,每个创建的logging对象都是root logging对象的孩子结点。当使用logging模块的getLogger(name=None)函数构造logging对象的时候,如果name为None,这样会产生一个root logging对象。如果name中含有.,比如name = 'a.b.c',通过这种方式会产生3个logging对象,分别为c、b、a,c->b->a->root,root树的根结点,a为root的孩子结点,b为a的孩子结点,c为a的孩子结点,依次类推。

2. root结点是全局的,虽然这过程中涉及到多个模块,但是它们会共享一个root结点。

3. 每个logging对象打LOG的时候,也会把LOG信息传递到传递到上层logging对象中,对于c->b->a->root这种情况,这个LOG其实会打4次,以c、b、a、root循序依次打一个LOG。

可能有人会问,像我之前一般用A模块或者B模块那样的方式去初始化一个logging对象,这样初始化的对象也会是root logging对象的一个孩子,而root logging对象通常会把LOG打到屏幕上,那按理说,正常情况下打LOG都会打两份,一份会打到文件中,一份会打到屏幕中。那为什么实际情况是,只有LOG文件中有对应的LOG,但是屏幕中并没有对象的显示呢?

其实,如果对这个过程有些好奇,对直接很习以为常的方式有些怀疑,而且抱着这样的好奇心去探索,相信肯定会有更多的收获。

所以,比较困惑的是,为什么我调用A模块产生的sLogger.info打出的LOG,只有LOG文件中有,而root logging为什么不打LOG打到屏幕上。为什么root logging不起作用。这个时候,可以看下logging __init__.py的代码,会发现,root logging info的代码如下:

def info(msg, *args, **kwargs):
  """
  Log a message with severity 'INFO' on the root logger.
  """
  if len(root.handlers) == 0:
    basicConfig()
  root.info(msg, *args, **kwargs)

 上面的代码中涉及到root.handlers,怀疑root.handlers跟打LOG的方式有关。因此,print len(root.handlers),发现结果为0。也就是说,默认的root logging对应的handlers为[],这样导致的结果是sLogger打LOG的时候,root logging并不会打任何LOG。在__main__中添加如下代码:

if __name__ == '__main__':
 
  sLogger.info('OK')
 
  print len(logging.root.handlers), logging.root.handlers
 
  logging.info('Bad')
 
  print len(logging.root.handlers), logging.root.handlers

运行程序,得到如下运行结果:

0 []

1 [<logging.StreamHandler instance at 0x7f066e3eef80>]。


第一行结果为0 []很好的解释了,为什么正常情况下,root logging对象为什么没有打出LOG。

而调用logging.info('Bad')之后,root.handlers对象为StreamHandler对象。通过这个程序可以看到调用logging.info对象前后root logging对象发生的变化。

还有一点需要验证,就是logging调用前后正常模块logging的方式。

在__main__中写下如下代码:

if __name__ == '__main__':
 
  for i in xrange(0, 2):
 
    sLogger.info('OK')
 
    logging.info('Bad')

根据之前分析的,第一次调用sLogger.info('OK')是不会打LOG的,而logging.info本身是由于不到WARNING级别,所以也没有打LOG,而第二次会打LOG在屏幕中。所以,看到的结果是,LOG文件中有三条LOG,而屏幕上有一条INFO:batch:OK。跟之前猜想到的挺吻合的。

 为什么调用了logging.info之后,会发生如此转变?

继续看完上面root logging info,并对照着下面的basicConfig代码。会注意到len(root.handlers) == 0会去调用basicConfig,这个时候就可以注意下,basicConfig这个模块的实现。

def basicConfig(**kwargs): 
 
  if len(root.handlers) == 0:
 
    filename = kwargs.get("filename")
 
    if filename:
 
      mode = kwargs.get("filemode", 'a')
 
      hdlr = FileHandler(filename, mode)
 
    else:
 
      stream = kwargs.get("stream")
 
      hdlr = StreamHandler(stream)
 
    fs = kwargs.get("format", BASIC_FORMAT)
 
    dfs = kwargs.get("datefmt", None)
 
    fmt = Formatter(fs, dfs)
 
    hdlr.setFormatter(fmt)
 
    root.addHandler(hdlr)
 
    level = kwargs.get("level")
 
    if level is not None:
 
      root.setLevel(level)

可以看出,当root.handlers的长度为0的时候,会创建一个默认的StreamHandler对象,而这个对象设置的模式导致的情况是LOG会打到屏幕上。这个跟之前打出的logging.root.handlers的结果挺吻合。通过这些想必明白了,为什么我之前遇到的C文件中调用logging.info的方式会影响到上层模块以及其调用的子模块。

通过我遇到的问题,以及对logging的这相关部分的分析,想必会对logging模块有更深刻的认识。最关键的一点,如果想尽可能精确的控制logging方式,一定要注意,主模块以及对应的子模块中具体不要直接使用logging打LOG。

更多关于多个python文件调用logging模块产生错误的问题请查看下面的相关链接

Python 相关文章推荐
重命名批处理python脚本
Apr 05 Python
python实现监控linux性能及进程消耗性能的方法
Jul 25 Python
把MySQL表结构映射为Python中的对象的教程
Apr 07 Python
举例讲解Python面向对象编程中类的继承
Jun 17 Python
Python实现将数据框数据写入mongodb及mysql数据库的方法
Apr 02 Python
Python if语句知识点用法总结
Jun 10 Python
TensorFlow实现iris数据集线性回归
Sep 07 Python
python 抓包保存为pcap文件并解析的实例
Jul 23 Python
详解PyTorch中Tensor的高阶操作
Aug 18 Python
tensorflow 获取checkpoint中的变量列表实例
Feb 11 Python
Python unittest单元测试openpyxl实现过程解析
May 27 Python
python如何输出反斜杠
Jun 18 Python
Python对Tornado请求与响应的数据处理
Feb 12 #Python
在PyCharm中实现添加快捷模块
Feb 12 #Python
Python的赋值、深拷贝与浅拷贝的区别详解
Feb 12 #Python
解决pyCharm中 module 调用失败的问题
Feb 12 #Python
Python写出新冠状病毒确诊人数地图的方法
Feb 12 #Python
pycharm通过ssh连接远程服务器教程
Feb 12 #Python
python日期与时间戳的各种转换示例
Feb 12 #Python
You might like
PHP 中的面向对象编程:通向大型 PHP 工程的办法
2006/12/03 PHP
ZF等常用php框架中存在的问题
2008/01/10 PHP
php header Content-Type类型小结
2011/07/03 PHP
PHP对表单提交特殊字符的过滤和处理方法汇总
2014/02/18 PHP
php构造函数的继承方法
2015/02/09 PHP
php命令行写shell实例详解
2018/07/19 PHP
如何在标题栏显示框架内页面的标题
2007/02/03 Javascript
用js判断页面是否加载完成实现代码
2012/12/11 Javascript
js post提交调用方法
2014/02/12 Javascript
Javascript基础教程之数组 array
2015/01/18 Javascript
javascript实现在网页任意处点左键弹出隐藏菜单的方法
2015/05/13 Javascript
js获取iframe中的window对象的实现方法
2016/05/20 Javascript
Bootstrap 折叠(Collapse)插件用法实例详解
2016/06/01 Javascript
JS实现滑动门效果的方法详解
2016/12/19 Javascript
JavaScript 获取元素在父节点中的下标(推荐)
2017/06/28 Javascript
jQuery读取本地的json文件(实例讲解)
2017/10/31 jQuery
ES6顶层对象、global对象实例分析
2019/06/14 Javascript
教你30秒发布一个TypeScript包到NPM的方法步骤
2019/07/22 Javascript
浅谈layui 数据表格前后台传值的问题
2019/09/12 Javascript
原生JS封装拖动验证滑块的实现代码示例
2020/06/01 Javascript
[02:36]DOTA2上海特锦赛 回忆电竞生涯的重要瞬间
2016/03/25 DOTA
基于Django模板中的数字自增(详解)
2017/09/05 Python
Python配置mysql的教程(推荐)
2017/10/13 Python
Python元组及文件核心对象类型详解
2018/02/11 Python
Windows 64位下python3安装nltk模块
2018/09/19 Python
Python3获取电脑IP、主机名、Mac地址的方法示例
2019/04/11 Python
图解python全局变量与局部变量相关知识
2019/11/02 Python
python 数据分析实现长宽格式的转换
2020/05/18 Python
PyQt5如何将.ui文件转换为.py文件的实例代码
2020/05/26 Python
如何理解python中数字列表
2020/05/29 Python
全球领先的鞋类零售商:The Walking Company
2016/07/21 全球购物
韩国著名的在线综合购物网站:Akmall
2016/08/07 全球购物
深深扎根运动世界的生活品牌:Tillys
2017/10/30 全球购物
享受加州生活方式的时尚舒适:XCVI
2018/07/09 全球购物
第一批党的群众路线教育实践活动总结报告
2014/07/03 职场文书
Redis入门教程详解
2021/08/30 Redis