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
@@ -0,0 +1,280 @@
1
+ import json
2
+ import urllib3
3
+
4
+ from sentry_sdk.integrations import Integration
5
+ from sentry_sdk.api import set_context
6
+ from sentry_sdk.utils import logger
7
+
8
+ from typing import TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from typing import Dict
12
+
13
+
14
+ CONTEXT_TYPE = "cloud_resource"
15
+
16
+ HTTP_TIMEOUT = 2.0
17
+
18
+ AWS_METADATA_HOST = "169.254.169.254"
19
+ AWS_TOKEN_URL = "http://{}/latest/api/token".format(AWS_METADATA_HOST)
20
+ AWS_METADATA_URL = "http://{}/latest/dynamic/instance-identity/document".format(
21
+ AWS_METADATA_HOST
22
+ )
23
+
24
+ GCP_METADATA_HOST = "metadata.google.internal"
25
+ GCP_METADATA_URL = "http://{}/computeMetadata/v1/?recursive=true".format(
26
+ GCP_METADATA_HOST
27
+ )
28
+
29
+
30
+ class CLOUD_PROVIDER: # noqa: N801
31
+ """
32
+ Name of the cloud provider.
33
+ see https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/cloud/
34
+ """
35
+
36
+ ALIBABA = "alibaba_cloud"
37
+ AWS = "aws"
38
+ AZURE = "azure"
39
+ GCP = "gcp"
40
+ IBM = "ibm_cloud"
41
+ TENCENT = "tencent_cloud"
42
+
43
+
44
+ class CLOUD_PLATFORM: # noqa: N801
45
+ """
46
+ The cloud platform.
47
+ see https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/cloud/
48
+ """
49
+
50
+ AWS_EC2 = "aws_ec2"
51
+ GCP_COMPUTE_ENGINE = "gcp_compute_engine"
52
+
53
+
54
+ class CloudResourceContextIntegration(Integration):
55
+ """
56
+ Adds cloud resource context to the Senty scope
57
+ """
58
+
59
+ identifier = "cloudresourcecontext"
60
+
61
+ cloud_provider = ""
62
+
63
+ aws_token = ""
64
+ http = urllib3.PoolManager(timeout=HTTP_TIMEOUT)
65
+
66
+ gcp_metadata = None
67
+
68
+ def __init__(self, cloud_provider=""):
69
+ # type: (str) -> None
70
+ CloudResourceContextIntegration.cloud_provider = cloud_provider
71
+
72
+ @classmethod
73
+ def _is_aws(cls):
74
+ # type: () -> bool
75
+ try:
76
+ r = cls.http.request(
77
+ "PUT",
78
+ AWS_TOKEN_URL,
79
+ headers={"X-aws-ec2-metadata-token-ttl-seconds": "60"},
80
+ )
81
+
82
+ if r.status != 200:
83
+ return False
84
+
85
+ cls.aws_token = r.data.decode()
86
+ return True
87
+
88
+ except urllib3.exceptions.TimeoutError:
89
+ logger.debug(
90
+ "AWS metadata service timed out after %s seconds", HTTP_TIMEOUT
91
+ )
92
+ return False
93
+ except Exception as e:
94
+ logger.debug("Error checking AWS metadata service: %s", str(e))
95
+ return False
96
+
97
+ @classmethod
98
+ def _get_aws_context(cls):
99
+ # type: () -> Dict[str, str]
100
+ ctx = {
101
+ "cloud.provider": CLOUD_PROVIDER.AWS,
102
+ "cloud.platform": CLOUD_PLATFORM.AWS_EC2,
103
+ }
104
+
105
+ try:
106
+ r = cls.http.request(
107
+ "GET",
108
+ AWS_METADATA_URL,
109
+ headers={"X-aws-ec2-metadata-token": cls.aws_token},
110
+ )
111
+
112
+ if r.status != 200:
113
+ return ctx
114
+
115
+ data = json.loads(r.data.decode("utf-8"))
116
+
117
+ try:
118
+ ctx["cloud.account.id"] = data["accountId"]
119
+ except Exception:
120
+ pass
121
+
122
+ try:
123
+ ctx["cloud.availability_zone"] = data["availabilityZone"]
124
+ except Exception:
125
+ pass
126
+
127
+ try:
128
+ ctx["cloud.region"] = data["region"]
129
+ except Exception:
130
+ pass
131
+
132
+ try:
133
+ ctx["host.id"] = data["instanceId"]
134
+ except Exception:
135
+ pass
136
+
137
+ try:
138
+ ctx["host.type"] = data["instanceType"]
139
+ except Exception:
140
+ pass
141
+
142
+ except urllib3.exceptions.TimeoutError:
143
+ logger.debug(
144
+ "AWS metadata service timed out after %s seconds", HTTP_TIMEOUT
145
+ )
146
+ except Exception as e:
147
+ logger.debug("Error fetching AWS metadata: %s", str(e))
148
+
149
+ return ctx
150
+
151
+ @classmethod
152
+ def _is_gcp(cls):
153
+ # type: () -> bool
154
+ try:
155
+ r = cls.http.request(
156
+ "GET",
157
+ GCP_METADATA_URL,
158
+ headers={"Metadata-Flavor": "Google"},
159
+ )
160
+
161
+ if r.status != 200:
162
+ return False
163
+
164
+ cls.gcp_metadata = json.loads(r.data.decode("utf-8"))
165
+ return True
166
+
167
+ except urllib3.exceptions.TimeoutError:
168
+ logger.debug(
169
+ "GCP metadata service timed out after %s seconds", HTTP_TIMEOUT
170
+ )
171
+ return False
172
+ except Exception as e:
173
+ logger.debug("Error checking GCP metadata service: %s", str(e))
174
+ return False
175
+
176
+ @classmethod
177
+ def _get_gcp_context(cls):
178
+ # type: () -> Dict[str, str]
179
+ ctx = {
180
+ "cloud.provider": CLOUD_PROVIDER.GCP,
181
+ "cloud.platform": CLOUD_PLATFORM.GCP_COMPUTE_ENGINE,
182
+ }
183
+
184
+ try:
185
+ if cls.gcp_metadata is None:
186
+ r = cls.http.request(
187
+ "GET",
188
+ GCP_METADATA_URL,
189
+ headers={"Metadata-Flavor": "Google"},
190
+ )
191
+
192
+ if r.status != 200:
193
+ return ctx
194
+
195
+ cls.gcp_metadata = json.loads(r.data.decode("utf-8"))
196
+
197
+ try:
198
+ ctx["cloud.account.id"] = cls.gcp_metadata["project"]["projectId"]
199
+ except Exception:
200
+ pass
201
+
202
+ try:
203
+ ctx["cloud.availability_zone"] = cls.gcp_metadata["instance"][
204
+ "zone"
205
+ ].split("/")[-1]
206
+ except Exception:
207
+ pass
208
+
209
+ try:
210
+ # only populated in google cloud run
211
+ ctx["cloud.region"] = cls.gcp_metadata["instance"]["region"].split("/")[
212
+ -1
213
+ ]
214
+ except Exception:
215
+ pass
216
+
217
+ try:
218
+ ctx["host.id"] = cls.gcp_metadata["instance"]["id"]
219
+ except Exception:
220
+ pass
221
+
222
+ except urllib3.exceptions.TimeoutError:
223
+ logger.debug(
224
+ "GCP metadata service timed out after %s seconds", HTTP_TIMEOUT
225
+ )
226
+ except Exception as e:
227
+ logger.debug("Error fetching GCP metadata: %s", str(e))
228
+
229
+ return ctx
230
+
231
+ @classmethod
232
+ def _get_cloud_provider(cls):
233
+ # type: () -> str
234
+ if cls._is_aws():
235
+ return CLOUD_PROVIDER.AWS
236
+
237
+ if cls._is_gcp():
238
+ return CLOUD_PROVIDER.GCP
239
+
240
+ return ""
241
+
242
+ @classmethod
243
+ def _get_cloud_resource_context(cls):
244
+ # type: () -> Dict[str, str]
245
+ cloud_provider = (
246
+ cls.cloud_provider
247
+ if cls.cloud_provider != ""
248
+ else CloudResourceContextIntegration._get_cloud_provider()
249
+ )
250
+ if cloud_provider in context_getters.keys():
251
+ return context_getters[cloud_provider]()
252
+
253
+ return {}
254
+
255
+ @staticmethod
256
+ def setup_once():
257
+ # type: () -> None
258
+ cloud_provider = CloudResourceContextIntegration.cloud_provider
259
+ unsupported_cloud_provider = (
260
+ cloud_provider != "" and cloud_provider not in context_getters.keys()
261
+ )
262
+
263
+ if unsupported_cloud_provider:
264
+ logger.warning(
265
+ "Invalid value for cloud_provider: %s (must be in %s). Falling back to autodetection...",
266
+ CloudResourceContextIntegration.cloud_provider,
267
+ list(context_getters.keys()),
268
+ )
269
+
270
+ context = CloudResourceContextIntegration._get_cloud_resource_context()
271
+ if context != {}:
272
+ set_context(CONTEXT_TYPE, context)
273
+
274
+
275
+ # Map with the currently supported cloud providers
276
+ # mapping to functions extracting the context
277
+ context_getters = {
278
+ CLOUD_PROVIDER.AWS: CloudResourceContextIntegration._get_aws_context,
279
+ CLOUD_PROVIDER.GCP: CloudResourceContextIntegration._get_gcp_context,
280
+ }
@@ -0,0 +1,274 @@
1
+ from functools import wraps
2
+
3
+ from sentry_sdk import consts
4
+ from sentry_sdk.ai.monitoring import record_token_usage
5
+ from sentry_sdk.consts import SPANDATA
6
+ from sentry_sdk.ai.utils import set_data_normalized
7
+
8
+ from typing import TYPE_CHECKING
9
+
10
+ from sentry_sdk.tracing_utils import set_span_errored
11
+
12
+ if TYPE_CHECKING:
13
+ from typing import Any, Callable, Iterator
14
+ from sentry_sdk.tracing import Span
15
+
16
+ import sentry_sdk
17
+ from sentry_sdk.scope import should_send_default_pii
18
+ from sentry_sdk.integrations import DidNotEnable, Integration
19
+ from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
20
+
21
+ try:
22
+ from cohere.client import Client
23
+ from cohere.base_client import BaseCohere
24
+ from cohere import (
25
+ ChatStreamEndEvent,
26
+ NonStreamedChatResponse,
27
+ )
28
+
29
+ if TYPE_CHECKING:
30
+ from cohere import StreamedChatResponse
31
+ except ImportError:
32
+ raise DidNotEnable("Cohere not installed")
33
+
34
+ try:
35
+ # cohere 5.9.3+
36
+ from cohere import StreamEndStreamedChatResponse
37
+ except ImportError:
38
+ from cohere import StreamedChatResponse_StreamEnd as StreamEndStreamedChatResponse
39
+
40
+
41
+ COLLECTED_CHAT_PARAMS = {
42
+ "model": SPANDATA.AI_MODEL_ID,
43
+ "k": SPANDATA.AI_TOP_K,
44
+ "p": SPANDATA.AI_TOP_P,
45
+ "seed": SPANDATA.AI_SEED,
46
+ "frequency_penalty": SPANDATA.AI_FREQUENCY_PENALTY,
47
+ "presence_penalty": SPANDATA.AI_PRESENCE_PENALTY,
48
+ "raw_prompting": SPANDATA.AI_RAW_PROMPTING,
49
+ }
50
+
51
+ COLLECTED_PII_CHAT_PARAMS = {
52
+ "tools": SPANDATA.AI_TOOLS,
53
+ "preamble": SPANDATA.AI_PREAMBLE,
54
+ }
55
+
56
+ COLLECTED_CHAT_RESP_ATTRS = {
57
+ "generation_id": SPANDATA.AI_GENERATION_ID,
58
+ "is_search_required": SPANDATA.AI_SEARCH_REQUIRED,
59
+ "finish_reason": SPANDATA.AI_FINISH_REASON,
60
+ }
61
+
62
+ COLLECTED_PII_CHAT_RESP_ATTRS = {
63
+ "citations": SPANDATA.AI_CITATIONS,
64
+ "documents": SPANDATA.AI_DOCUMENTS,
65
+ "search_queries": SPANDATA.AI_SEARCH_QUERIES,
66
+ "search_results": SPANDATA.AI_SEARCH_RESULTS,
67
+ "tool_calls": SPANDATA.AI_TOOL_CALLS,
68
+ }
69
+
70
+
71
+ class CohereIntegration(Integration):
72
+ identifier = "cohere"
73
+ origin = f"auto.ai.{identifier}"
74
+
75
+ def __init__(self, include_prompts=True):
76
+ # type: (CohereIntegration, bool) -> None
77
+ self.include_prompts = include_prompts
78
+
79
+ @staticmethod
80
+ def setup_once():
81
+ # type: () -> None
82
+ BaseCohere.chat = _wrap_chat(BaseCohere.chat, streaming=False)
83
+ Client.embed = _wrap_embed(Client.embed)
84
+ BaseCohere.chat_stream = _wrap_chat(BaseCohere.chat_stream, streaming=True)
85
+
86
+
87
+ def _capture_exception(exc):
88
+ # type: (Any) -> None
89
+ set_span_errored()
90
+
91
+ event, hint = event_from_exception(
92
+ exc,
93
+ client_options=sentry_sdk.get_client().options,
94
+ mechanism={"type": "cohere", "handled": False},
95
+ )
96
+ sentry_sdk.capture_event(event, hint=hint)
97
+
98
+
99
+ def _wrap_chat(f, streaming):
100
+ # type: (Callable[..., Any], bool) -> Callable[..., Any]
101
+
102
+ def collect_chat_response_fields(span, res, include_pii):
103
+ # type: (Span, NonStreamedChatResponse, bool) -> None
104
+ if include_pii:
105
+ if hasattr(res, "text"):
106
+ set_data_normalized(
107
+ span,
108
+ SPANDATA.AI_RESPONSES,
109
+ [res.text],
110
+ )
111
+ for pii_attr in COLLECTED_PII_CHAT_RESP_ATTRS:
112
+ if hasattr(res, pii_attr):
113
+ set_data_normalized(span, "ai." + pii_attr, getattr(res, pii_attr))
114
+
115
+ for attr in COLLECTED_CHAT_RESP_ATTRS:
116
+ if hasattr(res, attr):
117
+ set_data_normalized(span, "ai." + attr, getattr(res, attr))
118
+
119
+ if hasattr(res, "meta"):
120
+ if hasattr(res.meta, "billed_units"):
121
+ record_token_usage(
122
+ span,
123
+ input_tokens=res.meta.billed_units.input_tokens,
124
+ output_tokens=res.meta.billed_units.output_tokens,
125
+ )
126
+ elif hasattr(res.meta, "tokens"):
127
+ record_token_usage(
128
+ span,
129
+ input_tokens=res.meta.tokens.input_tokens,
130
+ output_tokens=res.meta.tokens.output_tokens,
131
+ )
132
+
133
+ if hasattr(res.meta, "warnings"):
134
+ set_data_normalized(span, SPANDATA.AI_WARNINGS, res.meta.warnings)
135
+
136
+ @wraps(f)
137
+ def new_chat(*args, **kwargs):
138
+ # type: (*Any, **Any) -> Any
139
+ integration = sentry_sdk.get_client().get_integration(CohereIntegration)
140
+
141
+ if (
142
+ integration is None
143
+ or "message" not in kwargs
144
+ or not isinstance(kwargs.get("message"), str)
145
+ ):
146
+ return f(*args, **kwargs)
147
+
148
+ message = kwargs.get("message")
149
+
150
+ span = sentry_sdk.start_span(
151
+ op=consts.OP.COHERE_CHAT_COMPLETIONS_CREATE,
152
+ name="cohere.client.Chat",
153
+ origin=CohereIntegration.origin,
154
+ )
155
+ span.__enter__()
156
+ try:
157
+ res = f(*args, **kwargs)
158
+ except Exception as e:
159
+ _capture_exception(e)
160
+ span.__exit__(None, None, None)
161
+ raise e from None
162
+
163
+ with capture_internal_exceptions():
164
+ if should_send_default_pii() and integration.include_prompts:
165
+ set_data_normalized(
166
+ span,
167
+ SPANDATA.AI_INPUT_MESSAGES,
168
+ list(
169
+ map(
170
+ lambda x: {
171
+ "role": getattr(x, "role", "").lower(),
172
+ "content": getattr(x, "message", ""),
173
+ },
174
+ kwargs.get("chat_history", []),
175
+ )
176
+ )
177
+ + [{"role": "user", "content": message}],
178
+ )
179
+ for k, v in COLLECTED_PII_CHAT_PARAMS.items():
180
+ if k in kwargs:
181
+ set_data_normalized(span, v, kwargs[k])
182
+
183
+ for k, v in COLLECTED_CHAT_PARAMS.items():
184
+ if k in kwargs:
185
+ set_data_normalized(span, v, kwargs[k])
186
+ set_data_normalized(span, SPANDATA.AI_STREAMING, False)
187
+
188
+ if streaming:
189
+ old_iterator = res
190
+
191
+ def new_iterator():
192
+ # type: () -> Iterator[StreamedChatResponse]
193
+
194
+ with capture_internal_exceptions():
195
+ for x in old_iterator:
196
+ if isinstance(x, ChatStreamEndEvent) or isinstance(
197
+ x, StreamEndStreamedChatResponse
198
+ ):
199
+ collect_chat_response_fields(
200
+ span,
201
+ x.response,
202
+ include_pii=should_send_default_pii()
203
+ and integration.include_prompts,
204
+ )
205
+ yield x
206
+
207
+ span.__exit__(None, None, None)
208
+
209
+ return new_iterator()
210
+ elif isinstance(res, NonStreamedChatResponse):
211
+ collect_chat_response_fields(
212
+ span,
213
+ res,
214
+ include_pii=should_send_default_pii()
215
+ and integration.include_prompts,
216
+ )
217
+ span.__exit__(None, None, None)
218
+ else:
219
+ set_data_normalized(span, "unknown_response", True)
220
+ span.__exit__(None, None, None)
221
+ return res
222
+
223
+ return new_chat
224
+
225
+
226
+ def _wrap_embed(f):
227
+ # type: (Callable[..., Any]) -> Callable[..., Any]
228
+
229
+ @wraps(f)
230
+ def new_embed(*args, **kwargs):
231
+ # type: (*Any, **Any) -> Any
232
+ integration = sentry_sdk.get_client().get_integration(CohereIntegration)
233
+ if integration is None:
234
+ return f(*args, **kwargs)
235
+
236
+ with sentry_sdk.start_span(
237
+ op=consts.OP.COHERE_EMBEDDINGS_CREATE,
238
+ name="Cohere Embedding Creation",
239
+ origin=CohereIntegration.origin,
240
+ ) as span:
241
+ if "texts" in kwargs and (
242
+ should_send_default_pii() and integration.include_prompts
243
+ ):
244
+ if isinstance(kwargs["texts"], str):
245
+ set_data_normalized(span, SPANDATA.AI_TEXTS, [kwargs["texts"]])
246
+ elif (
247
+ isinstance(kwargs["texts"], list)
248
+ and len(kwargs["texts"]) > 0
249
+ and isinstance(kwargs["texts"][0], str)
250
+ ):
251
+ set_data_normalized(
252
+ span, SPANDATA.AI_INPUT_MESSAGES, kwargs["texts"]
253
+ )
254
+
255
+ if "model" in kwargs:
256
+ set_data_normalized(span, SPANDATA.AI_MODEL_ID, kwargs["model"])
257
+ try:
258
+ res = f(*args, **kwargs)
259
+ except Exception as e:
260
+ _capture_exception(e)
261
+ raise e from None
262
+ if (
263
+ hasattr(res, "meta")
264
+ and hasattr(res.meta, "billed_units")
265
+ and hasattr(res.meta.billed_units, "input_tokens")
266
+ ):
267
+ record_token_usage(
268
+ span,
269
+ input_tokens=res.meta.billed_units.input_tokens,
270
+ total_tokens=res.meta.billed_units.input_tokens,
271
+ )
272
+ return res
273
+
274
+ return new_embed
@@ -1,13 +1,17 @@
1
- from sentry_sdk.hub import Hub
2
- from sentry_sdk.utils import ContextVar
1
+ import weakref
2
+
3
+ import sentry_sdk
4
+ from sentry_sdk.utils import ContextVar, logger
3
5
  from sentry_sdk.integrations import Integration
