20. logging
logging 模块是 Python 内置的标准模块,主要用于输出运行日志。
日志等级(从高到低):
CRITICAL
ERROR
WARNING
INFO
DEBUG
NOTSET
默认情况下只输出大于等于 WARNING
级别的日志。
若要对 logging 进行更多灵活的控制,必须了解 Logger
,Handler
,Formatter
,Filter
等概念:
Logger
提供了应用程序可以直接使用的接口;Handler
将Logger
创建的日志记录发送到合适的目的输出(控制台、文件、网络等);Filter
提供了细度设置来决定输出哪条日志记录;Formatter
决定日志记录的最终输出格式。
20.1. Logger
- class logging.Logger
不要直接实例化
Logger
,应当通过模块级别的函数logging.getLogger(name)
来得到对象。多次使用相同的name
调用会一直返回相同的Logger
对象的引用。 如果name
不给定默认为root
。name
是以点号分割的命名方式命名的(a.b.c)。这种命名方式里面,后面的 loggers 是前面 logger 的子 logger,自动继承父 logger 的 logging 设置。正因为此,没有必要把一个应用的所有 logger 都配置一遍,只要把顶层的 logger 配置好了,然后子 logger 根据需要继承就行了。- setLevel(level)
等级低于 level 的日志将不会输出。
- addHandler(hdlr)
添加 handler。
- removeHandler(hdlr)
删除 handler。
- addFilter(filter)
添加 filter。
- removeFilter(filter)
删除 filter。
- debug(msg, *args, **kwargs)
输出 debug 等级的信息,
info
warning
exception
error
critial
同理,其中exception
和error
同级。
20.2. Handler
- class logging.Handler
Handler
类也不直接实例化,而是作为其他常用 handler 的抽象基类。以下是几个常用的 handler。
- class logging.StreamHandler(stream=None)
可以像类似于
sys.stdout
或者sys.stderr
的任何文件对象(file object)输出信息。stream 默认是sys.stderr
,输出到控制台。
- class logging.FileHandler(filename, mode='a', encoding=None, delay=False)
向一个文件输出日志信息。
mode='a'
表示追加到文件末尾,'w'
表示写入。
- class logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False)
类似于上面的
FileHandler
,但是它可以管理文件大小。当文件达到一定大小之后,它会自动将当前日志文件改名,然后创建一个新的同名日志文件继续输出。
- class logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None)
间隔一定时间就自动创建新的日志文件。
成员方法:
- setLevel(level)
该 handler 对等级低于
level
的日志无效。
- setFormatter(fmt)
设置输出格式。
20.3. Formatter
- class logging.Formatter(fmt=None, datefmt=None, style='%')
Formatter
定义了最终 log 信息的内容格式,可以直接实例化 Foamatter
类。信息格式字符串用 %(<dictionary key>)s
风格的字符串做替换。
可能用到的格式化串:
%(name)s
logger 的名字%(levelno)s
数字形式的日志级别%(levelname)s
文本形式的日志级别%(pathname)s
调用日志输出函数的模块的完整路径名%(filename)s
调用日志输出函数的模块的文件名%(module)s
调用日志输出函数的模块名%(funcName)s
调用日志输出函数的函数名%(lineno)d
调用日志输出函数的语句所在的代码行%(created)f
当前时间,用 UNIX 标准的表示时间的浮点数表示%(relativeCreated)d
输出日志信息时的,自 logger 创建以来的毫秒数%(asctime)s
字符串形式的当前时间(默认格式是 “2003-07-08 16:49:45,896”,逗号后面的是毫秒)%(thread)d
线程 ID%(threadName)s
线程名%(process)d
进程 ID%(message)s
用户输出的消息
20.4. 示例
logging 基本设置
1import logging
2logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
3logger = logging.getLogger(__name__)
4
5logger.info("Start print log")
6logger.debug("Do something")
7logger.warning("Something maybe fail.")
8logger.info("Finish")
控制台输出:
2020-03-01 14:35:57,550 - __main__ - INFO - Start print log
2020-03-01 14:35:57,551 - __main__ - WARNING - Something maybe fail.
2020-03-01 14:35:57,551 - __main__ - INFO - Finish
同时输出到控制台和文件
1import logging
2logger = logging.getLogger(__name__)
3logger.setLevel(level = logging.INFO)
4
5handler = logging.FileHandler("log.txt") ## file
6handler.setLevel(logging.INFO)
7formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
8handler.setFormatter(formatter)
9
10console = logging.StreamHandler() ## console
11console.setLevel(logging.INFO)
12
13logger.addHandler(handler)
14logger.addHandler(console)
15
16logger.info("Start print log")
17logger.debug("Do something")
18logger.warning("Something maybe fail.")
19logger.info("Finish")
控制台输出:
Start print log
Something maybe fail.
Finish
文件输出:
2020-03-01 15:26:49,162 - __main__ - INFO - Start print log
2020-03-01 15:26:49,163 - __main__ - WARNING - Something maybe fail.
2020-03-01 15:26:49,163 - __main__ - INFO - Finish
20.5. 配置文件
可以从字典中加载 logging 配置,这也就意味着可以通过 JSON 或者 YAML 文件加载日志的配置。
以 YAML 为例,新建 log.yaml:
\(\color{darkgreen}{log.yaml}\)
1version: 1
2disable_existing_loggers: False
3formatters:
4 simple:
5 format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
6handlers:
7 console:
8 class: logging.StreamHandler
9 level: DEBUG
10 formatter: simple
11 stream: ext://sys.stdout
12 info_file_handler:
13 class: logging.handlers.RotatingFileHandler
14 level: INFO
15 formatter: simple
16 filename: info.log
17 maxBytes: 10485760
18 backupCount: 20
19 encoding: utf8
20 error_file_handler:
21 class: logging.handlers.RotatingFileHandler
22 level: ERROR
23 formatter: simple
24 filename: errors.log
25 maxBytes: 10485760
26 backupCount: 20
27 encoding: utf8
28loggers:
29 my_module:
30 level: ERROR
31 handlers: [info_file_handler]
32 propagate: no
33root:
34 level: DEBUG
35 handlers: [console, info_file_handler, error_file_handler]
导入:
1import yaml
2import logging
3## logging 的 __init__ 文件里面没有 config
4import logging.config
5import os
6
7def setup_logging(default_path="log.yaml", default_level=logging.INFO, env_key="LOG_CFG"):
8 path = default_path
9 ## getenv 获取全局变量
10 value = os.getenv(env_key, None)
11 if value:
12 path = value
13 if os.path.exists(path):
14 with open(path, "r") as f:
15 cfg = yaml.load(f, Loader=yaml.FullLoader)
16 logging.config.dictConfig(cfg)
17 else:
18 logging.basicConfig(level=default_level)
19
20def func():
21 logging.debug("start func")
22
23 logging.info("exec func")
24
25 logging.error("error end")
26
27if __name__ == "__main__":
28 setup_logging()
29 func()
控制台输出:
2020-03-01 14:54:21,566 - root - DEBUG - start func
2020-03-01 14:54:21,566 - root - INFO - exec func
2020-03-01 14:54:21,566 - root - ERROR - error end
文件输出:
2020-03-01 14:54:21,566 - root - ERROR - error end
从名字可以看出,程序中的 logging
默认使用的是 root
对应的设置,且 root
下设置的 level
会覆盖 handlers
下的 level
。这和使用对象
logger = logging.getLogger()
是等效的。如果想采用 my_module
对应的设置,则使用
logger = logging.getLogger("my_module")
20.6. 附录:print 函数
- print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
- Parameters
objects – 复数,表示可以一次输出多个对象。输出多个对象时,用
,
分隔。sep – 用来间隔多个对象,默认值是一个空格。
end – 用来设定以什么结尾,默认值是换行符
\n
。file – 要写入的文件对象,默认为
sys.stdout
。input()
对应sys.stdin
,exception
写入sys.stderr
。flush (bool) – 输出是否被缓存通常决定于 file ,但如果参数 flush 为 True,流会被强制刷新,立即输出。
例子:
控制台 loading 效果
设置
flush=True
,每隔 0.5 秒屏幕会打印一个点号。否则会在 5 秒之后输出 10 个点号。1import time 2 3print("Loading", end=" ") 4for i in range(10): 5 print(".", end='', flush=True) 6 time.sleep(0.5)
输出到文件
1>>> fw = open('a.txt', 'w') 2>>> print('hello', file=fw, flush=True) 3## 在关闭文件之前,此时打开文件已经可以看到输出了 4## 等效于 fw.write('hello') + fw.flush() 5>>> fw.close()
20.7. 参考资料
logging — Logging facility for Python
Python logger模块
python3 logging模块
print