简介
本节描述用于配置日志模块的API。
StreamHandler
下面的函数用于配置日志模块。它们位于 logging.config
模块。它们的使用是可选的——可以使用这些函数配置日志模块,或者通过调用主API(在 logging
中定义)和定义在 logging
或 logging.handlers
中声明的处理程序来配置日志模块。
-
logging.config.dictConfig(config)
从一个字典中获取日志记录配置。此字典的内容在下面的 配置字典模式 中描述。如果在配置过程中遇到错误,此函数会使用一个适当的描述性消息引发
ValueError
、TypeError
、AttributeError
或ImportError
异常。以下是(可能不完整的)会引起错误的条件列表:level
参数不是字符串或是一个与实际日志级别不对应的字符串。propagate
的值不是布尔值。- 一个没有相应目标的
id
。 - 在增量调用期间发现的不存在的处理程序
id
。 - 一个不可用的日志记录器名称。
- 无法解析到内部或外部对象。
解析由
DictConfigurator
类执行,该类的构造函数传递用于配置的字典,并具有configure()
方法。logging.config
模块有一个可调用的属性dictConfigClass
,它的初始值为DictConfigurator
。可以使用自己的适当实现替换dictConfigClass
的初始值。dictConfig()
调用dictConfigClass
传递指定的字典,然后调用返回对象上的configure()
方法,使配置生效:def dictConfig(config): dictConfigClass(config).configure()
例如,
DictConfigurator
的一个子类可以在它自己的__init__()
方法中调用DictConfigurator.__init__()
,然后设置自定义前缀,以便在后续的configure()
调用中使用。dictConfigClass
将绑定到这个新的子类,然后可以像在默认的非定制状态下一样调用dictConfig()
。 -
logging.config.fileConfig(fname, defaults=None, disable_existing_loggers=True)
从
configparser
格式的文件中读取日志配置。文件的格式应该按照配置文件格式描述。可以从应用程序中多次调用此函数,从而允许最终用户从各种预包装的配置中进行选择(如果开发人员提供一种机制来显示选择并加载所选择的配置)。-
fname
一个文件名、类文件对象或者从
RawConfigParser
派生的实例。如果传递了一个从RawConfigParser
派生的实例,则按原样使用它。否则,将实例化Configparser
,并由它从以 fname 传递的对象读取配置。如果它有一个readline()
方法,则假设它是一个类文件对象,并使用read_file()
进行读取;否则,假定它是一个文件名并传递给read()
。 -
defaults
可以在这个参数中指定传递给
ConfigParser
的默认值。 -
disable_existing_loggers
如果指定为 False,则保留启用此调用时存在的日志记录器。默认值为 True,因为这以向后兼容的方式支持以前行为。这种行为是禁用任何现有的非根日志记录器,除非在日志配置中显式地指定它们或它们的祖先。
从 3.4 版本开始,可以接受一个
RawConfigParser
子类的实例作为 fname 的值,这有助于:- 使用一个配置文件,其中日志记录配置只是整个应用程序配置的一部分。
- 使用从文件中读取的配置,然后由正在使用的应用程序(例如基于命令行参数或运行时环境的其他方面)修改,然后再传递给
fileConfig
。
-
-
logging.config.listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None)
在指定端口上启动套接字服务器,并侦听新配置。如果没有指定端口,则使用模块的默认
DEFAULT_LOGGING_CONFIG_PORT
。日志配置将作为适合由dictConfig()
或fileConfig()
处理的文件发送。返回一个Thread
实例,您可以在该实例上调用start()
启动服务器,并在适当的时候加入。要停止服务器,请调用stopListening()
。如果指定了 verify 参数,则它应该是一个可调用的参数,该参数应该验证通过套接字接收的字节是否有效以及是否应该被处理。这可以通过对通过套接字发送的内容进行加密和/或签名来实现,以便 verify 参数指定的可调用对象可以执行签名验证和/或解密。使用一个通过套接字接收的字节作为参数调用 verify 参数指定的可调用对象,并应该返回要处理的字节,或者不返回任何字节来表示应该丢弃这些字节。返回的字节可以与传入的字节相同(例如,只进行验证时),也可以完全不同(如果执行解密的话)。
要将配置发送到套接字,请读入配置文件并将其作为一个字节序列发送到套接字,这个字节序列以一个使用
struct.pack(‘>L’, n)
以二进制形式打包的四字节长度字符串作为前缀。注意:
由于配置的一部分是通过
eval()
传递的,因此使用该函数可能会使其用户面临安全风险。虽然该函数只绑定到localhost上的套接字,因此不接受来自远程机器的连接,但是在某些情况下,不受信任的代码可以在调用listen()
的进程的帐户下运行。具体来说,如果运行在用户不能互相信任的多用户机器上的进程调用listen()
,那么恶意用户可以安排在受害者用户的进程中运行任意代码,只需连接到受害者的listen()
套接字并发送一个配置,该配置可以运行攻击者希望在受害者的进程中执行的任何代码。如果使用默认端口,这尤其容易做到,但是即使使用了不同的端口,这也不难。为了避免发生这种情况的风险,可以将verify
参数应用到listen()
来防止应用未识别的配置。注意:
如果希望向侦听器发送不禁用现有日志记录程序的配置,则需要配置使用JSON格式,次配置将使用
dictConfig()
。此方法允许您在发送的配置中将disable_existing_loggers
指定为 False。 -
logging.config.stopListening()
停止使用
listen()
调用创建的侦听服务器。这通常在对listen()
的返回值调用join()
之前调用。
配置字典模式
描述日志配置需要列出要创建的各种对象及其之间的连接。例如,您可以创建一个名为 console
的处理程序,然后说名为 startup
的日志程序将向 console
处理程序发送消息。这些对象并不局限于日志模块提供的对象,因为您可以编写自己的格式化程序或处理程序类。这些类的参数可能还需要包括外部对象,如 sys.stderr
。描述这些对象和连接的语法在下面的 对象连接 中定义。
字典模式细节
传递给 dictConfig()
的字典应该包含下面的键:
- version:要设置为表示模式版本的整数值。目前唯一的有效值是1,但是有了这个键,模式就可以在保持向后兼容性的同时不断发展。
所有其他键都是可选的,但是如果存在,它们将被解释为下面描述的那样。在下面提到配置字典 的所有情况下,都将检查特殊的 ()
键,以查看是否需要自定义实例化。如果是,则使用下面 用户定义对象 中描述的机制创建实例;否则,上下文将用于确定要实例化什么。
-
formatters:对应的值应该是字典,其中每个键是格式化程序id,每个值是描述如何配置相应格式化程序实例的字典。
配置字典将搜索键
format
和datefmt
(默认值为None)并使用这些来构造Formatter
实例。 -
filters:对应的值是字典,其中每个键是过滤器id,每个值是描述如何配置相应过滤器实例的字典。
配置字典将搜索键
name
(默认为空字符串),并用于构造logging.Filter
实例。 -
handlers:对应的值是字典,其中每个键是处理程序id,每个值是描述如何配置相应处理程序实例的字典。
配置字典将搜索下面的键:
class
(强制的):这是 handler 类的完全限定名。level
(可选的):日志处理器的级别。formatter
(可选的):此日志处理器的格式化器的id。filters
(可选的):此日志处理器的格式化器的 id 的列表。
所有的其他键都做为日志处理器的构造方法的关键字参数传递。
例如,下面的片段:
handlers: console: class : logging.StreamHandler formatter: brief level : INFO filters: [allow_foo] stream : ext://sys.stdout file: class : logging.handlers.RotatingFileHandler formatter: precise filename: logconfig.log maxBytes: 1024 backupCount: 3
id为 console 的日志处理器实例化为
logging.StreamHandler
,使用sys.stdout
作为底层的流。id为 file 的日志处理器使用关键字参数filename=logconfig.log, maxBytes=1024, backupCount=3
实例化为logging.handlers.RotatingFileHandler
。 -
loggers:对应的值将是一个字典,其中每个键是一个日志记录器名称,每个值是一个描述如何配置相应的日志记录器实例的字典。
配置字典将搜索下面的键:
level
(可选的):日志记录器的级别。propagate
(可选的):日志记录器的传播设置。filters
(可选的):日志记录器的过滤器的id列表。handlers
(可选的):日志记录器的处理器的id列表。
指定的日志记录器将根据指定的级别、传播设置、过滤器和处理程序进行配置。
-
root:根日志记录器的配置。配置的处理将与任何日志程序一样,只是传播设置不适用。
-
incremental:是否将配置解释为对现有配置的增量配置。此值默认为 False,这意味着指定的配置将使用与现有
fileConfig()
API使用的相同语义替换现有配置。如果指定的值是 True,配置会像 增量配置 中描述的那样进行处理。
-
disable_existing_loggers:是否禁用任何现有的非根日志记录程序。此设置镜像
fileConfig()
中同名的参数。如果没有,此参数默认为 True。如果 incremental 为真,则忽略此值。
增量配置
很难为增量配置提供完全的灵活性。例如,由于筛选器和格式化器等对象是匿名的,一旦设置了配置,就不可能在扩展配置时引用这些匿名对象。
此外,一旦设置好配置,在运行时任意改变记录器、处理程序、过滤器、格式化程序的对象图并没有令人信服的理由;日志记录器和处理程序的冗长(verbosity)可以通过设置级别(对于日志记录器,通过传播标志)来控制。在多线程环境中,以安全的方式任意更改对象图是有问题的;虽然不是不可能,但是它所带来的好处并不值得为实现增加复杂性。
因此,当配置字典的 incremental
键存在且为 True 时,系统将完全忽略任何格式化器和过滤器条目,只处理处理程序条目中的 level 设置,以及 loggers 和 root 条目中的level 和 propagate 设置。
使用配置字典中的值可以将配置作为 pickled 的字典通过网络发送到套接字侦听器。因此,可以随着时间的推移更改长时间运行的应用程序的详细日志记录,而不需要停止和重新启动应用程序。
对象连接
模式描述了一组日志对象——记录器、处理程序、格式化程序、过滤器——它们在对象图中相互连接。因此,模式需要表示对象之间的连接。例如,假设,一旦配置好,一个特定的日志程序就附加了一个特定的处理程序。对于本讨论的目的,我们可以说日志记录器表示源,而处理程序表示两者之间连接的目的地。当然,在已配置的对象中,这是由持有对处理程序引用的日志程序表示的。在配置字典中,这是通过给每个目标对象一个明确标识它的id来实现的,然后在源对象的配置中使用该id来指示具有该id的源对象和目标对象之间存在连接。
所以,例如,考虑下面的YAML片段:
formatters:
brief:
# configuration for formatter with id 'brief' goes here
precise:
# configuration for formatter with id 'precise' goes here
handlers:
h1: #This is an id
# configuration of handler with id 'h1' goes here
formatter: brief
h2: #This is another id
# configuration of handler with id 'h2' goes here
formatter: precise
loggers:
foo.bar.baz:
# other configuration for logger 'foo.bar.baz'
handlers: [h1, h2]
(注意:这里使用YAML,因为它比字典的等效Python源形式更具可读性。)
日志记录器的id是日志记录器的名称,可以通过编程方式使用这些名称来获得对这些日志记录器的引用,例如 foo.bar.baz
。格式化程序和过滤器的id可以是任何字符串值(如上面的 brief
、precise
),它们是瞬态的,因为它们只对处理配置字典有意义,并用于确定对象之间的连接,在配置调用完成时不持久化。
上面的代码片段表明名为 foo.bar
的日志程序。baz
应该有两个附加的处理程序,它们由处理程序id h1
和 h2
描述。h1
的格式化程序是由id brief
描述的,h2的格式化程序是由id precise
描述的。
用户定义对象
该模式支持处理程序、过滤器和格式化程序的用户定义对象。(日志记录器不需要为不同的实例拥有不同的类型,因此在这个配置模式中不支持用户定义的日志记录器类。)
要配置的对象由详细描述其配置的字典描述。在某些地方,日志系统将能够从上下文推断对象将如何实例化,但是当要实例化用户定义的对象时,系统将不知道如何这样做。为了为用户定义的对象实例化提供完全的灵活性,用户需要提供一个 factory
——一个可调用的对象,它用配置字典调用,并返回实例化的对象。这是通过在特殊键 ()
下提供到工厂的绝对导入路径来表示的。下面是一个具体的例子:
formatters:
brief:
format: '%(message)s'
default:
format: '%(asctime)s %(levelname)-8s %(name)-15s %(message)s'
datefmt: '%Y-%m-%d %H:%M:%S'
custom:
(): my.package.customFormatterFactory
bar: baz
spam: 99.9
answer: 42
上面的YAML代码段定义了三个格式化器。第一个是id为 brief
的且具有指定格式字符串的标准的 logging.Formatter
实例。第二个是id为 default
,具有更长格式,显式定义了时间格式,并将导致 logging.Formatter
使用这两个格式字符串进行初始化。以Python源代码形式显示,brief
和 default
格式化器分别具有配置子字典:
{
'format' : '%(message)s'
}
和
{
'format' : '%(asctime)s %(levelname)-8s %(name)-15s %(message)s',
'datefmt' : '%Y-%m-%d %H:%M:%S'
}
由于这些字典不包含特殊的键 ()
,所以实例化是从上下文推断的:结果是创建了标准的 logging.Formatter
实例。第三个格式化程序的配置子字典是:
{
'()' : 'my.package.customFormatterFactory',
'bar' : 'baz',
'spam' : 99.9,
'answer' : 42
}
它包含特殊的键 ()
,这意味着需要用户定义的实例化。在本例中,将使用指定的工厂可调用对象。如果它是一个实际的可调用函数,那么它将被直接使用;否则,如果您指定一个字符串(如示例中所示),那么实际的可调用函数将使用常规导入机制来定位。调用可调用对象时,配置子字典中的 其余 项将作为关键字参数。在上面的例子中,id为 defualt
的格式化程序将被假设为由调用返回:
my.package.customFormatterFactory(bar='baz', spam=99.9, answer=42)
键 ()
被用作特殊键,因为它不是一个有效的关键字参数名,因此不会与调用中使用的关键字参数的名称冲突。()
还充当助记符,对应的值是可调用的。
访问外部对象
有时,配置需要引用配置外部的对象,例如 sys.stderr
。如果配置字典是使用Python代码构造的,这很简单,但是当通过文本文件(例如JSON、YAML)提供配置时,就会出现问题。在文本文件中,没有标准的方法来从字符串字面量 'sys.stderr'
中区分 sys.stderr
。为了便于区分,配置系统在字符串值中查找特定的前缀,并对它们进行特殊处理。例如,如果文本字符串 'ext://sys.stderr'
在配置中作为一个值提供,那么 'ext://'
将被删除,其余的值将使用常规导入机制处理。
处理这样的前缀都是在某种程度上类似于协议处理:有一个通用的机制,寻找匹配正则表达式 ^(?P<prefix>[a-z]+)://(?P<suffix>.*)$
的前缀,其中,如果 prefix
被识别,则以依赖前缀(prefix-dependent)的方式处理 suffix
,并且处理的结果将替换字符串值。如果前缀不能被识别,则字符串值将保持原样。
访问内部对象
除了外部对象之外,有时还需要引用配置中的对象。这将由配置系统隐式地为它所知道的内容完成。例如,日志记录器或日志处理器中的 level
的字符串值 DEBUG
将自动转换为值 logging.DEBUG
,并且,过滤器和格式化器项将使用对象id并解析到适当的目标对象。
但是,对于日志模块不知道的用户定义对象,需要一种更通用的机制。例如,考虑 log .handlers.MemoryHandler
,它接受另一个要委托给的处理程序的 target
参数。由于系统已经知道这个类,那么在配置中,给定的目标只需要是相关目标处理程序的对象id,系统将从id解析到处理程序。如果一个用户定义的 my.package.MyHandler
有一个 alternate
处理器,配置系统不会知道 alternate
引用了一个处理器。为配合这一点,一般的 resolution system 可让用户指定:
handlers:
file:
# configuration of file handler goes here
custom:
(): my.package.MyHandler
alternate: cfg://handlers.file
字符串字面量 'cfg://handlers.file'
将会以类似的方式使用 ext://
前缀进行解析,但要查看配置本身而不是导入名称空间。该机制允许按点或索引访问,其方法与struts .format
提供的方法类似。因此,给定以下代码片段:
handlers:
email:
class: logging.handlers.SMTPHandler
mailhost: localhost
fromaddr: my_app@domain.tld
toaddrs:
- support_team@domain.tld
- dev_team@domain.tld
subject: Houston, we have a problem.
在配置中,字符串 'cfg://handlers'
将会解析成字典中键为 handlers
的项;字符串 'cfg://handlers.email'
将会解析成 handlers
字典中键为 email
的项。字符串 'cfg://handlers.email.toaddrs[1]'
将会解析为 'dev_team.domain.tld'
,字符串 'cfg://handlers.email.toaddrs[0]'
将会解析成值 'support_team@domain.tld’
。subject
的值可以通过 'cfg://handlers.email.subject’
或 等价的 'cfg://handlers.email[subject]'
访问。后一种形式只在键包含空格或者非数字字母字符时使用。如果索引值仅由十进制数字组成,则将尝试使用相应的整数值进行访问,如果需要,将回退到字符串值。
给定一个将被解析成 config_dict['handlers']['myhandler']['mykey']['123']
的字符串 'cfg://handlers.myhandler.mykey.123'
。如果这个字符串指定为 'cfg://handlers.myhandler.mykey[123]'
,系统会尝试从 config_dict['handlers']['myhandler']['mykey'][123]
中检索值,并在失败时会回退到 config_dict['handlers']['myhandler']['mykey']['123']
。
导入解析和自定义导入程序
默认情况下,导入解析使用内置的 __import__()
函数进行导入。您可能想要用您自己的导入机制来替换它:如果是这样,您可以替换 DictConfigurator
的导入器属性或者它的超类 BaseConfigurator
类。但是,您需要小心,因为函数是通过描述符从类中访问的。如果使用Python可调用函数来执行导入,并且希望在类级别而不是实例级别定义它,则需要使用 staticmethod()
对其进行包装。例如:
from importlib import import_module
from logging.config import BaseConfigurator
BaseConfigurator.importer = staticmethod(import_module)
如果要在配置器 instance
上设置导入的可调用对象,则不需要使用 staticmethod()
进行包装。
配置文件格式
fileConfig()
所理解的配置文件格式基于 configparser
功能。该文件必须包含称为 [loggers]
、[handlers]
和 [formatters]
的部分,这些部分通过名称标识文件中定义的每种类型的实体。对于每个这样的实体,都有一个单独的部分来标识该实体是如何配置的。因此,对于 [loggers]
部分中名为 log01
的日志程序,相关的配置细节保存在 [logger_log01]
部分中。类似地,[handlers]
部分中名为 hand01
的处理程序将在 [handler_hand01]
部分中保存其配置,而 [formatters]
部分中名为form01
的格式化程序将在 [formatter_form01]
部分中指定其配置。根日志程序配置必须在一个名为[logger_root]
的部分中指定。
注意:
fileConfig()
API 比dictConfig()
API更老,并且不提供覆盖日志记录某些方面的功能。例如,您不能使用fileConfig()
配置Filter
对象,该对象用于过滤超出简单整数级别的消息。如果需要在日志配置中包含Filter
实例,则需要使用dictConfig()
。注意,将来对配置功能的增强将添加到dictConfig()
中,因此值得考虑在方便时转换到这个新的API。
下面给出了文件中这些部分的示例:
[loggers]
keys=root,log02,log03,log04,log05,log06,log07
[handlers]
keys=hand01,hand02,hand03,hand04,hand05,hand06,hand07,hand08,hand09
[formatters]
keys=form01,form02,form03,form04,form05,form06,form07,form08,form09
根日志记录器必须指定一个级别和一个日志处理器的列表。一个根日志记录器部分的例子如下所示:
[logger_root]
level=NOTSET
handlers=hand01
level
条目可以是 DEBUG
、INFO
、WARNING
、ERROR
、CRITICAL
或 NOTSET
中的一个。仅对于根日志记录器,NOTSET
意味着将记录所有消息。level
值在日志包的名称空间上下文中使用 eval()
进行求值。
handlers
条目是一个逗号分隔的处理程序名称列表,它必须出现在 [handlers]
部分中。这些名称必须出现在 [handlers]
部分中,并且在配置文件中有相应的部分。
对于根记录器之外的记录器,需要一些额外的信息。下面的例子说明了这一点:
[logger_parser]
level=DEBUG
handlers=hand01
propagate=1
qualname=compiler.parser
level
和 handlers
条目被解释为根日志记录程序的条目,除非非根日志记录程序的级别指定为 NOTSET
,否则系统会咨询层次结构更高的日志记录程序,以确定日志记录程序的有效级别。将 propagate
条目设置为1,以指示消息必须从该日志记录器传播到日志记录器层次结构较高的处理程序,或设置为0,以指示消息 没有 传播到层次结构较高的处理程序。qualname
条目是日志程序的层次通道名称,也就是说应用程序用于获取日志程序的名称。
下面举例说明指定处理程序配置的部分。
[handler_hand01]
class=StreamHandler
level=NOTSET
formatter=form01
args=(sys.stdout,)
class
条目指示日志处理器的类(在日志包的名称空间中使用 eval()
确定)。level
被解释为用于日志记录器,而 NOTSET
被理解为 “记录所有内容”。
formatter
条目指示这个日志处理器的格式化器的键名。如果为空,则使用默认的格式化器 (logging._defualtFormatter
)。如果指定了名称,它必须出现在 [formatters]
部分,并且必须有配置文件中有一个对应的部分。
当 eval()
在日志记录包的名称空间上下文中求值时,args
条目是处理程序类构造函数的参数列表。请参考相关处理程序的构造函数,或参考下面的示例,了解典型条目是如何构造的。如果没有提供,它默认为 ()
。
当 eval()
在日志记录包的名称空间上下文中求值时,可选的 kwargs
条目是处理程序类构造函数关键字参数字典。如果没有提供,默认为 {}
。
[handler_hand02]
class=FileHandler
level=DEBUG
formatter=form02
args=('python.log', 'w')
[handler_hand03]
class=handlers.SocketHandler
level=INFO
formatter=form03
args=('localhost', handlers.DEFAULT_TCP_LOGGING_PORT)
[handler_hand04]
class=handlers.DatagramHandler
level=WARN
formatter=form04
args=('localhost', handlers.DEFAULT_UDP_LOGGING_PORT)
[handler_hand05]
class=handlers.SysLogHandler
level=ERROR
formatter=form05
args=(('localhost', handlers.SYSLOG_UDP_PORT), handlers.SysLogHandler.LOG_USER)
[handler_hand06]
class=handlers.NTEventLogHandler
level=CRITICAL
formatter=form06
args=('Python Application', '', 'Application')
[handler_hand07]
class=handlers.SMTPHandler
level=WARN
formatter=form07
args=('localhost', 'from@abc', ['user1@abc', 'user2@xyz'], 'Logger Subject')
kwargs={'timeout': 10.0}
[handler_hand08]
class=handlers.MemoryHandler
level=NOTSET
formatter=form08
target=
args=(10, ERROR)
[handler_hand09]
class=handlers.HTTPHandler
level=NOTSET
formatter=form09
args=('localhost:9022', '/log', 'GET')
kwargs={'secure': True}
指定格式化程序配置的部分的类型如下:
[formatter_form01]
format=F1 %(asctime)s %(levelname)s %(message)s
datefmt=
class=logging.Formatter
formatter
条目是总体格式字符串,而 datefmt
条目是与 strftime()
兼容的日期/时间格式字符串。如果为空,该包将替换几乎等同于指定日期格式字符串 '%Y-%m-%d %H:% m:%S’
的内容。这种格式还指定了毫秒,用逗号分隔符将毫秒附加到使用上述格式字符串的结果之后。这种格式的示例时间是 2003-01-23 00:29:29 50,411
。
class
条目是可选的。它指示格式化程序的类的名称(作为点状模块和类名)。此选项对于实例化 Formatter
子类非常有用。Formatter
的子类可以以扩展或压缩格式显示异常回溯。
注意:
由于使用如上所述的
eval()
,使用listen()
通过套接字发送和接收配置会带来潜在的安全风险。风险仅限于没有相互信任的多个用户在同一台机器上运行代码;有关更多信息,请参见 listen() 文档。