Flask如何使用logging.FileHandler将日志保存到文件

需求

将日志尽可能往文件中输,自带的默认只输出到屏幕上。

代码

获取文件名

1
2
3
4
5
6
7
8
9
10
11
def get_custom_file_name():
def make_dir(make_dir_path):
path = make_dir_path.strip()
if not os.path.exists(path):
os.makedirs(path)
return path
log_dir = "ac_logs"
file_name = 'logger-' + time.strftime('%Y-%m-%d', time.localtime(time.time())) + '.log'
file_folder = os.path.abspath(os.path.dirname(__file__)) + os.sep + log_dir
make_dir(file_folder)
return file_folder + os.sep + file_name

配置logging

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
dictConfig({
'version': 1,
'formatters': {'default': {
'format': '%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)s - %(message)s',
}},
'handlers': {
'default': {
'class': 'logging.StreamHandler',
'stream': 'ext://flask.logging.wsgi_errors_stream',
'formatter': 'default'
},
'custom': {
'class' : 'logging.FileHandler',
'formatter': 'default',
'filename' : get_custom_file_name(),
'encoding' : 'utf-8'
},
},
'root': {
'level': 'INFO',
'handlers': ['custom']
}
})

代码分析

在官方文档中,有一个默认的handler,当我添加一个自定义的handler,名叫custom的时候,读取配置失败,程序中断,也就无法继续执行下去,提示说,少一个叫做filename的参数

loggin.FileHandler的构造函数中,有一个必填的参数,叫做filename。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class FileHandler(StreamHandler):
"""
A handler class which writes formatted logging records to disk files.
"""
def __init__(self, filename, mode='a', encoding=None, delay=False):
"""
Open the specified file and use it as the stream for logging.
"""
# Issue #27493: add support for Path objects to be passed in
filename = os.fspath(filename)
#keep the absolute path, otherwise derived classes which use this
#may come a cropper when the current directory changes
self.baseFilename = os.path.abspath(filename)
self.mode = mode
self.encoding = encoding
self.delay = delay

如何才能传入参数filename?

①进入dictConfig()

1
2
3
def dictConfig(config):
"""Configure logging using a dictionary."""
dictConfigClass(config).configure()

②进入configure(),找到对handler的处理逻辑,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 前面的代码省略
# 获取handlers
handlers = config.get('handlers', EMPTY_DICT)
deferred = []
# 遍历其中的每一个handler
for name in sorted(handlers):
try:
# 具体处理每一个handler
handler = self.configure_handler(handlers[name])
handler.name = name
handlers[name] = handler
except Exception as e:
if 'target not configured yet' in str(e.__cause__):
deferred.append(name)
else:
raise ValueError('Unable to configure handler '
'%r' % name) from e
# 后面的代码省略

③进入具体处理handler的逻辑,self.configure_handler()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def configure_handler(self, config):
# 省略代码若干行 ...
if '()' in config:
c = config.pop('()')
if not callable(c):
c = self.resolve(c)
factory = c
else:
cname = config.pop('class')
klass = self.resolve(cname)
# 省略代码若干行 ...
# 此处的kclass就是配置的class名所对应的类
factory = klass
props = config.pop('.', None)
# 读取完类名,获取到该类、获取了formatter之后,接着读取conf里面的数据
# 在这里将剩下所定义的参数,弄成dict类型的数据
kwargs = {k: config[k] for k in config if valid_ident(k)}
try:
# 直接将dict类型的数据作为函数入参,实例化出一个FileHandler
result = factory(**kwargs)
except TypeError as te:
if "'stream'" not in str(te):
raise

其中的调试数据截图如下:
在这里插入图片描述
如果没有配置filename的信息,那么实例化类的时候就报错也是理所应当,因此还可以尝试性地配置encoding参数到其中,工作正常。

总结

虽然对这一块的整体了解还不够,但是对于能够参照官方文档,参考源代码实现,完成自己的想法,做到的那一刻还是有点成就感。

【参考】
官方文档:http://flask.pocoo.org/docs/dev/logging

Flask如何使用logging.FileHandler将日志保存到文件

https://eucham.me/2019/04/20/789be84ae481.html

作者

遇寻

发布于

2019-04-20

更新于

2021-02-09

许可协议

评论