# 16.7. `logging.config` - 记录配置

源代码： Lib / logging / config.py

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

## 16.7.1. Configuration functions

下列函数配置日志模块。它们都在`logging.config`模块中。这些使用是可选的—你可以使用这些函数配置日志模块或者通过调用定义在`logging`模块中的主要API来配置，或者通过定义`logging` 和`logging.handlers`中的handlers来配置日志模块。

`logging.config.``dictConfig`(config)

  > Takes the logging configuration from a dictionary. The contents of this dictionary are described in Configuration dictionary schema below.

  > If an error is encountered during configuration, this function will raise a `ValueError`, `TypeError`, `AttributeError` or `ImportError` with a suitably descriptive message. The following is a (possibly incomplete) list of conditions which will raise an error:

  > - A `level` which is not a string or which is a string not corresponding to an actual logging level.
  > - A `propagate` value which is not a boolean.
  > - An id which does not have a corresponding destination.
  > - A non-existent handler id found during an incremental call.
  > - An invalid logger name.
  > - Inability to resolve to an internal or external object.

  > Parsing is performed by the `DictConfigurator` class, whose constructor is passed the dictionary used for configuration, and has a `configure()` method. The `logging.config` module has a callable attribute `dictConfigClass` which is initially set to `DictConfigurator`. You can replace the value of `dictConfigClass` with a suitable implementation of your own.

  > `dictConfig()` calls `dictConfigClass` passing the specified dictionary, and then calls the `configure()` method on the returned object to put the configuration into effect:

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

  > For example, a subclass of `DictConfigurator` could call `DictConfigurator.__init__()` in its own `__init__()`, then set up custom prefixes which would be usable in the subsequent `configure()` call. `dictConfigClass` would be bound to this new subclass, and then `dictConfig()` could be called exactly as in the default, uncustomized state.

  版本3.2中的新功能。

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

  从`configparser` -format文件读取日志记录配置。文件格式描述于Configuration file format。该函数可以从应用中多次调用，允许最终用户可以从各种预先配置好的配置中选择（如果开发者提供了展示配置并装载选择的配置的机制的话）。

  参数： | fname - 文件名或类文件对象，或从`RawConfigParser`派生的实例。If a `RawConfigParser`-derived instance is passed, it is used as is. 否则，将实例化`Configparser`，并且从`fname`中传递的对象读取配置。如果有一个`readline()`方法，则假定它是一个类文件对象，并使用`read_file()`读取；否则，假定它是一个文件名，并传递给`read()`。 defaults – 该参数指明了传给ConfigParser的默认值。 disable_existing_loggers - 如果指定为`False`，则在进行此调用时存在的记录器将保持启用状态。默认值为`True`以保证老的行为向前兼容。该行为禁用已存的logger，除非它们或者它们的祖先明确的出现在日志配置中。

  在版本3.4中更改： `RawConfigParser`的子类的实例现在被接受为`fname`的值。这有助于：

  - 使用配置文件，其中日志配置只是整个应用程序配置的一部分。
  - 使用从文件读取的配置，然后由使用应用程序修改（例如，基于命令行参数或运行时环境的其他方面），然后传递给`fileConfig`。

`logging.config.``listen`(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None)

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

  如果指定，`verify`参数应该是一个可调用，它应该验证在套接字上接收的字节是否有效并且应该被处理。这可以通过对通过套接字发送的内容进行加密和/或签名，使得`verify`可调用可以执行声明验证和/或解密来完成。使用单个参数调用`verify` callable - 跨套接字接收的字节，并应返回要处理的字节，或None，以指示应丢弃字节。返回的字节可以与以字节传递的字节相同（例如，当只进行验证时），或者它们可以完全不同（可能是如果执行解密）。

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

  注意

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

  在版本3.4中更改：添加了`verify`参数。

`logging.config.``stopListening`()

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

## 16.7.2. Configuration dictionary schema

描述日志记录配置需要列出要创建的各种对象以及它们之间的连接；例如，您可以创建一个名为“console”的处理程序，然后说，名为“startup”的记录器将其消息发送到“控制台”处理程序。并不限于`logging`模块所提供的对象，因为你可以写自己的formatter或者handler类。 这些类的参数也许需要包含`sys.stderr`这样的外部对象。描述对象将其连接的语法定义于下面的Object connections。

### 16.7.2.1. Dictionary Schema Details

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

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

