We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Recursive serializer stackoverflow error when serializing an object that logs on iteration
I'm currently having a problem with the SDKs integrated serialisation of local variables for sending with events.
I have in my local variables, an object that is a `Mapping` but that sometimes raises an event when keys are accessed. This is a normal behavior, since keys are lazy loaded and my code handles these correctly. However, when an event occurs and this variable is serialized for sending to sentry, the sdk iterates over all my keys and causes an infinite recursion.

Minimal code te reproduce :
```python
import logging
from collections.abc import Sequence
import sentry_sdk

logger = logging.getLogger(__name__)

class MyGenerator(Sequence):
    def __init__(self):
        self.values = [1, 2, 3]
        Sequence.__init__(self)
    
    def __iter__(self):
        for value in self.values:
            yield value
        logger.error(f"No values left", exc_info=True)

    def __len__(self):
        """List length"""
        return len(self.values)
        
    def __getitem__(self, ii):
        """Get a list item"""
        return self.values[ii]
    

if __name__ == "__main__":
    sentry_sdk.init(
        dsn="[DSN]"
    )

    gen = MyGenerator()

    logger.error("Testing", exc_info=True)
```

The only quick solution I found is to set `with_locals=False` on the sdk but it's a pity to lack all this functionality because of this case.
On the other hand, I'm not sure how the SDK could anticipate such problems, here's what I'm thinking:
* The SDK could check how deep in recursion it is when serializing and stop when it notices it's handling a log that was generated while handling an other log.
* In the same way that exceptions that occur when deserializing trigger `<failed to serialize, use init(debug=True) to see error logs>`, all logs could be disregarded
* In some cases it could be problematic that sentry iterates over those variables altogether (for example, emptying a generator when it should still have values available) and for those maybe a `serialize_ignore_classes=[MyDangerousClass]` on the SDK init could avoid interference with such classes.

