如何在不定义自定义日志级别时,使logging.exception仅输出至日志文件,stdout显示短错误信息
解决方案
核心思路
logging.exception()本质是ERROR级别日志+自动填充exc_info=True,我们可以利用这个特性,在标准输出的日志处理器中,针对带有exc_info的日志记录只输出简短错误消息;文件处理器则保留完整的异常栈追踪信息,全程无需自定义日志级别,完全保留用户的自定义空间。
修改步骤及代码实现
1. 调整标准输出的Formatter类
修改Formatter类的format方法,当检测到日志记录包含exc_info时,临时屏蔽异常栈追踪的渲染,只输出基础错误信息:
class Formatter(logging.Formatter): def format(self, record): color = { logging.CRITICAL: 31, logging.ERROR: 31, logging.FATAL: 31, logging.WARNING: 33, logging.DEBUG: 36, logging.INFO: 32 }.get(record.levelno, 0) self._style._fmt = f"\033[37m[%(asctime)s]\033[0m \033[{color}m%(levelname)-8s\033[0m %(message)s" # 处理exception生成的日志:临时清空exc_info,避免输出栈追踪 if record.exc_info: original_exc_info = record.exc_info record.exc_info = None formatted_msg = super().format(record) record.exc_info = original_exc_info return formatted_msg return super().format(record)
2. 保留文件处理器的完整异常输出逻辑
文件处理器的file_formatter使用原生格式逻辑即可,它会自动渲染exc_info对应的栈追踪,原代码中的设置已经满足需求:
file_formatter = logging.Formatter(f"[%(asctime)s] %(levelname)-8s [%(filename)s/%(funcName)s:%(lineno)s] %(message)s")
3. 完整修改后的代码
import logging import sys import os from pathlib import Path from dataclasses import fields from API.global_constants.dataclasses import LoggerTemplate from API.global_constants.vars import LOGGER_NAME def set_logger(**kwargs): logger_template = LoggerTemplate() for key, value in kwargs.items(): if key in [field.name for field in fields(logger_template)]: setattr(logger_template, key, value) if not isinstance(logger_template.logger, logging.Logger): logger_template.logger = logging.getLogger(LOGGER_NAME) else: # 用户自定义了logger,直接返回 return logger_template.logger logger = logger_template.logger if not logger_template.enable_logger: logger.setLevel(logging.CRITICAL + 1) else: logger.setLevel(logging.DEBUG) std_formatter = Formatter() std_handler = logging.StreamHandler(sys.stdout) std_handler.setLevel(logger_template.log_std_level) std_handler.setFormatter(std_formatter) logger.addHandler(std_handler) if not logger_template.enable_logfile: logger.info(f'Logging started without file.') else: file_formatter = logging.Formatter(f"[%(asctime)s] %(levelname)-8s [%(filename)s/%(funcName)s:%(lineno)s] %(message)s") Path.mkdir(Path(logger_template.logfile_path), parents=True, exist_ok=True) log_file_path = Path(logger_template.logfile_path) / Path(str(logger_template.logfile_name)) file_handler = logging.FileHandler(log_file_path) file_handler.setLevel(logger_template.logfile_level) file_handler.setFormatter(file_formatter) logger.addHandler(file_handler) logger.info(f'Logging started in the [{log_file_path}].') return logger class Formatter(logging.Formatter): def format(self, record): color = { logging.CRITICAL: 31, logging.ERROR: 31, logging.FATAL: 31, logging.WARNING: 33, logging.DEBUG: 36, logging.INFO: 32 }.get(record.levelno, 0) self._style._fmt = f"\033[37m[%(asctime)s]\033[0m \033[{color}m%(levelname)-8s\033[0m %(message)s" # 处理exception日志:临时清空exc_info,避免输出栈追踪 if record.exc_info: original_exc_info = record.exc_info record.exc_info = None result = super().format(record) record.exc_info = original_exc_info return result return super().format(record) if __name__ == '__main__': # 测试时开启日志文件 logger1 = set_logger(enable_logfile=True, logfile_name='test_log.log') logger1.debug('test message') logger1.info('test message') logger1.warning('test message') logger1.error('test message') logger1.critical('test message') try: 1 / 0 except ZeroDivisionError: logger1.exception('Division by zero occurred')
效果验证
- 标准输出(stdout):仅显示带彩色格式的简短错误消息,无栈追踪:
[2024-05-20 12:34:56,789] ERROR Division by zero occurred - 日志文件:显示完整的ERROR级别日志,包含完整栈追踪:
[2024-05-20 12:34:56,789] ERROR [test.py/<module>:87] Division by zero occurred Traceback (most recent call last): File "test.py", line 85, in <module> 1 / 0 ZeroDivisionError: division by zero
注意事项
- 方案完全基于Python logging原生机制,未新增自定义日志级别,保留了用户的自定义空间(比如用户可通过设置
log_std_level=logging.CRITICAL关闭标准输出的ERROR级日志) - 临时修改
record.exc_info后会立即恢复,不会影响其他日志处理器的正常工作
内容的提问来源于stack exchange,提问作者Mika
相关产品推荐
相关产品推荐

