python logging模块的作用及应用场景_Python常用模块功能简介(三)logging

logging基本介绍

先介绍一下我们为什么要使用日志,平常我们编写程序为了验证程序运行与debug,通常会使用print函数来对一些中间结果进行输出验证,在验证成功后再将print语句注释或删除掉。这样做在小型程序中还比较灵活,但是对于大型项目来说,就十分繁琐了----->所以使用日志log就很自然了,日志可以调整日志级别,来决定我们是否输出对应级别的日志,同时还可以将日志导入文件记录下来。

再介绍一下logging中的log级别:

Level

Numeric Value

logging.CRITICAL

50

logging.ERROR

40

logging.WARNING

30

logging.INFO

20

logging.DEBUG

10

实际上这些level都是整数值,可由如type(logging.info)验证为int类型。

模块级的使用方法

实际上logging是一个package而不是module,以下对于源码的讨论都是在logging的__init__.py文件中的。

logging模块的模块级使用方法就是使用一些模块级接口函数。而且还有一个比较重要的是对日志的输出形式和输出目的地等进行设置。

接口函数也是对应日志级别而输出信息的:

logging.debug(msg)

logging.info(msg)

logging.warning(msg)

logging.error(msg)

logging.critical(msg)

这几个函数除了日志级别上的区别,其实都是使用默认的root logger来对信息进行log的,它是处于日志器层级关系最顶层的日志器,且该实例是以单例模式存在的。见源码:

def info(msg, *args, **kwargs):

if len(root.handlers) == 0:

basicConfig()

root.info(msg, *args, **kwargs)

这里可以见到在logging模块的info函数中:(1)首先进行了一个对于root logger的handlers属性的长度判断是否调用basicConfig函数。(2)之后是调用root logger的info函数来实现功能的。

这里对于第(2)点我们进一下探寻:

root = RootLogger(WARNING)

在logging的源码中可以看到如上语句,即我们将logging模块import后,其实已经默认的创建了一个root logger对象,并且logging自己维护有一个Logger实例的hierarchy(通过Manager实例对象,它和root logger一样也是单例的使用模式),我们自己创建的logger实例对象,都是是root logger的孩子。

日志的设置

对于日志的设置,我们是使用logging.basicConfig(**kwargs)函数,它是对root logger进行设置的,一般使用较多的关键字参数如下:

level 决定root logger的日志级别。

format 它是日志格式字符串,指定日志输出的字段信息,如日期,日志级别,文件名,当然还有我们的msg信息等等。

datefmt 决定日期字段的输出格式。

filename 日志信息的输出目的地文件,不指定时默认输出到控制台。

filemode 目的地文件的打开模式,不指定默认为"a"。

对于logging.basicConfig函数有一点需要注意:我们不能在该函数前使用任何模块级日志输出函数如logging.info、logging.error,因为它们会调用一个不带参的basicConfig函数,使得logging.basicConfig函数失效。见源码(由于代码过多,建议参考注释进行阅读):

def basicConfig(**kwargs):

_acquireLock()

try:

#这里由于不带参调用basicConifg,

#而root.handlers默认为空列表

#在Logger定义中可见self.handlers被设为[],

#而默认的root实例在创建时只指定了log级别

#所以if条件必然通过

if len(root.handlers) == 0:

#由于不带参,所以handlers必为None

handlers = kwargs.pop("handlers", None)

if handlers is None:

#这里由于不带参,所以即是handlers为None

#通过上面的if判断,但kwargs同样为None,

#所以该if不通过

if "stream" in kwargs and "filename" in kwargs:

raise ValueError("'stream' and 'filename' should not be "

"specified together")

else:

if "stream" in kwargs or "filename" in kwargs:

raise ValueError("'stream' or 'filename' should not be "

"specified together with 'handlers'")

#这里由于handlers为None通过if判断继续执行

if handlers is None:

filename = kwargs.pop("filename", None)

mode = kwargs.pop("filemode", 'a')

if filename:

h = FileHandler(filename, mode)

#不带参,kwargs为None,所以filename

#在上面的执行语句的返回值为None,所以

#执行这个else分支

