python logging模块注册流程(以logging.config.dictConfig流程为例)
最近在查看python logging相关的模块,用到了dictConfig这一函数,尝试着跟踪了一下,捋一捋在调用dictConfig之后,这些都发生了什么,我想,用fileConfig的流程也差不多,先记载dictConfig的加载流程,后面跟踪源码了之后再做更新。
logging.config.dictconfig源码::
dictConfigClass = DictConfigurator
def dictConfig(config):
"""Configure logging using a dictionary."""
dictConfigClass(config).configure()
1. 代码中可以看到执行config操作的实际是DictConfigurator这一实例的configure方法.那么就涉及到DictConfigurator
实例的初始化:
class DictConfigurator(BaseConfigurator):
...
1.1 DictConfigurator
继承自BaseConfigurator
,查看其__init__
方法:
class BaseConfigurator(object):
....
def __init__(self, config):
self.config = ConvertingDict(config)
self.config.configurator = self
代码中有两点: ConvertingDict的实例self.config, 以及self.config.configurator引用了当前的Baseconfigurator
实例,
那么, 代码中的ConvertingDict(config)
是个什么?继续跟踪:
1.2 ConvertingDict
解析
class ConvertingDict(dict, ConvertingMixin):
"""A converting dictionary wrapper."""
def __getitem__(self, key):
value = dict.__getitem__(self, key)
return self.convert_with_key(key, value)
def get(self, key, default=None):
value = dict.get(self, key, default)
return self.convert_with_key(key, value)
def pop(self, key, default=None):
value = dict.pop(self, key, default)
return self.convert_with_key(key, value, replace=False)
可以看到,Convertingdict
继承自dict
, 重写了dict中的__getitem__
, get
, pop
三个方法,先调用dict原有的方法,然后对获取的值使用self.convert_with_key
进行转换,其代码在ConvertingMixin
类中。
1.3 ConvertingMixin
类
class ConvertingMixin(object):
"""For ConvertingXXX's, this mixin class provides common functions"""
def convert_with_key(self, key, value, replace=True):
result = self.configurator.convert(value)
#If the converted value is different, save for next time
if value is not result:
if replace:
self[key] = result
if type(result) in (ConvertingDict, ConvertingList,
ConvertingTuple):
result.parent = self
result.key = key
return result
def convert(self, value):
result = self.configurator.convert(value)
if value is not result:
if type(result) in (ConvertingDict, ConvertingList,
ConvertingTuple):
result.parent = self
return result
看到convert_with_key
方法中,每次都会调用它所引用的self.configurator
的convert
方法, 在这个流程中,self.configurator
就是当前的Baseconfigurator
实例.
1.4 Baseconfigurator
的convert
方法:
def convert(self, value):
"""
Convert values to an appropriate type. dicts, lists and tuples are
replaced by their converting alternatives. Strings are checked to
see if they have a conversion format and are converted if they do.
"""
if not isinstance(value, ConvertingDict) and isinstance(value, dict):
value = ConvertingDict(value)
value.configurator = self
elif not isinstance(value, ConvertingList) and isinstance(value, list):
value = ConvertingList(value)
value.configurator = self
elif not isinstance(value, ConvertingTuple) and\
isinstance(value, tuple):
value = ConvertingTuple(value)
value.configurator = self
elif isinstance(value, str): # str for py3k
m = self.CONVERT_PATTERN.match(value)
if m:
d = m.groupdict()
prefix = d['prefix']
converter = self.value_converters.get(prefix, None)
if converter:
suffix = d['suffix']
converter = getattr(self, converter)
value = converter(suffix)
return value
前面都是在递归,只有当value类型为str类型时,才会去进行转换,其他都是递归转换类型
1.4.1 str数据的转化
CONVERT_PATTERN = re.compile(r'^(?P[a-z]+)://(?P.*)$')
CONVERT_PATTERN
是一个正则,匹配类似于一个协议,如:prefix://suffix
.
接着value_converters
:
value_converters = {
'ext' : 'ext_convert',
'cfg' : 'cfg_convert',
}
可以看到,这对应了两种协议,ext
和cfg
. 看ext://
协议对应的ext_convert
方法:
def ext_convert(self, value):
"""Default converter for the ext:// protocol."""
return self.resolve(value)
它其实调用了self.resolve(value)
, self
是当前的的Baseconfigurator
, 查看resolve方法:
1.4.1 Baseconfigurator
的resolve
方法:
def resolve(self, s):
"""
Resolve strings to objects using standard import and attribute
syntax.
"""
name = s.split('.')
used = name.pop(0)
try:
found = self.importer(used)
for frag in name:
used += '.' + frag
try:
found = getattr(found, frag)
except AttributeError:
self.importer(used)
found = getattr(found, frag)
return found
except ImportError:
e, tb = sys.exc_info()[1:]
v = ValueError('Cannot resolve %r: %s' % (s, e))
v.__cause__, v.__traceback__ = e, tb
raise v
根据官方文档,ext
协议主要是用来引用其他的模块,例如自定义模块ext://a.b.c
, 分析源码:
先通过.
号分割出各个小段,self.importer
其实就是__import__
函数, resolve
方法会一直去循环加载模块,最后把模块返回。
1.5 总结一下:
-
Baseconfigurator
初始化过程是:初始化一个ConvertingDict
实例, 并将该实例的configurator
指向Baseconfigurator
自身, -
ConvertingDict
的作用: 以ext
为例,在获取对应的key:value值时,转化为协议对应的模块,以供使用。
2. DictConfigurator
的configure
方法:
源码如下:
"""Do the configuration."""
config = self.config
if 'version' not in config:
raise ValueError("dictionary doesn't specify a version")
if config['version'] != 1:
raise ValueError("Unsupported version: %s" % config['version'])
incremental = config.pop('incremental', False)
EMPTY_DICT = {}
logging._acquireLock()
try:
if incremental:
handlers = config.get('handlers', EMPTY_DICT)
for name in handlers:
if name not in logging._handlers:
raise ValueError('No handler found with '
'name %r' % name)
else:
try:
handler = logging._handlers[name]
handler_config = handlers[name]
level = handler_config.get('level', None)
if level:
handler.setLevel(logging._checkLevel(level))
except Exception as e:
raise ValueError('Unable to configure handler '
'%r: %s' % (name, e))
loggers = config.get('loggers', EMPTY_DICT)
for name in loggers:
try:
self.configure_logger(name, loggers[name], True)
except Exception as e:
raise ValueError('Unable to configure logger '
'%r: %s' % (name, e))
root = config.get('root', None)
if root:
try:
self.configure_root(root, True)
except Exception as e:
raise ValueError('Unable to configure root '
'logger: %s' % e)
else:
disable_existing = config.pop('disable_existing_loggers', True)
logging._handlers.clear()
del logging._handlerList[:]
# Do formatters first - they don't refer to anything else
formatters = config.get('formatters', EMPTY_DICT)
for name in formatters:
try:
formatters[name] = self.configure_formatter(
formatters[name])
except Exception as e:
raise ValueError('Unable to configure '
'formatter %r: %s' % (name, e))
# Next, do filters - they don't refer to anything else, either
filters = config.get('filters', EMPTY_DICT)
for name in filters:
try:
filters[name] = self.configure_filter(filters[name])
except Exception as e:
raise ValueError('Unable to configure '
'filter %r: %s' % (name, e))
# Next, do handlers - they refer to formatters and filters
# As handlers can refer to other handlers, sort the keys
# to allow a deterministic order of configuration
handlers = config.get('handlers', EMPTY_DICT)
deferred = []
for name in sorted(handlers):
try:
handler = self.configure_handler(handlers[name])
handler.name = name
handlers[name] = handler
except Exception as e:
if 'target not configured yet' in str(e):
deferred.append(name)
else:
raise ValueError('Unable to configure handler '
'%r: %s' % (name, e))
# Now do any that were deferred
for name in deferred:
try:
handler = self.configure_handler(handlers[name])
handler.name = name
handlers[name] = handler
except Exception as e:
raise ValueError('Unable to configure handler '
'%r: %s' % (name, e))
# Next, do loggers - they refer to handlers and filters
#we don't want to lose the existing loggers,
#since other threads may have pointers to them.
#existing is set to contain all existing loggers,
#and as we go through the new configuration we
#remove any which are configured. At the end,
#what's left in existing is the set of loggers
#which were in the previous configuration but
#which are not in the new configuration.
root = logging.root
existing = list(root.manager.loggerDict.keys())
#The list needs to be sorted so that we can
#avoid disabling child loggers of explicitly
#named loggers. With a sorted list it is easier
#to find the child loggers.
existing.sort()
#We'll keep the list of existing loggers
#which are children of named loggers here...
child_loggers = []
#now set up the new ones...
loggers = config.get('loggers', EMPTY_DICT)
for name in loggers:
if name in existing:
i = existing.index(name) + 1 # look after name
prefixed = name + "."
pflen = len(prefixed)
num_existing = len(existing)
while i < num_existing:
if existing[i][:pflen] == prefixed:
child_loggers.append(existing[i])
i += 1
existing.remove(name)
try:
self.configure_logger(name, loggers[name])
except Exception as e:
raise ValueError('Unable to configure logger '
'%r: %s' % (name, e))
#Disable any old loggers. There's no point deleting
#them as other threads may continue to hold references
#and by disabling them, you stop them doing any logging.
#However, don't disable children of named loggers, as that's
#probably not what was intended by the user.
#for log in existing:
# logger = root.manager.loggerDict[log]
# if log in child_loggers:
# logger.level = logging.NOTSET
# logger.handlers = []
# logger.propagate = True
# elif disable_existing:
# logger.disabled = True
_handle_existing_loggers(existing, child_loggers,
disable_existing)
# And finally, do the root logger
root = config.get('root', None)
if root:
try:
self.configure_root(root)
except Exception as e:
raise ValueError('Unable to configure root '
'logger: %s' % e)
finally:
logging._releaseLock()
2.1 incremental
从代码中可一个看到有个incremental
参数:
incremental = config.pop('incremental', False)
这个参数的作用是什么?
2.1.1 假设incremental
参数为True
其核心在这里:
for name in loggers:
try:
self.configure_logger(name, loggers[name], True)
except Exception as e:
raise ValueError('Unable to configure logger '
这个例子中, configure_logger
在DictConfigurator
中, 代码如下:
def common_logger_config(self, logger, config, incremental=False):
"""
Perform configuration which is common to root and non-root loggers.
"""
level = config.get('level', None)
if level is not None:
logger.setLevel(logging._checkLevel(level))
if not incremental:
#Remove any existing handlers
for h in logger.handlers[:]:
logger.removeHandler(h)
handlers = config.get('handlers', None)
if handlers:
self.add_handlers(logger, handlers)
filters = config.get('filters', None)
if filters:
self.add_filters(logger, filters)
def configure_logger(self, name, config, incremental=False):
"""Configure a non-root logger from a dictionary."""
logger = logging.getLogger(name)
self.common_logger_config(logger, config, incremental)
propagate = config.get('propagate', None)
if propagate is not None:
logger.propagate = propagate
从以上代码中可以看到, 当incremental
为True
时,只会将日志的等级level
修改.
2.2 incremental
参数为False
时:
2.2.1 清除logging中的_handler
和_handlerList
信息
logging._handlers.clear()
del logging._handlerList[:]
2.2.2 初始化formmaters, 源码如下
formatters = config.get('formatters', EMPTY_DICT)
for name in formatters:
try:
formatters[name] = self.configure_formatter(
formatters[name])
except Exception as e:
raise ValueError('Unable to configure '
'formatter %r: %s' % (name, e))
先会获取所有的formatter,然后通过configure_formatter方法来初始化.
def configure_formatter(self, config):
"""Configure a formatter from a dictionary."""
if '()' in config:
factory = config['()'] # for use in exception handler
try:
result = self.configure_custom(config)
except TypeError as te:
if "'format'" not in str(te):
raise
#Name of parameter changed from fmt to format.
#Retry with old name.
#This is so that code can be used with older Python versions
#(e.g. by Django)
config['fmt'] = config.pop('format')
config['()'] = factory
result = self.configure_custom(config)
else:
fmt = config.get('format', None)
dfmt = config.get('datefmt', None)
style = config.get('style', '%')
cname = config.get('class', None)
if not cname:
c = logging.Formatter
else:
c = _resolve(cname)
result = c(fmt, dfmt, style)
return result
可以看到,参数当formatter配置中,有两种情况:
- 有名为
()
的key
此时,调用configure_custom方法:
def configure_custom(self, config):
"""Configure an object with a user-supplied factory."""
c = config.pop('()')
if not callable(c):
c = self.resolve(c)
props = config.pop('.', None)
# Check for valid identifiers
kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
result = c(**kwargs)
if props:
for name, value in props.items():
setattr(result, name, value)
return result
从代码中可以看到,config.pop('()')
的调用,在上文中有提到:
def pop(self, key, default=None):
value = dict.pop(self, key, default)
return self.convert_with_key(key, value, replace=False)
这就是来获取需要调用的模块, 最后调用对应的模块,生成formatter,
注意一点: props = config.pop('.', None)
, 配置.
属性可以设置对应实例的自身属性,如:
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "simple",
".": {
"hei": 1
}
}
这个配置加上了.号属性的设置,初始化之后,对应的Streamhandler就会有属性hei
- 正常的key
直接调用对应的class模块,生成formatter
2.2.3 生成filter、Handler,与formatter类似, Handler最后会加上对应的formmaters以及filters
2.2.4 生成logger
这些就是logging.config.dictConfig的初始化过程,一步步解析,通过自带协议:ext://
和cfg://
协议来引用外部模块或是已有的配置,初始化你自己需求的logger,也可自定义相关模块,完成特殊化需求,粗糙之问,有点乱。有错误,麻烦之处,好更正。