Repository: getsentry/sentry-python

Issue:
[0.7.0] `CeleryIntegration` captures retries
Greetings fellows!

We are having an issue with `CeleryIntegration` in Sentry SDK.

### Current versions
Python 3.6.7
Django 2.1.5
Celery 4.1.1
Sentry SDK 0.7.0-0.7.1

### Current behavior
In our code (internal and 3rd-party) we are using [Celery tasks retry functionality](http://docs.celeryproject.org/en/latest/userguide/tasks.html#retrying).

> The app.Task.retry() call will raise an exception so any code after the retry won’t be reached. This is the Retry exception, it isn’t handled as an error but rather as a semi-predicate to signify to the worker that the task is to be retried, so that it can store the correct state when a result backend is enabled.

We did switch recently from Raven to Sentry SDK 0.6.9, everything seemed working as before.
But today we updated it to [0.7.0 release](https://github.com/getsentry/sentry-python/blob/master/CHANGES.md#070) (and later to 0.7.1)

This caused **every [`celery.exceptions.Retry`](http://docs.celeryproject.org/en/latest/reference/celery.exceptions.html#celery.exceptions.Retry) to be sent to Sentry**, which quickly filled Sentry server with thousands of events.
Previously (in old SDK and Raven), those exceptions were ignored and not sent to Sentry server.

### Expected behaviour
`CeleryIntegration` is not flooding Sentry server with every retry exception. Basically, the same behavior as it was in Raven and Sentry SDK<0.7.0.

### Open questions
I am not sure if the old behavior was done intentionally or by mistake.
If that was intended, we should reimplement it in current integration.
If not, there should be a way to filter/ignore that kind of exceptions (I am not sure if we can filter all retries from internal and 3rd-party code in`before_send` in a clean way).

Could you help me to clarify this issue?


Discussion:
This is absolutely a regression. Thanks for reporting! We recently rewrote the Celery integration in an attempt to fix other bugs. We will fix this within 1-2 days

Resolving patch:
diff --git a/sentry_sdk/integrations/celery.py b/sentry_sdk/integrations/celery.py
--- a/sentry_sdk/integrations/celery.py
+++ b/sentry_sdk/integrations/celery.py
@@ -2,7 +2,7 @@
 
 import sys
 
-from celery.exceptions import SoftTimeLimitExceeded
+from celery.exceptions import SoftTimeLimitExceeded, Retry
 
 from sentry_sdk.hub import Hub
 from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
@@ -82,6 +82,15 @@ def event_processor(event, hint):
             }
 
         if "exc_info" in hint:
+            with capture_internal_exceptions():
+                if isinstance(hint["exc_info"][1], Retry):
+                    return None
+
+                if hasattr(task, "throws") and isinstance(
+                    hint["exc_info"][1], task.throws
+                ):
+                    return None
+
             with capture_internal_exceptions():
                 if issubclass(hint["exc_info"][0], SoftTimeLimitExceeded):
                     event["fingerprint"] = [
@@ -90,12 +99,6 @@ def event_processor(event, hint):
                         getattr(task, "name", task),
                     ]
 
-            with capture_internal_exceptions():
-                if hasattr(task, "throws") and isinstance(
-                    hint["exc_info"][1], task.throws
-                ):
-                    return None
-
         return event
 
     return event_processor


Test patch:
diff --git a/tests/integrations/celery/test_celery.py b/tests/integrations/celery/test_celery.py
--- a/tests/integrations/celery/test_celery.py
+++ b/tests/integrations/celery/test_celery.py
@@ -103,3 +103,39 @@ def dummy_task(x, y):
         assert stack_lengths == [2]
     else:
         assert stack_lengths == [2, 2]
+
+
+@pytest.mark.skipif(
+    (4, 2, 0) <= VERSION < (4, 2, 2),
+    reason="https://github.com/celery/celery/issues/4661",
+)
+def test_retry(celery, capture_events):
+    events = capture_events()
+    failures = [True, True, False]
+    runs = []
+
+    @celery.task(name="dummy_task", bind=True)
+    def dummy_task(self):
+        runs.append(1)
+        try:
+            if failures.pop(0):
+                1 / 0
+        except Exception as exc:
+            self.retry(max_retries=2, exc=exc)
+
+    dummy_task.delay()
+
+    assert len(runs) == 3
+    assert not events
+
+    failures = [True, True, True]
+    runs = []
+
+    dummy_task.delay()
+
+    assert len(runs) == 3
+    event, = events
+    exceptions = event["exception"]["values"]
+
+    for e in exceptions:
+        assert e["type"] == "ZeroDivisionError"