15.8. logging.config — 日志配置

源代码: Lib/logging/config.py


该章描述用于配置日志模块的API。

15.8.1. 配置函数

下列函数配置日志模块。它们位于logging.config模块。使用它们是可选的 — 即可以使用这些函数来配置日志模块,也可以通过调用主API(定义于logging本身)并定义handler,这些handler声明于logging 或者 logging.handlers

logging.config.dictConfig(config)

从字典中得到日志配置。字典内容描述于下面的配置字典模式

如果配置过程中发生错误,该函数抛出ValueError, TypeError, AttributeError 或者 ImportError,以及合适的描述性消息。下面是抛出错误的条件列表(可能不完整):

  • A level which is not a string or which is a string not corresponding to an actual logging level.
  • propagate不是个布尔值。
  • 没有对应目的地的标识符。
  • 在增量调用中发现了不存在的handler标识符。
  • 不合法的logger名字。
  • 无法解析一个内部或者外部的对象。

DictConfigurator类完成解析,用于配置的字典传给它的构造函数,该类有个configure()方法。logging.config模块有个可调用的属性dictConfigClass,其初始设为DictConfigurator可以用自己的实现来替换dictConfigClass的值。

dictConfig()调用dictConfigClass传给它指明的字典,然后在返回的对象上调用configure()方法来使得配置生效:

def dictConfig(config):
    dictConfigClass(config).configure()

例如,DictConfigurator可以在其__init__()中调用DictConfigurator.__init__(),然后设置自定义前缀,可以用于接下来的configure()调用中。dictConfigClass可以绑定到该子类,然后可以像默认的、未定制的方式那样调用dictConfig()

出现于版本2.7。

logging.config.fileConfig(fname, defaults=None, disable_existing_loggers=True)

从名为fname的文件中读取configparser格式的日志配置。文件格式描述于配置文件格式该函数可以从应用中多次调用,允许最终用户可以从各种预先配置好的配置中选择(如果开发者提供了展示配置并装载选择的配置的机制的话)。

Parameters:
  • defaults – 该参数指明了传给ConfigParser的默认值。
  • disable_existing_loggers – 如果为False,调用此函数时已存的logger被保留。默认值为True以保证老的行为向前兼容。该行为禁用已存的logger,除非它们或者它们的祖先明确的出现在日志配置中。

改变于版本2.6:添加了disable_existing_loggers关键字参数。在此之前已存的logger总是被禁用。

logging.config.listen(port=DEFAULT_LOGGING_CONFIG_PORT)

在指定的端口起一个socket服务器,以监听新的配置。如果没有指定端口,使用模块默认的DEFAULT_LOGGING_CONFIG_PORT日志配置以文件形式发送,可以用fileConfig()处理。返回Thread实例,可以在该线程上调用start()来启动服务器,可以在合适的时候join()该线程。调用stopListening()以停止服务器。

要向socket发送配置,从配置文件中读入配置,将其以字节流的方式发送至socket,最开始为4字节长的字节流的二进制长度,即struct.pack('>L', n)

注意

因为部分配置会传给eval(),使用该函数可能有安全风险。当该函数只绑定于localhost的socket,也不接受远端机器的连接,调用listen()的进程的帐号就有可能运行不受信任的代码。特别的,如果调用listen()的进程运行于多用户机器,而这些用户彼此并不信任,那么一个恶意用户可以在受害用户的进程中运行本质上来说任意的代码,只需要连接到受害用户的listen() socket,然后发送配置,其实是攻击者想要在受害者进程中执行的任何代码。如果使用默认端口这尤为容易,就算是用了不同的端口也不算太难。

logging.config.stopListening()

停止listen()返回的服务器的监听。典型的在listen()的返回值上调用join()之前调用该函数。

15.8.2. 配置字典模式

描述日志配置需要列出要创建的各种对象及其它们彼此的连接;例如,你可以创建名为‘console’的handler,然后配置名为‘startup’的logger,它将消息发往‘console’ handler。并不限于logging模块所提供的对象,因为你可以写自己的formatter或者handler类。 这些类的参数也许需要包含sys.stderr这样的外部对象。描述对象将其连接的语法定义于下面的对象连接

15.8.2.1. 字典模式细节

传递给dictConfig()的字典必需包含下列键值:

  • version - 表示模式版本的整数值。目前唯一的有效值为1,但是有这个键允许模式演化,同时保留了向前兼容性。