else:

stream = kwargs.pop("stream", None)

h = StreamHandler(stream)

#注意这里,十分重要,可见handlers终于不为None

#被赋予了一个列表,该列表有一个元素h

handlers = [h]

dfs = kwargs.pop("datefmt", None)

style = kwargs.pop("style", '%')

if style not in _STYLES:

raise ValueError('Style must be one of: %s' % ','.join(

_STYLES.keys()))

fs = kwargs.pop("format", _STYLES[style][1])

fmt = Formatter(fs, dfs, style)

#再看这里,十分重要

for h in handlers:

#这个无所谓,就是对format进行默认设置

if h.formatter is None:

h.setFormatter(fmt)

#这里最为关键,可见root.addHandler(h)函数

#会把h添加进root.handlers列表中,那么很显然

#root.handlers不再是一个空列表

root.addHandler(h)

level = kwargs.pop("level", None)

if level is not None:

root.setLevel(level)

if kwargs:

keys = ', '.join(kwargs.keys())

raise ValueError('Unrecognised argument(s): %s' % keys)

finally:

_releaseLock()

所以即是不带参调用basicConfig(),但是经过其函数体执行,root.handlers的列表长度会不为0,所以之后再调用logging.basicConifg函数时,对root.handlers判断,就会因此而直接略过函数体中try部分(主要部分),直接执行finally,没有进行任何设置。(并且很关键的basicConfig函数就是对root logger的handlers进行设置)

对象级使用

在logging模块中logger对象从来都不是直接实例化,而是通过一个模块级借口完成:logging.getLogger(name=None),注意我们创建的logger都是root logger的子类。而通过我们自己创建的logger对象,使用日志记录也是和模块级接口一样的:

logger.debug(msg)

logger.info(msg)

logger.warning(msg)

logger.error(msg)

logger.critical(msg)

同样对于日志格式的设置也是通过logging.basicConfig函数完成的,虽然该函数是对root logger实例对象的日志格式设置,但由于Logger类方法的特殊实现,其实是能够沿用该设置的。

并且对于对象级接口,如logger.info函数:

def info(self, msg, *args, **kwargs):

if self.isEnabledFor(INFO):

self._log(INFO, msg, args, **kwargs)

可见其中没有对basicConfig函数的调用,所以也就没有修改root.handlers列表,即不会发生上文的logging.basciConfig函数失效的问题。

Logger类及basicConfig沿用问题(无兴趣可略)

上文提到了由于Logger类中方法的特殊实现,使得之后实例化的logger对象在进行日志输出记录时也能沿用root logger的basicConfig设定。而这背后的机制到底是怎样的呢?先看:

class RootLogger(Logger):

def __init__(self, level):

"""

Initialize the logger with the name "root".

"""

Logger.__init__(self, "root", level)

_loggerClass = Logger

.....

#由上面代码可见RootLogger其实就是调用了Logger类

root = RootLogger(WARNING)

#这里是对Logger类绑定两个类级属性

#把root实例作为Logger类的一个root属性

Logger.root = root

#把root实例作为参数传入Manager类,

#并用返回的实例定义Logger类的manager属性

#并且Manager类只会被实例化这一次

Logger.manager = Manager(Logger.root)

以上源码中出现的Manager类很重要,在我们的logging.getLogger函数中:

def getLogger(name=None):

"""

Return a logger with the specified name,

creating it if necessary.

If no name is specified, return the root logger.

"""

if name:

#结合上文的代码,我们使用root实例作为参数

#传入Manager类定义了Logger.manager属性,

#此处使用manager实例所有的getLogger方法

#来具体实现模块级的getLogger方法

return Logger.manager.getLogger(name)

else:

return root

小结:

Logger.manager属性是Manager(root)的实例,而logging.getLogger其实就是由Logger.manager对象调用它的getLogger方法。

再转到Manager类的定义中,来分析Manager(Logger.root)创建manager实例对象和用logger.getLogger时具体发生了什么:

class Manager(object):

"""

holds the hierarchy of loggers.

"""

#Logger.manager = Manager(Logger.root)发生的事情

def __init__(self, rootnode):

