官方文档: https://docs.python.org/zh-cn/3.8/library/logging.handlers.html#
一.logging构成
通常我们使用logging模块来输出日志,这是一个内置模块,不需要我们额外安装。
简单介绍下他的构成。
1.总体简析
通常由logger–日志器, handler–处理器, filter–过滤器, formatter–格式器组成(这其中,过滤器和格式器也可能不使用)。
过滤器的使用场景如:涉及到记录用户密码的日志,出于保密和安全考虑,不显示密码信息(或其他自定义内容)。

示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
stream_handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
logger.info("这是一条INFO级别的日志")
|
运行一下

2.日志级别
日志级别 |
说明 |
补充说明 |
NOTEST |
当启用unittest而没有的时候 |
|
DEBUG |
最详细的日志信息,典型应用场景是 问题诊断 |
|
INFO |
信息详细程度仅次于DEBUG,通常只记录关键节点信息,用于确认一切都是按照我们预期的那样进行工作 |
|
WARAING |
当某些不期望的事情发生时记录的信息(如,磁盘可用空间较低),但是此时应用程序还是正常运行的 |
|
ERROR |
由于一个更严重的问题导致某些功能不能正常运行时记录的信息 |
例如请求进来执行方法发生了exception,导致接口无法正常返回500;例如未对参数做校验,本应大于0,结果传了负数进来导致计算报错;例如访问一个不存在的接口,返回404给对方。 |
CRITICAL |
当发生严重错误,导致应用程序不能继续运行时记录的信息 |
|
3.logger与handler的level
logger可以定义level和formatter,而handler也可以定义level和formatter。但是假如你两个都设置了,将以logger的为主。
比如,logger.setLevel(logging.ERROR) , hanlder.setLevel(logging.INFO),因为logger享有优先级,因此你不看到这个handler下有INFO级别的日志被输出。
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import logging
logger = logging.getLogger() logger.setLevel(logging.ERROR)
stream_handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') stream_handler.setFormatter(formatter) stream_handler.setLevel(logging.INFO)
logger.addHandler(stream_handler)
logger.info("这是一条INFO级别的日志")
|
运行一下, 没有输出。

对调一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import logging
logger = logging.getLogger() logger.setLevel(logging.INFO)
stream_handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') stream_handler.setFormatter(formatter) stream_handler.setLevel(logging.ERROR)
logger.addHandler(stream_handler)
logger.info("这是一条INFO级别的日志") logger.error("这是一条ERROR级别的日志")
|
运行一下,虽然logger定义了INFO,但是handler设置了ERROR,可以看到只有ERROR 级别的输出了

4.logger的name参数
logger是通过logging.getLogger()得到,getLogger方法是有一个默认为None的参数的–name。
你可以定义多个logger,如果赋予了name的值,且他们是具有层级关系的,那么将自动转换成父子关系,默认子logger日志会向父logger传播。
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import logging
logger_1 = logging.getLogger("爷爷") logger_1.setLevel(logging.INFO)
logger_2 = logging.getLogger("爷爷.爸爸") logger_2.setLevel(logging.INFO)
logger_3 = logging.getLogger("爷爷.爸爸.孙子") logger_3.setLevel(logging.INFO)
stream_handler = logging.StreamHandler() stream_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger_1.addHandler(stream_handler) logger_2.addHandler(stream_handler) logger_3.addHandler(stream_handler)
logger_3.info("这是一条INFO级别的日志")
|
结果

可以看到,内容被打印了三遍,因为logger_3是logger_2的子日志器,logger_2是logger_1的子日志器。
可以通过一下设置关闭默认效果,使得子日志器不会打印多次:logger.propagate=False
formatter是handler的属性,要使用格式,首先是定义格式:logging.Formatter(),然后给handler添加应用formatter: handler_name.setFormatter(formatter_name)
如:定义一个当前时间+logger_name+日志等级+错误信息的格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import logging
logger = logging.getLogger() logger.setLevel(logging.INFO)
stream_handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler) logger.info("这是一条INFO级别的日志")
|
结果

