學習 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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章