prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Make EventScrubber recursive
  ### Problem Statement
  
  We have a custom `before_send` implementation that scrubs data recursively. I was hopping to replace the custom implementation with the built-in EventScrubber but I found out that it doesn't scrub `vars` recursively.
  
  As far as I can tell this was a consistency, perf trade-off thing but it would be nice to have a built-in option to make it recursive.
  
  Thank you!
  
  ### Solution Brainstorm
  
  `EventScrubber(recursive=True)`
  
  </issue>
  <code>
  [start of sentry_sdk/scrubber.py]
  1 from sentry_sdk.utils import (
  2     capture_internal_exceptions,
  3     AnnotatedValue,
  4     iter_event_frames,
  5 )
  6 from sentry_sdk._compat import string_types
  7 from sentry_sdk._types import TYPE_CHECKING
  8 
  9 if TYPE_CHECKING:
  10     from sentry_sdk._types import Event
  11     from typing import Any
  12     from typing import Dict
  13     from typing import List
  14     from typing import Optional
  15 
  16 
  17 DEFAULT_DENYLIST = [
  18     # stolen from relay
  19     "password",
  20     "passwd",
  21     "secret",
  22     "api_key",
  23     "apikey",
  24     "auth",
  25     "credentials",
  26     "mysql_pwd",
  27     "privatekey",
  28     "private_key",
  29     "token",
  30     "ip_address",
  31     "session",
  32     # django
  33     "csrftoken",
  34     "sessionid",
  35     # wsgi
  36     "remote_addr",
  37     "x_csrftoken",
  38     "x_forwarded_for",
  39     "set_cookie",
  40     "cookie",
  41     "authorization",
  42     "x_api_key",
  43     "x_forwarded_for",
  44     "x_real_ip",
  45     # other common names used in the wild
  46     "aiohttp_session",  # aiohttp
  47     "connect.sid",  # Express
  48     "csrf_token",  # Pyramid
  49     "csrf",  # (this is a cookie name used in accepted answers on stack overflow)
  50     "_csrf",  # Express
  51     "_csrf_token",  # Bottle
  52     "PHPSESSID",  # PHP
  53     "_session",  # Sanic
  54     "symfony",  # Symfony
  55     "user_session",  # Vue
  56     "_xsrf",  # Tornado
  57     "XSRF-TOKEN",  # Angular, Laravel
  58 ]
  59 
  60 
  61 class EventScrubber(object):
  62     def __init__(self, denylist=None):
  63         # type: (Optional[List[str]]) -> None
  64         self.denylist = DEFAULT_DENYLIST if denylist is None else denylist
  65         self.denylist = [x.lower() for x in self.denylist]
  66 
  67     def scrub_dict(self, d):
  68         # type: (Dict[str, Any]) -> None
  69         if not isinstance(d, dict):
  70             return
  71 
  72         for k in d.keys():
  73             if isinstance(k, string_types) and k.lower() in self.denylist:
  74                 d[k] = AnnotatedValue.substituted_because_contains_sensitive_data()
  75 
  76     def scrub_request(self, event):
  77         # type: (Event) -> None
  78         with capture_internal_exceptions():
  79             if "request" in event:
  80                 if "headers" in event["request"]:
  81                     self.scrub_dict(event["request"]["headers"])
  82                 if "cookies" in event["request"]:
  83                     self.scrub_dict(event["request"]["cookies"])
  84                 if "data" in event["request"]:
  85                     self.scrub_dict(event["request"]["data"])
  86 
  87     def scrub_extra(self, event):
  88         # type: (Event) -> None
  89         with capture_internal_exceptions():
  90             if "extra" in event:
  91                 self.scrub_dict(event["extra"])
  92 
  93     def scrub_user(self, event):
  94         # type: (Event) -> None
  95         with capture_internal_exceptions():
  96             if "user" in event:
  97                 self.scrub_dict(event["user"])
  98 
  99     def scrub_breadcrumbs(self, event):
  100         # type: (Event) -> None
  101         with capture_internal_exceptions():
  102             if "breadcrumbs" in event:
  103                 if "values" in event["breadcrumbs"]:
  104                     for value in event["breadcrumbs"]["values"]:
  105                         if "data" in value:
  106                             self.scrub_dict(value["data"])
  107 
  108     def scrub_frames(self, event):
  109         # type: (Event) -> None
  110         with capture_internal_exceptions():
  111             for frame in iter_event_frames(event):
  112                 if "vars" in frame:
  113                     self.scrub_dict(frame["vars"])
  114 
  115     def scrub_spans(self, event):
  116         # type: (Event) -> None
  117         with capture_internal_exceptions():
  118             if "spans" in event:
  119                 for span in event["spans"]:
  120                     if "data" in span:
  121                         self.scrub_dict(span["data"])
  122 
  123     def scrub_event(self, event):
  124         # type: (Event) -> None
  125         self.scrub_request(event)
  126         self.scrub_extra(event)
  127         self.scrub_user(event)
  128         self.scrub_breadcrumbs(event)
  129         self.scrub_frames(event)
  130         self.scrub_spans(event)
  131
  [end of sentry_sdk/scrubber.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/sentry_sdk/scrubber.py b/sentry_sdk/scrubber.py
  --- a/sentry_sdk/scrubber.py
  +++ b/sentry_sdk/scrubber.py
  @@ -59,19 +59,36 @@
   
   
   class EventScrubber(object):
  -    def __init__(self, denylist=None):
  -        # type: (Optional[List[str]]) -> None
  +    def __init__(self, denylist=None, recursive=False):
  +        # type: (Optional[List[str]], bool) -> None
           self.denylist = DEFAULT_DENYLIST if denylist is None else denylist
           self.denylist = [x.lower() for x in self.denylist]
  +        self.recursive = recursive
  +
  +    def scrub_list(self, lst):
  +        # type: (List[Any]) -> None
  +        if not isinstance(lst, list):
  +            return
  +
  +        for v in lst:
  +            if isinstance(v, dict):
  +                self.scrub_dict(v)
  +            elif isinstance(v, list):
  +                self.scrub_list(v)
   
       def scrub_dict(self, d):
           # type: (Dict[str, Any]) -> None
           if not isinstance(d, dict):
               return
   
  -        for k in d.keys():
  +        for k, v in d.items():
               if isinstance(k, string_types) and k.lower() in self.denylist:
                   d[k] = AnnotatedValue.substituted_because_contains_sensitive_data()
  +            elif self.recursive:
  +                if isinstance(v, dict):
  +                    self.scrub_dict(v)
  +                elif isinstance(v, list):
  +                    self.scrub_list(v)
   
       def scrub_request(self, event):
           # type: (Event) -> None
  
verification_info: {"golden_diff": "diff --git a/sentry_sdk/scrubber.py b/sentry_sdk/scrubber.py\n--- a/sentry_sdk/scrubber.py\n+++ b/sentry_sdk/scrubber.py\n@@ -59,19 +59,36 @@\n \n \n class EventScrubber(object):\n-    def __init__(self, denylist=None):\n-        # type: (Optional[List[str]]) -> None\n+    def __init__(self, denylist=None, recursive=False):\n+        # type: (Optional[List[str]], bool) -> None\n         self.denylist = DEFAULT_DENYLIST if denylist is None else denylist\n         self.denylist = [x.lower() for x in self.denylist]\n+        self.recursive = recursive\n+\n+    def scrub_list(self, lst):\n+        # type: (List[Any]) -> None\n+        if not isinstance(lst, list):\n+            return\n+\n+        for v in lst:\n+            if isinstance(v, dict):\n+                self.scrub_dict(v)\n+            elif isinstance(v, list):\n+                self.scrub_list(v)\n \n     def scrub_dict(self, d):\n         # type: (Dict[str, Any]) -> None\n         if not isinstance(d, dict):\n             return\n \n-        for k in d.keys():\n+        for k, v in d.items():\n             if isinstance(k, string_types) and k.lower() in self.denylist:\n                 d[k] = AnnotatedValue.substituted_because_contains_sensitive_data()\n+            elif self.recursive:\n+                if isinstance(v, dict):\n+                    self.scrub_dict(v)\n+                elif isinstance(v, list):\n+                    self.scrub_list(v)\n \n     def scrub_request(self, event):\n         # type: (Event) -> None\n", "issue": "Make EventScrubber recursive\n### Problem Statement\r\n\r\nWe have a custom `before_send` implementation that scrubs data recursively. I was hopping to replace the custom implementation with the built-in EventScrubber but I found out that it doesn't scrub `vars` recursively.\r\n\r\nAs far as I can tell this was a consistency, perf trade-off thing but it would be nice to have a built-in option to make it recursive.\r\n\r\nThank you!\r\n\r\n### Solution Brainstorm\r\n\r\n`EventScrubber(recursive=True)`\n", "before_files": [{"content": "from sentry_sdk.utils import (\n    capture_internal_exceptions,\n    AnnotatedValue,\n    iter_event_frames,\n)\nfrom sentry_sdk._compat import string_types\nfrom sentry_sdk._types import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from sentry_sdk._types import Event\n    from typing import Any\n    from typing import Dict\n    from typing import List\n    from typing import Optional\n\n\nDEFAULT_DENYLIST = [\n    # stolen from relay\n    \"password\",\n    \"passwd\",\n    \"secret\",\n    \"api_key\",\n    \"apikey\",\n    \"auth\",\n    \"credentials\",\n    \"mysql_pwd\",\n    \"privatekey\",\n    \"private_key\",\n    \"token\",\n    \"ip_address\",\n    \"session\",\n    # django\n    \"csrftoken\",\n    \"sessionid\",\n    # wsgi\n    \"remote_addr\",\n    \"x_csrftoken\",\n    \"x_forwarded_for\",\n    \"set_cookie\",\n    \"cookie\",\n    \"authorization\",\n    \"x_api_key\",\n    \"x_forwarded_for\",\n    \"x_real_ip\",\n    # other common names used in the wild\n    \"aiohttp_session\",  # aiohttp\n    \"connect.sid\",  # Express\n    \"csrf_token\",  # Pyramid\n    \"csrf\",  # (this is a cookie name used in accepted answers on stack overflow)\n    \"_csrf\",  # Express\n    \"_csrf_token\",  # Bottle\n    \"PHPSESSID\",  # PHP\n    \"_session\",  # Sanic\n    \"symfony\",  # Symfony\n    \"user_session\",  # Vue\n    \"_xsrf\",  # Tornado\n    \"XSRF-TOKEN\",  # Angular, Laravel\n]\n\n\nclass EventScrubber(object):\n    def __init__(self, denylist=None):\n        # type: (Optional[List[str]]) -> None\n        self.denylist = DEFAULT_DENYLIST if denylist is None else denylist\n        self.denylist = [x.lower() for x in self.denylist]\n\n    def scrub_dict(self, d):\n        # type: (Dict[str, Any]) -> None\n        if not isinstance(d, dict):\n            return\n\n        for k in d.keys():\n            if isinstance(k, string_types) and k.lower() in self.denylist:\n                d[k] = AnnotatedValue.substituted_because_contains_sensitive_data()\n\n    def scrub_request(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            if \"request\" in event:\n                if \"headers\" in event[\"request\"]:\n                    self.scrub_dict(event[\"request\"][\"headers\"])\n                if \"cookies\" in event[\"request\"]:\n                    self.scrub_dict(event[\"request\"][\"cookies\"])\n                if \"data\" in event[\"request\"]:\n                    self.scrub_dict(event[\"request\"][\"data\"])\n\n    def scrub_extra(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            if \"extra\" in event:\n                self.scrub_dict(event[\"extra\"])\n\n    def scrub_user(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            if \"user\" in event:\n                self.scrub_dict(event[\"user\"])\n\n    def scrub_breadcrumbs(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            if \"breadcrumbs\" in event:\n                if \"values\" in event[\"breadcrumbs\"]:\n                    for value in event[\"breadcrumbs\"][\"values\"]:\n                        if \"data\" in value:\n                            self.scrub_dict(value[\"data\"])\n\n    def scrub_frames(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            for frame in iter_event_frames(event):\n                if \"vars\" in frame:\n                    self.scrub_dict(frame[\"vars\"])\n\n    def scrub_spans(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            if \"spans\" in event:\n                for span in event[\"spans\"]:\n                    if \"data\" in span:\n                        self.scrub_dict(span[\"data\"])\n\n    def scrub_event(self, event):\n        # type: (Event) -> None\n        self.scrub_request(event)\n        self.scrub_extra(event)\n        self.scrub_user(event)\n        self.scrub_breadcrumbs(event)\n        self.scrub_frames(event)\n        self.scrub_spans(event)\n", "path": "sentry_sdk/scrubber.py"}]}