其他参数
属性名 |
格式 |
描述 |
asctime |
%(asctime)s |
易读的时间格式: 默认情况下是’2003-07-08 16:49:45,896’的形式(逗号之后的数字是毫秒部分的时间) |
filename |
%(filename)s |
路径名的文件名部分。 |
funcName |
%(funcName)s |
日志调用所在的函数名 |
levelname |
%(levelname)s |
消息的级别名称(‘DEBUG’, ‘INFO’, ‘WARNING’, ‘ERROR’, ‘CRITICAL’). |
levelno |
%(levelno)s |
对应数字格式的日志级别 (DEBUG, INFO, WARNING, ERROR,CRITICAL). |
lineno |
%(lineno)d |
发出日志记录调用的源码行号 (如果可用)。 |
module |
%(module)s |
所在的模块名(如test6.py模块则记录test6) |
message |
%(message)s |
记录的信息 |
name |
%(name)s |
调用的logger记录器的名称 |
process |
%(process)d |
进程ID |
processName |
%(processName)s |
进程名 |
thread |
%(thread)d |
线程ID |
threadName |
%(threadName)s |
线程名 |
6.filter过滤器
filter是在handler之后,在经过日志级别和输出位置之后,对内容进行过滤。
如日志中带有password信息的语句,将其后面的密码变为********。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import logging
def my_filter(record): if 'password' in record.msg: record.msg = "password: ********" return True
logger = logging.getLogger() logger.setLevel(logging.INFO)
stream_handler = logging.StreamHandler() logger.addHandler(stream_handler) logger.addFilter(my_filter)
logger.info("username: admin") logger.info("password: 123456")
|
打印结果

二.常用Handler
1.StreamHandler
前面一直拿StreamHandler举例子,那他的作用大家也看到了:流handler,能够将日志信息输出到sys.stdout, sys.stderr 或者类文件对象(更确切点,就是能够支持write()和flush()方法的对象)。
2.FileHandler
logging模块自带的三个handler之一。继承自StreamHandler。将日志信息输出到磁盘文件上。用法:logging.FileHandler()
示例
1 2 3 4 5 6 7 8 9 10 11 12 13
| import logging
logger = logging.getLogger("file") logger.setLevel(logging.INFO)
file_handler = logging.FileHandler('./test.log', encoding="UTF-8")
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger.addHandler(file_handler)
logger.info("这是一条INFO级别的日志")
|
结果,在当前目录下生成test.log文件,并在其中进行了输出

3.NullHandler
空操作handler,logging模块自带的三个handler之一。 用法: logging.NullHandler()
查看源码提示

