为何Python自定义Formatter无法处理RuntimeWarning日志?
问题:RuntimeWarning未被自定义JSON日志格式化器处理
我试图让Python应用的所有日志消息都通过pythonjsonlogger的JsonFormatter格式化,但发现numpy抛出的RuntimeWarning(比如__main__:1: RuntimeWarning: divide by zero encountered in true_divide)没有被处理成期望的JSON结构(示例:{"name": "root", "funcName": "<module>", "level": null, "lineno": 1, "threadName": "MainThread", "message": "RuntimeWarning: divide by zero encountered in true_divide"})。虽然已经配置了root logger使用自定义的json_handler,但该警告依然以默认格式输出。
复现代码:
import logging import os import sys import numpy as np import pandas as pd from pythonjsonlogger import jsonlogger from logging import getLogger, config log_level = os.environ.get("LOG_LEVEL", "INFO") log_format = "%(name)s %(funcName)s %(levelname)s %(lineno)d %(threadName)s %(message)s" config.dictConfig({ 'version': 1, 'formatters': {'json': { '()': jsonlogger.JsonFormatter, 'format': log_format, }}, 'handlers': { 'json_handler': { 'class': 'logging.StreamHandler', 'formatter': 'json', 'level': log_level, 'stream': sys.stdout } }, 'loggers': { '': { 'handlers': ['json_handler'], 'level': log_level, 'propagate': False }, }, 'root': { 'level': log_level, 'handlers': ['json_handler'], }, 'disable_existing_loggers': True }) logging.info("test") # 这条日志能正常格式化 series_raw = pd.Series([0, float('nan'), 1,4,6,-1], [0,1,2,3,4,5]) x = series_raw.values values = np.where(np.logical_or(x == 0, np.isnan(x)), np.nan, 10 /x) # 这里抛出的警告无法被格式化
原因分析
RuntimeWarning是Python的warnings模块发出的,并非直接通过logging模块生成。默认情况下,warnings会直接输出到标准错误流,不会经过logging系统,所以你的logging配置对它无效。
解决方案
要让警告被logging系统处理,需要:
- 启用logging对warnings的捕获功能
- 确保处理警告的logger(默认是
py.warnings)使用你的自定义格式化器
具体修改步骤:
- 导入
warnings模块 - 调用
logging.captureWarnings(True),将warnings转发到logging系统 - 在logging配置中,为
py.warningslogger配置使用json_handler,或者让它的日志 propagate 到root logger
修改后的代码
import logging import os import sys import warnings import numpy as np import pandas as pd from pythonjsonlogger import jsonlogger from logging import getLogger, config # 启用捕获warnings到logging系统 logging.captureWarnings(True) log_level = os.environ.get("LOG_LEVEL", "INFO") log_format = "%(name)s %(funcName)s %(levelname)s %(lineno)d %(threadName)s %(message)s" config.dictConfig({ 'version': 1, 'formatters': {'json': { '()': jsonlogger.JsonFormatter, 'format': log_format, }}, 'handlers': { 'json_handler': { 'class': 'logging.StreamHandler', 'formatter': 'json', 'level': log_level, 'stream': sys.stdout } }, 'loggers': { # 配置py.warnings logger使用json_handler 'py.warnings': { 'handlers': ['json_handler'], 'level': log_level, 'propagate': False }, '': { 'handlers': ['json_handler'], 'level': log_level, 'propagate': False }, }, 'root': { 'level': log_level, 'handlers': ['json_handler'], }, 'disable_existing_loggers': True }) logging.info("test") # 正常格式化 series_raw = pd.Series([0, float('nan'), 1,4,6,-1], [0,1,2,3,4,5]) x = series_raw.values values = np.where(np.logical_or(x == 0, np.isnan(x)), np.nan, 10 /x) # 现在警告会被JSON格式化
注:原log_format中的占位符存在错误,比如%(level)应改为%(levelname),%(name)需补充s变为%(name)s,否则会导致格式化异常,上述代码已修正这些问题。
内容的提问来源于stack exchange,提问作者Sam Comber
相关产品推荐
相关产品推荐