"""

Initialize the manager with the root node of the logger hierarchy.

"""

#结合上文Logger.manager = Manager(Logger.root)

#可见我们使用root logger作为rootnode,并把它赋予

#Manager类的self.root属性

self.root = rootnode

self.disable = 0

self.emittedNoHandlerWarning = False

#self.loggerDict维护logger hierarchy中的loggers

self.loggerDict = {}

self.loggerClass = None

self.logRecordFactory = None

#使用logging.getLogger时发生的事情,Logger.manager属性

#也就是使用root logger作为参数的Manager实例调用该实例

#所属Manager类的getLogger方法

def getLogger(self, name):

#这里创建一个值为None的rv

rv = None

#不用理会,只是判断name值是否为str

if not isinstance(name, str):

raise TypeError('A logger name must be a string')

_acquireLock()

try:

#这里由于是使用Logger.manager实例来调用

#getLogger方法,而Logger.manager实例的

#self.loggerDict初始值为空,所以在第一

#次使用getLogger方法时,这个判断不通过

#就算之后再次调用logging.getLogger(name)

#也要看name是否在loggerDict中

if name in self.loggerDict:

rv = self.loggerDict[name]

if isinstance(rv, PlaceHolder):

ph = rv

rv = (self.loggerClass or _loggerClass)(name)

rv.manager = self

self.loggerDict[name] = rv

self._fixupChildren(ph, rv)

self._fixupParents(rv)

#所以直接转到此处

else:

#这里self.loggerClass初始为空,使用

#_loggerClass其实就是Logger类实例化

#一个logger对象附于rv

rv = (self.loggerClass or _loggerClass)(name)

#这里再把self也就是Logger.manager赋予

#rv.manager,并未创建新的Manager实例

rv.manager = self

#把rv加入loggerDict中,name:rv键值对

self.loggerDict[name] = rv

#设定当前logger实例的parent

self._fixupParents(rv)

finally:

_releaseLock()

#返回rv,也就是创建的logger实例对象

return rv

大概介绍了Manager的部分源码,我们回到最初的问题,为何我们自己创建的logger实例能沿用root logger的basicConfig设定,再看logger.info方法的源码:

#logger实例的info方法

def info(self, msg, *args, **kwargs):

#对比INFO日志级别和当前logger实例的日志级别来决定是否进行log

if self.isEnabledFor(INFO):

#所以这里其实是调用了logger实例的底层_log方法

self._log(INFO, msg, args, **kwargs)

def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False):

"""

Low-level logging routine which

creates a LogRecord and then calls

all the handlers of this logger to

handle the record.

"""

#为避免繁琐,把一些简单使用时不会碰到的代码略过

...

#生成log record

record = self.makeRecord(self.name, level, fn, lno, msg, args,

exc_info, func, extra, sinfo)

#调用当前logger实例的handle方法来处理log record

self.handle(record)

def handle(self, record):

if (not self.disabled) and self.filter(record):

#可见logger类的handle方法又调用了该类的

#callHandlers方法来处理record

self.callHandlers(record)

#注意看源码中自带注释对于callHandlers方法解释,其中

#很重要的一点就是,该函数会loop through该logger乃至

#该logger的parent,parent的parent一直到root logger

#中的handlers,注意我们在前文中说了,basicConfig其实

#就是对root logger的handlers进行设置

def callHandlers(self, record):

"""

Pass a record to all relevant handlers.

Loop through all handlers for this logger

and its parents in the logger hierarchy.

If no handler was found, output a one-off error

message to sys.stderr. Stop searching up

the hierarchy whenever a logger with the

"propagate" attribute set to zero is found - that

will be the last logger whose handlers are called.

"""

#把当前logger实例赋予c

c = self

found = 0

#这个while一定可以进去,因为c是一个logger对象,不为None

while c:

#对于我们创建的logger,它的handlers初始值是空列表

#所以这个for一开始进不去,对于我们的简单使用场景

#使用了logging.basicConfig(...),然后创建自己的

#logger=logging.getLogger("mylogger"),当前

#logger的parent肯定是root logger,而前面的

