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.
- sentry_sdk/__init__.py +48 -30
- sentry_sdk/_compat.py +74 -61
- sentry_sdk/_init_implementation.py +84 -0
- sentry_sdk/_log_batcher.py +172 -0
- sentry_sdk/_lru_cache.py +47 -0
- sentry_sdk/_metrics_batcher.py +167 -0
- sentry_sdk/_queue.py +289 -0
- sentry_sdk/_types.py +338 -0
- sentry_sdk/_werkzeug.py +98 -0
- sentry_sdk/ai/__init__.py +7 -0
- sentry_sdk/ai/monitoring.py +137 -0
- sentry_sdk/ai/utils.py +144 -0
- sentry_sdk/api.py +496 -80
- sentry_sdk/attachments.py +75 -0
- sentry_sdk/client.py +1023 -103
- sentry_sdk/consts.py +1438 -66
- sentry_sdk/crons/__init__.py +10 -0
- sentry_sdk/crons/api.py +62 -0
- sentry_sdk/crons/consts.py +4 -0
- sentry_sdk/crons/decorator.py +135 -0
- sentry_sdk/debug.py +15 -14
- sentry_sdk/envelope.py +369 -0
- sentry_sdk/feature_flags.py +71 -0
- sentry_sdk/hub.py +611 -280
- sentry_sdk/integrations/__init__.py +276 -49
- sentry_sdk/integrations/_asgi_common.py +108 -0
- sentry_sdk/integrations/_wsgi_common.py +180 -44
- sentry_sdk/integrations/aiohttp.py +291 -42
- sentry_sdk/integrations/anthropic.py +439 -0
- sentry_sdk/integrations/argv.py +9 -8
- sentry_sdk/integrations/ariadne.py +161 -0
- sentry_sdk/integrations/arq.py +247 -0
- sentry_sdk/integrations/asgi.py +341 -0
- sentry_sdk/integrations/asyncio.py +144 -0
- sentry_sdk/integrations/asyncpg.py +208 -0
- sentry_sdk/integrations/atexit.py +17 -10
- sentry_sdk/integrations/aws_lambda.py +377 -62
- sentry_sdk/integrations/beam.py +176 -0
- sentry_sdk/integrations/boto3.py +137 -0
- sentry_sdk/integrations/bottle.py +221 -0
- sentry_sdk/integrations/celery/__init__.py +529 -0
- sentry_sdk/integrations/celery/beat.py +293 -0
- sentry_sdk/integrations/celery/utils.py +43 -0
- sentry_sdk/integrations/chalice.py +134 -0
- sentry_sdk/integrations/clickhouse_driver.py +177 -0
- sentry_sdk/integrations/cloud_resource_context.py +280 -0
- sentry_sdk/integrations/cohere.py +274 -0
- sentry_sdk/integrations/dedupe.py +48 -14
- sentry_sdk/integrations/django/__init__.py +584 -191
- sentry_sdk/integrations/django/asgi.py +245 -0
- sentry_sdk/integrations/django/caching.py +204 -0
- sentry_sdk/integrations/django/middleware.py +187 -0
- sentry_sdk/integrations/django/signals_handlers.py +91 -0
- sentry_sdk/integrations/django/templates.py +79 -5
- sentry_sdk/integrations/django/transactions.py +49 -22
- sentry_sdk/integrations/django/views.py +96 -0
- sentry_sdk/integrations/dramatiq.py +226 -0
- sentry_sdk/integrations/excepthook.py +50 -13
- sentry_sdk/integrations/executing.py +67 -0
- sentry_sdk/integrations/falcon.py +272 -0
- sentry_sdk/integrations/fastapi.py +141 -0
- sentry_sdk/integrations/flask.py +142 -88
- sentry_sdk/integrations/gcp.py +239 -0
- sentry_sdk/integrations/gnu_backtrace.py +99 -0
- sentry_sdk/integrations/google_genai/__init__.py +301 -0
- sentry_sdk/integrations/google_genai/consts.py +16 -0
- sentry_sdk/integrations/google_genai/streaming.py +155 -0
- sentry_sdk/integrations/google_genai/utils.py +576 -0
- sentry_sdk/integrations/gql.py +162 -0
- sentry_sdk/integrations/graphene.py +151 -0
- sentry_sdk/integrations/grpc/__init__.py +168 -0
- sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
- sentry_sdk/integrations/grpc/aio/client.py +95 -0
- sentry_sdk/integrations/grpc/aio/server.py +100 -0
- sentry_sdk/integrations/grpc/client.py +91 -0
- sentry_sdk/integrations/grpc/consts.py +1 -0
- sentry_sdk/integrations/grpc/server.py +66 -0
- sentry_sdk/integrations/httpx.py +178 -0
- sentry_sdk/integrations/huey.py +174 -0
- sentry_sdk/integrations/huggingface_hub.py +378 -0
- sentry_sdk/integrations/langchain.py +1132 -0
- sentry_sdk/integrations/langgraph.py +337 -0
- sentry_sdk/integrations/launchdarkly.py +61 -0
- sentry_sdk/integrations/litellm.py +287 -0
- sentry_sdk/integrations/litestar.py +315 -0
- sentry_sdk/integrations/logging.py +307 -96
- sentry_sdk/integrations/loguru.py +213 -0
- sentry_sdk/integrations/mcp.py +566 -0
- sentry_sdk/integrations/modules.py +14 -31
- sentry_sdk/integrations/openai.py +725 -0
- sentry_sdk/integrations/openai_agents/__init__.py +61 -0
- sentry_sdk/integrations/openai_agents/consts.py +1 -0
- sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
- sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
- sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
- sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
- sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
- sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
- sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
- sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
- sentry_sdk/integrations/openai_agents/utils.py +199 -0
- sentry_sdk/integrations/openfeature.py +35 -0
- sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
- sentry_sdk/integrations/opentelemetry/consts.py +5 -0
- sentry_sdk/integrations/opentelemetry/integration.py +58 -0
- sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
- sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
- sentry_sdk/integrations/otlp.py +82 -0
- sentry_sdk/integrations/pure_eval.py +141 -0
- sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
- sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
- sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
- sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
- sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
- sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
- sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
- sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
- sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
- sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
- sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
- sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
- sentry_sdk/integrations/pymongo.py +214 -0
- sentry_sdk/integrations/pyramid.py +112 -68
- sentry_sdk/integrations/quart.py +237 -0
- sentry_sdk/integrations/ray.py +165 -0
- sentry_sdk/integrations/redis/__init__.py +48 -0
- sentry_sdk/integrations/redis/_async_common.py +116 -0
- sentry_sdk/integrations/redis/_sync_common.py +119 -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 +118 -0
- sentry_sdk/integrations/redis/modules/queries.py +65 -0
- sentry_sdk/integrations/redis/rb.py +32 -0
- sentry_sdk/integrations/redis/redis.py +69 -0
- sentry_sdk/integrations/redis/redis_cluster.py +107 -0
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
- sentry_sdk/integrations/redis/utils.py +148 -0
- sentry_sdk/integrations/rq.py +95 -37
- sentry_sdk/integrations/rust_tracing.py +284 -0
- sentry_sdk/integrations/sanic.py +294 -123
- sentry_sdk/integrations/serverless.py +48 -19
- sentry_sdk/integrations/socket.py +96 -0
- sentry_sdk/integrations/spark/__init__.py +4 -0
- sentry_sdk/integrations/spark/spark_driver.py +316 -0
- sentry_sdk/integrations/spark/spark_worker.py +116 -0
- sentry_sdk/integrations/sqlalchemy.py +142 -0
- sentry_sdk/integrations/starlette.py +737 -0
- sentry_sdk/integrations/starlite.py +292 -0
- sentry_sdk/integrations/statsig.py +37 -0
- sentry_sdk/integrations/stdlib.py +235 -29
- sentry_sdk/integrations/strawberry.py +394 -0
- sentry_sdk/integrations/sys_exit.py +70 -0
- sentry_sdk/integrations/threading.py +158 -28
- sentry_sdk/integrations/tornado.py +84 -52
- sentry_sdk/integrations/trytond.py +50 -0
- sentry_sdk/integrations/typer.py +60 -0
- sentry_sdk/integrations/unleash.py +33 -0
- sentry_sdk/integrations/unraisablehook.py +53 -0
- sentry_sdk/integrations/wsgi.py +201 -119
- sentry_sdk/logger.py +96 -0
- sentry_sdk/metrics.py +81 -0
- sentry_sdk/monitor.py +120 -0
- sentry_sdk/profiler/__init__.py +49 -0
- sentry_sdk/profiler/continuous_profiler.py +730 -0
- sentry_sdk/profiler/transaction_profiler.py +839 -0
- sentry_sdk/profiler/utils.py +195 -0
- sentry_sdk/py.typed +0 -0
- sentry_sdk/scope.py +1713 -85
- sentry_sdk/scrubber.py +177 -0
- sentry_sdk/serializer.py +405 -0
- sentry_sdk/session.py +177 -0
- sentry_sdk/sessions.py +275 -0
- sentry_sdk/spotlight.py +242 -0
- sentry_sdk/tracing.py +1486 -0
- sentry_sdk/tracing_utils.py +1236 -0
- sentry_sdk/transport.py +806 -134
- sentry_sdk/types.py +52 -0
- sentry_sdk/utils.py +1625 -465
- sentry_sdk/worker.py +54 -25
- sentry_sdk-2.46.0.dist-info/METADATA +268 -0
- sentry_sdk-2.46.0.dist-info/RECORD +189 -0
- {sentry_sdk-0.7.5.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
- sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
- sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
- sentry_sdk/integrations/celery.py +0 -119
- sentry_sdk-0.7.5.dist-info/LICENSE +0 -9
- sentry_sdk-0.7.5.dist-info/METADATA +0 -36
- sentry_sdk-0.7.5.dist-info/RECORD +0 -39
- {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
|
-
|
|
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.
|
|
32
|
-
|
|
33
|
-
from sentry_sdk.consts import VERSION # noqa
|
|
8
|
+
from sentry_sdk.consts import VERSION
|
|
34
9
|
|
|
35
|
-
__all__ =
|
|
10
|
+
__all__ = [ # noqa
|
|
36
11
|
"Hub",
|
|
37
12
|
"Scope",
|
|
38
13
|
"Client",
|
|
39
14
|
"Transport",
|
|
40
15
|
"HttpTransport",
|
|
41
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
return x
|
|
9
|
+
T = TypeVar("T")
|
|
43
10
|
|
|
44
|
-
def implements_str(x):
|
|
45
|
-
return x
|
|
46
11
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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__(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def
|
|
64
|
-
# type: () ->
|
|
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
|
-
|
|
73
|
-
|
|
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
|
|
69
|
+
if lazy_mode and not threads_enabled:
|
|
76
70
|
from warnings import warn
|
|
77
71
|
|
|
78
72
|
warn(
|
|
79
73
|
Warning(
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
'
|
|
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
|
sentry_sdk/_lru_cache.py
ADDED
|
@@ -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())
|