学习 python logging(5): `Formatter`、`Filter`、`LogRecord`解析

不看日志信息中那些文件信息、行号、进程号等附加信息,我们来看看各个组件中的方法和实现,有助于我们去了解我们在配置时需要加那些配置信息以及配置信息的规则。

1. LogRecord

1.1 LogRecord.__init__

def __init__(self, name, level, pathname, lineno,
                 msg, args, exc_info, func=None, sinfo=None, **kwargs):

从声明中,我们可以看到参数是行号、日志等级、路径之类的信息。
而具体的代码实现中,有包括进程、线程、创建时间等信息的获取。

1.2 LogRecord.getMessage(self):

将用户自己打印的信息进行格式化。

def getMessage(self):
    msg = str(self.msg)
    if self.args:
        msg = msg % self.args
    return msg

2. Formatter

Formatter 中,有以下方法:

2.1 Formatter.__init__:

def __init__(self, fmt=None, datefmt=None, style='%'):
    if style not in _STYLES:
            raise ValueError('Style must be one of: %s' % ','.join(
                             _STYLES.keys()))
        self._style = _STYLES[style][0](fmt)
        self._fmt = self._style._fmt
        self.datefmt = datefmt

可以看到,有个_STYLES ,定义如下:

class PercentStyle(object):

    default_format = '%(message)s'
    asctime_format = '%(asctime)s'
    asctime_search = '%(asctime)'

    def __init__(self, fmt):
        self._fmt = fmt or self.default_format

    def usesTime(self):
        return self._fmt.find(self.asctime_search) >= 0

    def format(self, record):
        return self._fmt % record.__dict__
....

BASIC_FORMAT = "%(levelname)s:%(name)s:%(message)s"

_STYLES = {
    '%': (PercentStyle, BASIC_FORMAT),
    '{': (StrFormatStyle, '{levelname}:{name}:{message}'),
    '$': (StringTemplateStyle, '${levelname}:${name}:${message}'),
}

在以上代码中,是以 PercentStyle 类为例,有三个方法:

  • __init__(self, fmt): 初始化日志格式化的格式, self._fmt, 有默认的格式。
  • usesTime: 查看日志格式化是否包含日志创建时间。
  • format(self, record): 格式化日志信息,使用 LogRecord__dict__ 信息

系统提供的格式化方式有三种, ‘%’, ‘{’, ‘$’, 这就是之前在简介中提到的三种方式由来:_STYLES.

所以, Formatter 做了三件事: 根据用户的 style, 初始化日志格式化所用的样式处理类,日期格式化样式。

2.2 format(self, record)

处理日志记录的入口,处理流程:

1. `record.message = record.getMessage()`
2. 时间处理:

    ```python
    if self.usesTime():
        record.asctime = self.formatTime(record, self.datefmt)
    ```
3. `formatTime`:
    优先自定义的`Formatter.datefmt`,如果没有,用默认的:

    ```python
    default_time_format = '%Y-%m-%d %H:%M:%S'
    default_msec_format = '%s,%03d'
    ```

4. formatMessage:

    ```python
    def formatMessage(self, record):
       return self._style.format(record)
    ```
    可以看到,是使用初始化时根据用户指定的格式化样式类的实例,来格式化日志记录信息的。
5. 拼接错误信息,堆栈信息等。

3. Filter

Filter 中有一个方法

def filter(self, record):
       """
       Determine if the specified record is to be logged.

       Is the specified record to be logged? Returns 0 for no, nonzero for
       yes. If deemed appropriate, the record may be modified in-place.
       """
       # 如果没有名称,可以输出日志
       if self.nlen == 0:
           return True
       # 如果和记录的名称相等,则可以输出日志
       elif self.name == record.name:
           return True
       # 如果记录的名称不在filter的头部,则不能输出
       elif record.name.find(self.name, 0, self.nlen) != 0:
           return False
       # 如果日志记录的名称最后一个为 `.`, 则可以输出,是日志子模块的概念.
       return (record.name[self.nlen] == ".")

我们可以自定义我们需要的 Filter 来做我们自己的特殊记录或是屏蔽敏感信息。官网给出的例子:

import logging
from random import choice

class ContextFilter(logging.Filter):
    """
    This is a filter which injects contextual information into the log.

    Rather than use actual contextual information, we just use random
    data in this demo.
    """

    USERS = ['jim', 'fred', 'sheila']
    IPS = ['123.231.231.123', '127.0.0.1', '192.168.0.1']

    def filter(self, record):

        record.ip = choice(ContextFilter.IPS)
        record.user = choice(ContextFilter.USERS)
        return True

if __name__ == '__main__':
    levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL)
    logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)-15s %(name)-5s %(levelname)-8s IP: %(ip)-15s User: %(user)-8s %(message)s')
    a1 = logging.getLogger('a.b.c')
    a2 = logging.getLogger('d.e.f')

    f = ContextFilter()
    a1.addFilter(f)
    a2.addFilter(f)
    a1.debug('A debug message')
    a1.info('An info message with %s', 'some parameters')
    for x in range(10):
        lvl = choice(levels)
        lvlname = logging.getLevelName(lvl)
        a2.log(lvl, 'A message at %s level with %d %s', lvlname, 2, 'parameters')

以上就是Formatter, Filter, LogRecord 三个基础组件的解析。


参考:

  1. https://docs.python.org/3/howto/logging.html#logging-basic-tutorial
  2. https://docs.python.org/3/library/logging.html
  3. https://docs.python.org/3/howto/logging-cookbook.html#logging-cookbook
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章