所有其它键都是可选的，但是如果存在，如下述解读。在下面如果提到了‘配置字典’，特殊键`'()'`将会被检查，以确定是否需要自定义的初始化。如果是，则使用以下User-defined objects中描述的机制创建实例；否则，上下文用于确定要实例化的内容。

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

  搜寻配置字典中的`format` 和 `datefmt`键，（默认为`None`），以构造`Formatter`实例。

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

  对配置的dict搜索密钥`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`的处理程序将使用`sys.stdout`作为基础流实例化为`logging.StreamHandler`。标识符为`file`的handler初始化为`logging.handlers.RotatingFileHandler`，`filename='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`，配置的处理描述于Incremental Configuration章节。

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

### 16.7.2.2. Incremental Configuration

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

此外，一旦配置被建立，在运行时任意地改变记录器，处理器，过滤器，格式化器的对象图就不是有说服力的情况；可以通过设置级别（以及在记录器的情况下，传播标志）来控制记录器和处理器的冗长性。以安全的方式任意地改变对象图在多线程环境中是有问题的；虽然不是不可能，但是它的好处并不值得它为实现增加的复杂性。

因此，当配置中`incremental`键值存在且为`True`，系统会完全的忽略任何的`formatters`和`filters`条目，只处理`level`条目中的`handlers`设置，以及`level`和`propagate`条目中的`loggers`和`root`设置。

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

### 16.7.2.3. Object connections

模式描述了一组日志对象 - 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的字典代码稍微更可读。）

记录器的id是记录器名称，其将被编程地用于获得对这些记录器的引用，例如。`foo.bar.baz`。formatter和filter的id可以是任何的字符串（如上面的`brief`、`precise`）且是暂态的，意味着它们只在处理配置字典时有意义，用来决定对象间的连接，一旦配置结束，它们就不存在了。

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

### 16.7.2.4. User-defined objects

该模式支持用户自定义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。第一个，标识`brief`，是具有指定格式字符串的标准`logging.Formatter`实例。第二个，id `default`，具有更长的格式，并且还显式地定义时间格式，并且将导致使用这两个格式字符串初始化`logging.Formatter`。以Python源码来看，formatter `brief`和`default`有配置子字典：

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

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

### 16.7.2.5. Access to external objects

偶尔配置需要引用外部对象，比如`sys.stderr`。如果配置dict是使用Python代码构造的，这是直接的，但是当通过文本文件（例如，文本文件）提供配置时出现问题。JSON，YAML）。在文本文件中，没有标准的方法来区分`sys.stderr`和字符串`'sys.stderr'`。为了区分，配置系统会在字符串值中查找特定的特殊前缀，并对其特殊对待。例如，如果配置值为`'ext://sys.stderr'`，`ext://`被移除，'sys.stderr'会被用来import。

The handling of such prefixes is done in a way analogous to protocol handling: there is a generic mechanism to look for prefixes which match the regular expression `^(?P<prefix>[a-z]+)://(?P<suffix>.*)$` whereby, if the `prefix` is recognised, the `suffix` is processed in a prefix-dependent manner and the result of the processing replaces the string value. 如果不识别prefix，则字符串的在值保持不变。

### 16.7.2.6. Access to internal objects

和外部对象一样，偶尔需要引用配置中的内部对象。如果配置系统知道这些对象，这就会隐式的完成。例如，记录器或处理程序中`level`的字符串值`'DEBUG'`将自动转换为值`logging.DEBUG`， `handlers`，`filters`和`formatter`条目将获取对象标识并解析为适当的目标对象。

然而对于不知道`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`解析成`email`字典中键为`handlers`的字典；依此类推。字符串`'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]'`的值。如果键中包含空格或者非字母数字字符时才需要后面一种形式。如果索引值只包含数字，将先尝试用对应的整数值来访问，然后才是用字符串来访问。

给定字符串`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']`。

### 16.7.2.7. Import resolution and custom importers

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

    from importlib import import_module
    from logging.config import BaseConfigurator

    BaseConfigurator.importer = staticmethod(import_module)

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

## 16.7.3. Configuration file format

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

注意

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

    [logger_root]
    level=NOTSET
    handlers=hand01

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

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

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

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

`level` 和 `handlers` 的解读和根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,)

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

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

`logging`是handler类构造函数的参数列表（在`args`包空间通过`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`是整体的格式化字符串，`datefmt`是`strftime()`兼容的日期/时间格式化字符串。如果为空，使用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。