Search code examples
djangoexceptiondjango-channels

How to see exceptions raised from a channels consumer


I begin to use django-channels and I find it fantastic. However debugging consumers is painful because when some exceptions are raised from inside a consumer, nothing is printed to the terminal, the websocket is simply disconnected.

The kind of exception not shown is not obvious to identify. It is the case systematically for AssertionError, and also some others, for example the code below:

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

Calling this method with a wrong number of arguments does not print anything on the console and disconnect the websocket. Idem if the get on the last line fails.

Is there a way to see those exceptions as any other ones?


Solution

  • I found a solution to my problem. I first define a decorator:

    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
    

    Then I define a base class for all my consumers, that uses this decorator this way:

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

    But 2 problems persist:

    • Exceptions normally shown appear twice
    • Other exceptions are repeated 3 or 4 times! (as if each level of class hierarchy fires)

    Exemple of the first case (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'
    

    Example of the second case (misspelled variable):

    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
    

    If somebody has any idea to fix that, please advise.