在上一篇文章 Flask+uWSGI logging rotate:重要补充 中,我仔细分析了 uWSGI 中处理 logging rotate 的各种限制和解决方案,在文章最后,我提到:

因此,我们需要一个终极解决方案来解决这个问题。这需要新开一篇文章来说明。请等待下篇。

现在,终极解决方案 来了。

1. 测试支持的 logger 插件

上篇文章我们讨论过,uWGI 的 req-loggerlogger 两个参数是插件化的 logger,它们不支持 logreopen 功能。但也正是因为它们的插件化功能,让它们可以支持多种 logger 数据目标。

官方文档 中,介绍了多种 logger 支持插件。例如 socket/rsyslog/mongodblog/zeromq 等等。

可以使用 uwsgi --logger-list 来查看当前的 uWSGI 版本到底支持哪些 logger 插件:

uwsgi --logger-list
*** uWSGI loaded loggers ***
python
syslog
rsyslog
socket
redislog
mongodblog
file
fd
stdio
--- end of loggers list ---

我对几个插件进行了测试。结果如下:

  • syslog 工作正常。
  • python 插件可以让 uWSGI 使用一个来自于 python 标准库的 logger。但这个插件没有文档,阅读源码后进行相关配置,实例会崩溃且没有可读的错误日志。
  • 文档中提到的 zeromq 插件在 PyPI 最新 uWSGI 版本中没有提供,因此不可用。

我认为比较高效的方式,是使用 redislog 来分发日志。需要一个独立的日志接收服务,对发来的日志进行分类存储和集中化管理。

这个日志服务就是 pyzog

2. pyzog

pyzog 是一个独立的服务,用来支持从 Redis 或者 ZeroMQ 发来的日志信息。它的特点如下:

  • 使用 python 的 logging 标准库来处理日志,方便扩展。
  • 支持使用 Redis 的 Pub/Sub 机制来接收日志。
  • 支持使用 ZeroMQ 的 PUB-SUB/DEALER-ROUTER 模式来接收日志。
  • 使用 Python 标准库模块 logging.handlers.WatchedFileHandler 来支持文件 inode 改变的时候自动打开新的流,以实现自动 logrotate。
  • 根据发来的信息提供的数据自动分类写入 log 文件。
  • 提供命令行来启动服务。
  • 使用 Supervisor 守护 pyzog 服务。
  • 提供 systemd 配置文件的生成功能。
  • 提供 program 配置文件的生成功能。

使用 pyzog 命令行可以查看其支持的子命令:

$ pyzog --help

Usage: pyzog [OPTIONS] COMMAND [ARGS]...

  执行 pyzog 命令

Options:
  --help  Show this message and exit.

Commands:
  genprog  生成 supervisord 的 program 配置文件
  gensupe  在当前文件夹下生成 supervisord.conf 配置文件
  gensys   在当前文件夹下生成 systemd 需要的 supervisord.service 配置文件
  start    启动 pyzog receiver。 LOGPATH: log 文件存储路径

3. 部署 pyzog

接下来讲述将 pyzog 部署到服务器的流程。

3.1 安装 pyzog 到虚拟环境

pyzog 没有提交的 PyPI。PyPI 上已有的那个 PyZog 与本项目无关。

可以直接使用 pip 从 github 上安装 pyzog。创建虚拟环境并使用 pip 安装:

$ python3 -m venv ~/pyzog/venv
$ source ~/pyzog/venv/bin/activate
$ (venv) pip install git+https://github.com/zrong/pyzog

3.2 配置 Supervisor

pyzog 需要一个守护程序,保证进程 crash 的时候自动拉起。我选择使用 Supervisor。它已经和 pyzog 一起被安装了。

pyzog 可以生成 Supervisor 的配置文件。

$ cd ~/pyzog
$ (venv) pyzog gensupe --help

  在当前文件夹下生成 supervisord.conf 配置文件

Options:
  -p, --path PATH                 提供一个路径,配置中和路径相关的内容都放在这个路径下
  --unix-http-server-file TEXT
  --supervisord-logfile TEXT
  --supervisord-pidfile TEXT
  --supervisord-user TEXT
  --supervisord-directory TEXT
  --supervisorctl-serverurl TEXT
  --include-files TEXT
  --help                          Show this message and exit.

提供 --path--superversord-user 参数,其他的参数会自动根据 --path 设置:

# 这里采用 app 这个普通用户来执行 supervisord。如果不提供这个参数,那么将使用 root 来运行 supervisord
$ (venv) pyzog gensupe --path ~/pyzog --supervisord-user app
# 根据 --path 的设定,创建集中放置 program 配置的文件夹
mkdir ~/pyzog/conf.d

命令执行成功之后,会在当前文件夹下生成一个 supervisord.conf 配置文件。

3.3 使用 systempd 守护 Supervisor

Supervisor 是一个进程管理器,需要在服务器操作系统启动的时候自动启动。在 Supervisor 的文档 Running supervisord automatically on startup 中可以找到对应操作系统的 initscripts

