一文看懂Flask的日志使用姿势

前言

笔者主要的后端项目都是使用Flask。 在使用Python写代码的时候, 最喜欢的打印日志方式, 应该就是使用print了吧。 这种方式最简单, 但是也有一些天生无法克服的缺点。

比如:

  • python2 跟 python3 不兼容
  • 只能打印出消息, 相关的附加信息都没有。 在定位问题的时候, 可能缺少关键信息。

特别是在中大型项目里面, 想构建一个稳定的系统, 日志必不可少。

笔者尝试根据自己实践的经验,基于Flask这个框架,讲清楚以下几个问题:

  • 日志在Flask之中的基础使用方法

  • 如何在Flask之中配置日志的格式、文件存储地址、自动切分日志

  • 在Blueprint之中如何使用日志

  • 通过邮件或者Http接口输出错误日志

  • 多机环境下, 如何使用日志进行定位的思路。

    比如需要增加hostname 定位具体在哪个docker环境。

日志在Flask之中的基础使用方法

首先我们从最简单的Flask程序开始。 从官网复制一个最小的能运行的Flask程序, 如下:

毫无疑问, 访问http://127.0.0.1:5000 就能看到Hello World的输出。 接下来, 我们开始设置日志。 具体参考下面的代码:

我们在main 函数之中, 设定:

  • 我们会将日志写到flask.log之中
  • Flask自带的app.logger 使用我们设定好的handler

再次访问http://127.0.0.1:5000 ,在Console看到如下信息:

可以看到, 我们在hello_world 函数之中期望打印出来的日志都正常输出了。 同时, 你也应该能看到一个flask.log文件。 不过里面的内容就不如Console的log那样, 包含了时间来源等等的信息了。 内容如下:

日志配置

虽然是在Flask之中调用了自带的app.logger, 但是毕竟还是使用了公共库的logging。 配置方面应该能找到很多很多资料。 再次演示一下自认为比较好用的一个配置。

如果一个日志不停的增长下去, 显然不是什么好事。 因此日志必须要进行切分。 常见的两种方式:

  • 按照大小
  • 按照时间

按照日志大小切分

如果是按照大小进行切分, 引入RotatingFileHandler 即可。 举例:

简单解释一下:

  • “flask.log” 就是日志的文件名
  • maxBytes 就是 日志大小
  • backupCount 就是保留的日志个数。 比如flask.log 写满了, 就会被重命名成flask.log.1, 程序继续向flask.log写入。

更详细的解释可以看看官网说明: https://docs.python.org/2/library/logging.handlers.html#rotatingfilehandler

按照日期进行切分

个人比较习惯这种方式。 在logging这个库之中, 还支持按照分钟、小时、天等级别进行切分。 根据我们业务的大小, 我一般选择按照“天” 进行切分。 可以参考下面的配置:

  • when=D: 表示按天进行切分
  • interval=1: 每天都切分。 比如interval=2就表示两天切分一下。
  • backupCount=15: 保留15天的日志
  • encoding=UTF-8: 使用UTF-8的编码来写日志
  • utc=True: 使用UTC+0的时间来记录 (一般docker镜像默认也是UTC+0)

更详细的官网解释可以看看 这里

配置日志格式

前面我们也看到, 在日志文件之中, 除了记录下来的消息, 其他辅助信息完全没有。 以下是我自己的配置以及相应的输出

注意: 设置%(thread)d 并不是必须的。 但是如果你在一个多线程、或者多个docker环境的时候, 加上这个thread, 有助于你把同一个会话进程抽取出来。 因为多进程、多线程的时候, 日志的顺序可能会被打乱。

Blueprint 之中使用日志

当你的Flask项目膨胀到一定规模的时候, 全部都写到主入口之中。 一定需要按照模块进行拆分。 Blueprint(蓝图)就是这个时候需要使用的东西。 那么在Blueprint之中, 如何使用日志呢?

我们先基于前面的程序搭好框架:

主入口 main.py

注意: 已经注册好了蓝图simple_page, 并且设置了url_prefix=simple_page

蓝图 views.simple_page

文件目录长下面这样:

其实在blueprint之中, 使用日志的方式也很简单。 参考下面simple_page.py之中增加日志调用之后的代码:

关键就是from flask import current_app 就可以获取当前的flask的app了。 我们来看看在日志文件之中的日志的样子:

看起来一切正常。 Console 之中的日志:

看起来也不错。 完美!

错误日志发送邮件或者调用HTTP接口

当系统上线之后, 多多少少程序会因为各种各样的问题产生Error级别的日志。 但是我们又不能一直盯着线上日志。 一个简单的办法, 当出现错误日志的时候, 主动通知。 比如发邮件或者调用Http Webhook 接口。

在标准日志库logging 之中,就有SMTPHandlerHTTPHandler可以实现这个功能。 下面以发邮件为例子。

我们接着上面Blueprint的代码接着写。

主入口main.py

关键部分:

注意: 我们在函数之中, 连续输入了3条Error信息, EmailHandler并不会帮助我们合并这三条信息, 而是会分别发送邮件过来。

在Log之中增加其他的辅助信息

增加辅助信息有两种比较优雅的方式:

  • 通过LogFilter
  • 通过自定义的Formatter

假设我们在Log之中需要增加当前的环境的hostname,我们新的formatter长下面这样

使用LogFilter的方式

使用自定义的Formatter

TBD, 笔者暂时还没有调通

多机环境下的错误定位思路

比如我们使用前后端的架构, 会在多台机器甚至分布在不同数据中心的机器上面部署相同的程序, 相互构成一个集群。

这种场景下面, 出现错误的时候, 其实定位问题是非常麻烦的。 所以, 首先我们要定位在哪个环境上面出的问题。同时, 我们还需要借助其他的手段。 下面说一下思路:

  • 增加hostname 字段

    flask的日志默认是不带hostname字段的, 增加的方法就需要用到上一节提到的方法。

  • 通过ELK等方式, 将日志统一收集到一个集中的地方进行处理

  • 如果使用ELK, 可以直接在Kibana上面查找Error级别的日志, 并且通过thread / hostname 等过滤出某一个环境上面的日志进行查看。

本文原创, 转载需要注明出处:https://www.flyml.net

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注