sentry-sdk 0.18.0__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 -6
- sentry_sdk/_compat.py +64 -56
- 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 +81 -19
- sentry_sdk/_types.py +311 -11
- 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 +409 -67
- sentry_sdk/attachments.py +75 -0
- sentry_sdk/client.py +849 -103
- sentry_sdk/consts.py +1389 -34
- 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 +12 -15
- sentry_sdk/envelope.py +112 -61
- sentry_sdk/feature_flags.py +71 -0
- sentry_sdk/hub.py +442 -386
- sentry_sdk/integrations/__init__.py +228 -58
- sentry_sdk/integrations/_asgi_common.py +108 -0
- sentry_sdk/integrations/_wsgi_common.py +131 -40
- sentry_sdk/integrations/aiohttp.py +221 -72
- sentry_sdk/integrations/anthropic.py +439 -0
- sentry_sdk/integrations/argv.py +4 -6
- sentry_sdk/integrations/ariadne.py +161 -0
- sentry_sdk/integrations/arq.py +247 -0
- sentry_sdk/integrations/asgi.py +237 -135
- sentry_sdk/integrations/asyncio.py +144 -0
- sentry_sdk/integrations/asyncpg.py +208 -0
- sentry_sdk/integrations/atexit.py +13 -18
- sentry_sdk/integrations/aws_lambda.py +233 -80
- sentry_sdk/integrations/beam.py +27 -35
- sentry_sdk/integrations/boto3.py +137 -0
- sentry_sdk/integrations/bottle.py +91 -69
- 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 +35 -28
- 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 +32 -8
- sentry_sdk/integrations/django/__init__.py +343 -89
- sentry_sdk/integrations/django/asgi.py +201 -22
- sentry_sdk/integrations/django/caching.py +204 -0
- sentry_sdk/integrations/django/middleware.py +80 -32
- sentry_sdk/integrations/django/signals_handlers.py +91 -0
- sentry_sdk/integrations/django/templates.py +69 -2
- sentry_sdk/integrations/django/transactions.py +39 -14
- sentry_sdk/integrations/django/views.py +69 -16
- sentry_sdk/integrations/dramatiq.py +226 -0
- sentry_sdk/integrations/excepthook.py +19 -13
- sentry_sdk/integrations/executing.py +5 -6
- sentry_sdk/integrations/falcon.py +128 -65
- sentry_sdk/integrations/fastapi.py +141 -0
- sentry_sdk/integrations/flask.py +114 -75
- sentry_sdk/integrations/gcp.py +67 -36
- sentry_sdk/integrations/gnu_backtrace.py +14 -22
- 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 +261 -85
- sentry_sdk/integrations/loguru.py +213 -0
- sentry_sdk/integrations/mcp.py +566 -0
- sentry_sdk/integrations/modules.py +6 -33
- 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 +20 -11
- 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 +71 -60
- 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 +62 -52
- sentry_sdk/integrations/rust_tracing.py +284 -0
- sentry_sdk/integrations/sanic.py +248 -114
- sentry_sdk/integrations/serverless.py +13 -22
- sentry_sdk/integrations/socket.py +96 -0
- sentry_sdk/integrations/spark/spark_driver.py +115 -62
- sentry_sdk/integrations/spark/spark_worker.py +42 -50
- sentry_sdk/integrations/sqlalchemy.py +82 -37
- 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 +100 -58
- sentry_sdk/integrations/strawberry.py +394 -0
- sentry_sdk/integrations/sys_exit.py +70 -0
- sentry_sdk/integrations/threading.py +142 -38
- sentry_sdk/integrations/tornado.py +68 -53
- sentry_sdk/integrations/trytond.py +15 -20
- 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 +126 -125
- 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/scope.py +1542 -112
- sentry_sdk/scrubber.py +177 -0
- sentry_sdk/serializer.py +152 -210
- sentry_sdk/session.py +177 -0
- sentry_sdk/sessions.py +202 -179
- sentry_sdk/spotlight.py +242 -0
- sentry_sdk/tracing.py +1202 -294
- sentry_sdk/tracing_utils.py +1236 -0
- sentry_sdk/transport.py +693 -189
- sentry_sdk/types.py +52 -0
- sentry_sdk/utils.py +1395 -228
- sentry_sdk/worker.py +30 -17
- sentry_sdk-2.46.0.dist-info/METADATA +268 -0
- sentry_sdk-2.46.0.dist-info/RECORD +189 -0
- {sentry_sdk-0.18.0.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/_functools.py +0 -66
- sentry_sdk/integrations/celery.py +0 -275
- sentry_sdk/integrations/redis.py +0 -103
- sentry_sdk-0.18.0.dist-info/LICENSE +0 -9
- sentry_sdk-0.18.0.dist-info/METADATA +0 -66
- sentry_sdk-0.18.0.dist-info/RECORD +0 -65
- {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import sentry_sdk
|
|
2
|
+
from sentry_sdk.ai.utils import (
|
|
3
|
+
get_start_span_function,
|
|
4
|
+
set_data_normalized,
|
|
5
|
+
normalize_message_roles,
|
|
6
|
+
)
|
|
7
|
+
from sentry_sdk.consts import OP, SPANDATA
|
|
8
|
+
from sentry_sdk.scope import should_send_default_pii
|
|
9
|
+
from sentry_sdk.utils import safe_serialize
|
|
10
|
+
|
|
11
|
+
from ..consts import SPAN_ORIGIN
|
|
12
|
+
from ..utils import _set_agent_data
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
import agents
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def invoke_agent_span(context, agent, kwargs):
|
|
22
|
+
# type: (agents.RunContextWrapper, agents.Agent, dict[str, Any]) -> sentry_sdk.tracing.Span
|
|
23
|
+
start_span_function = get_start_span_function()
|
|
24
|
+
span = start_span_function(
|
|
25
|
+
op=OP.GEN_AI_INVOKE_AGENT,
|
|
26
|
+
name=f"invoke_agent {agent.name}",
|
|
27
|
+
origin=SPAN_ORIGIN,
|
|
28
|
+
)
|
|
29
|
+
span.__enter__()
|
|
30
|
+
|
|
31
|
+
span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent")
|
|
32
|
+
|
|
33
|
+
if should_send_default_pii():
|
|
34
|
+
messages = []
|
|
35
|
+
if agent.instructions:
|
|
36
|
+
message = (
|
|
37
|
+
agent.instructions
|
|
38
|
+
if isinstance(agent.instructions, str)
|
|
39
|
+
else safe_serialize(agent.instructions)
|
|
40
|
+
)
|
|
41
|
+
messages.append(
|
|
42
|
+
{
|
|
43
|
+
"content": [{"text": message, "type": "text"}],
|
|
44
|
+
"role": "system",
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
original_input = kwargs.get("original_input")
|
|
49
|
+
if original_input is not None:
|
|
50
|
+
message = (
|
|
51
|
+
original_input
|
|
52
|
+
if isinstance(original_input, str)
|
|
53
|
+
else safe_serialize(original_input)
|
|
54
|
+
)
|
|
55
|
+
messages.append(
|
|
56
|
+
{
|
|
57
|
+
"content": [{"text": message, "type": "text"}],
|
|
58
|
+
"role": "user",
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if len(messages) > 0:
|
|
63
|
+
normalized_messages = normalize_message_roles(messages)
|
|
64
|
+
set_data_normalized(
|
|
65
|
+
span,
|
|
66
|
+
SPANDATA.GEN_AI_REQUEST_MESSAGES,
|
|
67
|
+
normalized_messages,
|
|
68
|
+
unpack=False,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
_set_agent_data(span, agent)
|
|
72
|
+
|
|
73
|
+
return span
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def update_invoke_agent_span(context, agent, output):
|
|
77
|
+
# type: (agents.RunContextWrapper, agents.Agent, Any) -> None
|
|
78
|
+
span = sentry_sdk.get_current_span()
|
|
79
|
+
|
|
80
|
+
if span:
|
|
81
|
+
if should_send_default_pii():
|
|
82
|
+
set_data_normalized(
|
|
83
|
+
span, SPANDATA.GEN_AI_RESPONSE_TEXT, output, unpack=False
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
span.__exit__(None, None, None)
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import sentry_sdk
|
|
2
|
+
from sentry_sdk.ai.utils import (
|
|
3
|
+
GEN_AI_ALLOWED_MESSAGE_ROLES,
|
|
4
|
+
normalize_message_roles,
|
|
5
|
+
set_data_normalized,
|
|
6
|
+
normalize_message_role,
|
|
7
|
+
)
|
|
8
|
+
from sentry_sdk.consts import SPANDATA, SPANSTATUS, OP
|
|
9
|
+
from sentry_sdk.integrations import DidNotEnable
|
|
10
|
+
from sentry_sdk.scope import should_send_default_pii
|
|
11
|
+
from sentry_sdk.tracing_utils import set_span_errored
|
|
12
|
+
from sentry_sdk.utils import event_from_exception, safe_serialize
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from typing import Any
|
|
18
|
+
from agents import Usage
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
import agents
|
|
22
|
+
|
|
23
|
+
except ImportError:
|
|
24
|
+
raise DidNotEnable("OpenAI Agents not installed")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _capture_exception(exc):
|
|
28
|
+
# type: (Any) -> None
|
|
29
|
+
set_span_errored()
|
|
30
|
+
|
|
31
|
+
event, hint = event_from_exception(
|
|
32
|
+
exc,
|
|
33
|
+
client_options=sentry_sdk.get_client().options,
|
|
34
|
+
mechanism={"type": "openai_agents", "handled": False},
|
|
35
|
+
)
|
|
36
|
+
sentry_sdk.capture_event(event, hint=hint)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _set_agent_data(span, agent):
|
|
40
|
+
# type: (sentry_sdk.tracing.Span, agents.Agent) -> None
|
|
41
|
+
span.set_data(
|
|
42
|
+
SPANDATA.GEN_AI_SYSTEM, "openai"
|
|
43
|
+
) # See footnote for https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/#gen-ai-system for explanation why.
|
|
44
|
+
|
|
45
|
+
span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent.name)
|
|
46
|
+
|
|
47
|
+
if agent.model_settings.max_tokens:
|
|
48
|
+
span.set_data(
|
|
49
|
+
SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, agent.model_settings.max_tokens
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if agent.model:
|
|
53
|
+
model_name = agent.model.model if hasattr(agent.model, "model") else agent.model
|
|
54
|
+
span.set_data(SPANDATA.GEN_AI_REQUEST_MODEL, model_name)
|
|
55
|
+
|
|
56
|
+
if agent.model_settings.presence_penalty:
|
|
57
|
+
span.set_data(
|
|
58
|
+
SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY,
|
|
59
|
+
agent.model_settings.presence_penalty,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if agent.model_settings.temperature:
|
|
63
|
+
span.set_data(
|
|
64
|
+
SPANDATA.GEN_AI_REQUEST_TEMPERATURE, agent.model_settings.temperature
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if agent.model_settings.top_p:
|
|
68
|
+
span.set_data(SPANDATA.GEN_AI_REQUEST_TOP_P, agent.model_settings.top_p)
|
|
69
|
+
|
|
70
|
+
if agent.model_settings.frequency_penalty:
|
|
71
|
+
span.set_data(
|
|
72
|
+
SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY,
|
|
73
|
+
agent.model_settings.frequency_penalty,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if len(agent.tools) > 0:
|
|
77
|
+
span.set_data(
|
|
78
|
+
SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS,
|
|
79
|
+
safe_serialize([vars(tool) for tool in agent.tools]),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _set_usage_data(span, usage):
|
|
84
|
+
# type: (sentry_sdk.tracing.Span, Usage) -> None
|
|
85
|
+
span.set_data(SPANDATA.GEN_AI_USAGE_INPUT_TOKENS, usage.input_tokens)
|
|
86
|
+
span.set_data(
|
|
87
|
+
SPANDATA.GEN_AI_USAGE_INPUT_TOKENS_CACHED,
|
|
88
|
+
usage.input_tokens_details.cached_tokens,
|
|
89
|
+
)
|
|
90
|
+
span.set_data(SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS, usage.output_tokens)
|
|
91
|
+
span.set_data(
|
|
92
|
+
SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS_REASONING,
|
|
93
|
+
usage.output_tokens_details.reasoning_tokens,
|
|
94
|
+
)
|
|
95
|
+
span.set_data(SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS, usage.total_tokens)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _set_input_data(span, get_response_kwargs):
|
|
99
|
+
# type: (sentry_sdk.tracing.Span, dict[str, Any]) -> None
|
|
100
|
+
if not should_send_default_pii():
|
|
101
|
+
return
|
|
102
|
+
request_messages = []
|
|
103
|
+
|
|
104
|
+
system_instructions = get_response_kwargs.get("system_instructions")
|
|
105
|
+
if system_instructions:
|
|
106
|
+
request_messages.append(
|
|
107
|
+
{
|
|
108
|
+
"role": GEN_AI_ALLOWED_MESSAGE_ROLES.SYSTEM,
|
|
109
|
+
"content": [{"type": "text", "text": system_instructions}],
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
for message in get_response_kwargs.get("input", []):
|
|
114
|
+
if "role" in message:
|
|
115
|
+
normalized_role = normalize_message_role(message.get("role"))
|
|
116
|
+
request_messages.append(
|
|
117
|
+
{
|
|
118
|
+
"role": normalized_role,
|
|
119
|
+
"content": [{"type": "text", "text": message.get("content")}],
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
if message.get("type") == "function_call":
|
|
124
|
+
request_messages.append(
|
|
125
|
+
{
|
|
126
|
+
"role": GEN_AI_ALLOWED_MESSAGE_ROLES.ASSISTANT,
|
|
127
|
+
"content": [message],
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
elif message.get("type") == "function_call_output":
|
|
131
|
+
request_messages.append(
|
|
132
|
+
{
|
|
133
|
+
"role": GEN_AI_ALLOWED_MESSAGE_ROLES.TOOL,
|
|
134
|
+
"content": [message],
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
set_data_normalized(
|
|
139
|
+
span,
|
|
140
|
+
SPANDATA.GEN_AI_REQUEST_MESSAGES,
|
|
141
|
+
normalize_message_roles(request_messages),
|
|
142
|
+
unpack=False,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _set_output_data(span, result):
|
|
147
|
+
# type: (sentry_sdk.tracing.Span, Any) -> None
|
|
148
|
+
if not should_send_default_pii():
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
output_messages = {
|
|
152
|
+
"response": [],
|
|
153
|
+
"tool": [],
|
|
154
|
+
} # type: (dict[str, list[Any]])
|
|
155
|
+
|
|
156
|
+
for output in result.output:
|
|
157
|
+
if output.type == "function_call":
|
|
158
|
+
output_messages["tool"].append(output.dict())
|
|
159
|
+
elif output.type == "message":
|
|
160
|
+
for output_message in output.content:
|
|
161
|
+
try:
|
|
162
|
+
output_messages["response"].append(output_message.text)
|
|
163
|
+
except AttributeError:
|
|
164
|
+
# Unknown output message type, just return the json
|
|
165
|
+
output_messages["response"].append(output_message.dict())
|
|
166
|
+
|
|
167
|
+
if len(output_messages["tool"]) > 0:
|
|
168
|
+
span.set_data(
|
|
169
|
+
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS, safe_serialize(output_messages["tool"])
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if len(output_messages["response"]) > 0:
|
|
173
|
+
set_data_normalized(
|
|
174
|
+
span, SPANDATA.GEN_AI_RESPONSE_TEXT, output_messages["response"]
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _create_mcp_execute_tool_spans(span, result):
|
|
179
|
+
# type: (sentry_sdk.tracing.Span, agents.Result) -> None
|
|
180
|
+
for output in result.output:
|
|
181
|
+
if output.__class__.__name__ == "McpCall":
|
|
182
|
+
with sentry_sdk.start_span(
|
|
183
|
+
op=OP.GEN_AI_EXECUTE_TOOL,
|
|
184
|
+
description=f"execute_tool {output.name}",
|
|
185
|
+
start_timestamp=span.start_timestamp,
|
|
186
|
+
) as execute_tool_span:
|
|
187
|
+
set_data_normalized(execute_tool_span, SPANDATA.GEN_AI_TOOL_TYPE, "mcp")
|
|
188
|
+
set_data_normalized(
|
|
189
|
+
execute_tool_span, SPANDATA.GEN_AI_TOOL_NAME, output.name
|
|
190
|
+
)
|
|
191
|
+
if should_send_default_pii():
|
|
192
|
+
execute_tool_span.set_data(
|
|
193
|
+
SPANDATA.GEN_AI_TOOL_INPUT, output.arguments
|
|
194
|
+
)
|
|
195
|
+
execute_tool_span.set_data(
|
|
196
|
+
SPANDATA.GEN_AI_TOOL_OUTPUT, output.output
|
|
197
|
+
)
|
|
198
|
+
if output.error:
|
|
199
|
+
execute_tool_span.set_status(SPANSTATUS.ERROR)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Any
|
|
2
|
+
|
|
3
|
+
from sentry_sdk.feature_flags import add_feature_flag
|
|
4
|
+
from sentry_sdk.integrations import DidNotEnable, Integration
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from openfeature import api
|
|
8
|
+
from openfeature.hook import Hook
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from openfeature.hook import HookContext, HookHints
|
|
12
|
+
except ImportError:
|
|
13
|
+
raise DidNotEnable("OpenFeature is not installed")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OpenFeatureIntegration(Integration):
|
|
17
|
+
identifier = "openfeature"
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def setup_once():
|
|
21
|
+
# type: () -> None
|
|
22
|
+
# Register the hook within the global openfeature hooks list.
|
|
23
|
+
api.add_hooks(hooks=[OpenFeatureHook()])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OpenFeatureHook(Hook):
|
|
27
|
+
def after(self, hook_context, details, hints):
|
|
28
|
+
# type: (Any, Any, Any) -> None
|
|
29
|
+
if isinstance(details.value, bool):
|
|
30
|
+
add_feature_flag(details.flag_key, details.value)
|
|
31
|
+
|
|
32
|
+
def error(self, hook_context, exception, hints):
|
|
33
|
+
# type: (HookContext, Exception, HookHints) -> None
|
|
34
|
+
if isinstance(hook_context.default_value, bool):
|
|
35
|
+
add_feature_flag(hook_context.flag_key, hook_context.default_value)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IMPORTANT: The contents of this file are part of a proof of concept and as such
|
|
3
|
+
are experimental and not suitable for production use. They may be changed or
|
|
4
|
+
removed at any time without prior notice.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from sentry_sdk.integrations import DidNotEnable, Integration
|
|
8
|
+
from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator
|
|
9
|
+
from sentry_sdk.integrations.opentelemetry.span_processor import SentrySpanProcessor
|
|
10
|
+
from sentry_sdk.utils import logger
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from opentelemetry import trace
|
|
14
|
+
from opentelemetry.propagate import set_global_textmap
|
|
15
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
16
|
+
except ImportError:
|
|
17
|
+
raise DidNotEnable("opentelemetry not installed")
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from opentelemetry.instrumentation.django import DjangoInstrumentor # type: ignore[import-not-found]
|
|
21
|
+
except ImportError:
|
|
22
|
+
DjangoInstrumentor = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
CONFIGURABLE_INSTRUMENTATIONS = {
|
|
26
|
+
DjangoInstrumentor: {"is_sql_commentor_enabled": True},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class OpenTelemetryIntegration(Integration):
|
|
31
|
+
identifier = "opentelemetry"
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def setup_once():
|
|
35
|
+
# type: () -> None
|
|
36
|
+
logger.warning(
|
|
37
|
+
"[OTel] Initializing highly experimental OpenTelemetry support. "
|
|
38
|
+
"Use at your own risk."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
_setup_sentry_tracing()
|
|
42
|
+
# _setup_instrumentors()
|
|
43
|
+
|
|
44
|
+
logger.debug("[OTel] Finished setting up OpenTelemetry integration")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _setup_sentry_tracing():
|
|
48
|
+
# type: () -> None
|
|
49
|
+
provider = TracerProvider()
|
|
50
|
+
provider.add_span_processor(SentrySpanProcessor())
|
|
51
|
+
trace.set_tracer_provider(provider)
|
|
52
|
+
set_global_textmap(SentryPropagator())
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _setup_instrumentors():
|
|
56
|
+
# type: () -> None
|
|
57
|
+
for instrumentor, kwargs in CONFIGURABLE_INSTRUMENTATIONS.items():
|
|
58
|
+
instrumentor().instrument(**kwargs)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from opentelemetry import trace
|
|
2
|
+
from opentelemetry.context import (
|
|
3
|
+
Context,
|
|
4
|
+
get_current,
|
|
5
|
+
set_value,
|
|
6
|
+
)
|
|
7
|
+
from opentelemetry.propagators.textmap import (
|
|
8
|
+
CarrierT,
|
|
9
|
+
Getter,
|
|
10
|
+
Setter,
|
|
11
|
+
TextMapPropagator,
|
|
12
|
+
default_getter,
|
|
13
|
+
default_setter,
|
|
14
|
+
)
|
|
15
|
+
from opentelemetry.trace import (
|
|
16
|
+
NonRecordingSpan,
|
|
17
|
+
SpanContext,
|
|
18
|
+
TraceFlags,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from sentry_sdk.integrations.opentelemetry.consts import (
|
|
22
|
+
SENTRY_BAGGAGE_KEY,
|
|
23
|
+
SENTRY_TRACE_KEY,
|
|
24
|
+
)
|
|
25
|
+
from sentry_sdk.integrations.opentelemetry.span_processor import (
|
|
26
|
+
SentrySpanProcessor,
|
|
27
|
+
)
|
|
28
|
+
from sentry_sdk.tracing import (
|
|
29
|
+
BAGGAGE_HEADER_NAME,
|
|
30
|
+
SENTRY_TRACE_HEADER_NAME,
|
|
31
|
+
)
|
|
32
|
+
from sentry_sdk.tracing_utils import Baggage, extract_sentrytrace_data
|
|
33
|
+
|
|
34
|
+
from typing import TYPE_CHECKING
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from typing import Optional, Set
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SentryPropagator(TextMapPropagator):
|
|
41
|
+
"""
|
|
42
|
+
Propagates tracing headers for Sentry's tracing system in a way OTel understands.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def extract(self, carrier, context=None, getter=default_getter):
|
|
46
|
+
# type: (CarrierT, Optional[Context], Getter[CarrierT]) -> Context
|
|
47
|
+
if context is None:
|
|
48
|
+
context = get_current()
|
|
49
|
+
|
|
50
|
+
sentry_trace = getter.get(carrier, SENTRY_TRACE_HEADER_NAME)
|
|
51
|
+
if not sentry_trace:
|
|
52
|
+
return context
|
|
53
|
+
|
|
54
|
+
sentrytrace = extract_sentrytrace_data(sentry_trace[0])
|
|
55
|
+
if not sentrytrace:
|
|
56
|
+
return context
|
|
57
|
+
|
|
58
|
+
context = set_value(SENTRY_TRACE_KEY, sentrytrace, context)
|
|
59
|
+
|
|
60
|
+
trace_id, span_id = sentrytrace["trace_id"], sentrytrace["parent_span_id"]
|
|
61
|
+
|
|
62
|
+
span_context = SpanContext(
|
|
63
|
+
trace_id=int(trace_id, 16), # type: ignore
|
|
64
|
+
span_id=int(span_id, 16), # type: ignore
|
|
65
|
+
# we simulate a sampled trace on the otel side and leave the sampling to sentry
|
|
66
|
+
trace_flags=TraceFlags(TraceFlags.SAMPLED),
|
|
67
|
+
is_remote=True,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
baggage_header = getter.get(carrier, BAGGAGE_HEADER_NAME)
|
|
71
|
+
|
|
72
|
+
if baggage_header:
|
|
73
|
+
baggage = Baggage.from_incoming_header(baggage_header[0])
|
|
74
|
+
else:
|
|
75
|
+
# If there's an incoming sentry-trace but no incoming baggage header,
|
|
76
|
+
# for instance in traces coming from older SDKs,
|
|
77
|
+
# baggage will be empty and frozen and won't be populated as head SDK.
|
|
78
|
+
baggage = Baggage(sentry_items={})
|
|
79
|
+
|
|
80
|
+
baggage.freeze()
|
|
81
|
+
context = set_value(SENTRY_BAGGAGE_KEY, baggage, context)
|
|
82
|
+
|
|
83
|
+
span = NonRecordingSpan(span_context)
|
|
84
|
+
modified_context = trace.set_span_in_context(span, context)
|
|
85
|
+
return modified_context
|
|
86
|
+
|
|
87
|
+
def inject(self, carrier, context=None, setter=default_setter):
|
|
88
|
+
# type: (CarrierT, Optional[Context], Setter[CarrierT]) -> None
|
|
89
|
+
if context is None:
|
|
90
|
+
context = get_current()
|
|
91
|
+
|
|
92
|
+
current_span = trace.get_current_span(context)
|
|
93
|
+
current_span_context = current_span.get_span_context()
|
|
94
|
+
|
|
95
|
+
if not current_span_context.is_valid:
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
span_id = trace.format_span_id(current_span_context.span_id)
|
|
99
|
+
|
|
100
|
+
span_map = SentrySpanProcessor().otel_span_map
|
|
101
|
+
sentry_span = span_map.get(span_id, None)
|
|
102
|
+
if not sentry_span:
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
setter.set(carrier, SENTRY_TRACE_HEADER_NAME, sentry_span.to_traceparent())
|
|
106
|
+
|
|
107
|
+
if sentry_span.containing_transaction:
|
|
108
|
+
baggage = sentry_span.containing_transaction.get_baggage()
|
|
109
|
+
if baggage:
|
|
110
|
+
baggage_data = baggage.serialize()
|
|
111
|
+
if baggage_data:
|
|
112
|
+
setter.set(carrier, BAGGAGE_HEADER_NAME, baggage_data)
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def fields(self):
|
|
116
|
+
# type: () -> Set[str]
|
|
117
|
+
return {SENTRY_TRACE_HEADER_NAME, BAGGAGE_HEADER_NAME}
|