Repository: pretix/pretix

Issue:
Celery error in metrics endpoint 
<!-- Please only create issues for bug reports. Feature requests or general questions should start as a "Discussion" on GitHub. -->

**Describe the bug**
When using the metrics endpoint while celery is configured I get a 500 and the following error in the log:

```
ERROR 2021-04-18 15:19:39,868 django.request log Internal Server Error: /metrics
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.8/contextlib.py", line 75, in inner
    return func(*args, **kwds)
  File "/pretix/src/pretix/base/views/metrics.py", line 74, in serve_metrics
    m = metrics.metric_values()
  File "/pretix/src/pretix/base/metrics.py", line 289, in metric_values
    client = app.broker_connection().channel().client
AttributeError: 'Channel' object has no attribute 'client'
```
Relevant line: https://github.com/pretix/pretix/blob/1844a1fe6d8cfb6e0337fd404276286ca0e32f39/src/pretix/base/metrics.py#L289

**Expected behavior**
The metrics endpoint should return the metrics with status code 200, not an Internal Server Error.




Discussion:
To be fair I only tested this on the branch of https://github.com/pretix/pretix/pull/1909 for now but i find it rather unlikely that it is caused by that branch.
If anyone has a master/released version instance running, I'd be happy if you could test the `/metrics` endpoint.
I guess it might work on some brokers and fail on others as the type `Channel` is implemented by the used libary.
For `amqp` that would be https://github.com/celery/py-amqp. But there the `Channel` class (https://github.com/celery/py-amqp/blob/master/amqp/channel.py) simply does not have an attribute `client`

Correctly monitoring celery queue length for any broker seems to be really complicated.
See also https://stackoverflow.com/questions/61502706/celery-how-to-get-queue-size-in-a-reliable-and-testable-way
I suggest, for now we keep the current code (and therefore only support monitoring celery when redis is used as broker), 
but make it more resilient, so that it does not throw exceptions if the broker is something else.

Resolving patch:
diff --git a/src/pretix/base/metrics.py b/src/pretix/base/metrics.py
--- a/src/pretix/base/metrics.py
+++ b/src/pretix/base/metrics.py
@@ -286,17 +286,19 @@ def metric_values():
             metrics['pretix_model_instances']['{model="%s"}' % m._meta] = estimate_count_fast(m)
 
     if settings.HAS_CELERY:
-        client = app.broker_connection().channel().client
-        for q in settings.CELERY_TASK_QUEUES:
-            llen = client.llen(q.name)
-            lfirst = client.lindex(q.name, -1)
-            metrics['pretix_celery_tasks_queued_count']['{queue="%s"}' % q.name] = llen
-            if lfirst:
-                ldata = json.loads(lfirst)
-                dt = time.time() - ldata.get('created', 0)
-                metrics['pretix_celery_tasks_queued_age_seconds']['{queue="%s"}' % q.name] = dt
-            else:
-                metrics['pretix_celery_tasks_queued_age_seconds']['{queue="%s"}' % q.name] = 0
+        channel = app.broker_connection().channel()
+        if hasattr(channel, 'client') and channel.client is not None:
+            client = channel.client
+            for q in settings.CELERY_TASK_QUEUES:
+                llen = client.llen(q.name)
+                lfirst = client.lindex(q.name, -1)
+                metrics['pretix_celery_tasks_queued_count']['{queue="%s"}' % q.name] = llen
+                if lfirst:
+                    ldata = json.loads(lfirst)
+                    dt = time.time() - ldata.get('created', 0)
+                    metrics['pretix_celery_tasks_queued_age_seconds']['{queue="%s"}' % q.name] = dt
+                else:
+                    metrics['pretix_celery_tasks_queued_age_seconds']['{queue="%s"}' % q.name] = 0
 
     return metrics
 


Test patch: