多个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 相关文章推荐
详解C++编程中一元运算符的重载
Jan 19 Python
Python 探针的实现原理
Apr 23 Python
python利用paramiko连接远程服务器执行命令的方法
Oct 16 Python
Python数据分析之双色球统计两个红和蓝球哪组合比例高的方法
Feb 03 Python
Python字典创建 遍历 添加等实用基础操作技巧
Sep 13 Python
python制作填词游戏步骤详解
May 05 Python
实例详解python函数的对象、函数嵌套、名称空间和作用域
May 31 Python
Django 数据库同步操作技巧详解
Jul 19 Python
Django的Modelforms用法简介
Jul 27 Python
Python3将ipa包中的文件按大小排序
Apr 17 Python
详解Selenium 元素定位和WebDriver常用方法
Dec 04 Python
Python集合的基础操作
Nov 01 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自定义函数实现格式化秒的方法
2016/09/14 PHP
php微信公众平台开发(一) 配置接口
2016/12/06 PHP
PHP数据分析引擎计算余弦相似度算法示例
2017/08/08 PHP
PHP生成随机数的方法总结
2018/03/01 PHP
PHP程序员学习使用Swoole的理由
2018/06/24 PHP
prototype与jquery下Ajax实现的差别
2009/09/13 Javascript
表单的焦点顺序tabindex和对应enter键提交
2013/01/04 Javascript
5秒后跳转到另一个页面的js代码
2013/10/12 Javascript
Javascript浅谈之引用类型
2013/12/18 Javascript
jQuery Ajax使用实例
2015/04/16 Javascript
jQuery中通过ajax调用webservice传递数组参数的问题实例详解
2016/05/20 Javascript
JS采用绝对定位实现回到顶部效果完整实例
2016/06/20 Javascript
JS实现图片垂直居中显示小结
2016/12/13 Javascript
JavaScript简单计算人的年龄示例
2017/04/15 Javascript
微信JS-SDK选取手机照片上传功能
2017/04/21 Javascript
jQuery插件DataTables分页开发心得体会
2017/08/22 jQuery
JS立即执行函数功能与用法分析
2019/01/15 Javascript
ant design实现圈选功能
2019/12/17 Javascript
Jquery $.map使用方法实例详解
2020/09/01 jQuery
js实现右键弹出自定义菜单
2020/09/08 Javascript
Electron+vue从零开始打造一个本地播放器的方法示例
2020/10/27 Javascript
JS实现简单贪吃蛇小游戏
2020/10/28 Javascript
Python数据结构之Array用法实例
2014/10/09 Python
Python操作MongoDB数据库的方法示例
2018/01/04 Python
从DataFrame中提取出Series或DataFrame对象的方法
2018/11/10 Python
使用Python Pandas处理亿级数据的方法
2019/06/24 Python
win10下安装Anaconda的教程(python环境+jupyter_notebook)
2019/10/23 Python
python GUI库图形界面开发之PyQt5不规则窗口实现与显示GIF动画的详细方法与实例
2020/03/09 Python
Python可视化工具如何实现动态图表
2020/10/23 Python
用python计算文件的MD5值
2020/12/23 Python
世界上最大的在线学习和教学市场:Udemy
2017/11/08 全球购物
size?德国官方网站:英国伦敦的球鞋精品店
2018/03/17 全球购物
英国在线购买轮胎、预订汽车、汽车维修和装配网站:Protyre
2020/04/12 全球购物
村干部四风问题整改措施
2014/09/30 职场文书
医院合作意向书范本
2015/05/08 职场文书
父亲去世追悼词
2015/06/23 职场文书