循序渐进学习 Python loggin
logging 库采用模块化的方式提供了几种类型的组件:loggers,handlers,filters,fORMatters。
- Loggers 暴露了应用程序代码可以直接使用的接口
- Handlers 发送日志记录(由 loggers 创建)到合适的目的地
- Filters 提供了细粒度的方法来决定哪些日志记录需要被输出
- Formatters 指定了在最终输出时,日志记录的布局格式
日志事件消息被存在 LogRecord 实例中,然后在 loggers,handlers,filters,formatters 之间传递。
记录日志是通过调用 Logger 类的实例的方法来实现的。每个实例都有个名字,它们被概念上地安排在一个以点(.)分割的层次的命名空间。例如,一个名为 scan 的 logger,是名为 scan.text,scan.html,scan.pdf 这些 logger 的父亲。你可以随意设置 logger 对象的名称,它暗示了日志消息是从应用程序的哪个部分出来的。
当具名的 logger 是个模块级别的 logger 时,一个好的命名习惯时使用模块名作为 logger 名,这样只需在每个使用 logging 的模块中,使用如下代码来命名 logger:
logger = logging.getLogger(__name__)
这意味着 logger 的名字会和 包/模块的层次结果对应,这样从 logger 的名字就可以很容易的知道日志事件时从哪里产生的。
loggers 层次结构的根被称为 root logger。这就是 logging 模块的函数 debug(),info(),warning(),error() 和 critical() 所使用的 logger。这些函数只是调用了 root logger 的同名的方法。这些函数和方法有相同的签名。 在日志输出中,root logger 的名字会被打印成:root
当然,日志消息是可以写到不同的目的地的。logging 库支持将日志消息写道文件,Http GET/POST 地址(locations),SMTP 发送的邮件,普通套接字,或操作系统相关的日志记录机制(如,syslog 或 windows NT 事件日志)。日志目的地是由 handler 类处理的。如果内置的 handler 类不能满足你的需求,你可以创建自己的日志目的地类。
默认情况下,logging 消息并未设置目的地。你可以通过 basicConfig() 来指定一个目的地(如,控制台或文件)。如果你直接调用 debug(),info(),warning(),error(),critical() 函数,它们会先检查是否设置了日志目的地,如果没有设置,它们就会在委派 root logger 进行真实的消息输出之前,将目的地设置为控制台(sys.stderr),同时还会设置一个默认的格式化器来显示消息。
basicConfig() 函数设置的默认输出格式为:
severity:logger name:message
你可以通过向basicConfig() 的 format 关键字参数传递一个格式化字符串来改变这个行为,格式化字符串支持的选项请参考:https://docs.python.org/2.7/library/logging.html#formatter-objects
1. logging 工作流程
2. 日志记录器(loggers)
日志记录器 logger 对象有三重任务。首先,它们暴露了一些方法给应用程序代码调用,使得应用程序能够在运行时记录消息。其次,logger 对象根据日志级别(默认的过滤条件)或过滤器(filter)对象来决定要对哪些日志消息进行处理。最后,logger 对象会将日志消息传递给所有相关的日志处理器(handlers)。
logger 对象上最常被使用的方法(methods)可以分为两类:配置 和 消息发送
下面是最常见的配置方法:
- Logger.setLevel() 指定了该 logger 对象将会处理的日志消息的最低的级别。DEBUG 是内置的最低的日志严重性级别,CRITICAL 是内置的最高的日志严重性级别。例如,如果严重性级别被设置为 INFO,那么这个 logger 将只会处理 INFO,WARNING,ERROR,CRITICAL 级别的日志消息,而忽略 DEBUG 级别的消息。
- Logger.addHandler() 和 logger.removeHandler() 用于为 logger 对象增加和移除日志处理器(handler)对象。
- Logger.addFilter() 和 Logger.removeFilter() 用于为 logger 对象增加和移除过滤器(filter)对象。
你并不需要在你创建的每一个 logger 对象上调用这些方法,请看本段最后两节的说明。
当 logger 对象配置好后,下面的这些方法用于创建日志消息:
- Logger.debug(),Logger.info(),Logger.warning(),Logger.error() 和 Logger.critical() 都会用所提供的消息(message)来创建与它们名字一致的级别的日志记录。这里所说的消息其实是一个格式化字符串,它可以包含标准的字符串替换语法,如 %s,%d,%f 等。这些函数其余的参数是一列与消息中要替换的域相关数据。至于参数 **kwargs,logging 方法只关心一个关键字参数 exc_info,该参数用来决定是否要记录异常信息。
- Logger.exception() 创建一条类似与 Logger.error() 的日志消息。不同点在于,Logger.exception() 会转储栈追踪信息。该方法应该只在异常处理代码中调用。
- Logger.log() 接受一个日志级别作为显式的参数。该方法比上面列出的使用日志级别的简便方法记录的日志更繁琐,但它却是记录定制化日志级别消息的方法。
getLogger() 函数会返回一个对给定名称的 logger 对象实例的引用,如果参数中没有提供名称,则返回对 root 日志记录器的引用。logger 命名采用的是以点(.)作为分割的层次结构。使用相同名称作为参数来多次调用 getLogger() 会返回对同一个 logger 对象的引用。名称在层次结构底部的 logger 是名称在层次结构高处的 logger 的孩子。例如,名称为foo.bar,foo.bar.baz 以及 foo.bam 的日志记录器,都是名为 foo 的日志记录器的后代。
日志记录器有个有效级别的概念。如果某个 logger 未显式设置日志级别,那么它父亲的日志级别就是它的有效级别。如果它父亲也没有显式的设置级别,那么该父日志记录器的父亲就会被检查,重复此过程,对所有祖先进行搜索直到有个显式的级别设置被找到。root 日志记录器总是有一个显式的级别设置(默认是 WARNING)。当决定是否要处理某个事件时,logger 的有效级别就会被用来确定是否要将该事件传递给这个日志记录器的处理器。
子日志记录器会把消息传递给与其祖先们相关的处理器。因此,没有必要为应用程序中使用的所有 loggers 定义和配置日志处理器。为顶层的 logger 配置处理器,然后根据需要创建子 loggers,这样就以及足够了。(其实,你可以通过设置 logger 对象的 propagate 参数为 False,来关掉日志的传递)
3.日志处理器(Handlers)
Handler 对象负责(根据消息的严重性级别)分发相关的日志消息到处理器所指定的目的地。Logger 对象可以通过 addHandler() 方法为它们增加零个或多个处理器对象。举例来说,应用程序可能希望把所有日志消息发送到某个日志文件,所有错误或更高级别的日志到标准输出(stdout),所有 CRITICAL 的消息通过邮件发送。这个场景需要 3 个独立的处理器,每个处理器负责将特定级别的日志消息发送到指定的地点。
标准库包括很多日志处理器类型,本文主要使用 StreamHandler 和 FileHandler 作为示例。
日志处理器暴露给应用程序开发者的接口很少。对于使用内置 handler 对象的应用程序开发者来说,似乎 handler 方法中唯一和应用相关的就是下面的这些配置方法:
- 就像 logger 对象一样,handler 对象的 setLevel() 方法指定了会被分发到指定地点的最低日志级别。为什么会有两个 setLevel() 方法呢?因为,logger 上设置的级别时用于决定哪个级别的消息需要被传递给 handler,而 handler 上设置的级别则是决定这个 handler 会处理哪个级别的日志。
- setFormatter() 选择一个格式化器给该 handler 对象使用
- addFilter() 和 removeFilter() 分别为该 handler 添加和移除过滤器。
应用代码不应该直接初始化 Handler 类的实例,因为该类只是个基类,用于定义所有 handler 类的子类可以使用(或覆盖)的接口和应该具有的默认行为。
4. 格式化器(Formatters)
格式化器对象配置了日志消息最终的输出顺序,结构和内容。不像 logging.Handler 这个基类,应用代码可以直接实例化 formatter 类,如果你的应用程序需要特殊的行为,你也可以创建 formatter 的子类。其构造函数接受两个可选参数:一个消息格式化字符串和一个日期格式化字符串。
logging.Formatter(fmt=None, datefmt=None)
如果没有提供消息格式化字符串,默认会使用消息本身。如果没有提供日期格式化字符串,默认的日期格式如下(并且会在其后面加个逗号,然后接着输出毫秒值):
%Y-%m-%d %H:%M:%S
消息格式化字符串使用 %(<dictionary key>)s 这种分割进行字符串替换;可用的键值请参考:https://docs.Python.org/2.7/library/logging.html#logrecord-attributes
下面的格式化字符串将把日期以人类友好的方式,然后是消息的严重性级别,最后是消息内容:
'%(asctime)s - %(levelname)s - %(message)s'
格式化器使用了一个用户可配置的函数来将日志产生时间转换成一个元组。默认情况下,time.localtime() 会被使用;如果想改变某个格式化器实例的时间转化方式,可以设置该实例的 converter 属性成一个和 time.localtime() 或 time.gmtime() 有相同签名的函数。如果想改变所有 formatters 的时间转化方式,例如,如果你想 logging 时,所有的时间都采用 GMT 格式, 可以设置 Formatter 类的 converter 属性为 time.gmtime 来显示时间。
5. 配置 logging
程序员可以使用三种方式配置 logging:
1.使用 Python 代码调用前面提到的配置类方法来显式地创建 loggers,handlers,formatters
2.创建一个 logging 的配置文件,然后使用 fileConfig() 来读取该文件
3.创建一个保存了配置信息的字典,然后把它传递给 dictConfig() 函数
对于后两种方式的文献,请参考:https://docs.python.org/2.7/library/logging.config.html#logging-config-api
下面的示例中,使用 Python 代码配置了一个非常简单的 logger,一个控制台处理器,和一个简单的格式化器:
import logging
# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)
# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')
从命令行运行上面的代码,会产生如下输出:
$ python simple_logging_module.py
2005-03-19 15:10:26,618 - simple_example - DEBUG - debug message
2005-03-19 15:10:26,620 - simple_example - INFO - info message
2005-03-19 15:10:26,695 - simple_example - WARNING - warn message
2005-03-19 15:10:26,697 - simple_example - ERROR - error message
2005-03-19 15:10:26,773 - simple_example - CRITICAL - critical message
下面的 Python 模块会创建和前面的例子中几乎相同的 logger, handler 和 formatter,唯一不同的是对象的名字:
import logging
import logging.config
logging.config.fileConfig('logging.conf')
# create logger
logger = logging.getLogger('simpleExample')
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')
下面式对应的配置文件:
[loggers]
keys=root,simpleExample
[handlers]
keys=consoleHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=
输出的内容和前面的非配置文件形式的输出几乎一致:
$ python simple_logging_config.py
2005-03-19 15:38:55,977 - simpleExample - DEBUG - debug message
2005-03-19 15:38:55,979 - simpleExample - INFO - info message
2005-03-19 15:38:56,054 - simpleExample - WARNING - warn message
2005-03-19 15:38:56,055 - simpleExample - ERROR - error message
2005-03-19 15:38:56,130 - simpleExample - CRITICAL - critical message
这里如果在 logging.conf 文件中将 propagete 设置为 1,则 simpleExample 这个 logger 还会将日志消息传递给它祖先的 handlers,因为其祖先 root 也配置了控制台类型的日志处理器,所以得到的输出如下,每天信息都会打印两次:
2017-01-09 22:24:27,958 - simpleExample - DEBUG - debug message
2017-01-09 22:24:27,958 - simpleExample - DEBUG - debug message
2017-01-09 22:24:27,959 - simpleExample - INFO - info message
2017-01-09 22:24:27,959 - simpleExample - INFO - info message
2017-01-09 22:24:27,959 - simpleExample - WARNING - warn message
2017-01-09 22:24:27,959 - simpleExample - WARNING - warn message
2017-01-09 22:24:27,959 - simpleExample - ERROR - error message
2017-01-09 22:24:27,959 - simpleExample - ERROR - error message
2017-01-09 22:24:27,959 - simpleExample - CRITICAL - critical message
2017-01-09 22:24:27,959 - simpleExample - CRITICAL - critical message
此外,你可以看出,使用配置文件的方式有一些优点,主要是将配置和代码分离,这样非代码编写者也可以很容易的改变 logging 的特性。
注意:
fileConfig() 函数有一个默认参数: disable_existing_loggers,为了向后兼容,该参数的默认值被设置成了 True。这也许并不是你想要的,因为这会导致任何在 fileConfig() 调用之前已经存在的 logger 被关闭,除非它们或者它们的祖先被显式的写在配置文件中。请参看参考文献获得更多信息,如果你需要的话,请把该参数设置成 False。dictConfig() 会产生类似的问题。
请注意,配置文件中使用的类名要么和相关的 logging 模块相关,要可以通过正常的导入(import)机制解析出来。因此你可以使用 WatchFileHandler (和 logging 模块相关)或 mypackage.mymodule.MyHandler (MyHandler 定义在包 mypackage 的模块 mymodule 中,并且 mypackage 包在 Python 的导入路径中)。
在 Python 2.7 中引入了配置 logging 的新方式:使用字典来存储配置信息。这是上面配置文件方式的超集,也是新应用程序的推荐方式。因为配置信息存在 Python 字典中,并且创建 Python 字典的方法多种多样,所以你可以有更多配置 logging 的方式。例如,你可以使用一个 JSON 格式的配置文件,如果你可以访问能够处理 YAML 格式的函数,也可以使用一个 YAML 格式的配置文件来生成对应的配置字典。或者,你可以直接使用 Python 代码来创建该字典,从套接字接受序列化后的字典,以及其他对你的应用来说合理的方式。
下面是一个使用 YAML 格式配置文件来生成配置字典的配置方式:
version: 1
formatters:
simple:
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: simple
stream: ext://sys.stdout
loggers:
simpleExample:
level: DEBUG
handlers: [console]
propagate: no
root:
level: DEBUG
handlers: [console]
更详细的信息请参考:https://docs.python.org/2.7/library/logging.config.html#logging-config-api
6.如果没有提供任何配置会怎样
如果没有提供任何的 logging 配置,很可能出现一种情况:有 logging 事件需要被输出,但是找不到 handlers 来处理该输出。这种情况下,logging 的行为依赖于 Python 版本:
对于 Python 2.x 版本,行为如下:
- 如果 logging.raiseException 为 False(生产环境),事件就会默默的被丢弃
-
如果 logging.raiseException 为 True (开发环境),会打印一条类似:No handlers could be found for logger X.Y.Z 的消息
7. 为库(library)配置 logging
当开发一个使用了 logging 的库时,你应该小心在文档中写出该库是如何使用 logging的,例如,所使用的 loggers 的名字。你还需要对 logging 配置文件进行考虑。如果使用该库的应用程序没有配置 logging,但该库却进行了 logging 调用,那么如上一节所说的,一条错误信息就会打印到标准错误,sys.stderr。
如果因为某些原因,你不想在缺失 logging 配置文件的情况下打印出错误消息,你可以为你的库的顶层 logger 添加一个什么也不做的处理器。这样可以避免打印错误信息,因为库中的日志事件发生时,总可以找到一个日志处理器,只不过这个处理器什么也不做。如果用户应用程序配置了 logging,通常配置中都会加一些日志处理器,并且如果日志级别设置恰当的话,库里面的logger 调用就可以正常的发送消息给这些处理器。
Python logging 包中已经包含了一个什么也不做的处理器:NullHandler (从 Python 2.7 开始)。可以给库中顶层的 logger 添加一个 NullHandler 类的实例,这样就可以避免在缺失配置文件时报错。如果名为 foo 的库中,所有的日志记录都是使用名为:foo.x,foo.x.y 这样的 logger 的话,那么相应的代码如下:
import logging
logging.getLogger('foo').addHandler(logging.NullHandler())
如果你的组织开发了许多库,那么 logger 对象的名字可以命名为 ‘orgname.foo’,而不只是简单的 foo
8.日志级别
日志级别对应的数字值如下表所示。如果你想定义自己的日志级别,你可能对此感兴趣,你可以为你自己的日志级别定义相应的值。如果你所定义的值和 logging 预先定义的值重复,那么预先定义的值就会被覆盖,相应的日志级别的名称就丢掉了。
级别 | 数值 |
CRITICAL | 50 |
ERROR | 40 |
WARNING | 30 |
INFO | 20 |
DEBUG | 10 |
NOSET | 0 |
级别也可以和日志记录器相关联,可以有开发者设置,也可以从配置文件读取。当在某个 logger 上调用 logging 方法时,logger 就会拿自己的日志级别和相应方法对应的日志级别进行比较。如果 logger 的日志级别比方法对应的级别高,那么就不会记录日志消息。这是控制 logging 输出详细程度的基本机制。
Logging 消息会被编码成 LogRecord 类的实例。当一个日志记录器决定真的需要记录日志事件时,就会从所给的日志消息创建一个 LogRecord 对象。
日志消息通过使用 handlers 进行分发系统,这些 handlers 是 Handler 的子类的实例。日志处理器负责确保日志消息(以 LogRecord 的形式)被传送到某个对该消息的目标受众(如用户,支持,系统管理员,开发等)有用的指定位置。每个日志记录器,可以拥有零个或多个日志处理器(通过 addHandler() 进行添加)。除了那些与日子记录器自身直接相关的日志处理器之外,所有和它祖先相关的日志处理器也会被调用来分发日志消息(除非传递过程中某个 logger 的 propagete 参数被设置成了 False,这时在该点,日志就会停止向更上级的祖先的处理器进行传递)。
就像 logger 对象有关联的级别一样,日志处理器对象也有和它们相关连的级别。日志处理器的级别就像 logger 对象的级别那样,也充当过滤器的作用。如果一个日志处理器决定真的要分发一条日志事件,emit() 方法就会被调用来将消息发到目的地。大部分用户自定义的 Handler 子类,都需要覆盖这个 emit() 方法。
9.自定义日志级别(level)
自己定义日志级别是可以的,但是却不是必须的,因为默认的 logging 中的日志级别都是根据实际经验而来的。然而,如果你很确信,你需要自定义的日志级别,你应该很小心的来做这件事,并且如果你在开发某个库,那么自定义日志级别会是个非常坏的注意。因为,如果许多库的作者,都定义它们自己的自定义级别,那么很可能这些库一起使用时,使用者很难控制日志输出,因为一个给定的数值,在不同的库中可能具有不同的意义。
10.有用的日志处理器
logging 库中提供了很多有用的 Handler 类的子类:
1. StreamHandler:发送日志消息到流(类文件对象)
2. FileHandler:发送日志消息到磁盘文件
3. BaseRotatingHandler: 是在某些点会旋转日志文件的处理器的基类。不要实例化该对象,而应该使用它的子类:RotatingFileHandler 或 TimedRotatingFileHandler
4. RotatingFileHandler:发送日志消息到磁盘文件,支持指定最大的日志文件大小和数量对日志文件进行轮转
5. TimedRoatingFileHandler:发送日志消息到磁盘,根据时间周日轮转日志文件
6. SocketHandler:发送日志消息到 tcp/IP 套接字
7. DatagramHandler:发送日志消息到 UDP 套接字
8. SMTPHandler:发送消息到指定的邮件地址
9. SysLogHandler:发送日志消息到 Unix syslog daemon,可能位于远程机器上
10. NTEventLogHandler:发送日志消息到 Windows NT/2000/XP 事件日志
11. MemoryHandler:发送日志消息到内存缓冲区,当指定条件满足后,就会被清空
12. HTTPHandler:使用 GET 或 POST 方法发送日志到 HTTP 服务器
13. WatchedFileHandler:监视它们写入的日志文件。如果文件改变了,就会关掉并重写用相同的文件名打开。这个处理器只在类 Unix 系统中有用,Windows 不支持该底层使用的机制
14. NullHandler:什么也不做,它只是被库作者用来避免因未配置 logging 而产生的错误信息
NullHandler,StreamHandler 和 FileHandler 类定义在 logging 的核心包中,其他的 handlers 都定义在子模块中: logging.handlers
日志消息会使用 Formatter 类的实例进行格式化。如果想格式话一个 batch 中的多条消息,可以使用 BufferingFormatter 类的实例。除了格式化字符串(会被应用于 batch 中的每条消息),还可以提供对头部和尾部进行格式化的字符串。
当使用 logger 或 handler 对象的日志级别进行过滤不能满足需求时,可以通过 addFilter() 方法给 Logger 和 Handler 类的实例添加过滤器。在决定进一步处理一条日志消息之前,logger 和 handler 对象都会检查它们所有的过滤器,来确定是否要继续处理。如果任何一个过滤器返回 False,那么消息就不会继续处理。
基本的 Filter 允许指定 logger 的名字进行过滤。如果使用这个特性,发送给该 logger 以及它的子 logger 的消息都会被允许,其他消息就被丢掉。
11. 记录日志过程中抛出异常
logging 库被设计成会吞掉所有在记录日志时抛出的异常。这是为了使得处理日志过程中发生的错误(如 logging 配置错误,网络或其他相似的错误)不会使得使用 logging 的应用出现贸然终止。
SystemExit 和 KeyboardInterupt 异常不会被吞掉。其他在调用 Handler 子类的 emit() 方法时,产生的异常会被传递给 handlerError() 方法处理。handlerError() 的默认实现是检查是否有模块级别的 raiseExceptions 变量存在,如果有,栈追踪信息就会打印到标准错误,如果没有,异常就会被吞下。
注意:
raiseException 的默认值是 True. 这是因为在开发过程中,你可能想在任何异常发生时都被提醒。建议在生产环境中将 raiseExceptions 设置成 False.
12. 使用任意对象作为消息
在前面的章节和例子中,我们都假设传递给 logging 的日志消息是字符串。其实,这是不必的。你可以传递任意对象作为消息,当 logging 系统需要得到该对象的字符串表示时,会调用它的 __str__() 方法来转换。实际上,如果你想,你甚至可以避免计算字符串表示,例如,SocketHandler 就是直接把事件序列化然后通过网线发出去。
13. 优化
日志消息参数的格式化时被延迟执行的,直到无法避免才会最终执行。然而,计算传递给 logging 方法的参数也可能是比较昂贵的,如果 logger 会将某个消息直接丢弃的话,你可能也想避免对该消息参数的计算。为了决定该怎么做,你可以调用 isEnabledFor() 方法来看看该日志事件是否会被该 Logger 创建,isEnabledFor() 方法接受一个日志级别作为参数。你可以写类似下面的代码:
if logger.isEnabledFor(logging.DEBUG):
logger.debug('Message with %s, %s', expensive_func1(),
expensive_func2())
因此,如果该日志记录器的级别比 DEBUG 高的话,对 expensive_func1() 和 expensive_func2()的调用就不会发生。
注意:
在某些情况下,调用 isEnabledFor() 方法本身就可能比你想象的更昂贵(例如有一个处于层次结构深处的 logger,但显式的日志级别设置却在层次结构的顶部)。在这种情况下,你可以缓存对 isEnabledFor() 方法的调用结果到一个局部的变量中,每次使用该变量的值代替对 isEnabledFor() 方法的调用。这个缓存的值,仅当应用程序运行过程中 logging 的配置发生变化时(这种情况并不常见),才需要改变。
对于一些需要更精细的控制收集哪些日志信息的应用程序来说,还有一些其他的方面可以优化。下面是一个列表,它列出了那些你想在 logging 过程中避免不必要的处理时所能做的事情:
你不想收集的信息 | 如何避免收集这些信息 |
关于日志记录调用由哪里发出来的信息 | 将 logging._srcfile 设置成 None。这避免了调用 sys._getframe(),这可能在 PyPy 这样的环境中加速你的代码 (注意你无法加速调用 sys._getframe()的代码) |
线程信息 | 将 logging.logThreads 设置为 0 |
进程信息 | 将 logging.logProcesses 设置为 0 |
如前所述,logging 核心模块中仅包括基本的日志处理器。如果你不导入 logging.handlers 和 logging.config,它们就不会占用内存。
原文:https://docs.python.org/2.7/howto/logging.html
相关文章