【博文推荐 】 python Howto之logging模块(1)(2)
知识点1:logger间存在继承关系
logger 通过名字来决定继承关系,如果一个logger的名字是"mydest",另一个logger的名字是"mydest.dest1" getLogger("mydest.dest1")),那么就称后者是前者的子logger,会继承前者的配置。上面的代码没有指定logger,直接调用logging.debug等方法时,会使用所有logger的祖先类RootLogger。
从上面的代码运行结果可以猜测出,该RootLogger设置的日志级别是logging.WARN,输出目的地是标准流。从源码可以更清楚的看出来:
- root = RootLogger(WARNING) #设置WARNING的级别
至于rootLogger的输出目的地的配置,我们跟踪logging.debug的源代码来看一下:
- def debug(msg, *args, **kwargs):
- """
- Log a message with severity 'DEBUG' on the root logger.
- """
- if len(root.handlers) == 0:
- basicConfig()
- root.debug(msg, *args, **kwargs)
大约可以看到,如果rootLogger没有配置handler,就会不带参数运行basicConfig函数(*请看知识点2),我们看一下basicConfig的源代码:
- def basicConfig(**kwargs):
- _acquireLock()
- try:
- 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)
- finally:
- _releaseLock()
因 为参数为空,所以我们就看出了,该rootLoger使用了不带参数的StreamHandler,也可以看到诸如format之类的默认配置。之后我们 跟踪StreamHandler(因为我们想看到日志输出目的地的配置,而handler就是控制日志流向的,所以我们要跟踪它)的源代码:
- class StreamHandler(Handler):
- """
- A handler class which writes logging records, appropriately formatted,
- to a stream. Note that this class does not close the stream, as
- sys.stdout or sys.stderr may be used.
- """
- def __init__(self, stream=None):
- """
- Initialize the handler.
- If stream is not specified, sys.stderr is used.
- """
- Handler.__init__(self)
- if stream is None:
- stream = sys.stderr ####
- self.stream = stream
不带参数的StreamHandler将会把日志流定位到sys.stderr流,标准错误流同样会输出到控制台
知识点2:basicConfig函数用来配置RootLogger
basicConfig函数仅用来配置RootLogger,rootLogger是所有Logger的祖先Logger,所以其他一切Logger会继承该Logger的配置。
从上面的basicConfig源码看,它可以有六个关键字参数,分别为:
filename:执行使用该文件名为rootLogger创建FileHandler,而不是StreamHandler
filemode:指定文件打开方式,默认是"a"
stream:指定一个流来初始化StreamHandler。此参数不能和filename共存,如果同时提供了这两个参数,则stream参数被忽略
format:为rootLogger的handler指定输出格式
datefmt:指定输出的日期时间格式
level:设置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-8
- import logging
- log1 = logging.getLogger("mydear")
- log1.setLevel(logging.WARNING)
- log1.addHandler(StreamHandler())
- log2 = logging.getLogger("mydear.app")
- log2.error("display")
- log2.info("not display")
level的继承
原则:子logger写日志时,优先使用本身设置了的level;如果没有设置,则逐层向上级父logger查询,直到查询到为止。最极端的情况是,使用rootLogger的默认日志级别logging.WARNING。
从源代码中看更为清晰, 感谢python的所见即所得:
- def getEffectiveLevel(self):
- """
- Get the effective level for this logger.
- Loop through this logger and its parents in the logger hierarchy,
- looking for a non-zero logging level. Return the first one found.
- """
- logger = self
- while logger:
- if logger.level:
- return logger.level
- logger = logger.parent
- return NOTSET
handler的继承
原则:先将日志对象传递给子logger的所有handler处理,处理完毕后,如果该子logger的propagate属性没有设置为0,则将日志对象向上传递给第一个父Logger,该父logger的所有handler处理完毕后,如果它的propagate也没有设置为0,则继续向上层传递,以此类推。最终的状态,要么遇到一个Logger,它的propagate属性设置为了0;要么一直传递直到rootLogger处理完毕。
在上面实例代码的基础上,我们再添加一句代码,即:
- #coding:utf-8
- import logging
- log1 = logging.getLogger("mydear")
- log1.setLevel(logging.WARNING)
- log1.addHandler(StreamHandler())
- log2 = logging.getLogger("mydear.app")
- log2.error("display")
- log2.info("not display")
- print log2.handlers #打印log2绑定的handler
输出如下:
display
[]
说好的继承,但是子logger竟然没有绑定父类的handler,what's wrong?
看到下面调用handler的源代码,就真相大白了。可以理解成,这不是真正的(类)继承,只是"行为上的继承":
- 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.
- """
- c = self
- found = 0
- while c:
- for hdlr in c.handlers: #首先遍历子logger的所有handler
- found = found + 1
- if record.levelno >= hdlr.level:
- hdlr.handle(record)
- if not c.propagate: #如果logger的propagate属性设置为0,停止
- c = None#break out
- else: #否则使用直接父logger
- c = c.parent
- ...
额,最简单的样例牵引出来这么多后台的逻辑,不过我们懂一下也是有好处的。
下面,我们将一些零碎的不是很重要的东西罗列一下,这篇就结束了。
1.几种LogLevel是全局变量,以整数形式表示,也可以但是不推荐自定义日志级别,如果需要将level设置为用户配置,则获取level和检查level的一般代码是:
- #假设loglevel代表用户设置的level内容
- numeric_level = getattr(logging, loglevel.upper(), None)
- if not isinstance(numeric_level, int):
- raise ValueError('Invalid log level: %s' % loglevel)
- logging.basicConfig(level=numeric_level, ...)
2.format格式,用于创建formatter对象,或者basicConfig中,就不翻译了
%(name)s Name of the logger (logging channel)
%(levelno)s Numeric logging level for the message (DEBUG, INFO,
WARNING, ERROR, CRITICAL)
%(levelname)s Text logging level for the message ("DEBUG", "INFO",
"WARNING", "ERROR", "CRITICAL")
%(pathname)s Full pathname of the source file where the logging
call was issued (if available)
%(filename)s Filename portion of pathname
%(module)s Module (name portion of filename)
%(lineno)d Source line number where the logging call was issued
(if available)
%(funcName)s Function name
%(created)f Time when the LogRecord was created (time.time()
return value)
%(asctime)s Textual time when the LogRecord was created
%(msecs)d Millisecond portion of the creation time
%(relativeCreated)d Time in milliseconds when the LogRecord was created,
relative to the time the logging module was loaded
(typically at application startup time)
%(thread)d Thread ID (if available)
%(threadName)s Thread name (if available)
%(process)d Process ID (if available)
%(message)s The result of record.getMessage(), computed just as
the record is emitted
3.写日志接口
logging.warn("%s am a hero", "I") #1 %格式以参数形式提供实参
logging.warn("%s am a hero" % ("I",)) #2 直接提供字符串,也可以使用format,template
logging.warn("%(name)s am a hero", {'name':"I"}) #关键字参数
logging.warn("%(name)s am a hero" % {'name':"I"}) #甚至这样也可以
logging.warn("%(name)s am a hero, %(value)s" % {'name':"I", 'value':'Yes'}) #原来%也能解析关键字参数,不一定非是元组
如果关键字和位置参数混用呢,%应该不会有什么作为了,最强也就能这样:
logging.warn("%(name)s am a hero, %()s" % {'name':"I" ,'': 'Yes'})#也是字典格式化的原理
4.配置logging:
上 面已经讲了如果配置handler,绑定到logger。如果需要一个稍微庞大的日志系统,可以想象,我们会使用好多的 addHandler,SetFormatter之类的,有够烦了。幸好,logging模块提供了两种额外配置方法,不需要写众多代码,直接从配置结构 中获悉我们的配置意图
方式一:使用配置文件
- import logging
- import logging.config
- logging.config.fileConfig('logging.conf')
- # create logger
- logger = logging.getLogger('simpleExample')
- # 'application' code
- logger.debug('debug message')
- logger.info('info message')
- logger.warn('warn message')
- logger.error('error message')
- logger.critical('critical message')
- #配置文件logging.conf的内容
- [loggers]
- keys=root,simpleExample
- [handlers]
- keys=consoleHandler
- [formatters]
- keys=simpleFormatter
- [logger_root]
- level=DEBUG
- handlers=consoleHandler
- [logger_simpleExample]
- level=DEBUG
- handlers=consoleHandler
- qualname=simpleExample
- propagate=0
- [handler_consoleHandler]
- class=StreamHandler
- level=DEBUG
- formatter=simpleFormatter
- args=(sys.stdout,)
- [formatter_simpleFormatter]
- format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
- datefmt=
方式二:使用字典
请参阅python2.7.9 Library文档,链接:
https://docs.python.org/2/library/logging.config.html?highlight=dictconfig#configuration-dictionary-schema
5.众多的handler满足不同的输出需要
StreamHandler,FileHandler,NullHandler,RotatingFileHandler,TimedRotatingFileHandler,SocketHandler,DatagramHandler,SMTPHandler,SysLogHandler,NTEventLogHandler,MemoryHandler,HTTPHandler,WatchedFileHandler,
其中前三种在logging模块中给出,其他的在logging.handlers模块中给出。
评论关闭