【问题标题】:How to see exceptions raised from a channels consumer如何查看渠道消费者引发的异常
【发布时间】:2019-01-31 10:48:52
【问题描述】:

我开始使用django-channels,我觉得它很棒。然而调试消费者很痛苦,因为当消费者内部引发一些异常时,终端不会打印任何内容,websocket 只是断开连接。

未显示的异常类型不明显识别。 AssertionError 和其他一些系统都是这种情况,例如下面的代码:

class MexicoProgressConsumer(ProgressConsumer):
    def init(self, SSDBConfig, Sub_application):
        subappli = models.Sub_application.objects.get(pk=Sub_application)
        ...

使用错误数量的参数调用此方法不会在控制台上打印任何内容并断开 websocket。如果最后一行的 get 失败,则同上。

有没有办法将这些异常视为其他异常?

【问题讨论】:

  • 你能给出一个没有显示异常的示例代码吗?我得到了引发的大部分异常
  • 是的,你是对的,我意识到大多数异常都有效,除了系统地AssertionError,还有一些我不记得的其他情况。不走运,我经常使用assert
  • 你能举个具体的例子吗?
  • 问题是没有什么可显示的,因为它什么也没发生。我遇到了另一个未显示的异常:消费者方法调用中的参数数量错误。
  • 您可以发布应该引发错误的代码和示例值

标签: django exception django-channels


【解决方案1】:

从 albar 的 answer 开始,我达到的解决方案是定义一个这样的装饰器

from functools import wraps
from logging import getLogger

from channels.exceptions import AcceptConnection, DenyConnection, StopConsumer

logger = getLogger("foo-logger")

def log_exceptions(f):
    @wraps(f)
    async def wrapper(*args, **kwargs):
        try:
            return await f(*args, **kwargs)
        except (AcceptConnection, DenyConnection, StopConsumer):
            raise
        except Exception as exception:
            if not getattr(exception, "logged_by_wrapper", False):
                logger.error(
                    "Unhandled exception occurred in {}:".format(f.__qualname__),
                    exc_info=exception,
                )
                setattr(exception, "logged_by_wrapper", True)
            raise

    return wrapper

这有几个改进:

  • 使用 functools.wraps 使包装的函数更接近原始函数。
  • 使用 async/await 语法,因为我使用的是异步消费者(如果不是,请删除)
  • 不记录 django-channels 故意引发的多个异常。
  • 仅在没有设置logged_by_wrapper 属性时记录异常。这导致异常只记录一次,因为我们在第一次记录后设置了属性。
  • 使用python内置的logging模块来记录错误。这会自动格式化异常和回溯,因为我们在 exc_info=exception 中提供了异常。

然后,我定义了一个类装饰器来代替基类,以将其应用于消费者的方法

from inspect import iscoroutinefunction

def log_consumer_exceptions(klass):
    for method_name, method in list(klass.__dict__.items()):
        if iscoroutinefunction(method):
            setattr(klass, method_name, log_exceptions(method))

    return klass

这会将log_exceptions 应用于 Consumer 中定义的所有异步方法,但不适用于它继承的方法 - 即仅适用于我们为 Consumer 自定义的方法。

【讨论】:

    【解决方案2】:

    我找到了解决问题的方法。我先定义一个装饰器:

    import traceback
    def catch_exception(f):
        def wrapper(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except StopConsumer:
                raise
            except Exception as e:
                print(traceback.format_exc().strip('\n'), '<--- from consumer')
                raise
        return wrapper
    

    然后我为我的所有消费者定义一个基类,以这种方式使用这个装饰器:

    import inspect
    class BaseConsumer(JsonWebsocketConsumer):
        def __getattribute__(self, name):
            value = object.__getattribute__(self, name)
            if inspect.ismethod(value):
                return catch_exception(value)
            return value
    

    但仍然存在 2 个问题:

    • 通常显示的异常会出现两次
    • 其他异常重复3或4次! (好像每个级别的类层次结构都会触发)

    第一种情况(KeyError)的例子:

    Traceback (most recent call last):
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 19, in wrapper
        return f(*args, **kwargs)
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 31, in wrapper
        result = f(owner, **kwargs)
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 110, in refresh
        data = super().refresh.__wrapped__(self)
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 73, in refresh
        pvalue = round(data['toto'] * 100, 1)
    KeyError: 'toto' <--- from consumer
    Exception in thread Thread-3:
    Traceback (most recent call last):
      File "/usr/lib/python3.6/threading.py", line 916, in _bootstrap_inner
        self.run()
      File "/usr/lib/python3.6/threading.py", line 864, in run
        self._target(*self._args, **self._kwargs)
      File "/home/alain/ADN/simutool/dbsimu/utils.py", line 193, in repeat
        self.repeat_func()
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 19, in wrapper
        return f(*args, **kwargs)
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 31, in wrapper
        result = f(owner, **kwargs)
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 110, in refresh
        data = super().refresh.__wrapped__(self)
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 73, in refresh
        pvalue = round(data['toto'] * 100, 1)
    KeyError: 'toto'
    

    第二种情况的示例(拼写错误的变量):

    WebSocket CONNECT /ws/dbsimu/Simuflow_progress/ [127.0.0.1:55866]
    Traceback (most recent call last):
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 19, in wrapper
        return f(*args, **kwargs)
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 57, in receive_json
        return getattr(self, icommand)(**data)
    NameError: name 'icommand' is not defined <--- from consumer
    Traceback (most recent call last):
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 19, in wrapper
        return f(*args, **kwargs)
      File "/home/alain/.local/lib/python3.6/site-packages/channels/generic/websocket.py", line 125, in receive
        self.receive_json(self.decode_json(text_data), **kwargs)
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 19, in wrapper
        return f(*args, **kwargs)
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 57, in receive_json
        return getattr(self, icommand)(**data)
    NameError: name 'icommand' is not defined <--- from consumer
    Traceback (most recent call last):
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 19, in wrapper
        return f(*args, **kwargs)
      File "/home/alain/.local/lib/python3.6/site-packages/channels/generic/websocket.py", line 60, in websocket_receive
        self.receive(text_data=message["text"])
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 19, in wrapper
        return f(*args, **kwargs)
      File "/home/alain/.local/lib/python3.6/site-packages/channels/generic/websocket.py", line 125, in receive
        self.receive_json(self.decode_json(text_data), **kwargs)
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 19, in wrapper
        return f(*args, **kwargs)
      File "/home/alain/ADN/simutool/dbsimu/consumers.py", line 57, in receive_json
        return getattr(self, icommand)(**data)
    NameError: name 'icommand' is not defined <--- from consumer
    

    如果有人想解决这个问题,请提出建议。

    【讨论】:

    • 可能是因为您覆盖了__getattribute__,每次获取属性时都会调用它。我将定义一个类装饰器,而不是基类,当在导入时创建类(不是类的实例)时,它将异常捕获装饰器应用于每个方法。类似于六库中的python_2_unicode_compatible
    • 经过更多调查,我相信您多次打印异常的原因是它们通过多个级别的函数调用被提升,并在每个级别都被记录。请参阅我的答案以获得不同的解决方案
    猜你喜欢
    • 2023-03-20
    • 1970-01-01
    • 2016-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多