目录
1.日志的作用
项目部署上线后,开发者还需要监测程序运行的状态,但是又不能将运行的状态信息直接输出到控制台上,所以开发者可以把这些程序的运行状态信息根据不同的严重级别记录到日志文件中。这样的话当程序运行出现故障时,开发者可以查看日志文件中记录的错误信息快速定位到程序中存在的bug的位置,及时做出修复。
2.日志级别
logging模块把日志分为5个级别,详细描述信息如下(按照级别递增排序):
括号为级别所对应的数值
- DEBUG(10)
- INFO(20)
- WARNING(30)
- ERROR(40)
- CRITICAL(50)
使用logging模块时需要设置记录日志的级别,程序运行起来后logging会记录大于等于所设置的级别的日志记录。比如设置的日志级别是INFO,则程序运行时INFO、WARNING、ERROR、CRITICAL四个级别的日志记录都会被记录下来。
日志级别(level) | 描述信息 |
---|---|
DEBUG | 详细信息,一般用于调试程序,诊断程序中的问题 |
INFO | 记录关键节点信息,证明程序是按照开发者预期运行的 |
WARNING | 预料之外的事件发生记录的信息,但是此时程序还是可以正常运行的,比如:磁盘空间不足 |
ERROR | 由于严重的问题导致程序的一些功能已经不能正常运行了 |
CRITICAL | 发生严重错误导致程序崩溃 |
开发应用程序或部署开发环境时,可以使用DEBUG或INFO级别的日志获取尽可能详细的日志记录来进行开发或部署调试;
应用程序上线或部署生产环境时,应该使用WARNING或ERROR或CRITICAL级别的日志来降低机器的I/O压力和提高获取错误日志记录的效率。
日志级别的指定通常都是在应用程序的配置文件中进行指定的。指定日志记录器的日志级别,只有级别大于或等于该指定日志级别的日志记录才会被输出,小于该等级的日志记录将会被丢弃。
3.logging模块定义的格式字符串字段
字段/属性名称 | 使用格式 | 描述 |
---|---|---|
asctime | %(asctime)s | 日志事件发生的时间,如:2003-07-08 16:49:45,896 |
created | %(created)f | 日志事件发生的时间-时间戳,是当时调用time.time()函数返回的值 |
relativeCreated | %(relativeCreated)d | 日志事件发生的时间相对于logging模块加载时间的相对毫秒数 |
msecs | %(msecs)d | 日志事件发生时间的毫秒部分 |
levelname | %(levelname)s | 日志记录的文字形式的日志级别(‘DEBUG’、'INFO'、'WARNING'、'ERROR'、'CRITICAL') |
levelno | %(levelno)s | 日志记录的数字形式的日志级别(10、20、30、40、50) |
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 | 调用日志记录函数的函数名 |
process | %(process)d | 进程ID |
processName | %(processName)s | 进程名称,Python 3.1新增 |
thread | %(thread)d | 线程ID |
threadName | %(threadName)s | 线程名称 |
4.logging模块的使用方式
logging模块提供了两种记录日志信息的方式:
- 第一种方式是使用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库的四大组件如下:
组件 | 说明 |
---|---|
loggers | 提供应用程序代码直接使用的接口 |
handlers | 用于将日志记录发送到指定的目的位置 |
filters | 提供更细粒度的日志过滤功能,用于决定哪些日志记录将会被输出(其它的日志记录将会被忽略) |
formatters | 用于控制日志信息的最终输出格式 |
这些组件之间的关系:
- 日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置,如:文件、sys.stdout、网络等;
- 不同的处理器(handler)可以将日志输出到不同的位置;
- 日志器(logger)可以设置多个处理器(handler)将同一条日志记录输出到不同的位置;
- 每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志;
- 每个处理器(handler)都可以设置自己的格式器(formatter)实现同一条日志以不同的格式输出到不同的地方;
总结:日志器(logger)是入口,真正干活儿的是处理器(handler),处理器(handler)还可以通过过滤器(filter)和格式器(formatter)对要输出的日志内容做过滤和格式化等处理操作。
5.logging模块基本使用
logging 使用非常简单,使用 basicConfig() 方法就能满足基本的使用需要,如果方法没有传入参数,会根据默认的配置创建Logger 对象,默认的日志级别被设置为 WARNING,默认的日志输出格式如上图,该函数可选的参数如下:
参数名称 | 参数描述 |
---|---|
filename | 保存日志记录的文件名 |
filemode | 文件模式,'r'、'w'、'a' |
format | 日志输出的格式 |
datefat | 日志附带日期时间的格式 |
style | 格式占位符,默认为 "%" 和 “{}” |
level | 设置日志输出级别 |
stream | 定义输出流,用来初始化StreamHandler对象,不能和filename参数一起使用,否则会抛出ValueError异常 |
handles | 定义处理器,用来创建Handler对象,不能和filename、stream参数一起使用,否则会抛出ValueError异常 |
logging.basicConfig()函数使用默认参数打印日志记录到控制台示例如下:
import logging
logging.basicConfig()
logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')
logging.basicConfig()函数的level 默认是WARNING,所以只会打印出严重级别大于等于WARNING的日志记录,打印日志记录如下:
WARNING:root:This is a warning message
ERROR:root:This is an error message
CRITICAL:root:This is a critical message
上面输出结果中每行日志记录的各个字段含义分别是:
日志级别:日志器名称:日志内容
logging.basicConfig()函数设置filename和level参数后输出日志记录到log文件示例如下:
import logging
logging.basicConfig(filename="test.log", filemode="w", format="%(asctime)s %(name)s:%(levelname)s:%(message)s", datefmt="%d-%m-%Y %H:%M:%S", level=logging.DEBUG)
logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')
生成日志文件 test.log ,日志记录内容如下:
29-06-2021 20:26:30 root:DEBUG:This is a debug message 29-06-2021 20:26:30 root:INFO:This is an info message 29-06-2021 20:26:30 root:WARNING:This is a warning message 29-06-2021 20:26:30 root:ERROR:This is an error message 29-06-2021 20:26:30 root:CRITICAL:This is a critical message
但是当发生异常时。直接使用无参数的debug()、info()、warning()、error()、critical()、方法并不能记录异常信息,需要设置exc_info参数为True才可以,或者使用exception()方法,还可以使用log()方法,但要设置日志级别和exc_info参数,三种方法示例如下:
import logging
logging.basicConfig(filename="test.log", filemode="w", format="%(asctime)s %(name)s:%(levelname)s:%(message)s",
datefmt="%d-%M-%Y %H:%M:%S", level=logging.DEBUG)
a = 5
b = 0
try:
c = a / b
except Exception as e:
# 下面三种方式三选一,推荐使用第一种
logging.exception(e)
logging.error(e, exc_info=True)
logging.log(level=logging.DEBUG, msg=e, exc_info=True)
输出到test.log文件中的错误日志记录内容如下:
29-49-2021 20:49:21 root:DEBUG:division by zero Traceback (most recent call last): File "F:/proxyPool/3.py", line 7, in <module> c = a / b ZeroDivisionError: division by zero
在上面的基础上,再来设置一下日志格式和日期/时间格式
import logging
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
DATE_FORMAT = "%m/%d/%Y %H:%M:%S %p"
logging.basicConfig(filename='test.log', level=logging.DEBUG, format=LOG_FORMAT, datefmt=DATE_FORMAT)
logging.debug("This is a debug log.")
logging.info("This is a info log.")
logging.warning("This is a warning log.")
logging.error("This is a error log.")
logging.critical("This is a critical log.")
test.log日志文件中看到如下输出内容:
06/29/2021 21:19:30 PM - DEBUG - This is a debug log. 06/29/2021 21:19:30 PM - INFO - This is a info log. 06/29/2021 21:19:30 PM - WARNING - This is a warning log. 06/29/2021 21:19:30 PM - ERROR - This is a error log. 06/29/2021 21:19:30 PM - CRITICAL - This is a critical log.
6. logging日志模块相关类及其常用方法介绍
下面介绍与logging四大组件相关的类:Logger, Handler, Filter, Formatter
Logger类
Logger对象有3个任务要做:
- 向应用程序代码暴露几个方法,使应用程序可以在运行时记录日志消息;
- 基于日志严重等级(默认的过滤设施)或filter对象来决定要对哪些日志进行后续处理;
- 将日志消息传送给所有感兴趣的日志handlers;
Logger对象常用的方法分为两类:配置方法 和 消息发送方法
最常用的配置方法如下:
方法 | 描述 |
---|---|
Logger.setLevel() | 设置日志器将会处理的日志记录的最低严重级别 |
Logger.addHandler()和Logger.removeHandler() | 为该logger对象添加和移除一个handler对象 |
Logger.addFilter()和Logger.removeFilter() | 为该logger()对象添加和移除一个filter对象 |
logger对象配置完成后,可以使用下面的方法来创建日志记录:
方法 | 描述 |
---|---|
Logger.debug()、Logger.info()、Logger.warning、Logger.error()、Logger.critical() | 创建一个与他们的方法名对应严重级别的日志记录 |
Logger.exception() | 创建一个类似于Logger.error()的日志记录 |
Logger.log | 需要传入一个明确的日志level参数来创建一个日志记录 |
使用logging.getLogger()来获取Logger对象:logging.getLogger()方法有一个可选参数name,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值为’root’。若以相同的name参数值多次调用getLogger()方法,将会返回指向同一个logger对象的引用。
Handler类
Handler对象的作用是(基于日志消息的level)将消息分发到handler指定的位置(文件、网络、邮件等)。Logger对象可以通过addHandler()方法为自己添加0个或者更多个handler对象。比如,一个应用程序可能想要实现以下几个日志需求:
- 把所有日志都发送到一个日志文件中;
- 把所有严重级别大于等于error的日志发送到stdout(标准输出);
- 把所有严重级别为critical的日志发送到一个email邮件地址;
这种场景就需要3个不同的handler,每个handler负责发送一个特定严重级别的日志记录到一个特定的位置。
handler配置方法如下:
方法 | 描述 |
---|---|
Handler.setLevel() | 设置handler将会处理的日志记录的最低严重级别 |
Handler.setFormatter() | 为handler设置一个格式器对象 |
Handler.addFilter()和Handler.removeFilter() | 为handler添加和删除一个过滤器对象 |
常用的Handler类如下:
Handler | 描述 |
---|---|
logging.StreamHandler | 把日志记录输出到Stream,如std.out,std.err,或任何file-like对象 |
logging.FileHandler | 把日志记录输出到磁盘文件,默认情况下文件大小会无限增长 |
logging.handlers.RotatingFileHandler | 把日志记录输出到磁盘文件,并支持日志文件按大小切割 |
logging.handlers.TimedRotatingFileHandler | 把日志记录输出到磁盘文件,并支持日志文件按时间切割 |
logging.handlers.HTTPHandler | 把日志记录以GET或POST的请求方式发送给一个HTTP服务器 |
logging.handlers.SMTPHandler | 把日志记录发送给一个指定的email地址 |
logging.NullHandler | 该Handler实例会忽略error messages,通常被想使用logging的library开发者使用来避免 'No handlers could be found for logger XXX' 信息的出现 |
常用的handler是TimedRotatingFileHandler( filename [, when [, interval [, backupCount] ] ] )根据时间来切割日志文件,参数解释如下:
filename 是输出日志的文件名
when 是一个字符串,定义了日志切分的间隔时间单位,这是一个枚举类,可选参数如下:
"S":Second 秒
"M":Minutes 分钟
"H":Hour 小时
"D":Days 天
"W":Week day(0 = Monday)
"midnight":Roll over at midnight(凌晨进行切割)
interval 是间隔时间单位的个数,指等待多少个 when 的时间后 Logger 会自动重建日志文件继续进行日志记录
这里需要注意的一点是,如果创建的文件和已有文件存在重名的情况,则会对历史的文件进行覆盖操作,所以使用好 suffix 避免文件名称重复,如果修改了suffix也同时要修改extMatch(正则匹配规则)
backupCount 是保留日志的文件个数
默认的参数是0,这种设置下是不会自动删除文件的。如果设置为 N(正整数),则会在创建新的日志文件时会检查日志文件个数是否到达 N,达到了的话就会从最先创建的开始删除,从而达到维持日志文件个数为 N 的目标
Formater类
Formater对象用于配置日志信息的最终顺序、结构和内容。与logging.Handler基类不同的是,应用代码可以直接实例化Formatter类。
Formatter类的构造方法定义如下:
logging.Formatter(fmt=None, datefmt=None, style='%')
该构造方法接收以下3个可选参数:
- fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值
- datefmt:指定日期格式字符串,如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S"
- style:Python 3.2新增的参数,可取值为 '%'、'{}' 和 '$',如果不指定该参数则默认使用 '%'
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数量等。
7.使用logging四大组件记录日志
现在,我们对logging模块的重要组件及整个日志流处理流程都应该有了一个比较全面的了解,下面我们来看一个例子。
- 需求
- 要求将所有级别的所有日志都写入磁盘文件中;
- all.log文件中记录所有的日志信息,日志格式为:日期和时间 - 日志级别 - 日志信息;
- error.log文件中单独记录error及以上级别的日志信息,日志格式为:日期和时间 - 日志级别 - 文件名[:行号] - 日志信息;
- 要求all.log在每天凌晨进行日志切割;
- 分析
- 要记录所有级别的日志,因此日志器的有效level需要设置为最低级别–DEBUG;
- 日志需要被发送到两个不同的目的地,因此需要为日志器设置两个handler;另外,两个目的地都是磁盘文件,因此这两个handler都是与FileHandler相关的;
- all.log要求按照时间进行日志切割,因此他需要用logging.handlers.TimedRotatingFileHandler; 而error.log没有要求日志切割,因此可以使用FileHandler;
- 两个日志文件的格式不同,因此需要对这两个handler分别设置格式器;
-
代码实现
import logging
import logging.handlers
logger = logging.getLogger('mylogger')
logger.setLevel(logging.DEBUG)
rf_handler = logging.handlers.TimedRotatingFileHandler(filename='all.log', when='midnight', interval=1, backupCount=7)
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')
all.log文件输出
2021-06-30 10:25:27,531 - DEBUG - debug message 2021-06-30 10:25:27,532 - INFO - info message 2021-06-30 10:25:27,532 - WARNING - warning message 2021-06-30 10:25:27,532 - ERROR - error message 2021-06-30 10:25:27,532 - CRITICAL - critical message
error.log文件输出
2021-06-30 10:25:27,532 - ERROR - 3.py[:20] - error message 2021-06-30 10:25:27,532 - CRITICAL - 3.py[:21] - critical message
了解更多关于python和爬虫的知识欢迎关注微信公众号:丸子打豆豆