此博文来自未知博主。有问题欢迎进入博主专页进行互动讨论!博文地址:http://xdzw608.blog.51cto.com/4812210/1608718本文来自对py2.7.9docs中howto-logging部分的理解加上源码。官方文档链接如下。我用的是下载的pdf版,应该是一样的:https://docs.python.org/2/howto/logging.html我们不按照文档中讲解的顺序由浅入深,因为是这样的小东西没有“进”的动作。使用logging模块记录日志涉及到四大类,使用官方文档中的概括是最合适的:logger提供应用程序可以直接使用的接口;处理程序将日志记录(由记录器创建)发送到适当的目标输出;filter提供fineness设备决定输出哪条日志记录;格式化程序决定日志记录的最终输出格式。一般写日志的顺序是:1.创建logger:我们不需要直接通过logging.Logger实例化一个logger,而是需要通过logging.getLogger("name")生成一个logger对象。并不是我们不能实现Logger的实例化,而是我们期望的是同名可以得到同一个logger,这样多个模块可以一起使用同一个logger。getLogger正是这样一种解决方案,它在内部使用loggerDict字典进行维护,以确保与key同名的会得到相同的logger对象。我们可以通过一个例子来验证:#test_logger1.py#coding:utf-8importloggingprintlogging.getLogger("mydear")importtest_logger2test_logger2.run()#调用文件2中的函数,保证两个模块的生命周期相同#test_logger2.py#coding:utf-8importloggingdefrun():printlogging.getLogger("mydear")Output:结果表明,通过“mydear”调用getLogger在两个files可以保证得到的logger对象是一样的。不保证单独实例化Logger类。有了logger之后,就可以对这个logger进行配置,比如设置日志级别setLevel,绑定控制器addHandler,添加过滤器addFilter等。配置完成后,就可以调用logger方法写入日志了。根据五个日志级别,有五种日志记录方式,分别是logger.debug、logger.info、logger.warning、logger.error和logger.critical。2.配置Logger对象的日志级别:logger.setLevel(logging.DEBUG)#DEBUG以上的日志级别会被这个logger处理3.创建一个handler对象handler负责分发日志到某个目的地输出,并且有很多内置的Handlers将日志分发到不同的目的地,可以是console,也可以是file,也可以是某种形式的stream,或者socket等。一个logger可以绑定多个handler,比如可以输出一个log同时到控制台和文件。以FileHandler和StreamHandler为例:logfile=logging.FileHandler("./log.txt")#创建一个handler将日志输出到文件console=logging.StreamHandler()#创建另一个handler来引导日志流处理程序对象还需要设置日志级别。由于一个记录器可以包含多个处理程序,因此有必要为每个处理程序设置日志级别。通俗点说,比如我们需要处理debug级别以上的消息,那么我们设置logger的日志级别为DEBUG;那么我们要将error以上的日志输出到控制台,将DEBUG以上的信息输出到文件中。这个分流需要两个Handler来控制。logfile.setLevel(logging.DEBUG)console.setLevel(logging.ERROR)除了可以为handler对象设置日志级别,还可以指定formatter,也就是日志的输出格??式。在handler对象上设置日志格式,表示一条记录可以以不同的格式输出到控制台、文件或其他目的地。formatter=logging.Formatter('%(asctime)s-%(name)s-%(levelname)s-%(message)s')logfile.setFormatter(formatter)#设置formatter时使用的handler的日志输出格式被创建的关键字,***会以列表的形式显示出来,这不是重点。4.将handler绑定到logger至此,handlers和logger就准备好了。接下来,我们将处理程序绑定到记录器。一个记录器对象可以绑定多个处理程序。logger.addHandler(logfile)#logger是通过getLogger得到的Logger对象logger.addHandler(console)5.使用logger实际写入日志logger.debug("somedebugmessage.")logger.info("someinfomessage.")看起来中间的步骤(创建handler、设置日志级别、设置输出格式等)更像是配置Logger。配置完成后,可以直接调用写入日志的接口。后面会按照之前的配置输出这些日志。呜呜呜,内容很多,简单点吧。下面的代码是最简单的。logging导入后,执行日志操作:#coding:utf-8importlogginglogging.debug("debugmes")logging.info("infomes")logging.warning("warnmes")控制台输出如下:WARNING:root:warnmes嗯?怎么回事,为什么只输出warning?处理程序、记录器和格式化程序去了哪里?-_-!那最简单的呢?为了给自己加分,我尽量用“最简单”来解释。#p#知识点1:logger之间存在继承关系。记录器通过名称确定继承关系。如果一个logger的名称是“mydest”,另一个logger的名称是“mydest.dest1”(getLogger("mydest.dest1")),则后者被称为前者的子logger,将继承前者的配置。上面的代码没有指定记录器。当直接调用logging.debug等方法时,会使用到所有logger的祖先类RootLogger。从上面代码的运行结果可以猜测,RootLogger设置的日志级别是logging.WARN,输出目的地是标准流。从源码可以看得更清楚:root=RootLogger(WARNING)#设置WARNING的级别至于rootLogger的输出目的地的配置,我们跟踪一下logging.debug的源码看看:defdebug(msg,*args,**kwargs):"""Logamessagewithseverity'DEBUG'ontherootlogger."""iflen(root.handlers)==0:basicConfig()root.debug(msg,*args,**kwargs)你可以看到如果rootLogger没有配置handler,它会运行不带参数的basicConfig函数(*见知识点2),我们看basicConfig的源码:defbasicConfig(**kwargs):_acquireLock()try:iflen(root.handlers)==0:filename=kwargs.get("filename")iffilename: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")iflevelisnotNone:root.setLevel(level)finally:_releaseLock()因为参数为空,我们可以看到rootLoger使用了一个没有参数的StreamHandler,也可以看到格式配置等默认值。然后我们追溯StreamHandler的源码(因为要查看日志输出目的地的配置,而handler控制着日志流向,所以不得不追溯):流。请注意,此类不关闭流,可能会使用assys.stdout或sys.stderrm。"""def__init__(self,stream=None):"""初始化处理程序。如果未指定流,则使用sys.stderr。"""Handler.__init__(self)ifstreamisNone:stream=sys.stderr####self.stream=streamStreamHandler不带参数会将日志流定位到sys.stderr流,标准错误流也会输出到控制台知识点2:basicConfig函数用于配置RootLoggerbasicConfig函数只是用来配置RootLogger,rootLogger是所有Logger的祖先是Logger,所以其他所有Logger都会继承这个Logger的配置。从上面的basicConfig源码来看,它可以有六个关键字参数,分别是:filename:执行并使用文件名创建一个FileHandler而不是StreamHandler作为rootLoggerfilemode:指定文件打开方式,默认为“a”stream:指定一个流来初始化StreamHandler。该参数不能与文件名共存。如果同时提供这两个参数,stream参数将被忽略format:指定rootLoggerhandler的输出格式datefmt:指定输出日期和时间formatlevel:设置rootLogger的日志级别使用示例:logging。basicConfig(filename='./log.txt',filemode='a',#stream=sys.stdout,format='%(levelname)s:%(message)s',datefmt='%m/%d/%Y%I:%M:%S',level=logging.DEBUG知识点3通过实例详细讨论Logger配置的继承关系首先准备好继承条件:log2继承自log1,logger的名字可以任意,注意'.'表示的继承关系。#coding:utf-8importlogginglog1=logging.getLogger("mydear")log1.setLevel(logging.WARNING)log1.addHandler(StreamHandler())log2=logging.getLogger("mydear.app")log2.error("display")log2.info("notdisplay")level的继承原理:子logger写日志时,自己设置的level会我先用;如果不设置,会逐层查询parentlogger,直到查询完为止。在最极端的情况下,使用rootLogger的默认日志级别logging.WARNING。从源码看更清楚,感谢python所见即所得:defgetEffectiveLevel(self):"""Gettheeffectivelevelforthislogger.Loopthroughthisloggeranditsparentsintheloggerhierarchy,lookingforannon-zerogginglevel.Returnthefirststonefound."""logger=selfwhilelogger:iflogger.level:returnlogger.levellogger=的继承原理logger.parentreturnNOTSEhandler:首先将log对象传递给childlogger的所有handler进行处理。处理后,如果子Logger的propagate属性没有设置为0,则日志对象会向上传递给第一个父Logger。parentlogger的所有handler处理完后,如果它的propagate不设置为0,会继续向上层传递,以此类推。最后的状态,要么遇到一个propagate属性设置为0的Logger;或传递直到rootLogger被处理。在上面示例代码的基础上,我们再增加一行代码,即:#coding:utf-8importlogginglog1=logging.getLogger("mydear")log1.setLevel(logging.WARNING)log1.addHandler(StreamHandler())log2=logging.getLogger("mydear.app")log2.error("display")log2.info("notdisplay")printlog2.handlers#打印log2绑定的handler输出如下:display[]同意继承,但是子记录器竟然是Thereisnohandlerboundtotheparentclass,怎么了?看看下面调用处理程序的源代码,真相就大白了。可以理解成,这不是真正的(类)继承,只是"行为上的继承":defcallHandlers(self,record):"""Passarecordtoallrelevanthandlers.Loopthroughallhandlersforthisloggeranditsparentsintheloggerhierarchy.Ifnohandlerwasfound,outputaone-offerrormessagetosys.stderr.Stopsearchingupthehierarchywheneveraloggerwiththe"propagate"attributesettozeroisfound-thatwillbethelastloggerwhosehandlersarecalled."""c=selffound=0whilec:forhdlrinc.handlers:#先遍历子logger的所有handlersfound=found+1ifrecord.levelno>=hdlr.level:hdlr.handle(record)ifnotc.propagate:#如果传播logger的attribute设置为0,stopc=None#breakoutelse:#否则使用直接父loggerc=c.parent...嗯,最简单的例子抽出这么多背景逻辑,倒是有利于我们理解。下面,我们就罗列一些零碎的、不是很重要的东西,本篇到此结束。1、几种LogLevel是全局变量,以整数形式表示,也可以自定义日志级别,但不推荐。如果需要设置level为用户配置,一般获取level和查看level的代码为:#假设loglevel代表用户设置的level内容numeric_level=getattr(logging,loglevel.upper(),None)ifnotisinstance(numeric_level,int):raiseValueError('Invalidloglevel:%s'%loglevel)logging.basicConfig(level=numeric_level,...)2.format格式,用于创建格式化对象,或者在basicConfig中,不会被翻译%(name)s记录器的名称(记录通道)%(levelno)s消息的数字记录级别(DEBUG、INFO、WARNING、ERROR、CRITICAL)%(levelname)s消息的文本记录级别(“DEBUG”,"INFO","WARNING","ERROR","CRITICAL")%(pathname)s发出日志记录调用的源文件的完整路径名(如果可用)%(filename)s路径名的文件名部分%(module)s模块(文件名的名称部分)%(lineno)d发出日志调用的源行号(如果可用)%(funcName)s函数名%(created)f时间eLogRecord已创建(time.time()返回值)%(asctime)s创建LogRecord时的文本时间%(msecs)d创建时间的毫秒部分%(relativeCreated)d创建LogRecord时的时间(以毫秒为单位,相对于日志记录模块的时间)已加载(通常在应用程序启动时)%(thread)d线程ID(如果可用)%(threadName)s线程名称(如果可用)%(process)d进程ID(如果可用)%(message)s结果record.getMessage(),computedastherecordisemitted3.写日志接口logging.warn("%samahero","I")#1%格式以参数的形式提供实际参数logging.warn("%samahero"%("I",))#2直接提供字符串,或者使用格式,模板logging.warn("%(name)samahero",{'name':"I"})#关键字参数logging.warn("%(name)samahero"%{'name':"I"})#这个也可以logging.warn("%(name)samahero,%(value)s"%{'name':"I",'value':'Yes'})#原来%也可以解析关键字参数,不一定是元组。如果关键字和位置参数混用的话,%应该就没什么事了,****会可以这样:logging.warn("%(name)samahero,%()s"%{'name':"I",'':'Yes'})#也是字典的原理formatting4.配置logging:上面说了,如果配置handler,bind到logger,如果需要稍微大一点的日志系统,可以想象我们会用到很多addHandler,SetFormatter之类的,很烦人足够的。幸运的是,logging模块额外提供了两种配置方法,不用写很多代码,我们可以直接从配置结构中了解到我们的配置意图方法一:使用配置文件importloggingimportlogging.configlogging.config.fileConfig('logging.conf')#createloggerlogger=logging.getLogger('simpleExample')#'application'codelogger.debug('debugmessage')logger.info('infomessage')logger.warn('warnmessage')logger.error('errormessage')logger.critical('criticalmessage')#配置文件logging.conf内容[loggers]keys=root,simpleExample[handlers]keys=consoleHandler[formatters]keys=simpleFormatter[logger_root]level=DEBUGhandlers=consoleHandler[logger_simpleExample]level=DEBUGhandlers=consoleHandlerqualname=simpleExample0[handler_consoleHandler]class=StreamHandlerlevel=DEBUGformatter=simpleFormatterargs=(sys.stdout,)[formatter_simpleFormatter]format=%(asctime)s-%(name)s-%(levelname)s-%(message)sdatefmt=方法2:请参考蟒蛇2.7.9使用字典的库文档,链接:https://docs.python.org/2/library/logging.config.html?highlight=dictconfig#configuration-dictionary-schema5。许多处理程序满足不同的输出需求StreamHandler,FileHandler、NullHandler、RotatingFileHandler、TimedRotatingFileHandler、SocketHandler、DatagramHandler、SMTPHandler、SysLogHandler、NTEventLogHandler、MemoryHandler、HTTPHandler、WatchedFileHandler,前三个在logging模块中给出,其他在logging.handlers模块中给出