如何用Flask做应用程序日志

1,394 阅读11分钟

本教程包括:

  1. Flask的日志模块
  2. 按严重程度记录Flask事件
  3. 测试日志模块

如果没有日志,或者对日志没有很好的理解,调试应用程序或查看错误堆栈跟踪可能是一个挑战。幸运的是,Flask日志可以改变你对调试的理解以及你与应用程序产生的日志的互动方式。Flask的日志模块为你提供了一种记录不同严重程度的错误的方法。Python标准库中包含一个默认的日志模块,它提供了简单和高级的日志功能。

在本教程中,我将介绍如何根据严重程度记录Flask应用程序的事件以及如何测试日志模块。

前提条件

本教程需要以下技术。

  • Python编程语言的基本了解
  • Flask框架的理解
  • 对测试方法的基本了解

你还需要安装以下设备。

  1. 在你的机器上安装了Python版本>=3.5。
  2. 一个GitHub账户。你可以在这里创建一个。
  3. 一个CircleCI账户。在这里创建一个。

我们的教程是与平台无关的,但使用CircleCI作为一个例子。如果你没有CircleCI账户,请**在这里注册一个免费账户。**

克隆样本项目库

从GitHub上克隆项目库开始吧。

一旦项目被克隆,你还需要安装依赖项。在项目的根文件夹中使用命令pip install -r requirements.txt

了解Flask的日志记录