所有其它键都是可选的,但是如果存在,如下述解读。在下面如果提到了‘配置字典’,特殊键'()'将会被检查,以确定是否需要自定义的初始化。如果是,下述用户定义的对象的机制用于创建新实例;否则上下文被用来决定怎样初始化。

  • formatters - 对应的值是个字典,其每一个键都是formatter标识符,每一个值又是个字典,描述了如何配置对应的 Formatter 实例。

    搜寻配置字典中的formatdatefmt键,(默认为None),以构造Formatter实例。

  • filters - 对应的值是个字典,其每一个键都是filter标识符,每一个值又是个字典,描述了如何配置对应的 Filter 实例。

    搜寻配置字典中的name键,(默认为空字符串)以构造logging.Filter实例。

  • handlers - 对应的值是个字典,其每一个键都是handler标识符,每一个值又是个字典,描述了如何配置对应的Handler实例。

    搜寻配置字典中的下述键:

    • class (必需的)。handler类的完全限定名。
    • level (可选的)。handler的级别。
    • formatter (可选的)。该handler的formatter的标识符。
    • filters (可选的)。该handler的filter的标识符的列表。

    所有其它键被当作关键字参数传给handler的构造函数。例如,给定如下代码片段:

    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

    标识符为console的handler初始化为logging.StreamHandler使用sys.stdout作为底层的流。标识符为file的handler初始化为logging.handlers.RotatingFileHandlerfilename='logconfig.log', maxBytes=1024, backupCount=3为其构造函数的参数。

  • loggers - 对应的值是个字典,其每一个键都是logger的名字,每一个值又是个字典,描述了如何配置对应的Logger实例。

    搜寻配置字典中的下述键:

    • level (可选的)。logger的级别。
    • propagate (可选的)。logger的传播设置。
    • filters (可选的)。logger的filter的标识符的列表。
    • handlers (可选的)。logger的handler的标识符的列表。

    指定的logger通过级别、传播、filter和handler来配置。

  • root - 根logger的配置。配置处理和其它logger一样,propagate设定不可用。

  • incremental - 配置是否是当前配置的增量配置。默认值为False,意味着指定的配置取代已存的配置,就和fileConfig()API的语义一样。

    如果为True,配置的处理描述于增量配置章节。

  • disable_existing_loggers - 是否禁用已存的logger。该设定模仿了fileConfig()同名参数的行为。如果缺少该参数,默认为True如果incrementalTrue,该值被忽略。

15.8.2.2. 增量配置

很难为增量配置提供完整的弹性。例如,因为filter和formatter这样的对象是匿名的,一旦建立了配置,就很难在配置的时候去引用这些匿名对象。

进一步,一旦建立了配置,在运行期任意修改logger、handler、filter、formatter的对象图并不是特别吸引人;logger和handler的冗余度可以通过设置级别来控制(对于logger还可以设置传播标志位)。在多线程的环境中,以安全的方式任意修改对象图是容易产生问题的;不是不可能,只是好处对于实现的复杂度来说得不偿失。

因此,当配置中incremental键值存在且为True,系统会完全的忽略任何的formattersfilters条目,只处理handlers条目中的level设置,以及loggersroot条目中的levelpropagate设置。

配置可以以序列化的字典通过网络发往套接字监听器。因此,长时间运行的应用的日志冗余度可以随时间修改而不需要重启应用。

15.8.2.3. 对象连接

模式描述了一组日志对象 - logger、handler、formatter、filter - 它们在一个对象图中彼此连接。因此模式需要表示对象间的连接。例如,一旦配置好,一个特定的logger会附上一个handler。为了便于讨论,我们定义两个对象的连接中,logger是源头,handler是目的地。在配置好的对象中,这表示logger持有handler的一个引用。为了达到这个目的,在配置字典中每个目的对象都有一个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的字典代码稍微更可读。)

logger的id是logger的名字,可以用变成的方式通过这些名字得到对应的logger的引用,例如foo.bar.bazformatter和filter的id可以是任何的字符串(如上面的briefprecise)且是暂态的,意味着它们只在处理配置字典时有意义,用来决定对象间的连接,一旦配置结束,它们就不存在了。

上述片段表示名为foo.bar.baz的logger附有两个handler,分别是id为h1h2的handler来描述。h1的fotmatter由id为brief的formatter来描述,h2的formatter则是id为precise的formatter。

15.8.2.4. 用户定义的对象

