本文是对python文档中Logging HOWTO的中文翻译,在下英文水平有限,文中肯定
有很多不准确甚至错误的地方,本文仅作为抛砖引玉之用,请各位在看的过程中一定要
亲自动手验证,同时也希望大家能在评论中指出错误的地方,让大家共同进步
官方文档网址: https://docs.python.org/2/howto/logging.html
logging 基础指南
记录日志是一些应用程序在运行中跟踪事件的一种方式。软件开发人员在程序代码中添加一些记录日志的代码来确定某些事件是否发生。一个事件可以使用一些消息文字来描述,这些文字可以包含变量信息(比如,有些数据在每一次事件发生时都可能不一样)。事件也需要根据重要性来分级,这个被称作为 事件等级或事件严重性。
什么时候使用logging
logging 模块提供了一下简便的功能来实现日志记录。它们分别是 debug(), info(), warning(), error() 和critical(). 为了区分什么事好可以使用它们, 可用看下面的表格。表格说明了在什么任务情况下使用什么工具来实现。
要执行的任务 | 最佳工具选择 |
---|---|
控制台输出普通命令行脚本或程序信息 | print() |
报告普通应用程序在运行过程中出现的事件信息 | logging.info() 或者 logging.debug()输出详细诊断信息 |
发出特定运行时事件的警告 | 当错误可以避免并且客户端程序需要修改以避免这个错误时,使用warnings.warn() 。当客户端程序无法对这个问题进行处理但是这个事件又需要记录下来的时候,可以使用logging.warning() |
报告一个运行时事件的错误 | 抛出一个异常 |
在不抛出一个异常的情况下报告一个错误的抑制信息 | logging.error(), logging.exception() 或 logging.critical() 可以应用到这些地方 |
日志记录函数是根据要跟踪的事件的严重等级来命名的。下面的表格描述了等级划分以及相应等级的适用场景
等级 | 适用场景 |
---|---|
DEBUG | 详细信息,通常在诊断问题时比较关心 |
INFO | 确认功能已经按照预期执行 |
WARNING | 表示某个非预期的事件已经发生或表示某个非预期的行为将要发生(比如:磁盘空间不足).但程序任然能按预期的执行 |
ERROR | 因为某些严重的问题,程序不能够执行一些操作 |
CRITICAL | 严重错误,意味着程序可能不能继续执行下去 |
默认等级是ERROR ,也就是说只有等于或者高于这个等级的错误才会跟踪。可以配置logging package来改变这个行为。
事件跟踪信息可以以各种不同的方式来处理。最简单的方式是直接输出到控制台。其他常用的方式是写入到磁盘文件。
一个简单的示例
下面是一个简单的示例:
import logging
logging.warning('Watch out!') # will print a message to the console
logging.info('I told you so') # will not print anything
运行该程序,将会看到下面的输出
WARNING:root:Watch out!
信息被输出到控制台。INFO信息本没有被输出是因为这里的默认等级为 WARNING 。输出的信息包含了当前等级的标识信息,和在调用方法是输入的描述信息,比如. ‘Watch out!。现在不要担心‘root’部分是什么,我们将会在后面介绍它。输出信息具有灵活的格式化能力,这个功能在后面会介绍。
输出日志到文件中
一个常用的场景是记录跟踪信息到文件中。我们下面就来介绍它。确保重新打开一个新的python解释器控制台,然后像之前一样操作下面的代码:
import logging
logging.basicConfig(filename='example.log',level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')
那么现在打开文件,将会看到下面的输出
DEBUG:root:This message should go to the log file
INFO:root:So should this
WARNING:root:And this, too
这个代码还展示了怎样设置日志等级 。因为我们把日志等级设置为了DEBUG等级,所以全部的日志信息都输出了。
加入你以命令行参数的形式设置日志等级,比如:
--log=INFO
你通过获取命令行参数的值,并将值存储在变量 loglevel 中 那么你可以这样使用它:
getattr(logging, loglevel.upper())
使用上面的代码获取你将要传给 basicConfig()函数中的level参数的值。或许你想要像下面的示例代码一样检查用户传入的参数是否有效:
# assuming loglevel is bound to the string value obtained from the
# command line argument. Convert to upper case to allow the user to
# specify --log=DEBUG or --log=debug
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, ...)
basicConfig() 这个函数必选在debug(), info() 等函数之前调用。这个是配置项,对后续的操作会产生影响。
假如你多次运行这这个程序,那边日志信息会不断的追加到example.log文件中。假如你希望每次日志输出都会覆盖掉前面输出的内容,那么你可以使用filemode 选项。按照以下方式来修改上面的程序:
logging.basicConfig(filename='example.log', filemode='w', level=logging.DEBUG)
输出和之前一样,但是不是以追加的形式写入日志文件,而是每次都覆盖掉之前的日志信息。
多个模块日志记录
假如你的程序是由多个日志文件组成的。下面的示例如何在多模块的情况下组织日志:
# myapp.py
import logging
import mylib
def main():
logging.basicConfig(filename='myapp.log', level=logging.INFO)
logging.info('Started')
mylib.do_something()
logging.info('Finished')
if __name__ == '__main__':
main()
# mylib.py
import logging
def do_something():
logging.info('Doing something')
如果你运行这个程序 那么你可以在myapp.log文件中看到如下输出
INFO:root:Started
INFO:root:Doing something
INFO:root:Finished
这些是你想要看到的是结果吧。你可以使用上面的形式组织多模块的日志记录。注意在使用这个简单的日志模式中,如果你不看日志描述信息,你将不知道一个日志是从哪一个模块中输出的。如果你想要跟踪某条日志的出处,请看下面的高级指南部分。
日志信息中输出变量
要记录变量信息,可以使用格式化字符串作为描述信息,并将变量参数附加到后面的参数列表中。比如说:
import logging
logging.warning('%s before you %s', 'Look', 'leap!')
将会显示
WARNING:root:Look before you leap!
如你所见,通过使用%-style的格式化字符串信息,变量被合并到了日志信息中。这个是为了向后兼容。logging模块提供了比如 str.format() 和string.Template 的格式化选项,而且这些都已经得到了支持,但是这已经超出了本指南的范围。
修改消息的显示格式
要修改日志消息的显示格式,你可以通过制定自己的格式来实现:
import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
logging.info('So should this')
logging.warning('And this, too')
那么将会有如下输出
DEBUG:This message should appear on the console
INFO:So should this
WARNING:And this, too
注意 前面显示的 ‘root’ 在此处已经消失了。可以查看文档 LogRecord attributes来了解跟多的相关选项。但是为了使用简单, 你值需要制定等级名称,日志消息也许还需要制定事件是什么时候发生的.
在日志中显示时间
要在日志中显示一个事件发生的时间,可以在格式化字符串中使用‘%(asctime)s’ :
import logging
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')
那么将会有如下显示:
2010-12-12 11:41:42,612 is when this event was logged.
默认是按照ISO8601的标准显示时间。假如你想要更加精细的控制时间显示格式,那么你需要在basicConfig函数中的 datefmt 参数设定值,比如:
import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.warning('is when this event was logged.')
那么将会如下显示:
12/12/2010 11:46:36 AM is when this event was logged.
datefmt 参数的格式跟time.strftime()提供的格式一致.
logging 高级指南
logging模块使用了模块化的设计,并提供了下面几个分类:loggers, handlers, filters, and formatters
- Loggers 提供应用程序直接使用的接口
- Handlers 将日志记录发送到相应的目的地
- Filters 提供一个更精细的方法决定哪个日志记录需要输出
- Formatters 指定日志输出的最终格式
日志事件信息以logrecord实例的形式在loggers, handlers, filters 和 formatters 之间传递。
通过调用logger class对象中的方法来记录日志(后面称之为logger对象)。每一个对象都有名字,名字遵循以点号作为分割符的命名空间层次格式。比如,一个以’scan’命名的logger对象,那么这个对象就是以’scan.text’,’scan.html’,’scan.pdf’等作为名称的logger对象的父对象。logger对的名称可以是任意字符串,用来表示一个消息起源于程序中的某个区域。
一个给logger命名的比较好的并且常用的方式是使用模块名称作为logger的名称。比如
logger = logging.getLogger(__name__)
那么就意味着通过logger名字就能跟踪到对应的模块。也就是说通过logger名称就能明确的知道是哪里的事件被记录下来。
logger继承关系的根节点被称为root logger,debug(), info(), warning(), error() 和critical(),这些函数就是使用的root logger。root logger名字在输出日志中输出为’root’。
当然 日志数据可以被输出到不同的地方。当前logging模块中支持的如下输出方式:文件、HTTP GET/POST 网络资源、通过smtp发送邮件、普通的socket、特定于操作系统的日志机制比如 syslog 或 windows NT event log。输出位置的功能由handler class提供。如果内置的handler class不能满足你的需求 你可以提供自己的的handler class。
默认情况下,日志输出位置没有有设置。你可以像之前的 指南那样通过调用 basicConfig() 来设置日志输出位置。当你调用debug(), info(), warning(), error(), critical(), 这些方法时,它们首先会检查是否设置了输出位置,如果没有设置,那么将控制台作为默认的输出位置。并且在将消息传输到root logger去输出之前,使用默认格式化方式对日志数据进行格式化。
默认的日志输出格式为:
severity:logger name:message
你可以通过给basicConfig()的format 参数传值来制定日志格式化方式。
日志处理流程图
下图展示了日志处理流程
loggers
logger 对象有三种功能。首先:它提供了应用程序在运行时可以调用的生成日志的接口。Second, logger objects determine which log messages to act upon based upon severity (the default filtering facility) or filter objects.(第二点实在不懂) ;第三 logger对象将相关的日志消息传到所有需要处理这些消息的处理器中。
logger对象被广泛使用的两方面的方法分别是:配置和消息发送。
下面是常用的配置日志的方法:
- Logger.setLevel() 用来设置需要处理的最低日志等级,其中debug是最低处理等级 critical 是最高处理等级。
- Logger.addHandler() 和 Logger.removeHandler() 给logger对象添加或者删除处理器。后面会介绍处理器
- Logger.addFilter() 和 Logger.removeFilter() 给logger对象添加获取删除过滤器。
你不需要在每个你创建的logger上调用这些方法。
当配置好logger对象以后,调用下面的方法就可以生成日志了:
- Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), Logger.critical() 调用这些方法就可以生成对应等级的日志信息。这些日志信息其实就是格式化的日志字符串,可能包含字符替换语法比如%d、%d、%s等等… 其他的参数就是对应于前面格式化字符串中的要替换的内容。关于**kwargs类型的参数,这些方法只在乎exc_info 参数 并且使用该参数来识别是否要记录异常信息日志。
- Logger.exception()产生类似于Logger.error()的日志。不同之处在于Logger.exception()只记录堆栈跟踪信息,并且该调用只在异常处理时使用。
- Logger.log() 该方法需要明确传入一个日志level的参数。使用这个方法相对于上面列出的方法显得有点啰嗦,但是定制性更好
getLogger()方法返回一个指定名称的logger对象,如果参数中没有指定name值 则默认返回root对象。logger的名称具有以点号作为分隔符的层次关系。使用同样的名称作为参数调用getLogger()方法会返回同一个logger对象的引用。底层节点的logger对象是上层节点logger对象的子孙。比如有个名字为’foo’的logger对象,那么名称为’foo.bar’ 或 ‘foo.bar.baz’或着’foo.bam’都是foo logger对象的子孙。
logger对象有有效等级的概念。如果一个logger没有设置等级,那么他的父对象的等级就作为该对象的有效等级。如果父对象也没有设置有效等级,那么就再次检查该父对象的父对象,以此类推,所有的祖先对象都会被搜索直到找的一个等级设置。root logger对象总是会被显示或者隐式的设置等级。当需要确定一个事件是否需要被处理时,那么有效等级就是被用来确定一个事件是否需要被处理器处理。
子logger对象会将消息传播的父logger对象的处理器进行处理,因此,不需要给应用程序中所有要使用的logger对象进行配置和设置处理器。因此 只要对顶层的logger对象进行配置和设置处理器就可以了。当然你也可以通过设置logger对象的propagate属性来关闭日志传播的功能。
Handlers
处理器对象负责将相应的日志信息发送到对应的输出位置。logger对象可以通过addHandler()添加任意多个日志处理器。比如在某个情况下,应用程序可能想要将所有的日志发送到日志文件中, 将所有的错误信息或者更高级的信息发送到标准输出,并且将所有的严重错误信息通过邮件发送到邮箱中。在这个场景中就需要三个处理器来实现相应的处理过程。
在标准库中包含了许多类型的处理器,在这个指南中主要使用StreamHandler 和FileHandler 作为例子来使用。
在处理器对象中,只有少数几个方法需要开发者关注的。当开发者使用内置的处理器对象时只需要关注下面几个配置方法。
- setLevel()用来制定那种最低等级的日志消息需要发送到输出点的。为什么这有两个setLevel()方法需要使用?在logger对象中使用setLevel()方法用来确定那种等级的日志需要发送到处理器对象,而对象处理器中的setLevel()方法用来确定那个等级的日志会被处理。
- setFormatter() 这个用来设置该处理器要使用的格式工具
- addFilter() 和 removeFilter() 分别用来添加和删除过滤器
应用程序不应该实例化并且使用 Handler 对象。相反 Handler 定义了所有处理器类需要是实现的类接口。
Formatters
fomatter对象用来配置最终的日志数据的结构 和 内容。不同于logging.Handler类, 应用程序可以实例化该类,尽管,当应用程序需要定制的行为时需要使用之类来继承该类。该类的构造函数接受两个可选参数,分别是消息格式化参数和日期格式化参数。
如果没有制定消息格式化字符串,那么使用默认字符串,如果没有日期格式化字符串,则使用默认格式,默认格式如下:
%Y-%m-%d %H:%M:%S
下面的消息格式化参数将会记录对人友好的日期字符串、消息内容、和消息内容。
'%(asctime)s - %(levelname)s - %(message)s'