python logging
作用:报告状态、错误和信息消息。
logging模块定义了一个标准API,用来报告应用和库的错误及状态信息。由一个标准库模块提供日志API的主要好处在于:所有Python模块都可以参与日志记录,所有一个应用的日志还可以包含来自第三方模块的消息。
一、示例
把INFO等级以上日志输出到控制台, DEBUG等级以上日志写入日志文件。
示例:
#encoding:utf-8 import logging # 创建一个logger logger = logging.getLogger('mylogger') logger.setLevel(logging.DEBUG) # 创建一个handler,用于写入日志文件 fh = logging.FileHandler('test.log') fh.setLevel(logging.DEBUG) # 再创建一个handler,用于输出到控制台 ch = logging.StreamHandler() ch.setLevel(logging.INFO) # 定义handler的输出格式 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh.setFormatter(formatter) ch.setFormatter(formatter) # 给logger添加handler logger.addHandler(fh) logger.addHandler(ch) # 记录一条日志 logger.debug('foo') logger.info('foobar')
运行结果: 控制台显示一条日志:
2012-08-31 13:44:41,137 - mylogger - INFO - foobar
日志文件写入了两条日志:
2012-08-31 13:44:41,137 - mylogger - DEBUG - foo 2012-08-31 13:44:41,137 - mylogger - INFO - foobar
二、logging模块的API
结合上面的例子,我们说下几个最常使用的API:
logging.getLogger([name])返回一个logger实例,如果没有指定name,返回root logger。只要name相同,返回的logger实例都是同一个而且只有一个,即name和logger实例是一一对应的。这意味着,无需把logger实例在各个模块中传递。只要知道name,就能得到同一个logger实例。例如在一个模块新建了一个名字为mylogger的示例,在另一个模块,只需要通过logger =logging.getLogger(‘mylogger’)得到同一个示例。
Logger.setLevel(lvl)设置logger的level,NOTSET < DEBUG < INFO < WARNING < ERROR < CRITICAL,如果把looger的级别设置为INFO, 那么小于INFO级别的日志都不输出, 大于等于INFO级别的日志都输出。
import logging logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG) logging.debug('This message should appear on the console')
三、关于root logger以及logger的父子关系
前面多次提到root logger, 实际上logger实例之间还有父子关系, root logger就是处于最顶层的logger, 它是所有logger的祖先。
root logger是默认的logger
如果不创建logger实例, 直接调用logging.debug()、logging.info()logging.warning()、logging.error()、logging.critical()这些函数,那么使用的logger就是 root logger, 它可以自动创建,也是单实例的。
如何得到root logger
通过logging.getLogger()或者logging.getLogger("")得到root logger实例。
默认的level
root logger默认的level是logging.WARNING
如何表示父子关系
logger的name的命名方式可以表示logger之间的父子关系. 比如:parent_logger = logging.getLogger('foo')child_logger = logging.getLogger('foo.bar')
什么是effective level
logger有一个概念,叫effective level。 如果一个logger没有显示地设置level,那么它就用父亲的level。如果父亲也没有显示地设置level, 就用父亲的父亲的level,以此推....最后到达root logger,一定设置过level。默认为logging.WARNINGchild loggers得到消息后,既把消息分发给它的handler处理,也会传递给所有祖先logger处理。
#encoding:utf-8 import logging # 设置root logger r = logging.getLogger() ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') ch.setFormatter(formatter) r.addHandler(ch) # 创建一个logger作为父亲 p = logging.getLogger('foo') p.setLevel(logging.DEBUG) ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(message)s') ch.setFormatter(formatter) p.addHandler(ch) # 创建一个孩子logger c = logging.getLogger('foo.bar') c.debug('foo')
运行结果:
2012-08-31 14:00:24,295 - foo 2012-08-31 14:00:24,295 - DEBUG - foo
可见, 孩子logger没有任何handler,所以对消息不做处理。但是它把消息转发给了它的父亲以及root logger。最后输出两条日志。
要想每次程序运行时创建一个新文件,可以向basicConfig()的参数filemode传入值“w”。不过,最好不要采用这种方式管理文件的创建,更好的做法是使用一个RotatingFileHandler,它会自动创建新文件,同时保留原来的日志文件。
使用中发现,用RotatingHandler,并不是每次程序运行时创建一个新文件,日志信息依旧追加到原有的日志文件里。当文件大小超过maxBytes设置的大小限制后,重命名备份该文件。
不用basicConfig,想每次程序运行的日志文件都是新的怎么办?
参考: