sentry-sdk 0.7.5__py2.py3-none-any.whl → 2.46.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. sentry_sdk/__init__.py +48 -30
  2. sentry_sdk/_compat.py +74 -61
  3. sentry_sdk/_init_implementation.py +84 -0
  4. sentry_sdk/_log_batcher.py +172 -0
  5. sentry_sdk/_lru_cache.py +47 -0
  6. sentry_sdk/_metrics_batcher.py +167 -0
  7. sentry_sdk/_queue.py +289 -0
  8. sentry_sdk/_types.py +338 -0
  9. sentry_sdk/_werkzeug.py +98 -0
  10. sentry_sdk/ai/__init__.py +7 -0
  11. sentry_sdk/ai/monitoring.py +137 -0
  12. sentry_sdk/ai/utils.py +144 -0
  13. sentry_sdk/api.py +496 -80
  14. sentry_sdk/attachments.py +75 -0
  15. sentry_sdk/client.py +1023 -103
  16. sentry_sdk/consts.py +1438 -66
  17. sentry_sdk/crons/__init__.py +10 -0
  18. sentry_sdk/crons/api.py +62 -0
  19. sentry_sdk/crons/consts.py +4 -0
  20. sentry_sdk/crons/decorator.py +135 -0
  21. sentry_sdk/debug.py +15 -14
  22. sentry_sdk/envelope.py +369 -0
  23. sentry_sdk/feature_flags.py +71 -0
  24. sentry_sdk/hub.py +611 -280
  25. sentry_sdk/integrations/__init__.py +276 -49
  26. sentry_sdk/integrations/_asgi_common.py +108 -0
  27. sentry_sdk/integrations/_wsgi_common.py +180 -44
  28. sentry_sdk/integrations/aiohttp.py +291 -42
  29. sentry_sdk/integrations/anthropic.py +439 -0
  30. sentry_sdk/integrations/argv.py +9 -8
  31. sentry_sdk/integrations/ariadne.py +161 -0
  32. sentry_sdk/integrations/arq.py +247 -0
  33. sentry_sdk/integrations/asgi.py +341 -0
  34. sentry_sdk/integrations/asyncio.py +144 -0
  35. sentry_sdk/integrations/asyncpg.py +208 -0
  36. sentry_sdk/integrations/atexit.py +17 -10
  37. sentry_sdk/integrations/aws_lambda.py +377 -62
  38. sentry_sdk/integrations/beam.py +176 -0
  39. sentry_sdk/integrations/boto3.py +137 -0
  40. sentry_sdk/integrations/bottle.py +221 -0
  41. sentry_sdk/integrations/celery/__init__.py +529 -0
  42. sentry_sdk/integrations/celery/beat.py +293 -0
  43. sentry_sdk/integrations/celery/utils.py +43 -0
  44. sentry_sdk/integrations/chalice.py +134 -0
  45. sentry_sdk/integrations/clickhouse_driver.py +177 -0
  46. sentry_sdk/integrations/cloud_resource_context.py +280 -0
  47. sentry_sdk/integrations/cohere.py +274 -0
  48. sentry_sdk/integrations/dedupe.py +48 -14
  49. sentry_sdk/integrations/django/__init__.py +584 -191
  50. sentry_sdk/integrations/django/asgi.py +245 -0
  51. sentry_sdk/integrations/django/caching.py +204 -0
  52. sentry_sdk/integrations/django/middleware.py +187 -0
  53. sentry_sdk/integrations/django/signals_handlers.py +91 -0
  54. sentry_sdk/integrations/django/templates.py +79 -5
  55. sentry_sdk/integrations/django/transactions.py +49 -22
  56. sentry_sdk/integrations/django/views.py +96 -0
  57. sentry_sdk/integrations/dramatiq.py +226 -0
  58. sentry_sdk/integrations/excepthook.py +50 -13
  59. sentry_sdk/integrations/executing.py +67 -0
  60. sentry_sdk/integrations/falcon.py +272 -0
  61. sentry_sdk/integrations/fastapi.py +141 -0
  62. sentry_sdk/integrations/flask.py +142 -88
  63. sentry_sdk/integrations/gcp.py +239 -0
  64. sentry_sdk/integrations/gnu_backtrace.py +99 -0
  65. sentry_sdk/integrations/google_genai/__init__.py +301 -0
  66. sentry_sdk/integrations/google_genai/consts.py +16 -0
  67. sentry_sdk/integrations/google_genai/streaming.py +155 -0
  68. sentry_sdk/integrations/google_genai/utils.py +576 -0
  69. sentry_sdk/integrations/gql.py +162 -0
  70. sentry_sdk/integrations/graphene.py +151 -0
  71. sentry_sdk/integrations/grpc/__init__.py +168 -0
  72. sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
  73. sentry_sdk/integrations/grpc/aio/client.py +95 -0
  74. sentry_sdk/integrations/grpc/aio/server.py +100 -0
  75. sentry_sdk/integrations/grpc/client.py +91 -0
  76. sentry_sdk/integrations/grpc/consts.py +1 -0
  77. sentry_sdk/integrations/grpc/server.py +66 -0
  78. sentry_sdk/integrations/httpx.py +178 -0
  79. sentry_sdk/integrations/huey.py +174 -0
  80. sentry_sdk/integrations/huggingface_hub.py +378 -0
  81. sentry_sdk/integrations/langchain.py +1132 -0
  82. sentry_sdk/integrations/langgraph.py +337 -0
  83. sentry_sdk/integrations/launchdarkly.py +61 -0
  84. sentry_sdk/integrations/litellm.py +287 -0
  85. sentry_sdk/integrations/litestar.py +315 -0
  86. sentry_sdk/integrations/logging.py +307 -96
  87. sentry_sdk/integrations/loguru.py +213 -0
  88. sentry_sdk/integrations/mcp.py +566 -0
  89. sentry_sdk/integrations/modules.py +14 -31
  90. sentry_sdk/integrations/openai.py +725 -0
  91. sentry_sdk/integrations/openai_agents/__init__.py +61 -0
  92. sentry_sdk/integrations/openai_agents/consts.py +1 -0
  93. sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
  94. sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
  95. sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
  96. sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
  97. sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
  98. sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
  99. sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
  100. sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
  101. sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
  102. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
  103. sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
  104. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
  105. sentry_sdk/integrations/openai_agents/utils.py +199 -0
  106. sentry_sdk/integrations/openfeature.py +35 -0
  107. sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
  108. sentry_sdk/integrations/opentelemetry/consts.py +5 -0
  109. sentry_sdk/integrations/opentelemetry/integration.py +58 -0
  110. sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
  111. sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
  112. sentry_sdk/integrations/otlp.py +82 -0
  113. sentry_sdk/integrations/pure_eval.py +141 -0
  114. sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
  115. sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
  116. sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
  117. sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
  118. sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
  119. sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
  120. sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
  121. sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
  122. sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
  123. sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
  124. sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
  125. sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
  126. sentry_sdk/integrations/pymongo.py +214 -0
  127. sentry_sdk/integrations/pyramid.py +112 -68
  128. sentry_sdk/integrations/quart.py +237 -0
  129. sentry_sdk/integrations/ray.py +165 -0
  130. sentry_sdk/integrations/redis/__init__.py +48 -0
  131. sentry_sdk/integrations/redis/_async_common.py +116 -0
  132. sentry_sdk/integrations/redis/_sync_common.py +119 -0
  133. sentry_sdk/integrations/redis/consts.py +19 -0
  134. sentry_sdk/integrations/redis/modules/__init__.py +0 -0
  135. sentry_sdk/integrations/redis/modules/caches.py +118 -0
  136. sentry_sdk/integrations/redis/modules/queries.py +65 -0
  137. sentry_sdk/integrations/redis/rb.py +32 -0
  138. sentry_sdk/integrations/redis/redis.py +69 -0
  139. sentry_sdk/integrations/redis/redis_cluster.py +107 -0
  140. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
  141. sentry_sdk/integrations/redis/utils.py +148 -0
  142. sentry_sdk/integrations/rq.py +95 -37
  143. sentry_sdk/integrations/rust_tracing.py +284 -0
  144. sentry_sdk/integrations/sanic.py +294 -123
  145. sentry_sdk/integrations/serverless.py +48 -19
  146. sentry_sdk/integrations/socket.py +96 -0
  147. sentry_sdk/integrations/spark/__init__.py +4 -0
  148. sentry_sdk/integrations/spark/spark_driver.py +316 -0
  149. sentry_sdk/integrations/spark/spark_worker.py +116 -0
  150. sentry_sdk/integrations/sqlalchemy.py +142 -0
  151. sentry_sdk/integrations/starlette.py +737 -0
  152. sentry_sdk/integrations/starlite.py +292 -0
  153. sentry_sdk/integrations/statsig.py +37 -0
  154. sentry_sdk/integrations/stdlib.py +235 -29
  155. sentry_sdk/integrations/strawberry.py +394 -0
  156. sentry_sdk/integrations/sys_exit.py +70 -0
  157. sentry_sdk/integrations/threading.py +158 -28
  158. sentry_sdk/integrations/tornado.py +84 -52
  159. sentry_sdk/integrations/trytond.py +50 -0
  160. sentry_sdk/integrations/typer.py +60 -0
  161. sentry_sdk/integrations/unleash.py +33 -0
  162. sentry_sdk/integrations/unraisablehook.py +53 -0
  163. sentry_sdk/integrations/wsgi.py +201 -119
  164. sentry_sdk/logger.py +96 -0
  165. sentry_sdk/metrics.py +81 -0
  166. sentry_sdk/monitor.py +120 -0
  167. sentry_sdk/profiler/__init__.py +49 -0
  168. sentry_sdk/profiler/continuous_profiler.py +730 -0
  169. sentry_sdk/profiler/transaction_profiler.py +839 -0
  170. sentry_sdk/profiler/utils.py +195 -0
  171. sentry_sdk/py.typed +0 -0
  172. sentry_sdk/scope.py +1713 -85
  173. sentry_sdk/scrubber.py +177 -0
  174. sentry_sdk/serializer.py +405 -0
  175. sentry_sdk/session.py +177 -0
  176. sentry_sdk/sessions.py +275 -0
  177. sentry_sdk/spotlight.py +242 -0
  178. sentry_sdk/tracing.py +1486 -0
  179. sentry_sdk/tracing_utils.py +1236 -0
  180. sentry_sdk/transport.py +806 -134
  181. sentry_sdk/types.py +52 -0
  182. sentry_sdk/utils.py +1625 -465
  183. sentry_sdk/worker.py +54 -25
  184. sentry_sdk-2.46.0.dist-info/METADATA +268 -0
  185. sentry_sdk-2.46.0.dist-info/RECORD +189 -0
  186. {sentry_sdk-0.7.5.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
  187. sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
  188. sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
  189. sentry_sdk/integrations/celery.py +0 -119
  190. sentry_sdk-0.7.5.dist-info/LICENSE +0 -9
  191. sentry_sdk-0.7.5.dist-info/METADATA +0 -36
  192. sentry_sdk-0.7.5.dist-info/RECORD +0 -39
  193. {sentry_sdk-0.7.5.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
sentry_sdk/__init__.py CHANGED
@@ -1,45 +1,60 @@
1
- """
2
- The Sentry SDK is the new-style SDK for [sentry.io](https://sentry.io/). It implements
3
- the unified API that all modern SDKs follow for Python 2.7 and 3.5 or later.
4
-
5
- The user documentation can be found on [docs.sentry.io](https://docs.sentry.io/).
6
-
7
- ## Quickstart
8
-
9
- The only thing to get going is to call `sentry_sdk.init()`. When not passed any
10
- arguments the default options are used and the DSN is picked up from the `SENTRY_DSN`
11
- environment variable. Otherwise the DSN can be passed with the `dsn` keyword
12
- or first argument.
13
-
14
- import sentry_sdk
15
- sentry_sdk.init()
16
-
17
- This initializes the default integrations which will automatically pick up any
18
- uncaught exceptions. Additionally you can report arbitrary other exceptions:
19
-
20
- try:
21
- my_failing_function()
22
- except Exception as e:
23
- sentry_sdk.capture_exception(e)
24
- """
25
- from sentry_sdk.hub import Hub, init
1
+ from sentry_sdk import profiler
2
+ from sentry_sdk import metrics
26
3
  from sentry_sdk.scope import Scope
27
4
  from sentry_sdk.transport import Transport, HttpTransport
28
5
  from sentry_sdk.client import Client
29
6
 
30
7
  from sentry_sdk.api import * # noqa
31
- from sentry_sdk.api import __all__ as api_all
32
-
33
- from sentry_sdk.consts import VERSION # noqa
8
+ from sentry_sdk.consts import VERSION
34
9
 
35
- __all__ = api_all + [ # noqa
10
+ __all__ = [ # noqa
36
11
  "Hub",
37
12
  "Scope",
38
13
  "Client",
39
14
  "Transport",
40
15
  "HttpTransport",
41
- "init",
16
+ "VERSION",
42
17
  "integrations",
18
+ # From sentry_sdk.api
19
+ "init",
20
+ "add_attachment",
21
+ "add_breadcrumb",
22
+ "capture_event",
23
+ "capture_exception",
24
+ "capture_message",
25
+ "configure_scope",
26
+ "continue_trace",
27
+ "flush",
28
+ "get_baggage",
29
+ "get_client",
30
+ "get_global_scope",
31
+ "get_isolation_scope",
32
+ "get_current_scope",
33
+ "get_current_span",
34
+ "get_traceparent",
35
+ "is_initialized",
36
+ "isolation_scope",
37
+ "last_event_id",
38
+ "new_scope",
39
+ "push_scope",
40
+ "set_context",
41
+ "set_extra",
42
+ "set_level",
43
+ "set_measurement",
44
+ "set_tag",
45
+ "set_tags",
46
+ "set_user",
47
+ "start_span",
48
+ "start_transaction",
49
+ "trace",
50
+ "monitor",
51
+ "logger",
52
+ "metrics",
53
+ "profiler",
54
+ "start_session",
55
+ "end_session",
56
+ "set_transaction_name",
57
+ "update_current_span",
43
58
  ]
44
59
 
45
60
  # Initialize the debug support after everything is loaded
@@ -47,3 +62,6 @@ from sentry_sdk.debug import init_debug_support
47
62
 
48
63
  init_debug_support()
49
64
  del init_debug_support
65
+
66
+ # circular imports
67
+ from sentry_sdk.hub import Hub
sentry_sdk/_compat.py CHANGED
@@ -1,85 +1,98 @@
1
1
  import sys
2
2
 
3
- if False:
4
- from typing import Optional
5
- from typing import Tuple
6
- from typing import Any
7
- from typing import Type
8
-
9
-
10
- PY2 = sys.version_info[0] == 2
11
-
12
- if PY2:
13
- import urlparse # noqa
14
-
15
- text_type = unicode # noqa
16
- import Queue as queue # noqa
17
-
18
- string_types = (str, text_type)
19
- number_types = (int, long, float) # noqa
20
- int_types = (int, long) # noqa
21
- iteritems = lambda x: x.iteritems()
22
-
23
- def implements_str(cls):
24
- cls.__unicode__ = cls.__str__
25
- cls.__str__ = lambda x: unicode(x).encode("utf-8") # noqa
26
- return cls
3
+ from typing import TYPE_CHECKING
27
4
 
28
- exec("def reraise(tp, value, tb=None):\n raise tp, value, tb")
29
-
30
-
31
- else:
32
- import urllib.parse as urlparse # noqa
33
- import queue # noqa
34
-
35
- text_type = str
36
- string_types = (text_type,) # type: Tuple[type]
37
- number_types = (int, float) # type: Tuple[type, type]
38
- int_types = (int,) # noqa
39
- iteritems = lambda x: x.items()
5
+ if TYPE_CHECKING:
6
+ from typing import Any
7
+ from typing import TypeVar
40
8
 
41
- def _identity(x):
42
- return x
9
+ T = TypeVar("T")
43
10
 
44
- def implements_str(x):
45
- return x
46
11
 
47
- def reraise(tp, value, tb=None):
48
- # type: (Optional[Type[BaseException]], Optional[BaseException], Optional[Any]) -> None
49
- assert value is not None
50
- if value.__traceback__ is not tb:
51
- raise value.with_traceback(tb)
52
- raise value
12
+ PY37 = sys.version_info[0] == 3 and sys.version_info[1] >= 7
13
+ PY38 = sys.version_info[0] == 3 and sys.version_info[1] >= 8
14
+ PY310 = sys.version_info[0] == 3 and sys.version_info[1] >= 10
15
+ PY311 = sys.version_info[0] == 3 and sys.version_info[1] >= 11
53
16
 
54
17
 
55
18
  def with_metaclass(meta, *bases):
56
- class metaclass(type):
57
- def __new__(cls, name, this_bases, d):
19
+ # type: (Any, *Any) -> Any
20
+ class MetaClass(type):
21
+ def __new__(metacls, name, this_bases, d):
22
+ # type: (Any, Any, Any, Any) -> Any
58
23
  return meta(name, bases, d)
59
24
 
60
- return type.__new__(metaclass, "temporary_class", (), {})
61
-
62
-
63
- def check_thread_support():
64
- # type: () -> None
25
+ return type.__new__(MetaClass, "temporary_class", (), {})
26
+
27
+
28
+ def check_uwsgi_thread_support():
29
+ # type: () -> bool
30
+ # We check two things here:
31
+ #
32
+ # 1. uWSGI doesn't run in threaded mode by default -- issue a warning if
33
+ # that's the case.
34
+ #
35
+ # 2. Additionally, if uWSGI is running in preforking mode (default), it needs
36
+ # the --py-call-uwsgi-fork-hooks option for the SDK to work properly. This
37
+ # is because any background threads spawned before the main process is
38
+ # forked are NOT CLEANED UP IN THE CHILDREN BY DEFAULT even if
39
+ # --enable-threads is on. One has to explicitly provide
40
+ # --py-call-uwsgi-fork-hooks to force uWSGI to run regular cpython
41
+ # after-fork hooks that take care of cleaning up stale thread data.
65
42
  try:
66
43
  from uwsgi import opt # type: ignore
67
44
  except ImportError:
68
- return
45
+ return True
46
+
47
+ from sentry_sdk.consts import FALSE_VALUES
48
+
49
+ def enabled(option):
50
+ # type: (str) -> bool
51
+ value = opt.get(option, False)
52
+ if isinstance(value, bool):
53
+ return value
54
+
55
+ if isinstance(value, bytes):
56
+ try:
57
+ value = value.decode()
58
+ except Exception:
59
+ pass
60
+
61
+ return value and str(value).lower() not in FALSE_VALUES
69
62
 
70
63
  # When `threads` is passed in as a uwsgi option,
71
64
  # `enable-threads` is implied on.
72
- if "threads" in opt:
73
- return
65
+ threads_enabled = "threads" in opt or enabled("enable-threads")
66
+ fork_hooks_on = enabled("py-call-uwsgi-fork-hooks")
67
+ lazy_mode = enabled("lazy-apps") or enabled("lazy")
74
68
 
75
- if str(opt.get("enable-threads", "0")).lower() in ("false", "off", "no", "0"):
69
+ if lazy_mode and not threads_enabled:
76
70
  from warnings import warn
77
71
 
78
72
  warn(
79
73
  Warning(
80
- "We detected the use of uwsgi with disabled threads. "
81
- "This will cause issues with the transport you are "
82
- "trying to use. Please enable threading for uwsgi. "
83
- '(Enable the "enable-threads" flag).'
74
+ "IMPORTANT: "
75
+ "We detected the use of uWSGI without thread support. "
76
+ "This might lead to unexpected issues. "
77
+ 'Please run uWSGI with "--enable-threads" for full support.'
84
78
  )
85
79
  )
80
+
81
+ return False
82
+
83
+ elif not lazy_mode and (not threads_enabled or not fork_hooks_on):
84
+ from warnings import warn
85
+
86
+ warn(
87
+ Warning(
88
+ "IMPORTANT: "
89
+ "We detected the use of uWSGI in preforking mode without "
90
+ "thread support. This might lead to crashing workers. "
91
+ 'Please run uWSGI with both "--enable-threads" and '
92
+ '"--py-call-uwsgi-fork-hooks" for full support.'
93
+ )
94
+ )
95
+
96
+ return False
97
+
98
+ return True
@@ -0,0 +1,84 @@
1
+ import warnings
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import sentry_sdk
6
+
7
+ if TYPE_CHECKING:
8
+ from typing import Any, ContextManager, Optional
9
+
10
+ import sentry_sdk.consts
11
+
12
+
13
+ class _InitGuard:
14
+ _CONTEXT_MANAGER_DEPRECATION_WARNING_MESSAGE = (
15
+ "Using the return value of sentry_sdk.init as a context manager "
16
+ "and manually calling the __enter__ and __exit__ methods on the "
17
+ "return value are deprecated. We are no longer maintaining this "
18
+ "functionality, and we will remove it in the next major release."
19
+ )
20
+
21
+ def __init__(self, client):
22
+ # type: (sentry_sdk.Client) -> None
23
+ self._client = client
24
+
25
+ def __enter__(self):
26
+ # type: () -> _InitGuard
27
+ warnings.warn(
28
+ self._CONTEXT_MANAGER_DEPRECATION_WARNING_MESSAGE,
29
+ stacklevel=2,
30
+ category=DeprecationWarning,
31
+ )
32
+
33
+ return self
34
+
35
+ def __exit__(self, exc_type, exc_value, tb):
36
+ # type: (Any, Any, Any) -> None
37
+ warnings.warn(
38
+ self._CONTEXT_MANAGER_DEPRECATION_WARNING_MESSAGE,
39
+ stacklevel=2,
40
+ category=DeprecationWarning,
41
+ )
42
+
43
+ c = self._client
44
+ if c is not None:
45
+ c.close()
46
+
47
+
48
+ def _check_python_deprecations():
49
+ # type: () -> None
50
+ # Since we're likely to deprecate Python versions in the future, I'm keeping
51
+ # this handy function around. Use this to detect the Python version used and
52
+ # to output logger.warning()s if it's deprecated.
53
+ pass
54
+
55
+
56
+ def _init(*args, **kwargs):
57
+ # type: (*Optional[str], **Any) -> ContextManager[Any]
58
+ """Initializes the SDK and optionally integrations.
59
+
60
+ This takes the same arguments as the client constructor.
61
+ """
62
+ client = sentry_sdk.Client(*args, **kwargs)
63
+ sentry_sdk.get_global_scope().set_client(client)
64
+ _check_python_deprecations()
65
+ rv = _InitGuard(client)
66
+ return rv
67
+
68
+
69
+ if TYPE_CHECKING:
70
+ # Make mypy, PyCharm and other static analyzers think `init` is a type to
71
+ # have nicer autocompletion for params.
72
+ #
73
+ # Use `ClientConstructor` to define the argument types of `init` and
74
+ # `ContextManager[Any]` to tell static analyzers about the return type.
75
+
76
+ class init(sentry_sdk.consts.ClientConstructor, _InitGuard): # noqa: N801
77
+ pass
78
+
79
+ else:
80
+ # Alias `init` for actual usage. Go through the lambda indirection to throw
81
+ # PyCharm off of the weakly typed signature (it would otherwise discover
82
+ # both the weakly typed signature of `_init` and our faked `init` type).
83
+
84
+ init = (lambda: _init)()
@@ -0,0 +1,172 @@
1
+ import os
2
+ import random
3
+ import threading
4
+ from datetime import datetime, timezone
5
+ from typing import Optional, List, Callable, TYPE_CHECKING, Any
6
+
7
+ from sentry_sdk.utils import format_timestamp, safe_repr
8
+ from sentry_sdk.envelope import Envelope, Item, PayloadRef
9
+
10
+ if TYPE_CHECKING:
11
+ from sentry_sdk._types import Log
12
+
13
+
14
+ class LogBatcher:
15
+ MAX_LOGS_BEFORE_FLUSH = 100
16
+ MAX_LOGS_BEFORE_DROP = 1_000
17
+ FLUSH_WAIT_TIME = 5.0
18
+
19
+ def __init__(
20
+ self,
21
+ capture_func, # type: Callable[[Envelope], None]
22
+ record_lost_func, # type: Callable[..., None]
23
+ ):
24
+ # type: (...) -> None
25
+ self._log_buffer = [] # type: List[Log]
26
+ self._capture_func = capture_func
27
+ self._record_lost_func = record_lost_func
28
+ self._running = True
29
+ self._lock = threading.Lock()
30
+
31
+ self._flush_event = threading.Event() # type: threading.Event
32
+
33
+ self._flusher = None # type: Optional[threading.Thread]
34
+ self._flusher_pid = None # type: Optional[int]
35
+
36
+ def _ensure_thread(self):
37
+ # type: (...) -> bool
38
+ """For forking processes we might need to restart this thread.
39
+ This ensures that our process actually has that thread running.
40
+ """
41
+ if not self._running:
42
+ return False
43
+
44
+ pid = os.getpid()
45
+ if self._flusher_pid == pid:
46
+ return True
47
+
48
+ with self._lock:
49
+ # Recheck to make sure another thread didn't get here and start the
50
+ # the flusher in the meantime
51
+ if self._flusher_pid == pid:
52
+ return True
53
+
54
+ self._flusher_pid = pid
55
+
56
+ self._flusher = threading.Thread(target=self._flush_loop)
57
+ self._flusher.daemon = True
58
+
59
+ try:
60
+ self._flusher.start()
61
+ except RuntimeError:
62
+ # Unfortunately at this point the interpreter is in a state that no
63
+ # longer allows us to spawn a thread and we have to bail.
64
+ self._running = False
65
+ return False
66
+
67
+ return True
68
+
69
+ def _flush_loop(self):
70
+ # type: (...) -> None
71
+ while self._running:
72
+ self._flush_event.wait(self.FLUSH_WAIT_TIME + random.random())
73
+ self._flush_event.clear()
74
+ self._flush()
75
+
76
+ def add(
77
+ self,
78
+ log, # type: Log
79
+ ):
80
+ # type: (...) -> None
81
+ if not self._ensure_thread() or self._flusher is None:
82
+ return None
83
+
84
+ with self._lock:
85
+ if len(self._log_buffer) >= self.MAX_LOGS_BEFORE_DROP:
86
+ self._record_lost_func(
87
+ reason="queue_overflow",
88
+ data_category="log_item",
89
+ quantity=1,
90
+ )
91
+ return None
92
+
93
+ self._log_buffer.append(log)
94
+ if len(self._log_buffer) >= self.MAX_LOGS_BEFORE_FLUSH:
95
+ self._flush_event.set()
96
+
97
+ def kill(self):
98
+ # type: (...) -> None
99
+ if self._flusher is None:
100
+ return
101
+
102
+ self._running = False
103
+ self._flush_event.set()
104
+ self._flusher = None
105
+
106
+ def flush(self):
107
+ # type: (...) -> None
108
+ self._flush()
109
+
110
+ @staticmethod
111
+ def _log_to_transport_format(log):
112
+ # type: (Log) -> Any
113
+ def format_attribute(val):
114
+ # type: (int | float | str | bool) -> Any
115
+ if isinstance(val, bool):
116
+ return {"value": val, "type": "boolean"}
117
+ if isinstance(val, int):
118
+ return {"value": val, "type": "integer"}
119
+ if isinstance(val, float):
120
+ return {"value": val, "type": "double"}
121
+ if isinstance(val, str):
122
+ return {"value": val, "type": "string"}
123
+ return {"value": safe_repr(val), "type": "string"}
124
+
125
+ if "sentry.severity_number" not in log["attributes"]:
126
+ log["attributes"]["sentry.severity_number"] = log["severity_number"]
127
+ if "sentry.severity_text" not in log["attributes"]:
128
+ log["attributes"]["sentry.severity_text"] = log["severity_text"]
129
+
130
+ res = {
131
+ "timestamp": int(log["time_unix_nano"]) / 1.0e9,
132
+ "trace_id": log.get("trace_id", "00000000-0000-0000-0000-000000000000"),
133
+ "level": str(log["severity_text"]),
134
+ "body": str(log["body"]),
135
+ "attributes": {
136
+ k: format_attribute(v) for (k, v) in log["attributes"].items()
137
+ },
138
+ }
139
+
140
+ return res
141
+
142
+ def _flush(self):
143
+ # type: (...) -> Optional[Envelope]
144
+
145
+ envelope = Envelope(
146
+ headers={"sent_at": format_timestamp(datetime.now(timezone.utc))}
147
+ )
148
+ with self._lock:
149
+ if len(self._log_buffer) == 0:
150
+ return None
151
+
152
+ envelope.add_item(
153
+ Item(
154
+ type="log",
155
+ content_type="application/vnd.sentry.items.log+json",
156
+ headers={
157
+ "item_count": len(self._log_buffer),
158
+ },
159
+ payload=PayloadRef(
160
+ json={
161
+ "items": [
162
+ self._log_to_transport_format(log)
163
+ for log in self._log_buffer
164
+ ]
165
+ }
166
+ ),
167
+ )
168
+ )
169
+ self._log_buffer.clear()
170
+
171
+ self._capture_func(envelope)
172
+ return envelope
@@ -0,0 +1,47 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ if TYPE_CHECKING:
4
+ from typing import Any
5
+
6
+
7
+ _SENTINEL = object()
8
+
9
+
10
+ class LRUCache:
11
+ def __init__(self, max_size):
12
+ # type: (int) -> None
13
+ if max_size <= 0:
14
+ raise AssertionError(f"invalid max_size: {max_size}")
15
+ self.max_size = max_size
16
+ self._data = {} # type: dict[Any, Any]
17
+ self.hits = self.misses = 0
18
+ self.full = False
19
+
20
+ def set(self, key, value):
21
+ # type: (Any, Any) -> None
22
+ current = self._data.pop(key, _SENTINEL)
23
+ if current is not _SENTINEL:
24
+ self._data[key] = value
25
+ elif self.full:
26
+ self._data.pop(next(iter(self._data)))
27
+ self._data[key] = value
28
+ else:
29
+ self._data[key] = value
30
+ self.full = len(self._data) >= self.max_size
31
+
32
+ def get(self, key, default=None):
33
+ # type: (Any, Any) -> Any
34
+ try:
35
+ ret = self._data.pop(key)
36
+ except KeyError:
37
+ self.misses += 1
38
+ ret = default
39
+ else:
40
+ self.hits += 1
41
+ self._data[key] = ret
42
+
43
+ return ret
44
+
45
+ def get_all(self):
46
+ # type: () -> list[tuple[Any, Any]]
47
+ return list(self._data.items())