该模式支持用户自定义handler、filter和formatter对象。(对于logger不需要不同的实例有不同的类型,所以该配置模式不支持用户定义的logger类。)

对象的配置是由字典来具体描述的。某些情况下日志系统可以从上下文推断出如何实例化一个对象,但是当实例化一个用户定义的对象的时候,系统就不知道怎么做了。为了为用户定义的对象的实例化提供完整的弹性,用户需要提供一个‘工厂’——一个可调用对象,调用时以配置字典为参数,返回实例化的对象。这由键'()'及对应的工厂的绝对导入路径来表示。这里是个具体的例子:

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片段定义了三个formatter。第一个id为brief,是个标准的logging.Formatter实例,带指定的格式化字符串。第二个id为default ,实例化了一个logging.Formatter,带更长的格式化字符串,也明确定义了时间格式。以Python源码来看,formatter briefdefault有配置子字典:

{
  'format' : '%(message)s'
}

和:

{
  'format' : '%(asctime)s %(levelname)-8s %(name)-15s %(message)s',
  'datefmt' : '%Y-%m-%d %H:%M:%S'
}

且这些字典不需要包含特殊键'()',初始化从上下文中推断:创建了logging.Formatter的实例。第三个formatter的id为custom,其配置子字典是:

{
  '()' : 'my.package.customFormatterFactory',
  'bar' : 'baz',
  'spam' : 99.9,
  'answer' : 42
}

它包含了特殊键'()',意味着要实例化一个用户定义的对象。这种情况下,使用指定的工厂可调用对象。如果它是一个真的可调用对象就直接使用它,否则如果和例子中一样,指明了一个字符串,将通过常规的导入机制来确定真正的可调用对象。配置子字典中剩余的的项目会被当成关键字参数来调用可调用对象。上述例子中,id为custom的formatter会是下面调用的返回值:

my.package.customFormatterFactory(bar='baz', spam=99.9, answer=42)

使用'()'作为特殊键是因为它不是个合法的关键字参数名,也就不会和调用时的关键字参数的名字冲突。'()'也用来表示对应值是个可调用对象。

偶尔需要引用配置中的内部对象。如果配置系统知道这些对象,这就会隐式的完成。logger或者handler中的level值是字符串'DEBUG',它就会被自动转成logging.DEBUG;而handlers, filters 和 formatter条目中的对象id会解析成合适的目的对象。

偶尔配置需要引用外部对象,比如sys.stderr如果用Python代码来构造配置字典,这是很直接的事,但如果配置是由文本来提供,这就有些麻烦(如JSON, YAML)。在文本文件中,没有标准的方法来区分sys.stderr和字符串'sys.stderr'为了区分,配置系统会在字符串值中查找特定的特殊前缀,并对其特殊对待。例如,如果配置值为'ext://sys.stderr'ext://被移除,'sys.stderr'会被用来import。

该前缀处理类比于协议处理:和正则表达式^(?P<prefix>[a-z]+)://(?P<suffix>.*)$ 去匹配,如果识别prefix,则用前缀相关的方式来处理suffix部分,处理的结果以替换字符串值。如果不识别prefix,则字符串的在值保持不变。

15.8.2.6. 访问内部对象

和外部对象一样,偶尔需要引用配置中的内部对象。如果配置系统知道这些对象,这就会隐式的完成。logger或者handler中的level值是字符串'DEBUG',它就会被自动转成logging.DEBUGhandlers, filtersformatter条目中的对象id会解析成合适的目的对象。

然而对于不知道logging模块的用户定义的对象来说就需要一个更通用的机制。考虑logging.handlers.MemoryHandler,它有target参数,是另一个handler的代理。既然系统知道此类,配置中对于target只要给出相关目标handler的对象id,系统会根据id解析出对应的handler对象。然而对于用户定义的my.package.MyHandler,它有一个alternate handler,系统就不知道alternate引用handler。为了满足此场景,通用的解析系统允许用户指定:

handlers:
  file:
    # configuration of file handler goes here

  custom:
    (): my.package.MyHandler
    alternate: cfg://handlers.file

字符串'cfg://handlers.file'的解析类比于ext://的前缀处理,只是查找配置本身而不是导入空间。该机制允许点/索引访问,类似于str.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'可以通过'cfg://handlers.email.subject'或者'cfg://handlers.email[subject]'来访问subject的值。如果键中包含空格或者非字母数字字符时才需要后面一种形式。如果索引值只包含数字,将先尝试用对应的整数值来访问,然后才是用字符串来访问。

