python-日志模块logging
https://www.cnblogs.com/yyds/p/6901864.html
https://www.cnblogs.com/miki-peng/p/13416944.html
一 logging模块介绍
1 日志介绍
日志就是用于记录系统运行时的信息,对一个事件的记录。
日志的基本用途:
-
记录程序运行过程中的错误,方便跟踪定位问题,减少调试和维护成本;
-
通过日志能还原整个程序的执行过程,能了解程序的整体状态;
-
对用户行为分析和数据统计,知晓信息来自于哪个模块;
-
在设计测试框架的时候,也可以通过设计日志来记录框架的整个测试流程;
2 logging模块简介
最初运行代码时都是将日志直接输出到控制台,而实际项目中常常需要把日志存储到文件,便于查阅,如运行时间、描述信息以及错误或者异常发生时候的特定上下文信息。
logging模块是Python的一个标准库模块,由标准库模块提供日志记录API的关键好处是所有Python模块都可以使用这个日志记录功能。logging的优势就在于可以控制日志的级别,把不需要的信息进行过滤,且可以决定它输出到什么地方、如何输出,还可以通过控制等级把特定等级的信息输出到特定的位置等。
2.1 日志等级
logging模块默认定义了以下几个日志等级,logging默认收集的日志是WARNING
以上等级的。相关等级说明如下:
-
DEBUG:输出详细的运行信息,主要用于调试,追踪问题时使用
-
INFO:输出正常的运行的信息,一切按预期进行的情况
-
WARNING:一些已经或即将会发生的意外,如警告:内存空间不足。程序仍按预期进行
-
ERROR:由于某些问题,程序的某些功能已经不能正常执行
-
CRITICAL:一个严重的错误,表明程序本身无法继续运行
日志等级是从上到下依次升高的,即:DEBUG < INFO < WARNING < ERROR < CRITICAL,而日志的信息量是依次减少的。这些等级的日志中低包含高,比如INFO,会收集INFO及以上等级的日志,不会收集DEBUG等级的日志。
开发应用程序或部署开发环境时:可以使用DEBUG或INFO级别的日志获取尽可能详细的日志信息来进行开发或部署调试;
应用上线或部署生产环境时:应该使用WARNING或ERROR或CRITICAL级别的日志来降低机器的I/O压力和提高获取错误日志信息的效率。日志级别的指定通常都是在应用程序的配置文件中进行指定的。
2.2 logging模块的使用方式
logging模块提供了两种记录日志的方式:
第一种方式是使用logging提供的模块级别的函数;
第二种方式是使用logging日志系统的四大组件。
logging模块定义的模块级别的常用函数
logging.debug(msg,*args, **kwargs) # 创建一条严重级别为DEBUG的日志记录 logging.info(msg,*args, **kwargs) # 创建一条严重级别为INFO的日志记录 logging.warning(msg,*args, **kwargs) # 创建一条严重级别为WARNING的日志记录 logging.error(msg,*args, **kwargs) # 创建一条严重级别为ERROR的日志记录 logging.critical(msg,*args, **kwargs) # 创建一条严重级别为CRITICAL的日志记录 logging.log(level,*args, **kwargs) # 创建一条严重级别为level的日志记录 logging.basicConfig(**kwargs) # 对root logger进行一次性配置
其中logging.basicConfig(**kwargs)
函数用于指定“要记录的日志级别”、“日志格式”、“日志输出位置”、“日志文件的打开模式”等信息,其他几个都是用于记录各个级别日志的函数。
logging模块的四大组件
-
记录器:Logger,可供程序直接调用的接口,app通过调用提供的api来记录日志
-
处理器:Handler,决定将日志记录分配至正确的目的地
-
过滤器:Filter,对日志信息进行过滤,提供更细粒度的日志是否输出的判断,用于确定要输出的日志记录
-
格式器:Formatter,指定最终输出中日志记录的样式
说明:logging模块提供的模块级别的函数是通过这几个组件的相关实现类来记录日志的,只是在创建这些类的实例时设置了一些默认值。
二 使用logging提供的模块级别的函数记录日志
1 简单的日志输出
先来试着分别输出一条不同日志级别的日志记录:
import logging logging.debug("This is a debuglog.") logging.info("This is a infolog.") logging.warning("This is a warninglog.") logging.error("This is a errorlog.") logging.critical("This is a criticallog.")
也可以这样写:
logging.log(logging.DEBUG, "This is adebug log.") logging.log(logging.INFO, "This is ainfo log.") logging.log(logging.WARNING, "This isa warning log.") logging.log(logging.ERROR, "This is aerror log.") logging.log(logging.CRITICAL, "This isa critical log.")
输出结果:
WARNING:root:This is a warning log. ERROR:root:This is a error log. CRITICAL:root:This is a critical log.
疑问解答
1、为什么前面两条日志没有被打印出来?
这是因为logging模块提供的日志记录函数所使用的日志器设置的日志级别是WARNING
。
2、打印出来的日志信息中各字段表示什么意思?为什么会这样输出?
上面输出的日志记录的各个字段含义分别是:日志级别:日志器名称:日志内容。
这样输出的原因:logging模块提供的日志记录函数所使用的日志器设置的日志格式format
默认是BASIC_FORMAT
,其值为:"%(levelname)s:%(name)s:%(message)s"
。
注意:日志器(Logger)是有层级关系的,上面调用的logging模块级别的函数所使用的日志器是RootLogger类的实例,其名称为'root',它是处于日志器层级关系最顶层的日志器,且该实例是以单例模式存在的。
3、如果将日志记录输出到文件中,而不是打印到控制台?
因为在logging模块提供的日志记录函数所使用的日志器设置的处理器所指定的日志输出位置默认为:sys.stderr
。
4、怎么修改这些默认设置?
在我们调用上面这些日志记录函数之前,手动调用一下basicConfig()
方法,把我们想设置的内容以参数的形式传递进去就可以了。
2 logging.basicConfig()说明
该方法用于为logging日志系统做一些基本配置,方法定义为:logging.basicConfig(**kwargs)
logging.basicConfig()函数是一个一次性的简单配置工具使,也就是说只有在第一次调用该函数时会起作用,后续再次调用该函数时完全不会产生任何操作的,多次调用的设置并不是累加操作。
该函数可接收的关键字常用参数
-
filename:指定日志输出目标文件的文件名,指定该项后日志就不会被输出到控制台
-
filemode:指定日志文件的打开模式,默认为'a'。注意:该选项要在filename指定时才有效
-
format:指定日志格式字符串,指定日志输出时所包含的字段信息以及它们的顺序。logging模块定义的格式字段下面会列出。
-
datefmt:指定日期/时间格式。注意:该选项要在format中包含时间字段%(asctime)s时才有效
-
level:指定日志器的日志级别
-
stream:指定日志输出目标stream,如sys.stdout、sys.stderr以及网络stream。注意:stream和filename不能同时提供,否则会引发ValueError异常
logging模块中定义的常用格式字段
以下格式字段是用于format格式字符串中的常用字段:
字段/属性名称:使用格式:描述
-
asctime:%(asctime)s:日志事件发生的时间--人类可读时间,如:2003-07-08 16:49:45,896
-
created:%(created)f:日志事件发生的时间--时间戳,就是当时调用time.time()函数返回的值
-
msecs:%(msecs)d:日志事件发生事件的毫秒部分
-
levelname:%(levelname)s:该日志记录的文字形式的日志级别('DEBUG','INFO', 'WARNING', 'ERROR', 'CRITICAL')
-
name:%(name)s:所使用的日志器名称,默认是'root',因为默认使用的是 rootLogger
-
message:%(message)s:日志记录的文本内容,通过msg % args计算得到的
-
pathname:%(pathname)s:调用日志记录函数的源码文件的全路径
-
filename:%(filename)s:pathname的文件名部分,包含文件后缀
-
module:%(module)s:filename的名称部分,不包含后缀
-
lineno:%(lineno)d:调用日志记录函数的源代码所在的行号
-
funcName:%(funcName)s:调用日志记录函数的函数名
3 经过配置的日志输出
LOG_FORMAT = "%(asctime)s -%(levelname)s - %(message)s" # 2、datefmt指定日期/时间格式,注意该选项要在format中包含时间字段%(asctime)s时才有效 DATE_FORMAT = "%m/%d/%Y %H:%M:%S%p" # 1、level配置日志器的日志级别; filename和format配置日志输出目标文件和日志格式 logging.basicConfig(filename='my.log',level=logging.DEBUG, format=LOG_FORMAT, datefmt=DATE_FORMAT) logging.debug("This is a debuglog.") logging.info("This is a infolog.") logging.warning("This is a warninglog.") logging.error("This is a errorlog.") logging.critical("This is a criticallog.")
此时控制台没有输出日志内容,但在python文件的相同目录下会生成一个名为'my.log'的日志文件,该文件中的内容为:
05/08/2017 14:29:04 PM - DEBUG - This is adebug log.
05/08/2017 14:29:04 PM - INFO - This is ainfo log.
05/08/2017 14:29:04 PM - WARNING - This isa warning log.
05/08/2017 14:29:04 PM - ERROR - This is aerror log.
05/08/2017 14:29:04 PM - CRITICAL - This isa critical log.
三 logging模块日志流处理流程
1 logging模块的四大组件
在学习logging模块的高级用法之前,有必要全面了解下logging模块所包含的重要组件以及其工作流程。有助于我们更好的理解所写的代码(将会触发什么样的操作)。
logging模块的四大组件:日志器Logger、处理器Handler、过滤器Filter、格式器Formatter。
logging模块是通过这些组件来完成日志处理的,logging模块级别的函数也是通过这些组件对应的类来实现的。
四大组件之间的关系:
-
日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置,如:文件、sys.stdout、网络等;
-
不同的处理器(handler)可以将日志输出到不同的位置;
-
日志器(logger)可以设置多个处理器(handler)将同一条日志记录输出到不同的位置;
-
每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志;
-
每个处理器(handler)都可以设置自己的格式器(formatter)实现同一条日志以不同的格式输出到不同的地方。
总结:日志器(logger)是入口,真正干活儿的是处理器(handler),处理器(handler)还可以通过过滤器(filter)和格式器(formatter)对要输出的日志内容做过滤和格式化等处理操作。
2 logging模块的相关类和常用方法
2.1 Logger类
Logger对象的3个任务:
-
向应用程序代码暴露几个方法,使应用程序可以在运行时记录日志消息;
-
基于日志严重等级(默认的过滤设施)或filter对象来决定要对哪些日志进行后续处理;
-
将日志消息传送给所有感兴趣的日志handlers。
Logger对象最常用的方法分为两类:配置方法和消息发送方法
1、最常用的配置方法如下:
-
Logger.setLevel():设置日志器将会处理的日志消息的最低严重级别
-
Logger.addHandler()和Logger.removeHandler():为该logger对象添加和移除一个handler对象
-
Logger.addFilter()和Logger.removeFilter():为该logger对象添加和移除一个filter对象
2、logger对象配置完成后,可使用下面的方法来创建日志记录:
-
Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), Logger.critical():创建一个与它们的方法名对应等级的日志记录
-
Logger.exception():创建一个类似于Logger.error()的日志消息
-
Logger.log():需要获取一个明确的日志level参数来创建一个日志记录
Logger.exception()与Logger.error()的区别:Logger.exception()将会输出堆栈追踪信息,通常只是在一个exception handler中调用该方法。
Logger.log()与Logger.debug()、Logger.info()等方法相比,需要多传一个level参数
3、Logger对象的说明
得到一个Logger对象的两种方式:第一种是通过Logger类的实例化方法创建一个Logger类的实例,但通常都是用第二种-logging.getLogger()方法。
logging.getLogger()方法有一个可选参数name,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值为'root'。若以相同的name参数值多次调用getLogger()方法,将会返回指向同一个logger对象的引用。
4、logger的层级结构与有效等级的说明:
logger的名称是一个以'.'分割的层级结构,每个'.'后面的logger都是'.'前面的logger的children。
logger有一个"有效等级(effective level)"的概念。如果一个logger上没有被明确设置一个level,那么该logger就是使用它parent以上的level,直到找到个一个明确设置了level的祖先为止。当决定是否去处理一个已发生的事件时,logger的有效等级将会被用来决定是否将该事件传递给该logger的handlers进行处理。
child loggers在完成对日志消息的处理后,默认会将日志消息传递给与它们的祖先loggers相关的handlers。因此,不必为一个程序中所使用的所有loggers都定义和配置一个handlers,只需要为一个顶层的logger配置handlers,然后按需创建child loggers即可。可以通过将一个logger的propagate属性设置为False来关闭这种传递机制。
2.2 Handler类
Handler对象的作用是(基于日志消息的level)将消息分发到handler指定的位置(文件、网络、邮件等)。Logger对象可以通过addHandler()方法为自己添加0个或者更多个handler对象。比如,实现以下的日志需求:
-
把所有日志都发送到一个日志文件中;
-
把所有严重级别大于等于error的日志发送到stdout(标准输出);
这种场景就需要2个不同的handlers,每个handler复杂发送一个特定严重级别的日志到一个特定的位置。对于使用内建handler对象的应用开发人员来说,似乎唯一相关的handler方法就是下面这几个配置方法:
-
Handler.setLevel():设置handler将会处理的日志消息的最低严重级别
-
Handler.setFormatter():为handler设置一个格式器对象
-
Handler.addFilter()和Handler.removeFilter():为handler添加和删除一个过滤器对象
注意:应用程序代码不应该直接实例化和使用Handler实例。因为Handler是一个基类,它只定义了素有handlers都应该有的接口,同时提供了一些子类可以直接使用或覆盖的默认行为。下面是一些常用的Handler:
-
logging.StreamHandler:将日志消息发送到Stream,如std.out, std.err或任何file-like对象。
-
logging.FileHandler:将日志消息发送到磁盘文件,默认情况下文件大小会无限增长
-
logging.handlers.RotatingFileHandler:将日志消息发送到磁盘文件,并支持日志文件按大小切割
-
logging.hanlders.TimedRotatingFileHandler:将日志消息发送到磁盘文件,并支持日志文件按时间切割
-
logging.handlers.HTTPHandler:将日志消息以GET或POST的方式发送给一个HTTP服务器
-
logging.handlers.SMTPHandler:将日志消息发送给一个指定的email地址
2.3 Formater类
Formater对象用于配置日志信息的最终顺序、结构和内容。与logging.Handler基类不同的是,应用代码可以直接实例化Formatter类。如果你的应用程序需要一些特殊的处理行为,也可以实现一个Formatter的子类来完成。
Formatter类的构造方法的定义:logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
可见,该构造方法接收3个可选参数:
-
fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值
-
datefmt:指定日期格式字符串,如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S"
-
style:Python 3.2新增的参数,可取值为 '%', '{'和 '$',如果不指定该参数则默认使用'%'
2.4 Filter类
Filter可以被Handler和Logger用来做比level更细粒度的、更复杂的过滤功能。Filter是一个过滤器基类,它只允许某个logger层级下的日志事件通过过滤。该类定义如下:
class logging.Filter(name='')
filter(record)
如,一个filter实例化时传递的name参数值为'A.B',那么该filter实例将只允许名称为类似如下规则的loggers产生的日志记录通过过滤:'A.B','A.B,C','A.B.C.D','A.B.D',而名称为'A.BB', 'B.A.B'的loggers产生的日志则会被过滤掉。如果name的值为空字符串,则允许所有的日志事件通过过滤。
filter方法用于具体控制传递的record记录是否能通过过滤,如果该方法返回值为0表示不能通过过滤,返回值为非0表示可以通过过滤。
说明:如果有需要,也可以在filter(record)方法内部改变该record,比如添加、删除或修改一些属性。我们还可以通过filter做一些统计工作,比如可以计算下被一个特殊的logger或handler所处理的record数量等。
3 logging日志流处理流程
-
日志记录函数调用,如:logger.info(...),logger.debug(...)等;
-
判断要记录的日志级别是否满足日志器设置的级别要求;
-
根据日志记录函数调用时掺入的参数,创建一个日志记录(LogRecord类)对象;
-
判断日志记录器上设置的过滤器是否拒绝这条日志记录,再将日志记录分别交给该日志器上添加的各个处理器;
-
判断要记录的日志级别是否满足处理器设置的级别要求;
-
判断该处理器上设置的过滤器是否拒绝这条日志记录;
-
到这一步,说明这条日志记录经过了层层关卡允许被输出了,此时当前处理器会根据自身被设置的格式器将这条日志记录进行格式化,最后将格式化后的结果输出到指定位置;
-
如果日志器被设置了多个处理器的话,上面的第5-8步会执行多次;
-
这里是完整流程的最后一步:判断该日志器输出的日志消息是否需要传递给上一级logger(之前提到过,日志器是有层级关系的)的处理器。如果propagate属性值为1则表示日志消息将会被输出到处理器指定的位置,同时还会被传递给parent日志器的handlers进行处理,直到当前日志器的propagate属性为0停止,如果propagate值为0则表示不向parent日志器的handlers传递该消息,到此结束。<u>注意: </u>如果propagate值为1,那么日志消息会直接传递交给上一级logger的handlers进行处理,此时上一级logger的日志等级并不会对该日志消息进行等级过滤。
总结:一条日志信息要想被最终输出需要依次经过以下几次过滤:
-
日志器等级过滤;
-
日志器的过滤器过滤;
-
日志器的处理器等级过滤;
-
日志器的处理器的过滤器过滤;
四 使用logging四大组件记录日志
-
现在有以下几个日志记录的需求:
-
要求将所有级别的所有日志都写入磁盘文件中
-
all.log文件中记录所有的日志信息,日志格式为:日期和时间 - 日志级别 - 日志信息
-
error.log文件中单独记录error及以上级别的日志信息,日志格式为:日期和时间 - 日志级别 - 文件名[:行号] - 日志信息
-
要求all.log在每天凌晨进行日志切割
-
代码实现
import logging import logging.handlers import datetime logger = logging.getLogger('mylogger') logger.setLevel(logging.DEBUG) rf_handler = logging.handlers.TimedRotatingFileHandler('all.log', when='midnight', interval=1, backupCount=7, atTime=datetime.time(0, 0, 0, 0)) rf_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) f_handler = logging.FileHandler('error.log') f_handler.setLevel(logging.ERROR) f_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(filename)s[:%(lineno)d] - %(message)s")) logger.addHandler(rf_handler) logger.addHandler(f_handler) logger.debug('debug message') logger.info('info message') logger.warning('warning message') logger.error('error message') logger.critical('critical message')