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
@@ -1,51 +1,80 @@
1
- from __future__ import absolute_import
2
-
3
1
  import weakref
4
2
 
5
- from sentry_sdk.hub import Hub
6
- from sentry_sdk.integrations import Integration
7
- from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
8
-
9
- from rq.timeouts import JobTimeoutException # type: ignore
10
- from rq.worker import Worker # type: ignore
11
-
12
- if False:
13
- from typing import Any
14
- from typing import Dict
15
- from typing import Callable
16
-
17
- from rq.job import Job # type: ignore
18
- from rq.queue import Queue # type: ignore
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
- hub = Hub.current
35
- integration = hub.get_integration(RqIntegration)
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
- if integration is None:
38
- return old_perform_job(self, job, *args, **kwargs)
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
- with hub.push_scope() as scope:
41
- scope.add_event_processor(_make_event_processor(weakref.ref(job)))
42
- rv = old_perform_job(self, job, *args, **kwargs)
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
- hub.client.flush()
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
- _capture_exception(exc_info)
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]) -> Callable
119
+ # type: (Callable[[], Job]) -> EventProcessor
65
120
  def event_processor(event, hint):
66
- # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
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
- extra["rq-job"] = {
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
- hub = Hub.current
95
- if hub.get_integration(RqIntegration) is None:
96
- return
153
+ client = sentry_sdk.get_client()
154
+
97
155
  event, hint = event_from_exception(
98
156
  exc_info,
99
- client_options=hub.client.options,
157
+ client_options=client.options,
100
158
  mechanism={"type": "rq", "handled": False},
101
159
  )
102
160
 
103
- hub.capture_event(event, hint=hint)
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