20. logging

logging 模块是 Python 内置的标准模块,主要用于输出运行日志。

日志等级(从高到低):

  • CRITICAL

  • ERROR

  • WARNING

  • INFO

  • DEBUG

  • NOTSET

默认情况下只输出大于等于 WARNING 级别的日志。

若要对 logging 进行更多灵活的控制,必须了解 LoggerHandlerFormatterFilter 等概念:

  • Logger 提供了应用程序可以直接使用的接口;

  • HandlerLogger 创建的日志记录发送到合适的目的输出(控制台、文件、网络等);

  • 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 同理,其中 exceptionerror 同级。

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.stdoutinput() 对应 sys.stdinexception 写入 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. 参考资料

  1. logging — Logging facility for Python

  1. Python logger模块

  1. python3 logging模块

  1. print