目前,许多 Linux 发行版都已经使用 systemd 替换了标准的 initscript。我们这里使用 systemd 来代替老旧的 initscript。

pyzog 可以创建 systemd 配置文件。确保当前位于 ~/pyzog 文件夹中,虚拟环境激活状态:

$ (venv) pyzog gensys --help
Usage: pyzog gensys [OPTIONS]

  在当前文件夹下生成 systemd 需要的 supervisord.service 配置文件

Options:
  --supervisord-exec TEXT
  --supervisorctl-exec TEXT
  --supervisord-conf TEXT
  --help                     Show this message and exit.

提供以上三个参数的值,:

$ (venv) pyzog gensys --supervisord-exec ~/pyzog/venv/bin/supervisord --supervisorctl-exec ~/pyzog/venv/binsupervisorctl --supervisord-conf ~/pyzog/supervisord.conf

命令执行成功之后,会在当前文件夹下生成一个 supervisord.service 配置文件。

继续配置 systemd:

$ mkdir -p ~/.config/systemd/user
$ mv ~/pyzog/supervisord.service ~/.config/systemd/user/
$ systemctl --user enable supervisord
$ systemctl --user start supervisord

至此, supervisord 已经被正常启动,可以通过 systemctl --user status supervisord 查看其状态。

3.4 配置 Supervisor 中的 program

上面的配置指定了 Supervisor 管理的 program 配置文件位于 ~/pyzog/conf.d 中。让我们来生成一个配置:

$ (venv) pyzog genprog --help
Usage: pyzog genprog [OPTIONS]

  生成 supervisord 的 program 配置文件

Options:
  -n, --name TEXT         输入 program 名称  [required]
  -t, --type [redis|zmq]  指定服务器类型  [required]
  -a, --addr TEXT         形如 tcp://127.0.0.1:5011 或者 password@127.0.0.1:6379/0
                          [required]
  -c, --channel TEXT      仅当 type 为 redis 的时候提供,允许指定多个 channel 名称
  -u, --user TEXT         执行 program 的 user
  -p, --logpath TEXT      log 文件的路径。若不提供则使用当前文件夹下的 logs 文件夹
  --help                  Show this message and exit.

创建一个名称为 p1 的配置。使用 redis 接收日志,支持两个 channel req.*app.*,log 文件写入 ~/pyzog/logs 目录,并将生成的 p1.conf 文件夹移动到 supervisord 的 program 配置文件夹中:

$ (venv) pyzog genprog -n p1 -t redis -a '127.0.0.1:6372' -c 'app.*' -c 'req.*' -u app -p ~/pyzog/logs
$ mv ~/pyzog/p1.conf ~/pyzog/conf.d

3.5 使用 supervisor 启动 pyzog 进程

启动 uspervisorctl,调用 reread 命令读取配置文件:

$ (venv) supervisorctl -c ~/pyzog/supervisord.conf
supervisor> reread
p1: available

启动这个 program:

supervisor> start p1
p1: started

查看状态:

supervisor> status
p1                         RUNNING   pid 12848, uptime 0:00:01

3.6 为什么不使用 systemd 直接管理 pyzog?

因为 Supervisor 在管理上更加直观,在分组管理 program 的时候更加方便。

4. 发送日志给 pyzog program

4.1 uWSGI 发送请求日志给 pyzog

Flask+uWSGI logging rotate:重要补充 中提到的 req-logger 无法进行 logrotate 的问题,可以使用 pyzog 解决了。只需要配置 uwsgi.ini

[uwsgi]
daemonize = true
log-master = true
req-logger = redislog:127.0.0.1:6379,publish req.mjp2401

uWSGI 会自动将 request log 内容使用 publish 命令发送给指定的 redis, 使用的 channel 名称为 req.mjp2401

上面配置了名为 p1 的 pyzog 进程用来接收 req.* 这个 channel 名称,当 pyzog 接收到 uWSGI 传来的 log 时,会自动创建 ~/pyzog/logs/req.mjp2401.log 这个日志文件。

如果采用不同的 channel(req 前缀),pyzog 会创建不同的日志文件。这就解决了数十个 uWSGI 实例分开处理日志的问题。

4.2 python logging 发送日志给 pyzog

Flask+uWSGI logging rotate:重要补充 中的 5. Flask 中的日志处理 这个章节,我封装的 get_logger 方法支持 stream/file/zmq 三种不同类型的 logger。

pyzog 提供了 RedisHandler,扩展了 get_logger 方法,增加了对 redis 的支持,详情参考 pyzog.logging 源码。

5. 设置 logrotate

增加一个 logrotate 配置文件 /etc/logrotate.d/pyzog,内容如下:

/home/app/pyzog/logs/*.log {
    su app app
    create 0644 app app
    daily
    rotate 5
    missingok
    notifempty
    compress
    dateext
    dateyesterday
}

重启 logrotate 服务,终极方案完成。

完美!

6. 相关阅读

全文完