#basicConfig对root logger的handlers设置了,

#所以root logger的handlers不为空,可以进入

for hdlr in c.handlers:

found = found + 1

if record.levelno >= hdlr.level:

#使用handlers中的handler对

#log record进行handle处理

hdlr.handle(record)

#这个也进不去,logger实例的propagate都是默认True

if not c.propagate:

c = None #break out

#只能进这里了

else:

#把当前logger实例的parent赋予c

#然后继续循环,如果handlers仍然为空

#则继续把parent赋予c直到root logger

c = c.parent

...

简而言之,我们自己创建的logger能使用parent(Manager实例所维持的一个hierarchy)的handlers对log信息进行handle,而向上追溯的祖先就是root logger,我们已经通过basicConfig的话,实质上就是对它的handlers进行了设置,所以在这个hierarchy中的后代便能享用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/534686.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机科技文献中 CAM,计算机辅助设计、制造(CAD、CAM)和《机械制图》 课程的结合、探索与实践研究...

王建臣摘要:随着我国科学技术的迅猛发展,计算机技术也随之得到大范围推广,并迎来跨越式的发展,在机械行业中,管理、设计、制造都已经逐渐换装计算机来进行。计算机机械制图作为机械专业的基础课程,肩负着培…

docker linux 快速开窗口_Linux搭建docker环境并简单实用

记录生活:配置阿里云镜像源docker YUM[rootcentos-linux ~]# sudo yum install -y yum-utils device-mapper-persistent-data lvm2[rootcentos-linux ~]# sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo安装…

清华计算机接口原理,微机原理与接口技术课后习题答案清华大学

微机原理与接口技术课后习题答案清华大学 微机原理与接口技术课后部分习题参考答案 第一章 2. 第 3项任务,根据状态标志位的状态决定转移方向。 3. 程序存储是将要执行的程序的全部指令存储到存储器中,程序控制指程序开始执行后,通过指令流控…

vf求计算机系统当前日期的年份数,计算机二级VF常用函数列表

数值函数:1.绝对值和符号函数格式:ABS()SIGN()例如:ABS(-5)5,ABS(4)4,SIGN(8)1,SIGN(-3)-1,SIGN(0)02.求平方根表达式格式:SQRT()例如:SQRT(16)4,它与开二分之一次方等同。3.圆周率函数格式:PI()4.求整数函…

element走马灯自动_详细element-ui的走马灯carousel轻松实现自适应全屏banner详细过程...