该Handler实例会忽略error messages,通常被想使用logging的library开发者使用来避免’No handlers could be found for logger XXX’信息的出现。
开发者只需要定义一个NullHandler并将他置于最顶层logger里,就可以避免找不到记录器xxx的这种报错。
什么意思?
这句话或者说这个功能的意思是,你写了一个lib,并在这个lib定义一个logger,name是”me”,且lib本身执行了日志输出,级别是error,然后我要使用你的这个lib,我可以直接获取me, getLogger(“me”),然后往里面添加handler,即便我在使用过程中没有主动使用logger.error这类方式写日志,但由于lib里有定义,那么就会主动去打印使用lib过程中产生的lib本身的error日志。
而NullHandler的作用就是,你定义名字是”me”,但是我 getLogger(“aaa”),即便我没有主动logger.error,但是由于lib本身有使用,则会报找不到aaa的错误,而在lib定义中加入一个NullHandler,就可以避免这种报错。
三.handler进阶
(这块内容来自于:https://blog.csdn.net/yypsober/article/details/51800120)
1.WatchedFileHandler
用于监视文件的状态,如果文件被改变了,那么就关闭当前流,重新打开文件,创建一个新的流。由于newsyslog或者logrotate的使用会导致文件改变。这个handler是专门为linux/unix系统设计的,因为在windows系统下,正在被打开的文件是不会被改变的。
参数和FileHandler相同:
1 2
| import logging logging.handlers.WatchedFileHandler(filename, mode='a', encoding=None, delay=False)
|
2.RotatingFileHandler
支持循环日志文件,参数maxBytes和backupCount允许日志文件在达到maxBytes时rollover.当文件大小达到或者超过maxBytes时,就会新创建一个日志文件。上述的这两个参数任一一个为0时,rollover都不会发生。也就是就文件没有maxBytes限制。backupcount是备份数目,也就是最多能有多少个备份。命名会在日志的base_name后面加上.0-.n的后缀,如example.log.1,example.log.1,…,example.log.10。当前使用的日志文件为base_name.log。
1 2
| import logging logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0)
|
3.TimedRotatingFileHandler
定时循环日志handler,支持定时生成新日志文件。参数when决定了时间间隔的类型,参数interval决定了多少的时间间隔。如when=‘D’,interval=2,就是指两天的时间间隔,backupCount决定了能留几个日志文件。超过数量就会丢弃掉老的日志文件。
1 2
| import logging logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False)
|
when的参数决定了时间间隔的类型。两者之间的关系如下:
‘S’ |
秒 |
‘M’ |
分 |
‘H’ |
时 |
‘D’ |
天 |
‘W0’-‘W6’ |
周一至周日 |
‘midnight’ |
每天的凌晨 |
四.日志配置的三种方式
1.basicConfig
这种方式毕竟简单,直接作用在py文件,使用logging.basicConfig()方法。
如
1
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
logging.basicConfig函数各参数:
filename:指定日志文件名;
filemode:和file函数意义相同,指定日志文件的打开模式,’w’或者’a’;
format:指定输出的格式和内容,format可以输出很多有用的信息,
datefmt:指定时间格式,同time.strftime();
level:设置日志级别,默认为logging.WARNNING;
stream:指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略;
2.json配置文件
使用json配置文件,还需要一个方法来读取json格式的配置文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import logging.config import json import os
""" 从json获取配置 """
def setup_logging(default_path="logging_getconfig_fromjson.json", default_level=logging.INFO, env_key="LOG_CFG"): path = default_path value = os.getenv(env_key,None) if value: path = value if os.path.exists(path): with open(path, "r") as f: config = json.load(f) logging.config.dictConfig(config) else: logging.basicConfig(level = default_level)
def func(): logging.info("start func") logging.info("exec func") logging.info("end func")
if __name__ == "__main__": setup_logging(default_path="logging_getconfig_fromjson.json") func()
|
配置文件格式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| { "version":1, "disable_existing_loggers":false, "formatters":{ "simple":{ "format":"%(asctime)s - %(name)s - %(levelname)s - %(message)s" } }, "handlers":{ "console":{ "class":"logging.StreamHandler", "level":"DEBUG", "formatter":"simple", "stream":"ext://sys.stdout" }, "info_file_handler":{ "class":"logging.handlers.RotatingFileHandler", "level":"INFO", "formatter":"simple", "filename":"info.log", "maxBytes":"10485760", "backupCount":20, "encoding":"utf8" }, "error_file_handler":{ "class":"logging.handlers.RotatingFileHandler", "level":"ERROR", "formatter":"simple", "filename":"errors.log", "maxBytes":10485760, "backupCount":20, "encoding":"utf8" } }, "loggers":{ "my_module":{ "level":"ERROR", "handlers":["info_file_handler"], "propagate":"no" } }, "root":{ "level":"INFO", "handlers":["console","info_file_handler","error_file_handler"] } }
|
3.yaml配置文件
方法同使用json差不多, yaml通过 pip install pyyaml安装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import yaml import logging.config import os
""" 从YAML获取配置 """
def setup_logging(default_path="logging_getconfig_fromjson.yaml", default_level=logging.INFO, env_key="LOG_CFG"): path = default_path value = os.getenv(env_key, None) if value: path = value if os.path.exists(path): with open(path, "r") as f: config = yaml.load(f) logging.config.dictConfig(config) else: logging.basicConfig(level=default_level)
def func(): logging.info("start func") logging.info("exec func") logging.info("end func")
if __name__ == "__main__": setup_logging(default_path="logging_getconfig_fromjson.yaml") func()
|
yaml配置文件格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| version: 1 disable_existing_loggers: False formatters: simple: format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" handlers: console: class: logging.StreamHandler level: DEBUG formatter: simple stream: ext://sys.stdout info_file_handler: class: logging.handlers.RotatingFileHandler level: INFO formatter: simple filename: info.log maxBytes: 10485760 backupCount: 20 encoding: utf8 error_file_handler: class: logging.handlers.RotatingFileHandler level: ERROR formatter: simple filename: errors.log maxBytes: 10485760 backupCount: 20 encoding: utf8 loggers: my_module: level: ERROR handlers: [info_file_handler] propagate: no root: level: INFO handlers: [console,info_file_handler,error_file_handler]
|
五.其他handler(无需关注)
人的记忆有限,一个模块肯定包含常用和选用的东西,未必所有都需要用到,这一部分无需刻意关注,使用的时候再去翻阅文档即可。
其他不常用的handlers包含以下:SocketHandler
、DatagramHandler
、SysLogHandler
、NtEventHandler
、SMTPHandler
、MemoryHandler
、HTTPHandler
。