Flask使用标准的Python日志模块来记录信息:[app.logger](https://docs.python.org/3/library/logging.html#module-logging) 。这个日志器可以被扩展并用于记录自定义的消息。为Flask应用程序实现一个灵活的事件日志系统,可以让你在应用程序执行时或遇到错误时知道出了什么问题。下图显示了Flask日志模块的不同部分,以及它们对处理应用日志的贡献。

Flask logging module

Python日志模块使用了四个子模块。

  • 日志器是记录来自你的应用程序的事件的主要接口。这些事件被记录下来后,被称为日志记录。
  • 处理程序将日志事件/记录导向各自的目的地。
  • 格式化器指定你的消息在被记录器写入时的布局。
  • 过滤器帮助开发者使用参数管理日志记录。这些参数可以是日志级别之外的。

实现Flask日志记录器

日志允许开发人员用所采取的行动来监控程序的流程。你可以使用日志记录器来跟踪应用程序的流程,如跟踪电子商务应用程序中的交易数据或记录API调用与服务交互时的事件。

要在Flask中开始使用日志,首先从Python中导入日志模块。这个日志模块是Python安装时开箱即用的,不需要配置。Python日志模块会根据预先定义的级别记录事件。记录的日志事件被称为日志记录。每个记录级别都有不同的严重性级别。

  • Debug : 10
  • 信息:20
  • 警告:30
  • 错误。40
  • 危急:50

只有当严重程度大于其日志级别时,日志记录器才会记录日志。然后,日志器将它们传递给处理程序。

这个片段显示了不同类型的日志器以及它们在Flask路由中的用法/

@app.route('/')
def main():
  app.logger.debug("Debug log level")
  app.logger.info("Program running correctly")
  app.logger.warning("Warning; low disk space!")
  app.logger.error("Error!")
  app.logger.critical("Program halt!")
  return "logger levels!"

你可以在app.py 文件中找到这个片段。当Flask应用程序运行时,在浏览器中导航到/ home路由,以查看日志。

下面是如何使用日志的方法。

  • Debug 为开发者提供详细的信息,以诊断程序的错误。
  • Info 显示一个确认信息,说明程序的流程行为正在按照预期执行。
  • Warning 显示发生了一些意想不到的事情,或在不久的将来可能会发生问题(例如,磁盘空间不足)。
  • Error 显示一个严重的问题,如程序未能执行某些功能。
  • Critical 显示应用程序中发生了严重的错误,如程序失败。

配置一个基本的记录仪

一个只提供基本功能的日志记录器对许多应用程序来说已经足够了。要在你的app.py 文件中配置这种类型的日志,请添加这个。

from flask import Flask
import logging

logging.basicConfig(filename='record.log', level=logging.DEBUG)
app = Flask(__name__)

@app.route('/')
def main():
  # showing different logging levels
  app.logger.debug("debug log info")
  app.logger.info("Info log information")
  app.logger.warning("Warning log info")
  app.logger.error("Error log info")
  app.logger.critical("Critical log info")
  return "testing logging levels."


if __name__ == '__main__':
  app.run(debug=True)

这段话根据DEBUG 中的级别指定Flask将在哪里记录你的应用程序。它还设置了当你使用Postman等客户端调用你的/ 主路径时将会记录的消息。

注意日志配置应该在你创建Flask应用对象之前完成。如果在配置之前访问app.logger ,它将使用默认的Python处理程序。

这种使用logging.basicConfig 的基本配置会记录消息,并将日志信息存储在一个.log 文件中。对于我们的示例项目,它是record.log 文件。

现在,使用这个命令执行你的Flask应用程序。

FLASK_APP=app.py flask run

打开你的客户端应用程序,并为正在运行的应用程序向你的路由发出一个GET 请求。在这种情况下,它是http://127.0.0.1:5000/ 。当你程序中的main 函数被调用时,它会创建record.log 文件,然后将日志级别设为DEBUG。日志活动应该出现在文件record.log ,输出应该是这样的。

## record.log file output

DEBUG:app:debug log info
INFO:app:Info log information
WARNING:app:Warning log info
ERROR:app:Error log info
CRITICAL:app:Critical log info
INFO:werkzeug:127.0.0.1 - - [01/Mar/2022 12:35:19] "GET / HTTP/1.1" 200 -

你能够根据不同的记录仪级别操纵记录仪对象,使其记录所有配置的记录仪。当你为记录不同级别的信息设置了不同的记录器时,你可以禁止一些日志在控制台显示,而启用其他日志。对于这种配置在终端上打印出日志,你可以在记录日志的logging.basicConfig() 对象中删除文件配置filename='record.log'

虽然这些日志输出是可读的,但它们可能不是很有用,特别是你不知道事件发生的时间。为了解决这个问题,你可以给你的日志添加一个格式,如下一节所述。

格式化日志输出

Python格式化器将记录的结构格式化为特定的结构,使之易于阅读日志并将其与特定的事件联系起来。它可以在basicConfig 配置里面应用。

日志格式化器由以下配置组成。

  • %(asctime)s 将时间戳配置为一个字符串
  • %(levelname)s 将日志级别配置为一个字符串。
  • %(name)s 将记录器名称配置为字符串。
  • %(threadName)s 是线程名称。
  • %(message)s 将日志信息配置为字符串。

你可以将这些格式化选项应用于你的配置,以获得更准确的对象输出。

.........
logging.basicConfig(filename='record.log',
                level=logging.DEBUG, format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s')
app = Flask(__name__)
.........

使用前面片段中指定的format 配置,你的日志输出可以与特定的时间戳、特定的线程,甚至是特定的线程名相联系。当你运行应用程序并观察你的output.log 文件时,所产生的输出日志更有意义,看起来更干净。

2022-03-01 13:09:11,787 DEBUG app Thread-3 : debug log info
2022-03-01 13:09:11,788 INFO app Thread-3 : Info log information
2022-03-01 13:09:11,788 WARNING app Thread-3 : Warning log info
2022-03-01 13:09:11,788 ERROR app Thread-3 : Error log info
2022-03-01 13:09:11,788 CRITICAL app Thread-3 : Critical log info
2022-03-01 13:09:11,788 INFO werkzeug Thread-3 : 127.0.0.1 - - [01/Mar/2022 13:09:11] "GET / HTTP/1.1" 200 -

日志级别有消息,但也有时间戳、日志的级别、应用程序的名称、进程的线程,最后是你定义的日志的消息。如果你遇到了一个错误,这使得你很容易通过时间戳来确定具体的进程、时间戳甚至是消息。这个过程在调试时比仅仅阅读普通的错误日志要有用得多。

现在,你已经用PythonbasicConfig 创建了日志,你可以为你的日志模块编写测试。

测试日志器

测试Python记录器与编写普通Python函数的测试几乎是一样的。要写第一个测试,创建一个名为test_app.py 的文件。打开它并添加你的第一个测试片段。

from flask import Flask
from  app import app

import logging
import unittest

class TestLogConfiguration(unittest.TestCase):
    """[config set up]
    """
    def test_INFO__level_log(self):
        """
        Verify log for INFO level
        """
        self.app = app
        self.client = self.app.test_client

        with self.assertLogs() as log:
            user_logs = self.client().get('/')
            self.assertEqual(len(log.output), 4)
            self.assertEqual(len(log.records), 4)
            self.assertIn('Info log information', log.output[0])

在上面的测试片段中,我们使用test_client ,首先向/ 路由发出请求,就像我们在运行我们的应用程序时做的那样,在做完这些之后,我们可以验证日志输出记录了INFO 水平的日志信息。正如智者所说,知道我们的测试是否运行的唯一方法是执行它们,我们可以在你选择的终端上用下面的命令来做。

pytest -s

审查你的运行结果。

Test Execution

恭喜你!你已经成功地执行了第一个测试。你已经成功地执行了你的第一个测试。现在你可以将你的测试扩展到应用程序中定义的其他日志级别。这里有一个例子。

def test_WARNING__level_log(self):
      """
      Verify log for WARNING level
      """
      self.app = app
      self.client = self.app.test_client

      with self.assertLogs() as log:
          user_logs = self.client().get('/')
          self.assertEqual(len(log.output), 4)
          self.assertIn('Warning log info', log.output[1])


def test_ERROR__level_log(self):
    """
    Verify log for ERROR level
    """
    self.app = app
    self.client = self.app.test_client

    with self.assertLogs() as log:
        user_logs = self.client().get('/')
        self.assertEqual(len(log.output), 4)
        self.assertIn('Error log info', log.output[2])

这些测试的是片段中每个不同阶段的日志级别。

注意日志对象包含更多的信息,然后可以根据应用程序的需要进行测试和断言。

你的最后一项任务是与世界分享你的测试。你可以使用CircleCI作为你的持续集成平台来完成这个任务。

设置Git并推送到CircleCI

为了设置CircleCI,在项目中通过运行初始化一个Git仓库。

git init

在根目录下创建一个.gitignore 文件。在该文件中,添加任何你想防止被添加到远程仓库的模块。添加一个提交,然后推送你的项目到GitHub

现在,登录CircleCI仪表板,导航到Projects 。那里会有一个与你的GitHub用户名或组织相关的所有GitHub仓库的列表。本教程的具体仓库是logging-with-flask

在项目仪表板上,选择设置所选项目的选项,并使用现有配置的选项,它使用仓库中的config.yml 。开始构建。

提示 启动构建后,预计你的管道会失败。这是因为你还没有把你定制的.circleci/config.yml 配置文件添加到GitHub,这是项目正常构建所需要的。

设置CircleCI

在你的根目录下创建一个.circleci 目录,然后在其中添加一个config.yml 文件。该配置文件包含每个项目的CircleCI配置。对于这个设置,你将使用CircleCI Python orb。使用这个配置来执行你的测试。

version: 2.1
orbs:
  python: circleci/python@1.4.0
workflows:
  build-app-with-test:
    jobs:
      - build-and-test
jobs:
  build-and-test:
    docker:
      - image: cimg/python:3.9
    steps:
      - checkout
      - python/install-packages:
          pkg-manager: pip
      - run:
          name: Run tests
          command: pytest

注意CircleCI orb是可重复使用的YAML配置包,它将多行代码浓缩成一个:python: circleci/python@1.4.0 。你可能需要启用组织设置(如果你是管理员)或向你的组织的CircleCI管理员申请许可,以允许在CircleCI仪表板上使用第三方的轨道。

设置好配置后,把你的配置推送到GitHub。CircleCI将自动开始构建你的项目。

检查CircleCI仪表板并展开构建细节,以验证你已经成功运行了你的第一个PyTest测试。它应该被集成到CircleCI中。

Pipeline step success

太棒了!你不仅写好了Flask日志,还写好了你的项目。你不仅编写了Flask记录器,而且还测试了它们,并与世界分享 - 或者至少与你的团队其他成员分享。

总结

在本教程中,你已经学会了如何为不同的日志输出级别配置API,以及如何将日志输出的格式化,不仅简单明了,而且在发生问题时也能轻松识别。此外,你还学会了为日志方法编写测试,并断言已经定义好的不同信息要被记录下来。