抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

官方文档: https://docs.python.org/zh-cn/3.8/library/logging.handlers.html#

一.logging构成

通常我们使用logging模块来输出日志,这是一个内置模块,不需要我们额外安装。

简单介绍下他的构成。

1.总体简析

通常由logger–日志器, handler–处理器, filter–过滤器, formatter–格式器组成(这其中,过滤器和格式器也可能不使用)。

过滤器的使用场景如:涉及到记录用户密码的日志,出于保密和安全考虑,不显示密码信息(或其他自定义内容)。

image.png

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# coding: utf8
import logging

# 日志的起始需要一个日志器logger,通过一下方式,定义
logger = logging.getLogger()
# 必须设置一下日志级别,这里是INFO及以上级别的日志会得到输出
logger.setLevel(logging.INFO)

# 然后是定义各种各样的处理器handler,例如你在本地学习日志模块,想运行后在看到窗口输出,定义一个streamhandler
stream_handler = logging.StreamHandler()

# 设置一下格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 将格式应用于handler
stream_handler.setFormatter(formatter)

# 然后将handler添加到logger
logger.addHandler(stream_handler)

# 这里不设置格式来,直接输出一个info级别的日志
logger.info("这是一条INFO级别的日志")

运行一下

image.png

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
# coding: utf8
import logging

logger = logging.getLogger()
logger.setLevel(logging.ERROR) # logger的设置为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) # handler的设置为INFO

logger.addHandler(stream_handler)

logger.info("这是一条INFO级别的日志")

运行一下, 没有输出。

image.png

对调一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# coding: utf8
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO) # logger的设置为ERROR

stream_handler = logging.StreamHandler()

formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
stream_handler.setFormatter(formatter)
stream_handler.setLevel(logging.ERROR) # handler的设置为INFO

logger.addHandler(stream_handler)

logger.info("这是一条INFO级别的日志")
logger.error("这是一条ERROR级别的日志")

运行一下,虽然logger定义了INFO,但是handler设置了ERROR,可以看到只有ERROR 级别的输出了

image.png

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
# coding: utf8
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级别的日志")

结果

image.png

可以看到,内容被打印了三遍,因为logger_3是logger_2的子日志器,logger_2是logger_1的子日志器。

可以通过一下设置关闭默认效果,使得子日志器不会打印多次:logger.propagate=False

5.formatter格式说明

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
# coding: utf8
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级别的日志")

结果

image.png

其他参数

属性名 格式 描述
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
# coding:utf8
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")

打印结果

image.png

二.常用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
# coding: utf8
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文件,并在其中进行了输出

image.png

3.NullHandler

空操作handler,logging模块自带的三个handler之一。 用法: logging.NullHandler()

查看源码提示

image.png

该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
# coding: utf8
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
# coding: utf8
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包含以下:SocketHandlerDatagramHandlerSysLogHandlerNtEventHandlerSMTPHandlerMemoryHandlerHTTPHandler

评论