pyzog:uWSGI logging rotate 的终极方案

文章目录

在上一篇文章 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 插件:

 1uwsgi --logger-list
 2*** uWSGI loaded loggers ***
 3python
 4syslog
 5rsyslog
 6socket
 7redislog
 8mongodblog
 9file
10fd
11stdio
12--- 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 命令行可以查看其支持的子命令:

 1$ pyzog --help
 2
 3Usage: pyzog [OPTIONS] COMMAND [ARGS]...
 4
 5  执行 pyzog 命令
 6
 7Options:
 8  --help  Show this message and exit.
 9
10Commands:
11  genprog   生成 supervisord 的 program 配置文件
12  genpyzog  在当前文件夹下生成 pyzog.conf 配置文件
13  gensupe   在当前文件夹下生成 supervisord.conf 配置文件
14  gensys    在当前文件夹下生成 systemd 需要的 supervisord.service 配置文件
15  start     启动 pyzog。请先使用 pyzog genpyzog 生成配置文件。

3. 部署 pyzog

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

3.1 安装 pyzog 到虚拟环境

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

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

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

3.2 配置 Supervisor

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

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

 1$ cd ~/pyzog
 2$ (venv) pyzog gensupe --help
 3
 4  在当前文件夹下生成 supervisord.conf 配置文件
 5
 6Options:
 7  -p, --path PATH                 提供一个路径,配置中和路径相关的内容都放在这个路径下
 8  --unix-http-server-file TEXT
 9  --supervisord-logfile TEXT
10  --supervisord-pidfile TEXT
11  --supervisord-user TEXT
12  --supervisord-directory TEXT
13  --supervisorctl-serverurl TEXT
14  --include-files TEXT
15  --help                          Show this message and exit.

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

1# 这里采用 app 这个普通用户来执行 supervisord。如果不提供这个参数,那么将使用 root 来运行 supervisord
2$ (venv) pyzog gensupe --path ~/pyzog --supervisord-user app
3# 根据 --path 的设定,创建集中放置 program 配置的文件夹
4mkdir ~/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 文件夹中,虚拟环境激活状态:

 1$ (venv) pyzog gensys --help
 2Usage: pyzog gensys [OPTIONS]
 3
 4  在当前文件夹下生成 systemd 需要的 supervisord.service 配置文件
 5
 6Options:
 7  --supervisord-exec TEXT
 8  --supervisorctl-exec TEXT
 9  --supervisord-conf TEXT
10  --help                     Show this message and exit.

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

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

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

继续配置 systemd:

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

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

3.4 创建 pyzog 运行需要的配置文件

pyzog 运行需要配置文件,pyzog 提供了子命令来生成它们:

 1$ (venv) pyzog genpyzog --help
 2Usage: pyzog genpyzog [OPTIONS]
 3
 4  在当前文件夹下生成 pyzog.conf 配置文件
 5
 6Options:
 7  -n, --name TEXT                 pyzog 实例的名称  [required]
 8  -l, --logpath PATH              提供一个路径,pyzog 接收到的日志将放在这里  [required]
 9  -t, --type [redis|zmq]          指定服务器类型,可选值 redis/zmq  [required]
10  -a, --addr TEXT                 服务器地址,形如 tcp://127.0.0.1:5011 或者
11                                  password@127.0.0.1:6379/0  [required]
12  -m, --get-message-type [thread|block|listen]
13  -s, --sleep-time FLOAT          调用 redis.pubsub.get_message 时提供的 sleep_time
14                                  参数
15  -c, --channel TEXT              仅当 type 为 redis 的时候提供,允许指定多个 channel 名称
16  --help                          Show this message and exit.

创建一个名称为 pyzog1.conf 的配置。使用 redis 接收日志,支持两个 channel req.*app.*,log 文件写入 ~/pyzog/logs 目录。然后创建一个名为 conf 的文件夹,将所有的运行配置放入其中:

1$ (venv) pyzog genpyzog -n pyzog1 -l ~/pyzog/logs -t redis -a '127.0.0.1:6372' -c 'app.*' -c 'req.*'
2$ mkdir ~/pyzog/conf
3$ mv ~/pyzog/pyzog1.conf ~/pyzog/conf

3.5 配置 Supervisor 中的 program

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

 1$ (venv) pyzog genprog --help
 2Usage: pyzog genprog [OPTIONS]
 3
 4  生成 supervisord 的 program 配置文件
 5
 6Options:
 7  -n, --name TEXT         Supervisor program 名称  [required]
 8  -c, --config_file PATH  [required]
 9  -u, --user TEXT         Supervisor program 的 user
10  --help                  Show this message and exit.

创建一个名称为 p1 的配置。使用刚才生成的 pyzog1.conf 配置文件运行,并将生成的 p1.conf 文件夹移动到 supervisord 的 program 配置文件夹中:

1$ (venv) pyzog genprog -n p1 -c ~/pyzog/conf/pyzog1.conf -u app
2$ mv ~/pyzog/p1.conf ~/pyzog/conf.d

3.6 使用 supervisor 启动 pyzog 进程

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

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

启动这个 program:

1supervisor> start p1
2p1: started

查看状态:

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

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

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

4. 发送日志给 pyzog program

4.1 uWSGI 发送请求日志给 pyzog

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

1[uwsgi]
2daemonize = true
3log-master = true
4req-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,内容如下:

 1/home/app/pyzog/logs/*.log {
 2    su app app
 3    # WatchedFileHandler 会自动创建新文件
 4    nocreate
 5    daily
 6    rotate 5
 7    missingok
 8    notifempty
 9    compress
10    dateext
11    dateyesterday
12}

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

完美!

6. 相关阅读

全文完