div部分CSS部分.bannerImg{width: 100%;height: inherit;min-height: 600px;min-width: 1400px;}vue.JS部分var vm new Vue({el : #apptwo,data : {bannerHeight:"",BannerImg:["${request.contextPath}/statics/BCHimg/b6.png","${request.contextP…

4n35光耦引脚图_光耦继电器的主要特点以及输入特性!先进光半导体

光耦合器是一种电光电转换设备,可将电信号作为介质传输。它由两部分组成:光源和光接收器。发光源和光接收器被组装在同一气密壳体中,并通过透明绝缘体彼此隔开。发光源的引脚为输入端子,光接收器的引脚为输出端子,公共…

系统流畅度测试软件,如何通过FPS显示快速测试自己手机流畅度

对于手机流畅度测试除了系统自带的功能能测试手机流畅度之外,那么就没有其它方法能测试手机流畅度了么?那么接下来由小编为大家推荐一款小工具FPS显示(FPS Meter),你可以通过它快速的测试自己手机流畅度,接下来一起看看吧&#xf…

中软国际软件测试培训中心,中软国际准员工培养计划C++开发/软件测试方向开班典礼...

2010年8月31日上午9点半,中软国际准员工培养计划—C开发/软件测试方向开班典礼在无锡ETC隆重举行。开班典礼在热烈的掌声中拉开了序幕。典礼由教务部经理陈晨老师主持,首先她对近20名学员的到来表示欢迎,并对本次学习班顺利开班表示祝贺。中软…

django filter查询多选_动态filter查询数据Django实现方法

这是我在学习Django时看到的关于动态filter查询数据Django实现方法。当时很受用,解决了我在工作中遇到的一个问题。可能有朋友会需要,转来玩蛇网python学习平台和大家一同分享下。在平时用Djangoa工作时,需要处理像是对多个字段进行查询找结果…

二本考北航计算机经历,我(来自二本学校)考上北航的一些经历

大家很想知道二本考上是怎麽考上北航的吧!我就把我的一点经历告诉大家吧。本人由于高考发挥的不好来到了一所很不入流的二本学校。所以我决定利用考研的机会进入理想大学。跟大家一样我也是从大三下学期开始准备考研的,由于本人英语还可以,所…

pytorch 命令行运行_PyTorch简介与相关安装

PyTorch简介PyTorch的前身是Torch,Torch是一个有大量机器学习算法支持的科学计算框架,灵活度很高,是一个与NumPy类似的张量(Tensor)操作库。但是Torch采用的是小众的编程语言Lua,因此流行度很低,于是就有了PyTorch的出…

软件测试用例分享ppt,分享:测试用例设计方法.ppt

分享:测试用例设计方法.ppt还剩122页未读,继续阅读下载文档到电脑,马上远离加班熬夜!亲,很抱歉,此页已超出免费预览范围啦!如果喜欢就下载吧,价低环保!内容要点&#xff…

python 命名空间冲突_python-命名空间

通俗的来说,Python中所谓的命名空间可以理解为一个容器。在这个容器中可以装许多标识符。不同容器中的同名的标识符是不会相互冲突的。理解python的命名空间需要掌握三条规则:第一,赋值(包括显式赋值和隐式赋值)产生标识符,赋值的…

修改tomcat服务器图标,修改tomcat小猫图标,设置项目的favicon图标

JAVA设计模式之模板模式在阎宏博士的一书中开头是这样描述模板方法(Template Method)模式的: 模板方法模式是类的行为模式.准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式 ...从scheduler is shutted down看程序员的英文水平我有个windows服务程序,今天重点在测试…

golang 安全的tcp server_Go 语言使用 TCP_NODELAY 控制发包流量

编写健壮且高性能的网络服务需要付出大量的努力。提高服务性能的方式有很多种,比如优化应用层的代码,更进一步,还可以看看垃圾回收器,操作系统,网络传输,以及部署我们服务的硬件是否有优化空间。TCP/IP 协议…

react取消所有请求_react 组件关闭后怎么消除还在进行中的ajax

把你的请求做成可以取消的, 这里的取消不是取消发送。 因为请求已经发送了,没有办法终止的。所谓的取消其实就是取消回调函数, react官方给出了一种最佳实践。const makeCancelable (promise) > { let hasCanceled_ false; const wrap…

css 涟漪,CSS3水波涟漪动画定位样式如何制作

CSS3水波涟漪动画定位样式如何制作宝剑锋从磨砺出,梅花香自苦寒来。以下是小编为大家搜索整理的CSS3水波涟漪动画定位样式如何制作,希望能给大家带来帮助!更多精彩内容请及时关注我们应届毕业生考试网!先上效果图:教程本动画需要用到的主要属性:animatio…

python基础实训_python基础实践(三)

-*-列表是新手可直接使用的最强大的python功能之一,它融合了众多重要的编程概念。-*-# -*- coding:utf-8 -*-# Author:sweeping-monkQuestion_1 "什么是列表?"print(Question_1)smg "列表由一系列按特定顺序排列的元素组成。你可以创建…

python axis 0 1_python pandas 中axis值0 1怎么分行还是列

axis的重点在于方向,而不是行和列。1表示横轴,方向从左到右;0表示纵轴,方向从上到下。即axis1为横向,axis0为纵向,而不是行和列,具体到各种用法而言也是如此。当axis1时,如果是求平均…

ajax php接收不到数据库,PHP更新MySQL数据库与AJAX调用没有做任何事情

我已经测试过,发现正确的数据被发送,但PHP更新数据库中的字段即处理更新无法正常工作。发生的一切就是我在条件中得到了else响应。我需要根据用户输入是什么来更新数据库。就像我说的,我得到的回应是else回应。$youruname $_POST[youruname]…