天天看點

學習 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