logging的使用分析

标签: Uliweb | 发表时间:2011-09-18 12:59 | 作者:limodou jeff
出处:http://hi.baidu.com/limodou

昨天,Lord Leatherface问我为什么使用了suds后,输出日志变成了两条。于是我仔细地研究了logging的日志处理机制,并且再次考虑了uliweb的日志处理,最终对uliweb的日志进行了重构,并且弄清楚了为什么会有两条日志的现象,因此以本文作一个记录。

对于logging我想大家用得应该不少,那么我先提几个问题:

1. logging直接输出日志,如logging.info()与log = logging.getLogger('name') log.info()有什么不同?

2. “No handlers could be found for logger”是怎么回事?

3. root logger有什么用,如何获得?

4. 形如"a.b.c"的logger名有什么用?

5. 如果一个logger执行多次addHandler,那么每个handler都会被执行吗?

6. logger的传播(propagate)是怎么回事?如何阻止它?

有时候带着问题学习可能会更快。那么下面根据我的理解一点点进行解释。

logging中log是可以分级的,它的级别与你使用getLogger()中的名字有关系。比如,你可以使用"uliweb"或"uliweb.app",它们的区别就是"uliweb.app"中有一个'.'。那么logging会自动生成"uliweb"和"uliweb.app"的logger,并且"uliweb"将是"uliweb.app"的logger的父对象。这一点可以这样验证:

>>> import logging

>>> log = logging.getLogger('uliweb.app')

>>> log.manager.loggerDict.items()

[('uliweb.app', <logging.Logger instance at 0x017CE238>), ('uliweb', <logging.PlaceHolder instance at 0x017CE2D8>)]

这里有一个挺有意思的,就是'uliweb'这个logger它的对象是PlaceHolder,相当于是一个“虚"日志对象。logging可以允许通过getLogger()来自动生成对应的logger对象。对于分级的日志名字,它会一级级地创建,如果父对象还不存在,则会自动创建一个占位对象。如果,后面用户又通过getLogger()来创建一个父对象,则logging会自动对父子关系进行重新修订,保证对象关系的正确性。

再看一看父子关系:

>>> log.parent

<logging.RootLogger instance at 0x017E3AD0>

>>> log1 = logging.getLogger('uliweb')

>>> log.parent

<logging.Logger instance at 0x01820DA0>

>>> log.parent.name

'uliweb'

挺有意思。当我们直接通过getLogger('uliweb.app')获得日志对象时,它的parent并不是占位对象'uliweb',而是RootLogger。这个RootLogger是在导入logging时自动由模块创建的,它的创建代码是:

root = RootLogger(WARNING)

Logger.root = root

Logger.manager = Manager(Logger.root)

Logger是真正的logger的类。可以看它在运行时会给它赋予root和manager对象。而manager就是用来创建logger并进行管理的类。前面的代码就是通过查看manager.loggerDict来查看所有已经定义的logger。
因此从上面的代码我们可以理解,在直接创建一个多级的logger时,如果父对象不存在,则父对象自然是root对象。如果父对象被创建,则父子关系被修正。

root对象有什么用呢?它就是缺省的日志对象。使用logging的最简单的方法是:

import logging

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进行判断。那么handlers是什么东西?它就是用来处理每条日志的处理类的实例。每个logger可以有不止一个handler实例。并且每个handler对象可以有自已的日志输出级别,可以和logger的不同。handler的处理我们后面再说。上面的代码意思就是,如果root还没有定义处理对象,则执行basicConfig()进行缺省配置。然后使用root.info()进行输出。所以我们可以理解:logging.info()其实就是使用root来进行输出的,你可以理解它是一个全局性的,缺省的日志对象。并且可以自动进行配置。
那么,为什么要配置?因为RootLogger(WARNING)只是创建了logger类,但是还没有添加任何的handler,因此handlers是空的。也就是说,创建了一个logger,并不表示它就可以输出日志。是不是有些奇怪?还是以代码为上:

    def callHandlers(self, record):

        c = self

        found = 0

        while c:

            for hdlr in c.handlers:

                found = found + 1

                if record.levelno >= hdlr.level:

                    hdlr.handle(record)

            if not c.propagate:

                c = None    #break out

            else:

                c = c.parent

        if (found == 0) and raiseExceptions and not self.manager.emittedNoHandlerWarning:

            sys.stderr.write("No handlers could be found for logger"

                             " \"%s\"\n" % self.name)

            self.manager.emittedNoHandlerWarning = 1