4
6
  from sentry_sdk.scope import add_global_event_processor
5
7
 
6
- if False:
7
- from typing import Any
8
- from typing import Dict
8
+ from typing import TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
9
11
  from typing import Optional
10
12
 
13
+ from sentry_sdk._types import Event, Hint
14
+
11
15
 
12
16
  class DedupeIntegration(Integration):
13
17
  identifier = "dedupe"
@@ -21,13 +25,43 @@ class DedupeIntegration(Integration):
21
25
  # type: () -> None
22
26
  @add_global_event_processor
23
27
  def processor(event, hint):
24
- # type: (Dict[str, Any], Dict[str, Any]) -> Optional[Dict[str, Any]]
25
- integration = Hub.current.get_integration(DedupeIntegration)
26
- if integration is not None:
27
- exc_info = hint.get("exc_info", None)
28
- if exc_info is not None:
29
- exc = exc_info[1]
30
- if integration._last_seen.get(None) is exc:
31
- return None
32
- integration._last_seen.set(exc)
28
+ # type: (Event, Optional[Hint]) -> Optional[Event]
29
+ if hint is None:
30
+ return event
31
+
32
+ integration = sentry_sdk.get_client().get_integration(DedupeIntegration)
33
+ if integration is None:
34
+ return event
35
+
36
+ exc_info = hint.get("exc_info", None)
37
+ if exc_info is None:
38
+ return event
39
+
40
+ last_seen = integration._last_seen.get(None)
41
+ if last_seen is not None:
42
+ # last_seen is either a weakref or the original instance
43
+ last_seen = (
44
+ last_seen() if isinstance(last_seen, weakref.ref) else last_seen
45
+ )
46
+
47
+ exc = exc_info[1]
48
+ if last_seen is exc:
49
+ logger.info("DedupeIntegration dropped duplicated error event %s", exc)
50
+ return None
51
+
52
+ # we can only weakref non builtin types
53
+ try:
54
+ integration._last_seen.set(weakref.ref(exc))
55
+ except TypeError:
56
+ integration._last_seen.set(exc)
57
+
33
58
  return event
59
+
60
+ @staticmethod
61
+ def reset_last_seen():
62
+ # type: () -> None
63
+ integration = sentry_sdk.get_client().get_integration(DedupeIntegration)
64
+ if integration is None:
65
+ return
66
+
67
+ integration._last_seen.set(None)