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/integrations/rq.py
CHANGED
|
@@ -1,51 +1,80 @@
|
|
|
1
|
-
from __future__ import absolute_import
|
|
2
|
-
|
|
3
1
|
import weakref
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
from sentry_sdk.
|
|
7
|
-
from sentry_sdk.
|
|
8
|
-
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
3
|
+
import sentry_sdk
|
|
4
|
+
from sentry_sdk.consts import OP
|
|
5
|
+
from sentry_sdk.api import continue_trace
|
|
6
|
+
from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
|
|
7
|
+
from sentry_sdk.integrations.logging import ignore_logger
|
|
8
|
+
from sentry_sdk.tracing import TransactionSource
|
|
9
|
+
from sentry_sdk.utils import (
|
|
10
|
+
capture_internal_exceptions,
|
|
11
|
+
ensure_integration_enabled,
|
|
12
|
+
event_from_exception,
|
|
13
|
+
format_timestamp,
|
|
14
|
+
parse_version,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from rq.queue import Queue
|
|
19
|
+
from rq.timeouts import JobTimeoutException
|
|
20
|
+
from rq.version import VERSION as RQ_VERSION
|
|
21
|
+
from rq.worker import Worker
|
|
22
|
+
from rq.job import JobStatus
|
|
23
|
+
except ImportError:
|
|
24
|
+
raise DidNotEnable("RQ not installed")
|
|
25
|
+
|
|
26
|
+
from typing import TYPE_CHECKING
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from typing import Any, Callable
|
|
30
|
+
|
|
31
|
+
from sentry_sdk._types import Event, EventProcessor
|
|
20
32
|
from sentry_sdk.utils import ExcInfo
|
|
21
33
|
|
|
34
|
+
from rq.job import Job
|
|
35
|
+
|
|
22
36
|
|
|
23
37
|
class RqIntegration(Integration):
|
|
24
38
|
identifier = "rq"
|
|
39
|
+
origin = f"auto.queue.{identifier}"
|
|
25
40
|
|
|
26
41
|
@staticmethod
|
|
27
42
|
def setup_once():
|
|
28
43
|
# type: () -> None
|
|
44
|
+
version = parse_version(RQ_VERSION)
|
|
45
|
+
_check_minimum_version(RqIntegration, version)
|
|
29
46
|
|
|
30
47
|
old_perform_job = Worker.perform_job
|
|
31
48
|
|
|
49
|
+
@ensure_integration_enabled(RqIntegration, old_perform_job)
|
|
32
50
|
def sentry_patched_perform_job(self, job, *args, **kwargs):
|
|
33
51
|
# type: (Any, Job, *Queue, **Any) -> bool
|
|
34
|
-
|
|
35
|
-
|
|
52
|
+
with sentry_sdk.new_scope() as scope:
|
|
53
|
+
scope.clear_breadcrumbs()
|
|
54
|
+
scope.add_event_processor(_make_event_processor(weakref.ref(job)))
|
|
36
55
|
|
|
37
|
-
|
|
38
|
-
|
|
56
|
+
transaction = continue_trace(
|
|
57
|
+
job.meta.get("_sentry_trace_headers") or {},
|
|
58
|
+
op=OP.QUEUE_TASK_RQ,
|
|
59
|
+
name="unknown RQ task",
|
|
60
|
+
source=TransactionSource.TASK,
|
|
61
|
+
origin=RqIntegration.origin,
|
|
62
|
+
)
|
|
39
63
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
64
|
+
with capture_internal_exceptions():
|
|
65
|
+
transaction.name = job.func_name
|
|
66
|
+
|
|
67
|
+
with sentry_sdk.start_transaction(
|
|
68
|
+
transaction,
|
|
69
|
+
custom_sampling_context={"rq_job": job},
|
|
70
|
+
):
|
|
71
|
+
rv = old_perform_job(self, job, *args, **kwargs)
|
|
43
72
|
|
|
44
73
|
if self.is_horse:
|
|
45
74
|
# We're inside of a forked process and RQ is
|
|
46
75
|
# about to call `os._exit`. Make sure that our
|
|
47
76
|
# events get sent out.
|
|
48
|
-
|
|
77
|
+
sentry_sdk.get_client().flush()
|
|
49
78
|
|
|
50
79
|
return rv
|
|
51
80
|
|
|
@@ -54,24 +83,47 @@ class RqIntegration(Integration):
|
|
|
54
83
|
old_handle_exception = Worker.handle_exception
|
|
55
84
|
|
|
56
85
|
def sentry_patched_handle_exception(self, job, *exc_info, **kwargs):
|
|
57
|
-
|
|
86
|
+
# type: (Worker, Any, *Any, **Any) -> Any
|
|
87
|
+
retry = (
|
|
88
|
+
hasattr(job, "retries_left")
|
|
89
|
+
and job.retries_left
|
|
90
|
+
and job.retries_left > 0
|
|
91
|
+
)
|
|
92
|
+
failed = job._status == JobStatus.FAILED or job.is_failed
|
|
93
|
+
if failed and not retry:
|
|
94
|
+
_capture_exception(exc_info)
|
|
95
|
+
|
|
58
96
|
return old_handle_exception(self, job, *exc_info, **kwargs)
|
|
59
97
|
|
|
60
98
|
Worker.handle_exception = sentry_patched_handle_exception
|
|
61
99
|
|
|
100
|
+
old_enqueue_job = Queue.enqueue_job
|
|
101
|
+
|
|
102
|
+
@ensure_integration_enabled(RqIntegration, old_enqueue_job)
|
|
103
|
+
def sentry_patched_enqueue_job(self, job, **kwargs):
|
|
104
|
+
# type: (Queue, Any, **Any) -> Any
|
|
105
|
+
scope = sentry_sdk.get_current_scope()
|
|
106
|
+
if scope.span is not None:
|
|
107
|
+
job.meta["_sentry_trace_headers"] = dict(
|
|
108
|
+
scope.iter_trace_propagation_headers()
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return old_enqueue_job(self, job, **kwargs)
|
|
112
|
+
|
|
113
|
+
Queue.enqueue_job = sentry_patched_enqueue_job
|
|
114
|
+
|
|
115
|
+
ignore_logger("rq.worker")
|
|
116
|
+
|
|
62
117
|
|
|
63
118
|
def _make_event_processor(weak_job):
|
|
64
|
-
# type: (Callable[[], Job]) ->
|
|
119
|
+
# type: (Callable[[], Job]) -> EventProcessor
|
|
65
120
|
def event_processor(event, hint):
|
|
66
|
-
# type: (
|
|
121
|
+
# type: (Event, dict[str, Any]) -> Event
|
|
67
122
|
job = weak_job()
|
|
68
123
|
if job is not None:
|
|
69
|
-
with capture_internal_exceptions():
|
|
70
|
-
event["transaction"] = job.func_name
|
|
71
|
-
|
|
72
124
|
with capture_internal_exceptions():
|
|
73
125
|
extra = event.setdefault("extra", {})
|
|
74
|
-
|
|
126
|
+
rq_job = {
|
|
75
127
|
"job_id": job.id,
|
|
76
128
|
"func": job.func_name,
|
|
77
129
|
"args": job.args,
|
|
@@ -79,6 +131,13 @@ def _make_event_processor(weak_job):
|
|
|
79
131
|
"description": job.description,
|
|
80
132
|
}
|
|
81
133
|
|
|
134
|
+
if job.enqueued_at:
|
|
135
|
+
rq_job["enqueued_at"] = format_timestamp(job.enqueued_at)
|
|
136
|
+
if job.started_at:
|
|
137
|
+
rq_job["started_at"] = format_timestamp(job.started_at)
|
|
138
|
+
|
|
139
|
+
extra["rq-job"] = rq_job
|
|
140
|
+
|
|
82
141
|
if "exc_info" in hint:
|
|
83
142
|
with capture_internal_exceptions():
|
|
84
143
|
if issubclass(hint["exc_info"][0], JobTimeoutException):
|
|
@@ -91,13 +150,12 @@ def _make_event_processor(weak_job):
|
|
|
91
150
|
|
|
92
151
|
def _capture_exception(exc_info, **kwargs):
|
|
93
152
|
# type: (ExcInfo, **Any) -> None
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return
|
|
153
|
+
client = sentry_sdk.get_client()
|
|
154
|
+
|
|
97
155
|
event, hint = event_from_exception(
|
|
98
156
|
exc_info,
|
|
99
|
-
client_options=
|
|
157
|
+
client_options=client.options,
|
|
100
158
|
mechanism={"type": "rq", "handled": False},
|
|
101
159
|
)
|
|
102
160
|
|
|
103
|
-
|
|
161
|
+
sentry_sdk.capture_event(event, hint=hint)
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This integration ingests tracing data from native extensions written in Rust.
|
|
3
|
+
|
|
4
|
+
Using it requires additional setup on the Rust side to accept a
|
|
5
|
+
`RustTracingLayer` Python object and register it with the `tracing-subscriber`
|
|
6
|
+
using an adapter from the `pyo3-python-tracing-subscriber` crate. For example:
|
|
7
|
+
```rust
|
|
8
|
+
#[pyfunction]
|
|
9
|
+
pub fn initialize_tracing(py_impl: Bound<'_, PyAny>) {
|
|
10
|
+
tracing_subscriber::registry()
|
|
11
|
+
.with(pyo3_python_tracing_subscriber::PythonCallbackLayerBridge::new(py_impl))
|
|
12
|
+
.init();
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Usage in Python would then look like:
|
|
17
|
+
```
|
|
18
|
+
sentry_sdk.init(
|
|
19
|
+
dsn=sentry_dsn,
|
|
20
|
+
integrations=[
|
|
21
|
+
RustTracingIntegration(
|
|
22
|
+
"demo_rust_extension",
|
|
23
|
+
demo_rust_extension.initialize_tracing,
|
|
24
|
+
event_type_mapping=event_type_mapping,
|
|
25
|
+
)
|
|
26
|
+
],
|
|
27
|
+
)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Each native extension requires its own integration.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import json
|
|
34
|
+
from enum import Enum, auto
|
|
35
|
+
from typing import Any, Callable, Dict, Tuple, Optional
|
|
36
|
+
|
|
37
|
+
import sentry_sdk
|
|
38
|
+
from sentry_sdk.integrations import Integration
|
|
39
|
+
from sentry_sdk.scope import should_send_default_pii
|
|
40
|
+
from sentry_sdk.tracing import Span as SentrySpan
|
|
41
|
+
from sentry_sdk.utils import SENSITIVE_DATA_SUBSTITUTE
|
|
42
|
+
|
|
43
|
+
TraceState = Optional[Tuple[Optional[SentrySpan], SentrySpan]]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RustTracingLevel(Enum):
|
|
47
|
+
Trace = "TRACE"
|
|
48
|
+
Debug = "DEBUG"
|
|
49
|
+
Info = "INFO"
|
|
50
|
+
Warn = "WARN"
|
|
51
|
+
Error = "ERROR"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class EventTypeMapping(Enum):
|
|
55
|
+
Ignore = auto()
|
|
56
|
+
Exc = auto()
|
|
57
|
+
Breadcrumb = auto()
|
|
58
|
+
Event = auto()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def tracing_level_to_sentry_level(level):
|
|
62
|
+
# type: (str) -> sentry_sdk._types.LogLevelStr
|
|
63
|
+
level = RustTracingLevel(level)
|
|
64
|
+
if level in (RustTracingLevel.Trace, RustTracingLevel.Debug):
|
|
65
|
+
return "debug"
|
|
66
|
+
elif level == RustTracingLevel.Info:
|
|
67
|
+
return "info"
|
|
68
|
+
elif level == RustTracingLevel.Warn:
|
|
69
|
+
return "warning"
|
|
70
|
+
elif level == RustTracingLevel.Error:
|
|
71
|
+
return "error"
|
|
72
|
+
else:
|
|
73
|
+
# Better this than crashing
|
|
74
|
+
return "info"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def extract_contexts(event: Dict[str, Any]) -> Dict[str, Any]:
|
|
78
|
+
metadata = event.get("metadata", {})
|
|
79
|
+
contexts = {}
|
|
80
|
+
|
|
81
|
+
location = {}
|
|
82
|
+
for field in ["module_path", "file", "line"]:
|
|
83
|
+
if field in metadata:
|
|
84
|
+
location[field] = metadata[field]
|
|
85
|
+
if len(location) > 0:
|
|
86
|
+
contexts["rust_tracing_location"] = location
|
|
87
|
+
|
|
88
|
+
fields = {}
|
|
89
|
+
for field in metadata.get("fields", []):
|
|
90
|
+
fields[field] = event.get(field)
|
|
91
|
+
if len(fields) > 0:
|
|
92
|
+
contexts["rust_tracing_fields"] = fields
|
|
93
|
+
|
|
94
|
+
return contexts
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def process_event(event: Dict[str, Any]) -> None:
|
|
98
|
+
metadata = event.get("metadata", {})
|
|
99
|
+
|
|
100
|
+
logger = metadata.get("target")
|
|
101
|
+
level = tracing_level_to_sentry_level(metadata.get("level"))
|
|
102
|
+
message = event.get("message") # type: sentry_sdk._types.Any
|
|
103
|
+
contexts = extract_contexts(event)
|
|
104
|
+
|
|
105
|
+
sentry_event = {
|
|
106
|
+
"logger": logger,
|
|
107
|
+
"level": level,
|
|
108
|
+
"message": message,
|
|
109
|
+
"contexts": contexts,
|
|
110
|
+
} # type: sentry_sdk._types.Event
|
|
111
|
+
|
|
112
|
+
sentry_sdk.capture_event(sentry_event)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def process_exception(event: Dict[str, Any]) -> None:
|
|
116
|
+
process_event(event)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def process_breadcrumb(event: Dict[str, Any]) -> None:
|
|
120
|
+
level = tracing_level_to_sentry_level(event.get("metadata", {}).get("level"))
|
|
121
|
+
message = event.get("message")
|
|
122
|
+
|
|
123
|
+
sentry_sdk.add_breadcrumb(level=level, message=message)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def default_span_filter(metadata: Dict[str, Any]) -> bool:
|
|
127
|
+
return RustTracingLevel(metadata.get("level")) in (
|
|
128
|
+
RustTracingLevel.Error,
|
|
129
|
+
RustTracingLevel.Warn,
|
|
130
|
+
RustTracingLevel.Info,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def default_event_type_mapping(metadata: Dict[str, Any]) -> EventTypeMapping:
|
|
135
|
+
level = RustTracingLevel(metadata.get("level"))
|
|
136
|
+
if level == RustTracingLevel.Error:
|
|
137
|
+
return EventTypeMapping.Exc
|
|
138
|
+
elif level in (RustTracingLevel.Warn, RustTracingLevel.Info):
|
|
139
|
+
return EventTypeMapping.Breadcrumb
|
|
140
|
+
elif level in (RustTracingLevel.Debug, RustTracingLevel.Trace):
|
|
141
|
+
return EventTypeMapping.Ignore
|
|
142
|
+
else:
|
|
143
|
+
return EventTypeMapping.Ignore
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class RustTracingLayer:
|
|
147
|
+
def __init__(
|
|
148
|
+
self,
|
|
149
|
+
origin: str,
|
|
150
|
+
event_type_mapping: Callable[
|
|
151
|
+
[Dict[str, Any]], EventTypeMapping
|
|
152
|
+
] = default_event_type_mapping,
|
|
153
|
+
span_filter: Callable[[Dict[str, Any]], bool] = default_span_filter,
|
|
154
|
+
include_tracing_fields: Optional[bool] = None,
|
|
155
|
+
):
|
|
156
|
+
self.origin = origin
|
|
157
|
+
self.event_type_mapping = event_type_mapping
|
|
158
|
+
self.span_filter = span_filter
|
|
159
|
+
self.include_tracing_fields = include_tracing_fields
|
|
160
|
+
|
|
161
|
+
def _include_tracing_fields(self) -> bool:
|
|
162
|
+
"""
|
|
163
|
+
By default, the values of tracing fields are not included in case they
|
|
164
|
+
contain PII. A user may override that by passing `True` for the
|
|
165
|
+
`include_tracing_fields` keyword argument of this integration or by
|
|
166
|
+
setting `send_default_pii` to `True` in their Sentry client options.
|
|
167
|
+
"""
|
|
168
|
+
return (
|
|
169
|
+
should_send_default_pii()
|
|
170
|
+
if self.include_tracing_fields is None
|
|
171
|
+
else self.include_tracing_fields
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def on_event(self, event: str, _span_state: TraceState) -> None:
|
|
175
|
+
deserialized_event = json.loads(event)
|
|
176
|
+
metadata = deserialized_event.get("metadata", {})
|
|
177
|
+
|
|
178
|
+
event_type = self.event_type_mapping(metadata)
|
|
179
|
+
if event_type == EventTypeMapping.Ignore:
|
|
180
|
+
return
|
|
181
|
+
elif event_type == EventTypeMapping.Exc:
|
|
182
|
+
process_exception(deserialized_event)
|
|
183
|
+
elif event_type == EventTypeMapping.Breadcrumb:
|
|
184
|
+
process_breadcrumb(deserialized_event)
|
|
185
|
+
elif event_type == EventTypeMapping.Event:
|
|
186
|
+
process_event(deserialized_event)
|
|
187
|
+
|
|
188
|
+
def on_new_span(self, attrs: str, span_id: str) -> TraceState:
|
|
189
|
+
attrs = json.loads(attrs)
|
|
190
|
+
metadata = attrs.get("metadata", {})
|
|
191
|
+
|
|
192
|
+
if not self.span_filter(metadata):
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
module_path = metadata.get("module_path")
|
|
196
|
+
name = metadata.get("name")
|
|
197
|
+
message = attrs.get("message")
|
|
198
|
+
|
|
199
|
+
if message is not None:
|
|
200
|
+
sentry_span_name = message
|
|
201
|
+
elif module_path is not None and name is not None:
|
|
202
|
+
sentry_span_name = f"{module_path}::{name}" # noqa: E231
|
|
203
|
+
elif name is not None:
|
|
204
|
+
sentry_span_name = name
|
|
205
|
+
else:
|
|
206
|
+
sentry_span_name = "<unknown>"
|
|
207
|
+
|
|
208
|
+
kwargs = {
|
|
209
|
+
"op": "function",
|
|
210
|
+
"name": sentry_span_name,
|
|
211
|
+
"origin": self.origin,
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
scope = sentry_sdk.get_current_scope()
|
|
215
|
+
parent_sentry_span = scope.span
|
|
216
|
+
if parent_sentry_span:
|
|
217
|
+
sentry_span = parent_sentry_span.start_child(**kwargs)
|
|
218
|
+
else:
|
|
219
|
+
sentry_span = scope.start_span(**kwargs)
|
|
220
|
+
|
|
221
|
+
fields = metadata.get("fields", [])
|
|
222
|
+
for field in fields:
|
|
223
|
+
if self._include_tracing_fields():
|
|
224
|
+
sentry_span.set_data(field, attrs.get(field))
|
|
225
|
+
else:
|
|
226
|
+
sentry_span.set_data(field, SENSITIVE_DATA_SUBSTITUTE)
|
|
227
|
+
|
|
228
|
+
scope.span = sentry_span
|
|
229
|
+
return (parent_sentry_span, sentry_span)
|
|
230
|
+
|
|
231
|
+
def on_close(self, span_id: str, span_state: TraceState) -> None:
|
|
232
|
+
if span_state is None:
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
parent_sentry_span, sentry_span = span_state
|
|
236
|
+
sentry_span.finish()
|
|
237
|
+
sentry_sdk.get_current_scope().span = parent_sentry_span
|
|
238
|
+
|
|
239
|
+
def on_record(self, span_id: str, values: str, span_state: TraceState) -> None:
|
|
240
|
+
if span_state is None:
|
|
241
|
+
return
|
|
242
|
+
_parent_sentry_span, sentry_span = span_state
|
|
243
|
+
|
|
244
|
+
deserialized_values = json.loads(values)
|
|
245
|
+
for key, value in deserialized_values.items():
|
|
246
|
+
if self._include_tracing_fields():
|
|
247
|
+
sentry_span.set_data(key, value)
|
|
248
|
+
else:
|
|
249
|
+
sentry_span.set_data(key, SENSITIVE_DATA_SUBSTITUTE)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class RustTracingIntegration(Integration):
|
|
253
|
+
"""
|
|
254
|
+
Ingests tracing data from a Rust native extension's `tracing` instrumentation.
|
|
255
|
+
|
|
256
|
+
If a project uses more than one Rust native extension, each one will need
|
|
257
|
+
its own instance of `RustTracingIntegration` with an initializer function
|
|
258
|
+
specific to that extension.
|
|
259
|
+
|
|
260
|
+
Since all of the setup for this integration requires instance-specific state
|
|
261
|
+
which is not available in `setup_once()`, setup instead happens in `__init__()`.
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
def __init__(
|
|
265
|
+
self,
|
|
266
|
+
identifier: str,
|
|
267
|
+
initializer: Callable[[RustTracingLayer], None],
|
|
268
|
+
event_type_mapping: Callable[
|
|
269
|
+
[Dict[str, Any]], EventTypeMapping
|
|
270
|
+
] = default_event_type_mapping,
|
|
271
|
+
span_filter: Callable[[Dict[str, Any]], bool] = default_span_filter,
|
|
272
|
+
include_tracing_fields: Optional[bool] = None,
|
|
273
|
+
):
|
|
274
|
+
self.identifier = identifier
|
|
275
|
+
origin = f"auto.function.rust_tracing.{identifier}"
|
|
276
|
+
self.tracing_layer = RustTracingLayer(
|
|
277
|
+
origin, event_type_mapping, span_filter, include_tracing_fields
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
initializer(self.tracing_layer)
|
|
281
|
+
|
|
282
|
+
@staticmethod
|
|
283
|
+
def setup_once() -> None:
|
|
284
|
+
pass
|