给定字符串cfg://handlers.myhandler.mykey.123,它将被解析成config_dict['handlers']['myhandler']['mykey']['123']如果是cfg://handlers.myhandler.mykey[123],系统先尝试config_dict['handlers']['myhandler']['mykey'][123],如果尝试失败再尝试config_dict['handlers']['myhandler']['mykey']['123']

15.8.2.7. 导入解析和自定义导入器

导入默认使用内置的__import__()函数。也许希望使用自己的导入机制:如果是这样,替换DictConfigurator或者其超类BaseConfiguratorimporter属性。然而从类的描述符访问函数时要小心。如果使用Python可调用对象来做导入,而且希望在类级别而不是实例级别来定义它,使用staticmethod()包装它。例如:

from importlib import import_module
from logging.config import BaseConfigurator

BaseConfigurator.importer = staticmethod(import_module)

如果在配置器实例上设置导入可调用对象,不需要用staticmethod()来包装它。

15.8.3. 配置文件格式

fileConfig()理解的配置文件格式基于configparser的功能。文件必需包含名为[loggers], [handlers][formatters]的节,标识了文件中定义的各种类型的对象的名字。对于每一个实体,有一个独立的节标识如何配置该实体。[loggers]节中有名为log01的logger,[logger_log01]的节中包含了相关的配置细节。类似的,[handlers]节中有名为hand01的handler,[handler_hand01]节中包含了相关的配置;[formatters]节中有名为form01的formatter,[formatter_form01]节中包含了相关的配置。[logger_root]节中包含了根logger的配置。

这些节的例子给定如下。

[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必需指定级别和handler列表。根logger节的例子给定如下。

[logger_root]
level=NOTSET
handlers=hand01

level可以是DEBUG, INFO, WARNING, ERROR, CRITICALNOTSET其中之一。NOTSET表示所有的消息都要记录,这只对根logger有效。级别值使用eval()logging包空间演算得到。

handlers是逗号分隔的handler名字的列表,这些必需出现在[handlers]节中。这些名字必需出现在[handlers]节中,且在配置文件中有对应的节。

对于非根logger的logger来说,需要一些额外的信息。如下例所示。

[logger_parser]
level=DEBUG
handlers=hand01
propagate=1
qualname=compiler.parser

levelhandlers 的解读和根logger的一样,如果非根logger的级别为NOTSET,系统参考高层次的logger来决定logger的有效级别。propagate为1表示将消息传递给高层次logger的handler,为0表示传播。qualname是logger在层次中的名字,应用通过该名字得到logger。

handler配置节如下所示。

[handler_hand01]
class=StreamHandler
level=NOTSET
formatter=form01
args=(sys.stdout,)

class表示handler的类(在logging包空间中通过eval()演算得到。)level的解读和logger的一样,NOTSET表示‘记录所有日志’。

改变于版本2.6:添加了对带点的模块和类名的handler类的解析的支持。

formatter表示了该handler的formatter的名字。如果为空,使用默认formatter (logging._defaultFormatter)。如果指定了名字,它必需出现在[formatters]节中,且在配置文件中有对应的节。

args是handler类构造函数的参数列表(在logging包空间通过eval()演算得到)。参考相关handler的构造函数,或者参考下面的例子,以了解典型的构造。

[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')

[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')

指定了格式化配置的节典型如下。

[formatter_form01]
format=F1 %(asctime)s %(levelname)s %(message)s
datefmt=
class=logging.Formatter

format是整体的格式化字符串,datefmtstrftime()兼容的日期/时间格式化字符串。如果为空,使用ISO8601格式,基本上等同于格式'%Y-%m-%d %H:%M:%S'ISO8601格式在上述格式的末尾指明了毫秒,中间有个逗号。ISO8601格式的例子:2003-01-23 00:29:50,411

class可选。它表示formatter类的名字(带点的模块和类名)。实例化Formatter子类时该选项很有用。Formatter子类可以用来表示详细/概要的异常回溯。

注意

如上所述由于使用了eval(),如果使用listen()来通过套接字发送和接受配置,这会导致潜在的安全风险。风险在于同一机器上互不信任的多个用户可以运行恶意代码。参见listen()文档以得到详细信息。

参见

模块logging
日志模块的API参考。
模块logging.handlers
日志模块所带的有用的handler。