Happy to open a PR to follow-up on this when a proper solution is found !

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sentry_sdk/client.py`
Content:
```
1 import os
2 import uuid
3 import random
4 from datetime import datetime
5 import socket
6 
7 from sentry_sdk._compat import string_types, text_type, iteritems
8 from sentry_sdk.utils import (
9     handle_in_app,
10     get_type_name,
11     capture_internal_exceptions,
12     current_stacktrace,
13     logger,
14 )
15 from sentry_sdk.serializer import Serializer
16 from sentry_sdk.transport import make_transport
17 from sentry_sdk.consts import DEFAULT_OPTIONS, SDK_INFO, ClientConstructor
18 from sentry_sdk.integrations import setup_integrations
19 from sentry_sdk.utils import ContextVar
20 
21 from sentry_sdk._types import MYPY
22 
23 if MYPY:
24     from typing import Any
25     from typing import Callable
26     from typing import Dict
27     from typing import Optional
28 
29     from sentry_sdk.scope import Scope
30     from sentry_sdk._types import Event, Hint
31 
32 
33 _client_init_debug = ContextVar("client_init_debug")
34 
35 
36 def _get_options(*args, **kwargs):
37     # type: (*Optional[str], **Any) -> Dict[str, Any]
38     if args and (isinstance(args[0], (text_type, bytes, str)) or args[0] is None):
39         dsn = args[0]  # type: Optional[str]
40         args = args[1:]
41     else:
42         dsn = None
43 
44     rv = dict(DEFAULT_OPTIONS)
45     options = dict(*args, **kwargs)  # type: ignore
46     if dsn is not None and options.get("dsn") is None:
47         options["dsn"] = dsn  # type: ignore
48 
49     for key, value in iteritems(options):
50         if key not in rv:
51             raise TypeError("Unknown option %r" % (key,))
52         rv[key] = value
53 
54     if rv["dsn"] is None:
55         rv["dsn"] = os.environ.get("SENTRY_DSN")
56 
57     if rv["release"] is None:
58         rv["release"] = os.environ.get("SENTRY_RELEASE")
59 
60     if rv["environment"] is None:
61         rv["environment"] = os.environ.get("SENTRY_ENVIRONMENT")
62 
63     if rv["server_name"] is None and hasattr(socket, "gethostname"):
64         rv["server_name"] = socket.gethostname()
65 
66     return rv  # type: ignore
67 
68 
69 class _Client(object):
70     """The client is internally responsible for capturing the events and
71     forwarding them to sentry through the configured transport.  It takes
72     the client options as keyword arguments and optionally the DSN as first
73     argument.
74     """
75 
76     def __init__(self, *args, **kwargs):
77         # type: (*Any, **Any) -> None
78         self.options = get_options(*args, **kwargs)  # type: Dict[str, Any]
79         self._init_impl()
80 
81     def __getstate__(self):
82         # type: () -> Any
83         return {"options": self.options}
84 
85     def __setstate__(self, state):
86         # type: (Any) -> None
87         self.options = state["options"]
88         self._init_impl()
89 
90     def _init_impl(self):
91         # type: () -> None
92         old_debug = _client_init_debug.get(False)
93         try:
94             _client_init_debug.set(self.options["debug"])
95             self.transport = make_transport(self.options)
96 
97             request_bodies = ("always", "never", "small", "medium")
98             if self.options["request_bodies"] not in request_bodies:
99                 raise ValueError(
100                     "Invalid value for request_bodies. Must be one of {}".format(
101                         request_bodies
102                     )
103                 )
104 
105             self.integrations = setup_integrations(
106                 self.options["integrations"],
107                 with_defaults=self.options["default_integrations"],
108             )
109         finally:
110             _client_init_debug.set(old_debug)
111 
112     @property
113     def dsn(self):
114         # type: () -> Optional[str]
115         """Returns the configured DSN as string."""
116         return self.options["dsn"]
117 
118     def _prepare_event(
119         self,
120         event,  # type: Event
121         hint,  # type: Optional[Hint]
122         scope,  # type: Optional[Scope]
123     ):
124         # type: (...) -> Optional[Event]
125         if event.get("timestamp") is None:
126             event["timestamp"] = datetime.utcnow()
127 
128         hint = dict(hint or ())  # type: Hint
129 
130         if scope is not None:
131             event_ = scope.apply_to_event(event, hint)
132             if event_ is None:
133                 return None
134             event = event_
135 
136         if (
137             self.options["attach_stacktrace"]
138             and "exception" not in event
139             and "stacktrace" not in event
140             and "threads" not in event
141         ):
142             with capture_internal_exceptions():
143                 event["threads"] = {
144                     "values": [
145                         {
146                             "stacktrace": current_stacktrace(
147                                 self.options["with_locals"]
148                             ),
149                             "crashed": False,
150                             "current": True,
151                         }
152                     ]
153                 }
154 
155         for key in "release", "environment", "server_name", "dist":
156             if event.get(key) is None and self.options[key] is not None:  # type: ignore
157                 event[key] = text_type(self.options[key]).strip()  # type: ignore
158         if event.get("sdk") is None:
159             sdk_info = dict(SDK_INFO)
160             sdk_info["integrations"] = sorted(self.integrations.keys())
161             event["sdk"] = sdk_info
162 
163         if event.get("platform") is None:
164             event["platform"] = "python"
165 
166         event = handle_in_app(
167             event, self.options["in_app_exclude"], self.options["in_app_include"]
168         )
169 
170         # Postprocess the event here so that annotated types do
171         # generally not surface in before_send
172         if event is not None:
173             event = Serializer().serialize_event(event)
174 
175         before_send = self.options["before_send"]
176         if before_send is not None:
177             new_event = None
178             with capture_internal_exceptions():
179                 new_event = before_send(event, hint or {})
180             if new_event is None:
181                 logger.info("before send dropped event (%s)", event)
182             event = new_event  # type: ignore
183 
184         return event
185 
186     def _is_ignored_error(self, event, hint):
187         # type: (Event, Hint) -> bool
188         exc_info = hint.get("exc_info")
189         if exc_info is None:
190             return False
191 
192         type_name = get_type_name(exc_info[0])
193         full_name = "%s.%s" % (exc_info[0].__module__, type_name)
194 
195         for errcls in self.options["ignore_errors"]:
196             # String types are matched against the type name in the
197             # exception only
198             if isinstance(errcls, string_types):
199                 if errcls == full_name or errcls == type_name:
200                     return True
201             else:
202                 if issubclass(exc_info[0], errcls):  # type: ignore
203                     return True
204 
205         return False
206 
207     def _should_capture(
208         self,
209         event,  # type: Event
210         hint,  # type: Hint
211         scope=None,  # type: Optional[Scope]
212     ):
213         # type: (...) -> bool
214         if scope is not None and not scope._should_capture:
215             return False
216 
217         if (
218             self.options["sample_rate"] < 1.0
219             and random.random() >= self.options["sample_rate"]
220         ):
221             return False
222 
223         if self._is_ignored_error(event, hint):
224             return False
225 
226         return True
227 
228     def capture_event(
229         self,
230         event,  # type: Event
231         hint=None,  # type: Optional[Hint]
232         scope=None,  # type: Optional[Scope]
233     ):
234         # type: (...) -> Optional[str]
235         """Captures an event.
236 
237         :param event: A ready-made event that can be directly sent to Sentry.
238 
239         :param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object.
240 
241         :returns: An event ID. May be `None` if there is no DSN set or of if the SDK decided to discard the event for other reasons. In such situations setting `debug=True` on `init()` may help.
242         """
243         if self.transport is None:
244             return None
245         if hint is None:
246             hint = {}
247         rv = event.get("event_id")
248         if rv is None:
249             event["event_id"] = rv = uuid.uuid4().hex
250         if not self._should_capture(event, hint, scope):
251             return None
252         event = self._prepare_event(event, hint, scope)
253         if event is None:
254             return None
255         self.transport.capture_event(event)
256         return rv
257 
258     def close(
259         self,
260         timeout=None,  # type: Optional[float]
261         callback=None,  # type: Optional[Callable[[int, float], None]]
262     ):
263         # type: (...) -> None
264         """
265         Close the client and shut down the transport. Arguments have the same
266         semantics as :py:meth:`Client.flush`.
267         """
268         if self.transport is not None:
269             self.flush(timeout=timeout, callback=callback)
270             self.transport.kill()
271             self.transport = None
272 
273     def flush(
274         self,
275         timeout=None,  # type: Optional[float]
276         callback=None,  # type: Optional[Callable[[int, float], None]]
277     ):
278         # type: (...) -> None
279         """
280         Wait for the current events to be sent.
281 
282         :param timeout: Wait for at most `timeout` seconds. If no `timeout` is provided, the `shutdown_timeout` option value is used.
283 
284         :param callback: Is invoked with the number of pending events and the configured timeout.
285         """
286         if self.transport is not None:
287             if timeout is None:
288                 timeout = self.options["shutdown_timeout"]
289             self.transport.flush(timeout=timeout, callback=callback)
290 
291     def __enter__(self):
292         # type: () -> _Client
293         return self
294 
295     def __exit__(self, exc_type, exc_value, tb):
296         # type: (Any, Any, Any) -> None
297         self.close()
298 
299 
300 from sentry_sdk._types import MYPY
301 
302 if MYPY:
303     # Make mypy, PyCharm and other static analyzers think `get_options` is a
304     # type to have nicer autocompletion for params.
305     #
306     # Use `ClientConstructor` to define the argument types of `init` and
307     # `Dict[str, Any]` to tell static analyzers about the return type.
308 
309     class get_options(ClientConstructor, Dict[str, Any]):
310         pass
311 
312     class Client(ClientConstructor, _Client):
313         pass
314 
315 
316 else:
317     # Alias `get_options` for actual usage. Go through the lambda indirection
318     # to throw PyCharm off of the weakly typed signature (it would otherwise
319     # discover both the weakly typed signature of `_init` and our faked `init`
320     # type).
321 
322     get_options = (lambda: _get_options)()
323     Client = (lambda: _Client)()
324
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py
--- a/sentry_sdk/client.py
+++ b/sentry_sdk/client.py
@@ -31,6 +31,7 @@
 
 
 _client_init_debug = ContextVar("client_init_debug")
