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
|
@@ -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
|
-
|
|
2
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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: (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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)
|