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,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,7 @@
1
+ from sentry_sdk.integrations.opentelemetry.span_processor import SentrySpanProcessor
2
+ from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator
3
+
4
+ __all__ = [
5
+ "SentryPropagator",
6
+ "SentrySpanProcessor",
7
+ ]
@@ -0,0 +1,5 @@
1
+ from opentelemetry.context import create_key
2
+
3
+
4
+ SENTRY_TRACE_KEY = create_key("sentry-trace")
5
+ SENTRY_BAGGAGE_KEY = create_key("sentry-baggage")
@@ -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}