如何将traceback.print_exc重定向至logging?eventlet异常日志未写入文件
解决Eventlet spawn_n异常日志未写入文件的问题
刚好碰到过类似的场景,eventlet的spawn_n在协程抛出异常时默认会把栈追踪输出到stderr,而你的日志系统用WatchedFileHandler写文件,自然捕获不到这些内容。下面给你几个靠谱的解决办法:
方法一:给任务函数加异常捕获(最推荐)
最直接的方式是包装你的任务函数,在内部捕获所有异常,然后用logging.error把异常信息(包括栈追踪)写入日志文件。可以写一个通用的装饰器来复用:
import logging import eventlet from logging.handlers import WatchedFileHandler # 先配置好你的logging系统 logger = logging.getLogger("eventlet_tasks") logger.setLevel(logging.ERROR) handler = WatchedFileHandler("app.log") formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") handler.setFormatter(formatter) logger.addHandler(handler) def log_exceptions(func): """装饰器:捕获函数执行中的异常并记录到日志""" def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception: # exc_info=True会自动带上完整的栈追踪信息 logger.error(f"任务 {func.__name__} 执行失败:", exc_info=True) return wrapper # 你的原始任务函数 @log_exceptions def my_faulty_task(): raise ValueError("模拟一个异常情况!") # 用spawn_n启动包装后的任务 eventlet.spawn_n(my_faulty_task)
这个方法的好处是灵活,你可以针对不同任务定制异常处理逻辑,而且不会影响其他非eventlet的stderr输出。
方法二:重定向stderr到logging系统
如果你不想修改每个任务函数,可以把整个程序的stderr输出重定向到logging。写一个自定义的类来替换sys.stderr,把写入的内容转发到日志:
import sys import logging import eventlet from logging.handlers import WatchedFileHandler # 配置logging logger = logging.getLogger("stderr_logger") logger.setLevel(logging.ERROR) handler = WatchedFileHandler("app.log") formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") handler.setFormatter(formatter) logger.addHandler(handler) class StderrRedirector: def __init__(self, target_logger, log_level=logging.ERROR): self.logger = target_logger self.log_level = log_level self.buffer = [] def write(self, data): # 处理换行,把零散的输出拼接成完整的日志行 self.buffer.append(data) if "\n" in data: full_msg = "".join(self.buffer).rstrip() if full_msg: self.logger.log(self.log_level, full_msg) self.buffer = [] def flush(self): # 刷新缓冲区,避免残留未输出的内容 if self.buffer: full_msg = "".join(self.buffer).rstrip() if full_msg: self.logger.log(self.log_level, full_msg) self.buffer = [] # 替换sys.stderr sys.stderr = StderrRedirector(logger) # 现在spawn_n的异常会自动进入日志文件 def my_faulty_task(): raise ValueError("模拟一个异常情况!") eventlet.spawn_n(my_faulty_task)
注意:这个方法会捕获所有输出到stderr的内容,包括其他库的错误信息,如果你只想针对eventlet的异常,这个方法可能会引入冗余日志。
方法三:自定义GreenPool重写错误处理
从你提到的eventlet源码来看,spawn_n属于GreenPool的功能,异常处理在_run方法里。我们可以继承GreenPool,重写这个方法,把traceback.print_exc()换成logging记录:
import logging import eventlet from eventlet.greenpool import GreenPool, IMMEDIATE_RETVAL from logging.handlers import WatchedFileHandler # 配置logging logger = logging.getLogger("eventlet_pool") logger.setLevel(logging.ERROR) handler = WatchedFileHandler("app.log") formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") handler.setFormatter(formatter) logger.addHandler(handler) class LoggingGreenPool(GreenPool): def _run(self, func, args, kwargs, waiter): try: result = func(*args, **kwargs) waiter.switch(result) except Exception: # 用logging记录异常,替代默认的stderr输出 logger.error(f"GreenPool任务执行失败:", exc_info=True) waiter.switch(IMMEDIATE_RETVAL) # 使用自定义的Pool来启动任务 pool = LoggingGreenPool() def my_faulty_task(): raise ValueError("模拟一个异常情况!") pool.spawn_n(my_faulty_task)
这个方法最贴合eventlet的内部机制,只处理GreenPool中spawn的任务异常,不会干扰其他部分的日志。
内容的提问来源于stack exchange,提问作者Ryan
相关产品推荐
相关产品推荐

