sentry-sdk 2.59.0a1__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.
- sentry_sdk/__init__.py +70 -0
- sentry_sdk/_batcher.py +183 -0
- sentry_sdk/_compat.py +94 -0
- sentry_sdk/_init_implementation.py +79 -0
- sentry_sdk/_log_batcher.py +56 -0
- sentry_sdk/_lru_cache.py +43 -0
- sentry_sdk/_metrics_batcher.py +45 -0
- sentry_sdk/_queue.py +289 -0
- sentry_sdk/_span_batcher.py +242 -0
- sentry_sdk/_types.py +376 -0
- sentry_sdk/_werkzeug.py +103 -0
- sentry_sdk/ai/__init__.py +8 -0
- sentry_sdk/ai/_openai_completions_api.py +66 -0
- sentry_sdk/ai/_openai_responses_api.py +22 -0
- sentry_sdk/ai/consts.py +6 -0
- sentry_sdk/ai/monitoring.py +148 -0
- sentry_sdk/ai/utils.py +195 -0
- sentry_sdk/api.py +572 -0
- sentry_sdk/attachments.py +72 -0
- sentry_sdk/client.py +1407 -0
- sentry_sdk/consts.py +1648 -0
- sentry_sdk/crons/__init__.py +10 -0
- sentry_sdk/crons/api.py +60 -0
- sentry_sdk/crons/consts.py +4 -0
- sentry_sdk/crons/decorator.py +137 -0
- sentry_sdk/debug.py +37 -0
- sentry_sdk/envelope.py +338 -0
- sentry_sdk/feature_flags.py +66 -0
- sentry_sdk/hub.py +741 -0
- sentry_sdk/integrations/__init__.py +349 -0
- sentry_sdk/integrations/_asgi_common.py +139 -0
- sentry_sdk/integrations/_wsgi_common.py +263 -0
- sentry_sdk/integrations/aiohttp.py +362 -0
- sentry_sdk/integrations/anthropic.py +1040 -0
- sentry_sdk/integrations/argv.py +29 -0
- sentry_sdk/integrations/ariadne.py +160 -0
- sentry_sdk/integrations/arq.py +237 -0
- sentry_sdk/integrations/asgi.py +504 -0
- sentry_sdk/integrations/asyncio.py +268 -0
- sentry_sdk/integrations/asyncpg.py +218 -0
- sentry_sdk/integrations/atexit.py +52 -0
- sentry_sdk/integrations/aws_lambda.py +489 -0
- sentry_sdk/integrations/beam.py +168 -0
- sentry_sdk/integrations/boto3.py +138 -0
- sentry_sdk/integrations/bottle.py +211 -0
- sentry_sdk/integrations/celery/__init__.py +618 -0
- sentry_sdk/integrations/celery/beat.py +290 -0
- sentry_sdk/integrations/celery/utils.py +31 -0
- sentry_sdk/integrations/chalice.py +132 -0
- sentry_sdk/integrations/clickhouse_driver.py +172 -0
- sentry_sdk/integrations/cloud_resource_context.py +272 -0
- sentry_sdk/integrations/cohere.py +269 -0
- sentry_sdk/integrations/dedupe.py +63 -0
- sentry_sdk/integrations/django/__init__.py +784 -0
- sentry_sdk/integrations/django/asgi.py +244 -0
- sentry_sdk/integrations/django/caching.py +211 -0
- sentry_sdk/integrations/django/middleware.py +182 -0
- sentry_sdk/integrations/django/signals_handlers.py +90 -0
- sentry_sdk/integrations/django/tasks.py +40 -0
- sentry_sdk/integrations/django/templates.py +188 -0
- sentry_sdk/integrations/django/transactions.py +159 -0
- sentry_sdk/integrations/django/views.py +96 -0
- sentry_sdk/integrations/dramatiq.py +228 -0
- sentry_sdk/integrations/excepthook.py +81 -0
- sentry_sdk/integrations/executing.py +66 -0
- sentry_sdk/integrations/falcon.py +259 -0
- sentry_sdk/integrations/fastapi.py +161 -0
- sentry_sdk/integrations/flask.py +265 -0
- sentry_sdk/integrations/gcp.py +234 -0
- sentry_sdk/integrations/gnu_backtrace.py +96 -0
- sentry_sdk/integrations/google_genai/__init__.py +315 -0
- sentry_sdk/integrations/google_genai/consts.py +16 -0
- sentry_sdk/integrations/google_genai/streaming.py +160 -0
- sentry_sdk/integrations/google_genai/utils.py +708 -0
- sentry_sdk/integrations/gql.py +168 -0
- sentry_sdk/integrations/graphene.py +156 -0
- sentry_sdk/integrations/grpc/__init__.py +190 -0
- sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
- sentry_sdk/integrations/grpc/aio/client.py +99 -0
- sentry_sdk/integrations/grpc/aio/server.py +102 -0
- sentry_sdk/integrations/grpc/client.py +98 -0
- sentry_sdk/integrations/grpc/consts.py +1 -0
- sentry_sdk/integrations/grpc/server.py +68 -0
- sentry_sdk/integrations/httpx.py +261 -0
- sentry_sdk/integrations/huey.py +166 -0
- sentry_sdk/integrations/huggingface_hub.py +388 -0
- sentry_sdk/integrations/langchain.py +1168 -0
- sentry_sdk/integrations/langgraph.py +375 -0
- sentry_sdk/integrations/launchdarkly.py +63 -0
- sentry_sdk/integrations/litellm.py +301 -0
- sentry_sdk/integrations/litestar.py +311 -0
- sentry_sdk/integrations/logging.py +479 -0
- sentry_sdk/integrations/loguru.py +209 -0
- sentry_sdk/integrations/mcp.py +673 -0
- sentry_sdk/integrations/modules.py +27 -0
- sentry_sdk/integrations/openai.py +1326 -0
- sentry_sdk/integrations/openai_agents/__init__.py +242 -0
- sentry_sdk/integrations/openai_agents/consts.py +1 -0
- sentry_sdk/integrations/openai_agents/patches/__init__.py +10 -0
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +255 -0
- sentry_sdk/integrations/openai_agents/patches/error_tracing.py +67 -0
- sentry_sdk/integrations/openai_agents/patches/models.py +191 -0
- sentry_sdk/integrations/openai_agents/patches/runner.py +172 -0
- sentry_sdk/integrations/openai_agents/patches/tools.py +71 -0
- sentry_sdk/integrations/openai_agents/spans/__init__.py +9 -0
- sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +19 -0
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +68 -0
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +55 -0
- sentry_sdk/integrations/openai_agents/spans/handoff.py +25 -0
- sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +111 -0
- sentry_sdk/integrations/openai_agents/utils.py +232 -0
- sentry_sdk/integrations/openfeature.py +34 -0
- sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
- sentry_sdk/integrations/opentelemetry/consts.py +9 -0
- sentry_sdk/integrations/opentelemetry/integration.py +55 -0
- sentry_sdk/integrations/opentelemetry/propagator.py +128 -0
- sentry_sdk/integrations/opentelemetry/span_processor.py +396 -0
- sentry_sdk/integrations/otlp.py +226 -0
- sentry_sdk/integrations/pure_eval.py +137 -0
- sentry_sdk/integrations/pydantic_ai/__init__.py +172 -0
- sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
- sentry_sdk/integrations/pydantic_ai/patches/__init__.py +3 -0
- sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +228 -0
- sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +106 -0
- sentry_sdk/integrations/pydantic_ai/patches/tools.py +178 -0
- sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
- sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +278 -0
- sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +58 -0
- sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +149 -0
- sentry_sdk/integrations/pydantic_ai/spans/utils.py +49 -0
- sentry_sdk/integrations/pydantic_ai/utils.py +217 -0
- sentry_sdk/integrations/pymongo.py +211 -0
- sentry_sdk/integrations/pyramid.py +223 -0
- sentry_sdk/integrations/pyreqwest.py +136 -0
- sentry_sdk/integrations/quart.py +233 -0
- sentry_sdk/integrations/ray.py +181 -0
- sentry_sdk/integrations/redis/__init__.py +50 -0
- sentry_sdk/integrations/redis/_async_common.py +160 -0
- sentry_sdk/integrations/redis/_sync_common.py +160 -0
- sentry_sdk/integrations/redis/consts.py +19 -0
- sentry_sdk/integrations/redis/modules/__init__.py +0 -0
- sentry_sdk/integrations/redis/modules/caches.py +135 -0
- sentry_sdk/integrations/redis/modules/queries.py +86 -0
- sentry_sdk/integrations/redis/rb.py +31 -0
- sentry_sdk/integrations/redis/redis.py +67 -0
- sentry_sdk/integrations/redis/redis_cluster.py +112 -0
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +49 -0
- sentry_sdk/integrations/redis/utils.py +159 -0
- sentry_sdk/integrations/rq.py +184 -0
- sentry_sdk/integrations/rust_tracing.py +272 -0
- sentry_sdk/integrations/sanic.py +354 -0
- sentry_sdk/integrations/serverless.py +65 -0
- sentry_sdk/integrations/socket.py +98 -0
- sentry_sdk/integrations/spark/__init__.py +4 -0
- sentry_sdk/integrations/spark/spark_driver.py +278 -0
- sentry_sdk/integrations/spark/spark_worker.py +111 -0
- sentry_sdk/integrations/sqlalchemy.py +188 -0
- sentry_sdk/integrations/starlette.py +808 -0
- sentry_sdk/integrations/starlite.py +290 -0
- sentry_sdk/integrations/statsig.py +37 -0
- sentry_sdk/integrations/stdlib.py +368 -0
- sentry_sdk/integrations/strawberry.py +398 -0
- sentry_sdk/integrations/sys_exit.py +65 -0
- sentry_sdk/integrations/threading.py +200 -0
- sentry_sdk/integrations/tornado.py +224 -0
- sentry_sdk/integrations/trytond.py +53 -0
- sentry_sdk/integrations/typer.py +62 -0
- sentry_sdk/integrations/unleash.py +33 -0
- sentry_sdk/integrations/unraisablehook.py +52 -0
- sentry_sdk/integrations/wsgi.py +434 -0
- sentry_sdk/logger.py +88 -0
- sentry_sdk/metrics.py +62 -0
- sentry_sdk/monitor.py +139 -0
- sentry_sdk/profiler/__init__.py +49 -0
- sentry_sdk/profiler/continuous_profiler.py +710 -0
- sentry_sdk/profiler/transaction_profiler.py +809 -0
- sentry_sdk/profiler/utils.py +189 -0
- sentry_sdk/py.typed +0 -0
- sentry_sdk/scope.py +2180 -0
- sentry_sdk/scrubber.py +172 -0
- sentry_sdk/serializer.py +390 -0
- sentry_sdk/session.py +169 -0
- sentry_sdk/sessions.py +269 -0
- sentry_sdk/spotlight.py +331 -0
- sentry_sdk/traces.py +771 -0
- sentry_sdk/tracing.py +1470 -0
- sentry_sdk/tracing_utils.py +1728 -0
- sentry_sdk/transport.py +1168 -0
- sentry_sdk/types.py +52 -0
- sentry_sdk/utils.py +2153 -0
- sentry_sdk/worker.py +311 -0
- sentry_sdk-2.59.0a1.dist-info/METADATA +269 -0
- sentry_sdk-2.59.0a1.dist-info/RECORD +197 -0
- sentry_sdk-2.59.0a1.dist-info/WHEEL +5 -0
- sentry_sdk-2.59.0a1.dist-info/entry_points.txt +2 -0
- sentry_sdk-2.59.0a1.dist-info/licenses/LICENSE +21 -0
- sentry_sdk-2.59.0a1.dist-info/top_level.txt +1 -0
sentry_sdk/__init__.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from sentry_sdk import profiler
|
|
2
|
+
from sentry_sdk import metrics
|
|
3
|
+
from sentry_sdk.scope import Scope
|
|
4
|
+
from sentry_sdk.transport import Transport, HttpTransport
|
|
5
|
+
from sentry_sdk.client import Client
|
|
6
|
+
|
|
7
|
+
from sentry_sdk.api import * # noqa
|
|
8
|
+
from sentry_sdk.consts import VERSION
|
|
9
|
+
|
|
10
|
+
__all__ = [ # noqa
|
|
11
|
+
"Hub",
|
|
12
|
+
"Scope",
|
|
13
|
+
"Client",
|
|
14
|
+
"Transport",
|
|
15
|
+
"HttpTransport",
|
|
16
|
+
"VERSION",
|
|
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
|
+
"flush_async",
|
|
29
|
+
"get_baggage",
|
|
30
|
+
"get_client",
|
|
31
|
+
"get_global_scope",
|
|
32
|
+
"get_isolation_scope",
|
|
33
|
+
"get_current_scope",
|
|
34
|
+
"get_current_span",
|
|
35
|
+
"get_traceparent",
|
|
36
|
+
"is_initialized",
|
|
37
|
+
"isolation_scope",
|
|
38
|
+
"last_event_id",
|
|
39
|
+
"new_scope",
|
|
40
|
+
"push_scope",
|
|
41
|
+
"remove_attribute",
|
|
42
|
+
"set_attribute",
|
|
43
|
+
"set_context",
|
|
44
|
+
"set_extra",
|
|
45
|
+
"set_level",
|
|
46
|
+
"set_measurement",
|
|
47
|
+
"set_tag",
|
|
48
|
+
"set_tags",
|
|
49
|
+
"set_user",
|
|
50
|
+
"start_span",
|
|
51
|
+
"start_transaction",
|
|
52
|
+
"trace",
|
|
53
|
+
"monitor",
|
|
54
|
+
"logger",
|
|
55
|
+
"metrics",
|
|
56
|
+
"profiler",
|
|
57
|
+
"start_session",
|
|
58
|
+
"end_session",
|
|
59
|
+
"set_transaction_name",
|
|
60
|
+
"update_current_span",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
# Initialize the debug support after everything is loaded
|
|
64
|
+
from sentry_sdk.debug import init_debug_support
|
|
65
|
+
|
|
66
|
+
init_debug_support()
|
|
67
|
+
del init_debug_support
|
|
68
|
+
|
|
69
|
+
# circular imports
|
|
70
|
+
from sentry_sdk.hub import Hub
|
sentry_sdk/_batcher.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import random
|
|
3
|
+
import threading
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import TYPE_CHECKING, TypeVar, Generic
|
|
6
|
+
import weakref
|
|
7
|
+
|
|
8
|
+
from sentry_sdk.utils import format_timestamp
|
|
9
|
+
from sentry_sdk.envelope import Envelope, Item, PayloadRef
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from typing import Optional, Callable, Any
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Batcher(Generic[T]):
|
|
18
|
+
MAX_BEFORE_FLUSH = 100
|
|
19
|
+
MAX_BEFORE_DROP = 1_000
|
|
20
|
+
FLUSH_WAIT_TIME = 5.0
|
|
21
|
+
|
|
22
|
+
TYPE = ""
|
|
23
|
+
CONTENT_TYPE = ""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
capture_func: "Callable[[Envelope], None]",
|
|
28
|
+
record_lost_func: "Callable[..., None]",
|
|
29
|
+
) -> None:
|
|
30
|
+
self._buffer: "list[T]" = []
|
|
31
|
+
self._capture_func = capture_func
|
|
32
|
+
self._record_lost_func = record_lost_func
|
|
33
|
+
self._running = True
|
|
34
|
+
self._lock = threading.Lock()
|
|
35
|
+
self._active: "threading.local" = threading.local()
|
|
36
|
+
|
|
37
|
+
self._flush_event: "threading.Event" = threading.Event()
|
|
38
|
+
|
|
39
|
+
self._flusher: "Optional[threading.Thread]" = None
|
|
40
|
+
self._flusher_pid: "Optional[int]" = None
|
|
41
|
+
|
|
42
|
+
# See https://github.com/getsentry/sentry-python/blob/051cc01640a29bfd64b1f1e2e3414c02f027dd1b/sentry_sdk/monitor.py#L41-L50
|
|
43
|
+
if hasattr(os, "register_at_fork"):
|
|
44
|
+
weak_reset = weakref.WeakMethod(self._reset_thread_state)
|
|
45
|
+
|
|
46
|
+
def _reset_in_child() -> None:
|
|
47
|
+
method = weak_reset()
|
|
48
|
+
if method is not None:
|
|
49
|
+
method()
|
|
50
|
+
|
|
51
|
+
os.register_at_fork(after_in_child=_reset_in_child)
|
|
52
|
+
|
|
53
|
+
def _reset_thread_state(self) -> None:
|
|
54
|
+
self._buffer = []
|
|
55
|
+
self._running = True
|
|
56
|
+
self._lock = threading.Lock()
|
|
57
|
+
self._active = threading.local()
|
|
58
|
+
self._flush_event = threading.Event()
|
|
59
|
+
self._flusher = None
|
|
60
|
+
self._flusher_pid = None
|
|
61
|
+
|
|
62
|
+
def _ensure_thread(self) -> bool:
|
|
63
|
+
"""For forking processes we might need to restart this thread.
|
|
64
|
+
This ensures that our process actually has that thread running.
|
|
65
|
+
"""
|
|
66
|
+
if not self._running:
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
pid = os.getpid()
|
|
70
|
+
if self._flusher_pid == pid:
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
with self._lock:
|
|
74
|
+
# Recheck to make sure another thread didn't get here and start the
|
|
75
|
+
# the flusher in the meantime
|
|
76
|
+
if self._flusher_pid == pid:
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
self._flusher_pid = pid
|
|
80
|
+
|
|
81
|
+
self._flusher = threading.Thread(target=self._flush_loop)
|
|
82
|
+
self._flusher.daemon = True
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
self._flusher.start()
|
|
86
|
+
except RuntimeError:
|
|
87
|
+
# Unfortunately at this point the interpreter is in a state that no
|
|
88
|
+
# longer allows us to spawn a thread and we have to bail.
|
|
89
|
+
self._running = False
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
def _flush_loop(self) -> None:
|
|
95
|
+
# Mark the flush-loop thread as active for its entire lifetime so
|
|
96
|
+
# that any re-entrant add() triggered by GC warnings during wait(),
|
|
97
|
+
# flush(), or Event operations is silently dropped instead of
|
|
98
|
+
# deadlocking on internal locks.
|
|
99
|
+
self._active.flag = True
|
|
100
|
+
while self._running:
|
|
101
|
+
self._flush_event.wait(self.FLUSH_WAIT_TIME + random.random())
|
|
102
|
+
self._flush_event.clear()
|
|
103
|
+
self._flush()
|
|
104
|
+
|
|
105
|
+
def add(self, item: "T") -> None:
|
|
106
|
+
# Bail out if the current thread is already executing batcher code.
|
|
107
|
+
# This prevents deadlocks when code running inside the batcher (e.g.
|
|
108
|
+
# _add_to_envelope during flush, or _flush_event.wait/set) triggers
|
|
109
|
+
# a GC-emitted warning that routes back through the logging
|
|
110
|
+
# integration into add().
|
|
111
|
+
if getattr(self._active, "flag", False):
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
self._active.flag = True
|
|
115
|
+
try:
|
|
116
|
+
if not self._ensure_thread() or self._flusher is None:
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
with self._lock:
|
|
120
|
+
if len(self._buffer) >= self.MAX_BEFORE_DROP:
|
|
121
|
+
self._record_lost(item)
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
self._buffer.append(item)
|
|
125
|
+
if len(self._buffer) >= self.MAX_BEFORE_FLUSH:
|
|
126
|
+
self._flush_event.set()
|
|
127
|
+
finally:
|
|
128
|
+
self._active.flag = False
|
|
129
|
+
|
|
130
|
+
def kill(self) -> None:
|
|
131
|
+
if self._flusher is None:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
self._running = False
|
|
135
|
+
self._flush_event.set()
|
|
136
|
+
self._flusher = None
|
|
137
|
+
|
|
138
|
+
def flush(self) -> None:
|
|
139
|
+
was_active = getattr(self._active, "flag", False)
|
|
140
|
+
self._active.flag = True
|
|
141
|
+
try:
|
|
142
|
+
self._flush()
|
|
143
|
+
finally:
|
|
144
|
+
self._active.flag = was_active
|
|
145
|
+
|
|
146
|
+
def _add_to_envelope(self, envelope: "Envelope") -> None:
|
|
147
|
+
envelope.add_item(
|
|
148
|
+
Item(
|
|
149
|
+
type=self.TYPE,
|
|
150
|
+
content_type=self.CONTENT_TYPE,
|
|
151
|
+
headers={
|
|
152
|
+
"item_count": len(self._buffer),
|
|
153
|
+
},
|
|
154
|
+
payload=PayloadRef(
|
|
155
|
+
json={
|
|
156
|
+
"items": [
|
|
157
|
+
self._to_transport_format(item) for item in self._buffer
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
),
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def _flush(self) -> "Optional[Envelope]":
|
|
165
|
+
envelope = Envelope(
|
|
166
|
+
headers={"sent_at": format_timestamp(datetime.now(timezone.utc))}
|
|
167
|
+
)
|
|
168
|
+
with self._lock:
|
|
169
|
+
if len(self._buffer) == 0:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
self._add_to_envelope(envelope)
|
|
173
|
+
self._buffer.clear()
|
|
174
|
+
|
|
175
|
+
self._capture_func(envelope)
|
|
176
|
+
return envelope
|
|
177
|
+
|
|
178
|
+
def _record_lost(self, item: "T") -> None:
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def _to_transport_format(item: "T") -> "Any":
|
|
183
|
+
pass
|
sentry_sdk/_compat.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from typing import Any
|
|
7
|
+
from typing import TypeVar
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
|
|
11
|
+
|
|
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
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def with_metaclass(meta: "Any", *bases: "Any") -> "Any":
|
|
19
|
+
class MetaClass(type):
|
|
20
|
+
def __new__(metacls: "Any", name: "Any", this_bases: "Any", d: "Any") -> "Any":
|
|
21
|
+
return meta(name, bases, d)
|
|
22
|
+
|
|
23
|
+
return type.__new__(MetaClass, "temporary_class", (), {})
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def check_uwsgi_thread_support() -> bool:
|
|
27
|
+
# We check two things here:
|
|
28
|
+
#
|
|
29
|
+
# 1. uWSGI doesn't run in threaded mode by default -- issue a warning if
|
|
30
|
+
# that's the case.
|
|
31
|
+
#
|
|
32
|
+
# 2. Additionally, if uWSGI is running in preforking mode (default), it needs
|
|
33
|
+
# the --py-call-uwsgi-fork-hooks option for the SDK to work properly. This
|
|
34
|
+
# is because any background threads spawned before the main process is
|
|
35
|
+
# forked are NOT CLEANED UP IN THE CHILDREN BY DEFAULT even if
|
|
36
|
+
# --enable-threads is on. One has to explicitly provide
|
|
37
|
+
# --py-call-uwsgi-fork-hooks to force uWSGI to run regular cpython
|
|
38
|
+
# after-fork hooks that take care of cleaning up stale thread data.
|
|
39
|
+
try:
|
|
40
|
+
from uwsgi import opt # type: ignore
|
|
41
|
+
except ImportError:
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
from sentry_sdk.consts import FALSE_VALUES
|
|
45
|
+
|
|
46
|
+
def enabled(option: str) -> bool:
|
|
47
|
+
value = opt.get(option, False)
|
|
48
|
+
if isinstance(value, bool):
|
|
49
|
+
return value
|
|
50
|
+
|
|
51
|
+
if isinstance(value, bytes):
|
|
52
|
+
try:
|
|
53
|
+
value = value.decode()
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
return value and str(value).lower() not in FALSE_VALUES
|
|
58
|
+
|
|
59
|
+
# When `threads` is passed in as a uwsgi option,
|
|
60
|
+
# `enable-threads` is implied on.
|
|
61
|
+
threads_enabled = "threads" in opt or enabled("enable-threads")
|
|
62
|
+
fork_hooks_on = enabled("py-call-uwsgi-fork-hooks")
|
|
63
|
+
lazy_mode = enabled("lazy-apps") or enabled("lazy")
|
|
64
|
+
|
|
65
|
+
if lazy_mode and not threads_enabled:
|
|
66
|
+
from warnings import warn
|
|
67
|
+
|
|
68
|
+
warn(
|
|
69
|
+
Warning(
|
|
70
|
+
"IMPORTANT: "
|
|
71
|
+
"We detected the use of uWSGI without thread support. "
|
|
72
|
+
"This might lead to unexpected issues. "
|
|
73
|
+
'Please run uWSGI with "--enable-threads" for full support.'
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
elif not lazy_mode and (not threads_enabled or not fork_hooks_on):
|
|
80
|
+
from warnings import warn
|
|
81
|
+
|
|
82
|
+
warn(
|
|
83
|
+
Warning(
|
|
84
|
+
"IMPORTANT: "
|
|
85
|
+
"We detected the use of uWSGI in preforking mode without "
|
|
86
|
+
"thread support. This might lead to crashing workers. "
|
|
87
|
+
'Please run uWSGI with both "--enable-threads" and '
|
|
88
|
+
'"--py-call-uwsgi-fork-hooks" for full support.'
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
return True
|
|
@@ -0,0 +1,79 @@
|
|
|
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: "sentry_sdk.Client") -> None:
|
|
22
|
+
self._client = client
|
|
23
|
+
|
|
24
|
+
def __enter__(self) -> "_InitGuard":
|
|
25
|
+
warnings.warn(
|
|
26
|
+
self._CONTEXT_MANAGER_DEPRECATION_WARNING_MESSAGE,
|
|
27
|
+
stacklevel=2,
|
|
28
|
+
category=DeprecationWarning,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return self
|
|
32
|
+
|
|
33
|
+
def __exit__(self, exc_type: "Any", exc_value: "Any", tb: "Any") -> None:
|
|
34
|
+
warnings.warn(
|
|
35
|
+
self._CONTEXT_MANAGER_DEPRECATION_WARNING_MESSAGE,
|
|
36
|
+
stacklevel=2,
|
|
37
|
+
category=DeprecationWarning,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
c = self._client
|
|
41
|
+
if c is not None:
|
|
42
|
+
c.close()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _check_python_deprecations() -> None:
|
|
46
|
+
# Since we're likely to deprecate Python versions in the future, I'm keeping
|
|
47
|
+
# this handy function around. Use this to detect the Python version used and
|
|
48
|
+
# to output logger.warning()s if it's deprecated.
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _init(*args: "Optional[str]", **kwargs: "Any") -> "ContextManager[Any]":
|
|
53
|
+
"""Initializes the SDK and optionally integrations.
|
|
54
|
+
|
|
55
|
+
This takes the same arguments as the client constructor.
|
|
56
|
+
"""
|
|
57
|
+
client = sentry_sdk.Client(*args, **kwargs)
|
|
58
|
+
sentry_sdk.get_global_scope().set_client(client)
|
|
59
|
+
_check_python_deprecations()
|
|
60
|
+
rv = _InitGuard(client)
|
|
61
|
+
return rv
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if TYPE_CHECKING:
|
|
65
|
+
# Make mypy, PyCharm and other static analyzers think `init` is a type to
|
|
66
|
+
# have nicer autocompletion for params.
|
|
67
|
+
#
|
|
68
|
+
# Use `ClientConstructor` to define the argument types of `init` and
|
|
69
|
+
# `ContextManager[Any]` to tell static analyzers about the return type.
|
|
70
|
+
|
|
71
|
+
class init(sentry_sdk.consts.ClientConstructor, _InitGuard): # noqa: N801
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
else:
|
|
75
|
+
# Alias `init` for actual usage. Go through the lambda indirection to throw
|
|
76
|
+
# PyCharm off of the weakly typed signature (it would otherwise discover
|
|
77
|
+
# both the weakly typed signature of `_init` and our faked `init` type).
|
|
78
|
+
|
|
79
|
+
init = (lambda: _init)()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from sentry_sdk._batcher import Batcher
|
|
4
|
+
from sentry_sdk.utils import serialize_attribute
|
|
5
|
+
from sentry_sdk.envelope import Item, PayloadRef
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Any
|
|
9
|
+
from sentry_sdk._types import Log
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LogBatcher(Batcher["Log"]):
|
|
13
|
+
MAX_BEFORE_FLUSH = 100
|
|
14
|
+
MAX_BEFORE_DROP = 1_000
|
|
15
|
+
FLUSH_WAIT_TIME = 5.0
|
|
16
|
+
|
|
17
|
+
TYPE = "log"
|
|
18
|
+
CONTENT_TYPE = "application/vnd.sentry.items.log+json"
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def _to_transport_format(item: "Log") -> "Any":
|
|
22
|
+
if "sentry.severity_number" not in item["attributes"]:
|
|
23
|
+
item["attributes"]["sentry.severity_number"] = item["severity_number"]
|
|
24
|
+
if "sentry.severity_text" not in item["attributes"]:
|
|
25
|
+
item["attributes"]["sentry.severity_text"] = item["severity_text"]
|
|
26
|
+
|
|
27
|
+
res = {
|
|
28
|
+
"timestamp": int(item["time_unix_nano"]) / 1.0e9,
|
|
29
|
+
"trace_id": item.get("trace_id", "00000000-0000-0000-0000-000000000000"),
|
|
30
|
+
"span_id": item.get("span_id"),
|
|
31
|
+
"level": str(item["severity_text"]),
|
|
32
|
+
"body": str(item["body"]),
|
|
33
|
+
"attributes": {
|
|
34
|
+
k: serialize_attribute(v) for (k, v) in item["attributes"].items()
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return res
|
|
39
|
+
|
|
40
|
+
def _record_lost(self, item: "Log") -> None:
|
|
41
|
+
# Construct log envelope item without sending it to report lost bytes
|
|
42
|
+
log_item = Item(
|
|
43
|
+
type=self.TYPE,
|
|
44
|
+
content_type=self.CONTENT_TYPE,
|
|
45
|
+
headers={
|
|
46
|
+
"item_count": 1,
|
|
47
|
+
},
|
|
48
|
+
payload=PayloadRef(json={"items": [self._to_transport_format(item)]}),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
self._record_lost_func(
|
|
52
|
+
reason="queue_overflow",
|
|
53
|
+
data_category="log_item",
|
|
54
|
+
item=log_item,
|
|
55
|
+
quantity=1,
|
|
56
|
+
)
|
sentry_sdk/_lru_cache.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
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: int) -> None:
|
|
12
|
+
if max_size <= 0:
|
|
13
|
+
raise AssertionError(f"invalid max_size: {max_size}")
|
|
14
|
+
self.max_size = max_size
|
|
15
|
+
self._data: "dict[Any, Any]" = {}
|
|
16
|
+
self.hits = self.misses = 0
|
|
17
|
+
self.full = False
|
|
18
|
+
|
|
19
|
+
def set(self, key: "Any", value: "Any") -> None:
|
|
20
|
+
current = self._data.pop(key, _SENTINEL)
|
|
21
|
+
if current is not _SENTINEL:
|
|
22
|
+
self._data[key] = value
|
|
23
|
+
elif self.full:
|
|
24
|
+
self._data.pop(next(iter(self._data)))
|
|
25
|
+
self._data[key] = value
|
|
26
|
+
else:
|
|
27
|
+
self._data[key] = value
|
|
28
|
+
self.full = len(self._data) >= self.max_size
|
|
29
|
+
|
|
30
|
+
def get(self, key: "Any", default: "Any" = None) -> "Any":
|
|
31
|
+
try:
|
|
32
|
+
ret = self._data.pop(key)
|
|
33
|
+
except KeyError:
|
|
34
|
+
self.misses += 1
|
|
35
|
+
ret = default
|
|
36
|
+
else:
|
|
37
|
+
self.hits += 1
|
|
38
|
+
self._data[key] = ret
|
|
39
|
+
|
|
40
|
+
return ret
|
|
41
|
+
|
|
42
|
+
def get_all(self) -> "list[tuple[Any, Any]]":
|
|
43
|
+
return list(self._data.items())
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from sentry_sdk._batcher import Batcher
|
|
4
|
+
from sentry_sdk.utils import serialize_attribute
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from typing import Any
|
|
8
|
+
from sentry_sdk._types import Metric
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MetricsBatcher(Batcher["Metric"]):
|
|
12
|
+
MAX_BEFORE_FLUSH = 1000
|
|
13
|
+
MAX_BEFORE_DROP = 10_000
|
|
14
|
+
FLUSH_WAIT_TIME = 5.0
|
|
15
|
+
|
|
16
|
+
TYPE = "trace_metric"
|
|
17
|
+
CONTENT_TYPE = "application/vnd.sentry.items.trace-metric+json"
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def _to_transport_format(item: "Metric") -> "Any":
|
|
21
|
+
res = {
|
|
22
|
+
"timestamp": item["timestamp"],
|
|
23
|
+
"trace_id": item["trace_id"],
|
|
24
|
+
"name": item["name"],
|
|
25
|
+
"type": item["type"],
|
|
26
|
+
"value": item["value"],
|
|
27
|
+
"attributes": {
|
|
28
|
+
k: serialize_attribute(v) for (k, v) in item["attributes"].items()
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if item.get("span_id") is not None:
|
|
33
|
+
res["span_id"] = item["span_id"]
|
|
34
|
+
|
|
35
|
+
if item.get("unit") is not None:
|
|
36
|
+
res["unit"] = item["unit"]
|
|
37
|
+
|
|
38
|
+
return res
|
|
39
|
+
|
|
40
|
+
def _record_lost(self, item: "Metric") -> None:
|
|
41
|
+
self._record_lost_func(
|
|
42
|
+
reason="queue_overflow",
|
|
43
|
+
data_category="trace_metric",
|
|
44
|
+
quantity=1,
|
|
45
|
+
)
|