上面的代码是Logger类中用来输出日志的一个方法,所有的秘密都在这里面了。这里不一条条解决了,说一下我的理解吧。它首先设置了一个found的标志,它的作用就是如果找到了一个可用的handler,则加1,然后等最后判断一下,如果found为0,则表示你还没有对logger进行配置,在最后会输出“No handlers could be found for logger”的信息。还记得root的事情吗?创建了一个logger并不表示它就带有handler了,也就是无法进行处理。因此logging.info()代码中,会当root没有配置handler时,使用basicConfig()来进行配置。那么在basicConfig()的代码中,你会看到它会根据传入的参数自动生成相应的Handler实例,添加到root中去。所以logging.info()不会报错,是因为它自动配置了。而你自已通过getLogger()得到的某个日志对象,可没有自动化的配置方式,所以有可能会报错。因此现在我们可以就有一个印象,日志使用前要配置,主要是添加相应的handler。那么这段代码还有什么特殊的?一个就是c.propagate(propagate的中文意思是传播),它是Logger的一个属性,缺省为1。从上面的代码可以看出,如果propagate为0或假值,则循环就退出了。如果为1,则循环会继续从父对象开始,直到找不到或propagate为假。因此logging会利用propagate和父对象进行递归处理或传播处理。因此就样就实现这样一种效果:在传播情况下,从子结点到父结点的handler都会被处理一番。当然,还有一个检查点就是信息输出的日志级别要大于等于handler的日志级别。因此,如果父结点和子结点都定义了handler,并且日志级别都符合要求,这样的确会输出两条日志,但是由不同的handler输出的。所以,如果子对象定义了自已的handler,为了避免传播,因此应该设置它的propagate=0。但是,利用传播,我们也可以实现,子日志对象不定义自已的handler,因此最终是使用父对象的handler。特别是对于父对象是root对象的情况,只要执行了basicConfig(),则一定会存在handler,就可以复用root的handler了。
上面的代码还有一点要注意的就是,它会对logger的所有handler进行循环,当然同时要检查日志输出级别。因此,如果你为一个logger定义了多个handler,那么就有可能都会输出。如果想区分不同的handler,可以利用级别来控制。
对于root对象,再说一点就是什么时候要执行basicConfig()进行配置?如果你没有自定义logger,只是使用logging中已经提供的如:info(), debug()之类的方法,的确不必特别调用basicConfig(),因为这些函数在执行时都做了检查,如果没通过,则自动会调用basicConfig()。但是,如果你不是这样使用,而是使用自定义的logger,因此还是建议你先执行basicConfig()进行缺省的配置。在使用自定义的logger,要注意的问题就是:handler的添加与propagate的处理。

那么如果我想获得root对象该如何做呢?使用logging.root吗?常用的方法还是通过getLogger('')来获得。参数可以是''或其它为假的值。

通过上面的讲解,我想你应该知道我提的几个问题的答案了吧。下面再简单总结一下:
logger是分级的,有父子关系。
logger的处理要靠handler来输出,获得了logger并不一定会自动创建handler,因此logger一般都需要配置。
在存在父子关系的情况下,日志是可以被传播输出的,并且可以通过propagate属性来控制。


类别:Uliweb 查看评论

相关 [logging 分析] 推荐:

logging的使用分析

- jeff - limodou的学习生活
昨天,Lord Leatherface问我为什么使用了suds后,输出日志变成了两条. 于是我仔细地研究了logging的日志处理机制,并且再次考虑了uliweb的日志处理,最终对uliweb的日志进行了重构,并且弄清楚了为什么会有两条日志的现象,因此以本文作一个记录. 对于logging我想大家用得应该不少,那么我先提几个问题:.

Python日志库logging总结

- - IT瘾-tuicool
在部署项目时,不可能直接将所有的信息都输出到控制台中,我们可以将这些信息记录到日志文件中,这样不仅方便我们查看程序运行时的情况,也可以在项目出现故障时根据运行时产生的日志快速定位问题出现的位置. Python 标准库 logging 用作记录日志,默认分为六种日志级别(括号为级别对应的数值),NOTSET(0)、DEBUG(10)、INFO(20)、WARNING(30)、ERROR(40)、CRITICAL(50).

55最佳实践系列:Logging最佳实践

- - 博客园_旁观者
@郑昀汇总 创建日期:2012/10. ASAP (As Soon As Possible)原则. 当你意识到靠现有的日志无法定位问题时,. 当现象难以在你的开发环境重现时,. 请不要执著于枯坐肉眼看代码,因为:一)不一定是你代码逻辑问题,可能是脏数据造成的,是老业务数据造成的,是分布式环境造成的,是其他子系统造成的;二)线上业务处于不稳定中,条件不允许问题定位无限期.

竞品分析

- 章明 - 互联网的那点事
关于竞品分析,之前天行(@天行Aeros)有篇文章《设计公式:简单有效的竞品分析》已经进行了介绍,本文在该文章的基础之上再进行一些分享,希望对大家有用. 竞品分析(Competitive Analysis)一词最早源于经济学领域. 市场营销和战略管理方面的竞品分析是指对现有的或潜在的竞争产品的优势和劣势进行评价.

Excel-对比图分析(差异分析)

- - CSDN博客数据库推荐文章
本文摘自作者《网站数据分析:数据驱动的网站管理、优化和运营 》: http://item.jd.com/11295690.html. 对比分析就是将两个或两个以上的数据进行比较,分析它们之间的差异,从而揭示这些数据所代表事物的发展变化情况和规律. 通过对比,可以很直观地看出事物在某方面的差距,并且可以准确、量化地表示出差距的多少.

Netty代码分析

- LightingMan - 淘宝JAVA中间件团队博客
Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序[官方定义],整体来看其包含了以下内容:1.提供了丰富的协议编解码支持,2.实现自有的buffer系统,减少复制所带来的消耗,3.整套channel的实现,4.基于事件的过程流转以及完整的网络事件响应与扩展,5.丰富的example.

分析帝转世

- JoyLee - Lzhi&#39;s Views
纪晓岚与和珅的经典对白(和绅说得是实话啊). 本文网址:http://www.lzhi.org/views/652030. 欢迎加入500人超级QQ群:108869281,交流最新好文章.

fqueue初步分析

- tangfl - 淘宝JAVA中间件团队博客
    fqueue是国产的一个类似memcacheq,kestrel这样的支持memcached协议的轻量级开源MQ. http://code.google.com/p/fqueue/downloads/list,介绍和特点都可以看主页,我就不废话了.     今天老大提到, co了源码看了下,写个初步分析报告.