如何查看渠道消费者提出的例外情况

时间:2019-01-31 10:48:52

标签: django exception django-channels

我开始使用django-channels,发现它很棒。但是,调试使用者很痛苦,因为当使用者内部引发某些异常时,什么都没有打印到终端上,则Websocket就会断开连接。

未显示的异常种类不明显。 AssertionError和其他一些系统地情况就是如此,例如下面的代码:

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

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

有没有办法像其他任何例外一样看到那些例外?

2 个答案:

答案 0 :(得分: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适用于使用者中定义的所有异步方法,但不适用于它继承的方法-即仅适用于我们为使用者定制的方法。

答案 1 :(得分:0)

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

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

如果有人有解决办法,请提出建议。