不看日志資訊中那些檔案資訊、行号、程序号等附加資訊,我們來看看各個元件中的方法和實作,有助于我們去了解我們在配置時需要加那些配置資訊以及配置資訊的規則。
1. LogRecord
LogRecord
1.1 LogRecord.__init__
LogRecord.__init__
def __init__(self, name, level, pathname, lineno,
msg, args, exc_info, func=None, sinfo=None, **kwargs):
從聲明中,我們可以看到參數是行号、日志等級、路徑之類的資訊。
而具體的代碼實作中,有包括程序、線程、建立時間等資訊的擷取。
1.2 LogRecord.getMessage(self)
:
LogRecord.getMessage(self)
将使用者自己列印的資訊進行格式化。
def getMessage(self):
msg = str(self.msg)
if self.args:
msg = msg % self.args
return msg
2. Formatter
Formatter
在
Formatter
中,有以下方法:
2.1 Formatter.__init__
:
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)
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
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
三個基礎元件的解析。
參考:
- https://docs.python.org/3/howto/logging.html#logging-basic-tutorial
- https://docs.python.org/3/library/logging.html
- https://docs.python.org/3/howto/logging-cookbook.html#logging-cookbook