语言、框架、环境
Python3.11
Gunicorn + Flask + logging
Debian 12
问题
我在开发一个小型的 Python Web 应用,选用的是 Gunicorn + Flask 的方案,日志采用了官方自带的 logging 库。
业务及其简单,但接口请求量比较大,日志记录比较多,我看到官方提供了一个logging.handlers.TimedRotatingFileHandler的日志轮转处理器,就直接用了。
但是过了一段时间,我发现了日志丢失的问题:
假设 Gunicorn 启动了 3 个 worker 进程,进程号分别是 1001 、1002 和 1003 ,一开始启动 Gunicorn 时,3 个进程的日志都能正确的写入到 app.log 中,但是一旦发生了日志轮转,最终只有一个进程(比如 1001 )能够写入到新的 app.log 中,另外的 1002 和 1003 的日志就再也没有写入成功了。
我的猜测
我猜应该是和多进程日志处理和日志轮转相关的问题,轮转的时候,只有一个进程在切换 app.log ,其他进程找不到文件了,日志就丢失了?(我的猜测很粗糙,我不太理解原理)
当然,官方文档也提到了这点:
文档的建议是,使用 SocketHandler 或者 QueueHandler ,总之是单独使用一个进程处理日志。
提问
-
生产环境下,有什么好的解决方案?
-
刚刚上面的轮转日志丢失,更加具体的,本质的原理是什么?
代码
日志配置文件 logging.yaml 如下:
version: 1 formatters: brief: format: '%(asctime)s - %(levelname)s - %(name)s - %(message)s' detail: format: '%(asctime)s - %(levelname)s - %(process)d - %(processName)s - %(name)s - %(filename)s - %(funcName)s - %(message)s' handlers: console_handler: class: logging.StreamHandler level: DEBUG formatter: brief stream: ext://sys.stdout info_handler: class: logging.handlers.TimedRotatingFileHandler level: INFO formatter: detail filename: logs/app.log when: midnight backupCount: 2 encoding: utf-8 error_handler: class: logging.handlers.TimedRotatingFileHandler level: ERROR formatter: detail filename: logs/error.log when: midnight backupCount: 2 encoding: utf-8 loggers: study-flask: level: DEBUG handlers: [console_handler, info_handler, error_handler] propagate: False root: level: DEBUG handlers: [console_handler] app.py 中关于日志配置的代码:
def log_config(log_config_file): dict_cOnfig= yaml.load( open(log_config_file, encoding='utf-8'), Loader=yaml.FullLoader ) Path.mkdir(Path.cwd().joinpath("logs"), parents=True, exist_ok=True) logging.config.dictConfig(dict_config) def create_app(config_mode): app = Flask(__name__) log_config('./logging.yaml') # ... 省略其他代码 