Koril:
语言、框架、环境
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 ,其他进程找不到文件了,日志就丢失了?(我的猜测很粗糙,我不太理解原理)
当然,官方文档也提到了这点:
https://docs.python.org/zh-cn/3/howto/logging-cookbook.html#logging-to-a-single-file-from-multiple-processes
文档的建议是,使用 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')
# ... 省略其他代码