+_client_in_capture_event = ContextVar("client_in_capture_event")
 
 
 def _get_options(*args, **kwargs):
@@ -240,20 +241,29 @@
 
         :returns: An event ID. May be `None` if there is no DSN set or of if the SDK decided to discard the event for other reasons. In such situations setting `debug=True` on `init()` may help.
         """
-        if self.transport is None:
+        is_recursive = _client_in_capture_event.get(False)
+        if is_recursive:
             return None
-        if hint is None:
-            hint = {}
-        rv = event.get("event_id")
-        if rv is None:
-            event["event_id"] = rv = uuid.uuid4().hex
-        if not self._should_capture(event, hint, scope):
-            return None
-        event = self._prepare_event(event, hint, scope)
-        if event is None:
-            return None
-        self.transport.capture_event(event)
-        return rv
+
+        _client_in_capture_event.set(True)
+
+        try:
+            if self.transport is None:
+                return None
+            if hint is None:
+                hint = {}
+            event_id = event.get("event_id")
+            if event_id is None:
+                event["event_id"] = event_id = uuid.uuid4().hex
+            if not self._should_capture(event, hint, scope):
+                return None
+            event_opt = self._prepare_event(event, hint, scope)
+            if event_opt is None:
+                return None
+            self.transport.capture_event(event_opt)
+            return event_id
+        finally:
+            _client_in_capture_event.set(False)
 
     def close(
         self,

{"golden_diff": "diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py\n--- a/sentry_sdk/client.py\n+++ b/sentry_sdk/client.py\n@@ -31,6 +31,7 @@\n \n \n _client_init_debug = ContextVar(\"client_init_debug\")\n+_client_in_capture_event = ContextVar(\"client_in_capture_event\")\n \n \n def _get_options(*args, **kwargs):\n@@ -240,20 +241,29 @@\n \n         :returns: An event ID. May be `None` if there is no DSN set or of if the SDK decided to discard the event for other reasons. In such situations setting `debug=True` on `init()` may help.\n         \"\"\"\n-        if self.transport is None:\n+        is_recursive = _client_in_capture_event.get(False)\n+        if is_recursive:\n             return None\n-        if hint is None:\n-            hint = {}\n-        rv = event.get(\"event_id\")\n-        if rv is None:\n-            event[\"event_id\"] = rv = uuid.uuid4().hex\n-        if not self._should_capture(event, hint, scope):\n-            return None\n-        event = self._prepare_event(event, hint, scope)\n-        if event is None:\n-            return None\n-        self.transport.capture_event(event)\n-        return rv\n+\n+        _client_in_capture_event.set(True)\n+\n+        try:\n+            if self.transport is None:\n+                return None\n+            if hint is None:\n+                hint = {}\n+            event_id = event.get(\"event_id\")\n+            if event_id is None:\n+                event[\"event_id\"] = event_id = uuid.uuid4().hex\n+            if not self._should_capture(event, hint, scope):\n+                return None\n+            event_opt = self._prepare_event(event, hint, scope)\n+            if event_opt is None:\n+                return None\n+            self.transport.capture_event(event_opt)\n+            return event_id\n+        finally:\n+            _client_in_capture_event.set(False)\n \n     def close(\n         self,\n", "issue": "Recursive serializer stackoverflow error when serializing an object that logs on iteration\nI'm currently having a problem with the SDKs integrated serialisation of local variables for sending with events.\r\nI have in my local variables, an object that is a `Mapping` but that sometimes raises an event when keys are accessed. This is a normal behavior, since keys are lazy loaded and my code handles these correctly. However, when an event occurs and this variable is serialized for sending to sentry, the sdk iterates over all my keys and causes an infinite recursion.\r\n\r\nMinimal code te reproduce :\r\n```python\r\nimport logging\r\nfrom collections.abc import Sequence\r\nimport sentry_sdk\r\n\r\nlogger = logging.getLogger(__name__)\r\n\r\nclass MyGenerator(Sequence):\r\n    def __init__(self):\r\n        self.values = [1, 2, 3]\r\n        Sequence.__init__(self)\r\n    \r\n    def __iter__(self):\r\n        for value in self.values:\r\n            yield value\r\n        logger.error(f\"No values left\", exc_info=True)\r\n\r\n    def __len__(self):\r\n        \"\"\"List length\"\"\"\r\n        return len(self.values)\r\n        \r\n    def __getitem__(self, ii):\r\n        \"\"\"Get a list item\"\"\"\r\n        return self.values[ii]\r\n    \r\n\r\nif __name__ == \"__main__\":\r\n    sentry_sdk.init(\r\n        dsn=\"[DSN]\"\r\n    )\r\n\r\n    gen = MyGenerator()\r\n\r\n    logger.error(\"Testing\", exc_info=True)\r\n```\r\n\r\nThe only quick solution I found is to set `with_locals=False` on the sdk but it's a pity to lack all this functionality because of this case.\r\nOn the other hand, I'm not sure how the SDK could anticipate such problems, here's what I'm thinking:\r\n* The SDK could check how deep in recursion it is when serializing and stop when it notices it's handling a log that was generated while handling an other log.\r\n* In the same way that exceptions that occur when deserializing trigger `<failed to serialize, use init(debug=True) to see error logs>`, all logs could be disregarded\r\n* In some cases it could be problematic that sentry iterates over those variables altogether (for example, emptying a generator when it should still have values available) and for those maybe a `serialize_ignore_classes=[MyDangerousClass]` on the SDK init could avoid interference with such classes.\r\n\r\nHappy to open a PR to follow-up on this when a proper solution is found !\n", "before_files": [{"content": "import os\nimport uuid\nimport random\nfrom datetime import datetime\nimport socket\n\nfrom sentry_sdk._compat import string_types, text_type, iteritems\nfrom sentry_sdk.utils import (\n    handle_in_app,\n    get_type_name,\n    capture_internal_exceptions,\n    current_stacktrace,\n    logger,\n)\nfrom sentry_sdk.serializer import Serializer\nfrom sentry_sdk.transport import make_transport\nfrom sentry_sdk.consts import DEFAULT_OPTIONS, SDK_INFO, ClientConstructor\nfrom sentry_sdk.integrations import setup_integrations\nfrom sentry_sdk.utils import ContextVar\n\nfrom sentry_sdk._types import MYPY\n\nif MYPY:\n    from typing import Any\n    from typing import Callable\n    from typing import Dict\n    from typing import Optional\n\n    from sentry_sdk.scope import Scope\n    from sentry_sdk._types import Event, Hint\n\n\n_client_init_debug = ContextVar(\"client_init_debug\")\n\n\ndef _get_options(*args, **kwargs):\n    # type: (*Optional[str], **Any) -> Dict[str, Any]\n    if args and (isinstance(args[0], (text_type, bytes, str)) or args[0] is None):\n        dsn = args[0]  # type: Optional[str]\n        args = args[1:]\n    else:\n        dsn = None\n\n    rv = dict(DEFAULT_OPTIONS)\n    options = dict(*args, **kwargs)  # type: ignore\n    if dsn is not None and options.get(\"dsn\") is None:\n        options[\"dsn\"] = dsn  # type: ignore\n\n    for key, value in iteritems(options):\n        if key not in rv:\n            raise TypeError(\"Unknown option %r\" % (key,))\n        rv[key] = value\n\n    if rv[\"dsn\"] is None:\n        rv[\"dsn\"] = os.environ.get(\"SENTRY_DSN\")\n\n    if rv[\"release\"] is None:\n        rv[\"release\"] = os.environ.get(\"SENTRY_RELEASE\")\n\n    if rv[\"environment\"] is None:\n        rv[\"environment\"] = os.environ.get(\"SENTRY_ENVIRONMENT\")\n\n    if rv[\"server_name\"] is None and hasattr(socket, \"gethostname\"):\n        rv[\"server_name\"] = socket.gethostname()\n\n    return rv  # type: ignore\n\n\nclass _Client(object):\n    \"\"\"The client is internally responsible for capturing the events and\n    forwarding them to sentry through the configured transport.  It takes\n    the client options as keyword arguments and optionally the DSN as first\n    argument.\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        # type: (*Any, **Any) -> None\n        self.options = get_options(*args, **kwargs)  # type: Dict[str, Any]\n        self._init_impl()\n\n    def __getstate__(self):\n        # type: () -> Any\n        return {\"options\": self.options}\n\n    def __setstate__(self, state):\n        # type: (Any) -> None\n        self.options = state[\"options\"]\n        self._init_impl()\n\n    def _init_impl(self):\n        # type: () -> None\n        old_debug = _client_init_debug.get(False)\n        try:\n            _client_init_debug.set(self.options[\"debug\"])\n            self.transport = make_transport(self.options)\n\n            request_bodies = (\"always\", \"never\", \"small\", \"medium\")\n            if self.options[\"request_bodies\"] not in request_bodies:\n                raise ValueError(\n                    \"Invalid value for request_bodies. Must be one of {}\".format(\n                        request_bodies\n                    )\n                )\n\n            self.integrations = setup_integrations(\n                self.options[\"integrations\"],\n                with_defaults=self.options[\"default_integrations\"],\n            )\n        finally:\n            _client_init_debug.set(old_debug)\n\n    @property\n    def dsn(self):\n        # type: () -> Optional[str]\n        \"\"\"Returns the configured DSN as string.\"\"\"\n        return self.options[\"dsn\"]\n\n    def _prepare_event(\n        self,\n        event,  # type: Event\n        hint,  # type: Optional[Hint]\n        scope,  # type: Optional[Scope]\n    ):\n        # type: (...) -> Optional[Event]\n        if event.get(\"timestamp\") is None:\n            event[\"timestamp\"] = datetime.utcnow()\n\n        hint = dict(hint or ())  # type: Hint\n\n        if scope is not None:\n            event_ = scope.apply_to_event(event, hint)\n            if event_ is None:\n                return None\n            event = event_\n\n        if (\n            self.options[\"attach_stacktrace\"]\n            and \"exception\" not in event\n            and \"stacktrace\" not in event\n            and \"threads\" not in event\n        ):\n            with capture_internal_exceptions():\n                event[\"threads\"] = {\n                    \"values\": [\n                        {\n                            \"stacktrace\": current_stacktrace(\n                                self.options[\"with_locals\"]\n                            ),\n                            \"crashed\": False,\n                            \"current\": True,\n                        }\n                    ]\n                }\n\n        for key in \"release\", \"environment\", \"server_name\", \"dist\":\n            if event.get(key) is None and self.options[key] is not None:  # type: ignore\n                event[key] = text_type(self.options[key]).strip()  # type: ignore\n        if event.get(\"sdk\") is None:\n            sdk_info = dict(SDK_INFO)\n            sdk_info[\"integrations\"] = sorted(self.integrations.keys())\n            event[\"sdk\"] = sdk_info\n\n        if event.get(\"platform\") is None:\n            event[\"platform\"] = \"python\"\n\n        event = handle_in_app(\n            event, self.options[\"in_app_exclude\"], self.options[\"in_app_include\"]\n        )\n\n        # Postprocess the event here so that annotated types do\n        # generally not surface in before_send\n        if event is not None:\n            event = Serializer().serialize_event(event)\n\n        before_send = self.options[\"before_send\"]\n        if before_send is not None:\n            new_event = None\n            with capture_internal_exceptions():\n                new_event = before_send(event, hint or {})\n            if new_event is None:\n                logger.info(\"before send dropped event (%s)\", event)\n            event = new_event  # type: ignore\n\n        return event\n\n    def _is_ignored_error(self, event, hint):\n        # type: (Event, Hint) -> bool\n        exc_info = hint.get(\"exc_info\")\n        if exc_info is None:\n            return False\n\n        type_name = get_type_name(exc_info[0])\n        full_name = \"%s.%s\" % (exc_info[0].__module__, type_name)\n\n        for errcls in self.options[\"ignore_errors\"]:\n            # String types are matched against the type name in the\n            # exception only\n            if isinstance(errcls, string_types):\n                if errcls == full_name or errcls == type_name:\n                    return True\n            else:\n                if issubclass(exc_info[0], errcls):  # type: ignore\n                    return True\n\n        return False\n\n    def _should_capture(\n        self,\n        event,  # type: Event\n        hint,  # type: Hint\n        scope=None,  # type: Optional[Scope]\n    ):\n        # type: (...) -> bool\n        if scope is not None and not scope._should_capture:\n            return False\n\n        if (\n            self.options[\"sample_rate\"] < 1.0\n            and random.random() >= self.options[\"sample_rate\"]\n        ):\n            return False\n\n        if self._is_ignored_error(event, hint):\n            return False\n\n        return True\n\n    def capture_event(\n        self,\n        event,  # type: Event\n        hint=None,  # type: Optional[Hint]\n        scope=None,  # type: Optional[Scope]\n    ):\n        # type: (...) -> Optional[str]\n        \"\"\"Captures an event.\n\n        :param event: A ready-made event that can be directly sent to Sentry.\n\n        :param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object.\n\n        :returns: An event ID. May be `None` if there is no DSN set or of if the SDK decided to discard the event for other reasons. In such situations setting `debug=True` on `init()` may help.\n        \"\"\"\n        if self.transport is None:\n            return None\n        if hint is None:\n            hint = {}\n        rv = event.get(\"event_id\")\n        if rv is None:\n            event[\"event_id\"] = rv = uuid.uuid4().hex\n        if not self._should_capture(event, hint, scope):\n            return None\n        event = self._prepare_event(event, hint, scope)\n        if event is None:\n            return None\n        self.transport.capture_event(event)\n        return rv\n\n    def close(\n        self,\n        timeout=None,  # type: Optional[float]\n        callback=None,  # type: Optional[Callable[[int, float], None]]\n    ):\n        # type: (...) -> None\n        \"\"\"\n        Close the client and shut down the transport. Arguments have the same\n        semantics as :py:meth:`Client.flush`.\n        \"\"\"\n        if self.transport is not None:\n            self.flush(timeout=timeout, callback=callback)\n            self.transport.kill()\n            self.transport = None\n\n    def flush(\n        self,\n        timeout=None,  # type: Optional[float]\n        callback=None,  # type: Optional[Callable[[int, float], None]]\n    ):\n        # type: (...) -> None\n        \"\"\"\n        Wait for the current events to be sent.\n\n        :param timeout: Wait for at most `timeout` seconds. If no `timeout` is provided, the `shutdown_timeout` option value is used.\n\n        :param callback: Is invoked with the number of pending events and the configured timeout.\n        \"\"\"\n        if self.transport is not None:\n            if timeout is None:\n                timeout = self.options[\"shutdown_timeout\"]\n            self.transport.flush(timeout=timeout, callback=callback)\n\n    def __enter__(self):\n        # type: () -> _Client\n        return self\n\n    def __exit__(self, exc_type, exc_value, tb):\n        # type: (Any, Any, Any) -> None\n        self.close()\n\n\nfrom sentry_sdk._types import MYPY\n\nif MYPY:\n    # Make mypy, PyCharm and other static analyzers think `get_options` is a\n    # type to have nicer autocompletion for params.\n    #\n    # Use `ClientConstructor` to define the argument types of `init` and\n    # `Dict[str, Any]` to tell static analyzers about the return type.\n\n    class get_options(ClientConstructor, Dict[str, Any]):\n        pass\n\n    class Client(ClientConstructor, _Client):\n        pass\n\n\nelse:\n    # Alias `get_options` for actual usage. Go through the lambda indirection\n    # to throw PyCharm off of the weakly typed signature (it would otherwise\n    # discover both the weakly typed signature of `_init` and our faked `init`\n    # type).\n\n    get_options = (lambda: _get_options)()\n    Client = (lambda: _Client)()\n", "path": "sentry_sdk/client.py"}], "after_files": [{"content": "import os\nimport uuid\nimport random\nfrom datetime import datetime\nimport socket\n\nfrom sentry_sdk._compat import string_types, text_type, iteritems\nfrom sentry_sdk.utils import (\n    handle_in_app,\n    get_type_name,\n    capture_internal_exceptions,\n    current_stacktrace,\n    logger,\n)\nfrom sentry_sdk.serializer import Serializer\nfrom sentry_sdk.transport import make_transport\nfrom sentry_sdk.consts import DEFAULT_OPTIONS, SDK_INFO, ClientConstructor\nfrom sentry_sdk.integrations import setup_integrations\nfrom sentry_sdk.utils import ContextVar\n\nfrom sentry_sdk._types import MYPY\n\nif MYPY:\n    from typing import Any\n    from typing import Callable\n    from typing import Dict\n    from typing import Optional\n\n    from sentry_sdk.scope import Scope\n    from sentry_sdk._types import Event, Hint\n\n\n_client_init_debug = ContextVar(\"client_init_debug\")\n_client_in_capture_event = ContextVar(\"client_in_capture_event\")\n\n\ndef _get_options(*args, **kwargs):\n    # type: (*Optional[str], **Any) -> Dict[str, Any]\n    if args and (isinstance(args[0], (text_type, bytes, str)) or args[0] is None):\n        dsn = args[0]  # type: Optional[str]\n        args = args[1:]\n    else:\n        dsn = None\n\n    rv = dict(DEFAULT_OPTIONS)\n    options = dict(*args, **kwargs)  # type: ignore\n    if dsn is not None and options.get(\"dsn\") is None:\n        options[\"dsn\"] = dsn  # type: ignore\n\n    for key, value in iteritems(options):\n        if key not in rv:\n            raise TypeError(\"Unknown option %r\" % (key,))\n        rv[key] = value\n\n    if rv[\"dsn\"] is None:\n        rv[\"dsn\"] = os.environ.get(\"SENTRY_DSN\")\n\n    if rv[\"release\"] is None:\n        rv[\"release\"] = os.environ.get(\"SENTRY_RELEASE\")\n\n    if rv[\"environment\"] is None:\n        rv[\"environment\"] = os.environ.get(\"SENTRY_ENVIRONMENT\")\n\n    if rv[\"server_name\"] is None and hasattr(socket, \"gethostname\"):\n        rv[\"server_name\"] = socket.gethostname()\n\n    return rv  # type: ignore\n\n\nclass _Client(object):\n    \"\"\"The client is internally responsible for capturing the events and\n    forwarding them to sentry through the configured transport.  It takes\n    the client options as keyword arguments and optionally the DSN as first\n    argument.\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        # type: (*Any, **Any) -> None\n        self.options = get_options(*args, **kwargs)  # type: Dict[str, Any]\n        self._init_impl()\n\n    def __getstate__(self):\n        # type: () -> Any\n        return {\"options\": self.options}\n\n    def __setstate__(self, state):\n        # type: (Any) -> None\n        self.options = state[\"options\"]\n        self._init_impl()\n\n    def _init_impl(self):\n        # type: () -> None\n        old_debug = _client_init_debug.get(False)\n        try:\n            _client_init_debug.set(self.options[\"debug\"])\n            self.transport = make_transport(self.options)\n\n            request_bodies = (\"always\", \"never\", \"small\", \"medium\")\n            if self.options[\"request_bodies\"] not in request_bodies:\n                raise ValueError(\n                    \"Invalid value for request_bodies. Must be one of {}\".format(\n                        request_bodies\n                    )\n                )\n\n            self.integrations = setup_integrations(\n                self.options[\"integrations\"],\n                with_defaults=self.options[\"default_integrations\"],\n            )\n        finally:\n            _client_init_debug.set(old_debug)\n\n    @property\n    def dsn(self):\n        # type: () -> Optional[str]\n        \"\"\"Returns the configured DSN as string.\"\"\"\n        return self.options[\"dsn\"]\n\n    def _prepare_event(\n        self,\n        event,  # type: Event\n        hint,  # type: Optional[Hint]\n        scope,  # type: Optional[Scope]\n    ):\n        # type: (...) -> Optional[Event]\n        if event.get(\"timestamp\") is None:\n            event[\"timestamp\"] = datetime.utcnow()\n\n        hint = dict(hint or ())  # type: Hint\n\n        if scope is not None:\n            event_ = scope.apply_to_event(event, hint)\n            if event_ is None:\n                return None\n            event = event_\n\n        if (\n            self.options[\"attach_stacktrace\"]\n            and \"exception\" not in event\n            and \"stacktrace\" not in event\n            and \"threads\" not in event\n        ):\n            with capture_internal_exceptions():\n                event[\"threads\"] = {\n                    \"values\": [\n                        {\n                            \"stacktrace\": current_stacktrace(\n                                self.options[\"with_locals\"]\n                            ),\n                            \"crashed\": False,\n                            \"current\": True,\n                        }\n                    ]\n                }\n\n        for key in \"release\", \"environment\", \"server_name\", \"dist\":\n            if event.get(key) is None and self.options[key] is not None:  # type: ignore\n                event[key] = text_type(self.options[key]).strip()  # type: ignore\n        if event.get(\"sdk\") is None:\n            sdk_info = dict(SDK_INFO)\n            sdk_info[\"integrations\"] = sorted(self.integrations.keys())\n            event[\"sdk\"] = sdk_info\n\n        if event.get(\"platform\") is None:\n            event[\"platform\"] = \"python\"\n\n        event = handle_in_app(\n            event, self.options[\"in_app_exclude\"], self.options[\"in_app_include\"]\n        )\n\n        # Postprocess the event here so that annotated types do\n        # generally not surface in before_send\n        if event is not None:\n            event = Serializer().serialize_event(event)\n\n        before_send = self.options[\"before_send\"]\n        if before_send is not None:\n            new_event = None\n            with capture_internal_exceptions():\n                new_event = before_send(event, hint or {})\n            if new_event is None:\n                logger.info(\"before send dropped event (%s)\", event)\n            event = new_event  # type: ignore\n\n        return event\n\n    def _is_ignored_error(self, event, hint):\n        # type: (Event, Hint) -> bool\n        exc_info = hint.get(\"exc_info\")\n        if exc_info is None:\n            return False\n\n        type_name = get_type_name(exc_info[0])\n        full_name = \"%s.%s\" % (exc_info[0].__module__, type_name)\n\n        for errcls in self.options[\"ignore_errors\"]:\n            # String types are matched against the type name in the\n            # exception only\n            if isinstance(errcls, string_types):\n                if errcls == full_name or errcls == type_name:\n                    return True\n            else:\n                if issubclass(exc_info[0], errcls):  # type: ignore\n                    return True\n\n        return False\n\n    def _should_capture(\n        self,\n        event,  # type: Event\n        hint,  # type: Hint\n        scope=None,  # type: Optional[Scope]\n    ):\n        # type: (...) -> bool\n        if scope is not None and not scope._should_capture:\n            return False\n\n        if (\n            self.options[\"sample_rate\"] < 1.0\n            and random.random() >= self.options[\"sample_rate\"]\n        ):\n            return False\n\n        if self._is_ignored_error(event, hint):\n            return False\n\n        return True\n\n    def capture_event(\n        self,\n        event,  # type: Event\n        hint=None,  # type: Optional[Hint]\n        scope=None,  # type: Optional[Scope]\n    ):\n        # type: (...) -> Optional[str]\n        \"\"\"Captures an event.\n\n        :param event: A ready-made event that can be directly sent to Sentry.\n\n        :param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object.\n\n        :returns: An event ID. May be `None` if there is no DSN set or of if the SDK decided to discard the event for other reasons. In such situations setting `debug=True` on `init()` may help.\n        \"\"\"\n        is_recursive = _client_in_capture_event.get(False)\n        if is_recursive:\n            return None\n\n        _client_in_capture_event.set(True)\n\n        try:\n            if self.transport is None:\n                return None\n            if hint is None:\n                hint = {}\n            event_id = event.get(\"event_id\")\n            if event_id is None:\n                event[\"event_id\"] = event_id = uuid.uuid4().hex\n            if not self._should_capture(event, hint, scope):\n                return None\n            event_opt = self._prepare_event(event, hint, scope)\n            if event_opt is None:\n                return None\n            self.transport.capture_event(event_opt)\n            return event_id\n        finally:\n            _client_in_capture_event.set(False)\n\n    def close(\n        self,\n        timeout=None,  # type: Optional[float]\n        callback=None,  # type: Optional[Callable[[int, float], None]]\n    ):\n        # type: (...) -> None\n        \"\"\"\n        Close the client and shut down the transport. Arguments have the same\n        semantics as :py:meth:`Client.flush`.\n        \"\"\"\n        if self.transport is not None:\n            self.flush(timeout=timeout, callback=callback)\n            self.transport.kill()\n            self.transport = None\n\n    def flush(\n        self,\n        timeout=None,  # type: Optional[float]\n        callback=None,  # type: Optional[Callable[[int, float], None]]\n    ):\n        # type: (...) -> None\n        \"\"\"\n        Wait for the current events to be sent.\n\n        :param timeout: Wait for at most `timeout` seconds. If no `timeout` is provided, the `shutdown_timeout` option value is used.\n\n        :param callback: Is invoked with the number of pending events and the configured timeout.\n        \"\"\"\n        if self.transport is not None:\n            if timeout is None:\n                timeout = self.options[\"shutdown_timeout\"]\n            self.transport.flush(timeout=timeout, callback=callback)\n\n    def __enter__(self):\n        # type: () -> _Client\n        return self\n\n    def __exit__(self, exc_type, exc_value, tb):\n        # type: (Any, Any, Any) -> None\n        self.close()\n\n\nfrom sentry_sdk._types import MYPY\n\nif MYPY:\n    # Make mypy, PyCharm and other static analyzers think `get_options` is a\n    # type to have nicer autocompletion for params.\n    #\n    # Use `ClientConstructor` to define the argument types of `init` and\n    # `Dict[str, Any]` to tell static analyzers about the return type.\n\n    class get_options(ClientConstructor, Dict[str, Any]):\n        pass\n\n    class Client(ClientConstructor, _Client):\n        pass\n\n\nelse:\n    # Alias `get_options` for actual usage. Go through the lambda indirection\n    # to throw PyCharm off of the weakly typed signature (it would otherwise\n    # discover both the weakly typed signature of `_init` and our faked `init`\n    # type).\n\n    get_options = (lambda: _get_options)()\n    Client = (lambda: _Client)()\n", "path": "sentry_sdk/client.py"}]}