日志

日志是一个商业网站必备的功能,用日志可以记录网站运行中产生的信息,这些信息包括网站运行时产生的异常或用户行为等,具体情况如下。

  • 用户相关的行为:如登录、退出登录、注册、找回密码、不正确的密码尝试等。通过日志记录和分析用户的行为,可以提高网站用户体验以及发现非法登录等。

  • 补充数据库记录:如后台用户谁禁用了帖子、谁添加了新员工。这些在数据库中没有记录的,可以用日志补充记录。

  • 错误:如程序出现异常、数据库操作异常、响应了错误状态码等,都可以通过日志记录下来,方便后期优化程序。

Flask 使用了 Python 中内置的 logging 模块。logging 模块有 4 个子模块,分别为 loggers、handlers、filters 和 formatters,下面分别进行讲解。

loggers模块

loggers 模块是用来创建日志的,在 Flask 中通过 app.logger 可以获取当前的 logger 对象,然后使用 logger.info、logger.debug 等级别函数创建日志。在 Python 内置的 logging 模块中,日志分为 6 个级别,如表 9-5 所示。

image 2025 01 22 17 25 28 282
Figure 1. 表9-5 日志级别

在记录日志时,只会记录比当前设置级别高的日志。如设置日志级别为 INFO,那么 logger.debug 将不会产生日志记录。Flask 中的默认级别为 DEBUG,如果要修改默认级别,可以通过 app.logger.setLevel 来修改。如修改为 INFO 级别,代码如下。

app.logger.setLevel(logging.INFO)

我们在 blueprints/front.pyindex 视图函数中,添加一条测试日志,代码如下。

current_app.logger.info("index页面被请求了")

在浏览器中访问首页后,在 PyCharm 的 Run 界面会显示如图 9-51 所示的日志。

image 2025 01 22 17 26 54 486
Figure 2. 图9-51 PyCharm的Run界面显示的日志

handlers模块

handlers 模块用来指定日志被定向到何处。在 Flask 中默认是定向到控制台打印。如果要定向到文件,那么就用 FileHandler 或 RotatingFileHandler;如果要使用邮箱发送日志,那么就用 SMTPHandler。

关于 logging 模块支持的所有 handler,读者如果感兴趣可以参阅 Python 官方文档 https://docs.python.org/3/library/logging.handlers.html 中的 logging 模块。

如果要将日志定向到文件中,则可以给 app.logger 再添加一个 handler,代码如下。

file_handler = logging.FileHandler("pythonbbs.log", encoding="utf-8")
app.logger.addHandler(file_handler)

我们在请求项目的首页可以看到,除了控制台显示日志信息,还会在 pythonbbs 项目的根路径下产生一个 pythonbbs.log 的文件,里面也有日志信息。如果不想要打印 Flask 默认在控制台的日志信息,可以通过以下代码移除。

from flask.logging import default_handler
app.logger.removeHandler(default_handler)

FileHandler 会把所有日志打印在一个文件中,在网站运行中这个文件大小将不断变大。在商业项目中可以使用 RotatingFileHandlerTimeRotatingFileHandler

  • RotatingFileHandler 可以指定文件大小,当超过最大文件限制时将自动开启一个新的日志文件。

  • TimeRotatingFileHandler 则是根据时间来判断是否要开启一个新的日志文件,下面分别进行讲解。

(1)RotatingFileHandler(filename, maxBytes=0, backupCount=0): 当 maxBytes ≥ 0 时,如果文件大小超过 maxBytes,则会创建一个新的文件,新的文件以 filename 为基本名称,并且在名称后面加上 .1 .2 等。如 filename 的值是 app.log,那么产生的新文件为 app.log.1app.log.2 等。backupCount 指定最多创建多少个文件,只有 backupCount ≥ 1 时,才会产生新的文件。RotatingFileHandler 写入日志的逻辑是这样的,先写入 filename 指定的文件中,如 app.log,如果 app.log 的大小接近 maxBytes,则将 app.log 重命名为 app.log.1,然后创建新的 app.log 用于写入日志。

(2)TimeRotatingFileHandler(filename, when, interval=1, backupCount=0): when 参数用于指定按什么时间单位创建新的日志文件,when 可以取以下值:

  • S:按照秒为单位。

  • M:按照分钟为单位。

  • H:按照小时为单位。

  • D:按照天为单位。

  • midnight:按照半夜 12 点为单位。

  • W{0-6}:按照星期为单位,W0 为星期一。

interval 表示等待多少个单位 when 的时间后创建新的日志文件。创建文件的规则与 RotatingFileHandler 类似,如果 backupCount 不为 0,则最多保留 backupCount 个文件,如果创建超过 backupCount 个的文件,则最旧的文件会被删除。

filters模块

filters 模块是用来给 LoggerHandler 提供过滤器的,只有过滤器返回 True 的日志才会被记录,先执行 Logger 的过滤器,再执行 Handler 的过滤器。这里以给 Logger 添加过滤器为例,代码如下。

class stringFilter(logging.Filter):
    def filter(self, record):
        if record.msg.startswith("abc"):
            return False
        return True

app.logger.addFilter(stringFilter())
app.logger.info("abc-test")
app.logger.info("123-test")

上述代码中,因为在 app.logger 中添加了 stringFilter 过滤器,会过滤掉以 "abc" 开头的日志,因此只会记录 123-test,而不会记录 abc-test。其中 filter 方法中的 record 参数为 logging.LogRecord 对象。

formatters模块

formatters 模块用来指定日志记录的格式。logging 模块内置了一些变量,我们在定义格式时可以直接使用。变量的说明如下:

  • %(asctime)s:日志被创建的时间。

  • %(filename)s:被创建的日志所在的文件。

  • %(funcName)s:被创建的日志所在的函数。

  • %(levelname)s:被创建的日志的级别。

  • %(lineno)d:被创建的日志所在文件的代码行号。

  • %(message)s:日志文本的内容。

  • %(module)s:被创建的日志所在的模块。

我们可以使用以上变量,灵活地组合自己想要的日志格式。完整的使用示例代码如下:

import logging
from flask.logging import default_handler
from logging.handlers import RotatingFileHandler

# 移除Flask自带的handler
app.logger.removeHandler(default_handler)

# 创建一个RotatingFileHandler对象
file_handler = RotatingFileHandler('pythonbbs.log', maxBytes=16384, backupCount=20)

# 设置handler级别为INFO
file_handler.setLevel(logging.INFO)

# 创建日志记录的格式
file_formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(filename)s: %(lineno)d]')

# 将日志格式对象添加到handler中
file_handler.setFormatter(file_formatter)

# 将handler添加到app.logger中
app.logger.addHandler(file_handler)