We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Link errors to OTel spans
### Problem Statement

If you use the Otel instrumentation caught errors are not associated with performance spans coming from otel

### Solution Brainstorm

Make sure errors are associated with the current otel span. See Java implementation for inspiration.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sentry_sdk/integrations/opentelemetry/span_processor.py`
Content:
```
1 from datetime import datetime
2 
3 from opentelemetry.context import get_value  # type: ignore
4 from opentelemetry.sdk.trace import SpanProcessor  # type: ignore
5 from opentelemetry.semconv.trace import SpanAttributes  # type: ignore
6 from opentelemetry.trace import (  # type: ignore
7     format_span_id,
8     format_trace_id,
9     SpanContext,
10     Span as OTelSpan,
11     SpanKind,
12 )
13 from sentry_sdk.consts import INSTRUMENTER
14 from sentry_sdk.hub import Hub
15 from sentry_sdk.integrations.opentelemetry.consts import (
16     SENTRY_BAGGAGE_KEY,
17     SENTRY_TRACE_KEY,
18 )
19 from sentry_sdk.tracing import Transaction, Span as SentrySpan
20 from sentry_sdk.utils import Dsn
21 from sentry_sdk._types import MYPY
22 
23 from urllib3.util import parse_url as urlparse  # type: ignore
24 
25 if MYPY:
26     from typing import Any
27     from typing import Dict
28     from typing import Union
29 
30 OPEN_TELEMETRY_CONTEXT = "otel"
31 
32 
33 class SentrySpanProcessor(SpanProcessor):  # type: ignore
34     """
35     Converts OTel spans into Sentry spans so they can be sent to the Sentry backend.
36     """
37 
38     # The mapping from otel span ids to sentry spans
39     otel_span_map = {}  # type: Dict[str, Union[Transaction, OTelSpan]]
40 
41     def __new__(cls):
42         # type: () -> SentrySpanProcessor
43         if not hasattr(cls, "instance"):
44             cls.instance = super(SentrySpanProcessor, cls).__new__(cls)
45 
46         return cls.instance
47 
48     def on_start(self, otel_span, parent_context=None):
49         # type: (OTelSpan, SpanContext) -> None
50         hub = Hub.current
51         if not hub:
52             return
53 
54         if hub.client and hub.client.options["instrumenter"] != INSTRUMENTER.OTEL:
55             return
56 
57         if not otel_span.context.is_valid:
58             return
59 
60         if self._is_sentry_span(hub, otel_span):
61             return
62 
63         trace_data = self._get_trace_data(otel_span, parent_context)
64 
65         parent_span_id = trace_data["parent_span_id"]
66         sentry_parent_span = (
67             self.otel_span_map.get(parent_span_id, None) if parent_span_id else None
68         )
69 
70         sentry_span = None
71         if sentry_parent_span:
72             sentry_span = sentry_parent_span.start_child(
73                 span_id=trace_data["span_id"],
74                 description=otel_span.name,
75                 start_timestamp=datetime.fromtimestamp(otel_span.start_time / 1e9),
76                 instrumenter=INSTRUMENTER.OTEL,
77             )
78         else:
79             sentry_span = hub.start_transaction(
80                 name=otel_span.name,
81                 span_id=trace_data["span_id"],
82                 parent_span_id=parent_span_id,
83                 trace_id=trace_data["trace_id"],
84                 baggage=trace_data["baggage"],
85                 start_timestamp=datetime.fromtimestamp(otel_span.start_time / 1e9),
86                 instrumenter=INSTRUMENTER.OTEL,
87             )
88 
89         self.otel_span_map[trace_data["span_id"]] = sentry_span
90 
91     def on_end(self, otel_span):
92         # type: (OTelSpan) -> None
93         hub = Hub.current
94         if not hub:
95             return
96 
97         if hub.client and hub.client.options["instrumenter"] != INSTRUMENTER.OTEL:
98             return
99 
100         if not otel_span.context.is_valid:
101             return
102 
103         span_id = format_span_id(otel_span.context.span_id)
104         sentry_span = self.otel_span_map.pop(span_id, None)
105         if not sentry_span:
106             return
107 
108         sentry_span.op = otel_span.name
109 
110         if isinstance(sentry_span, Transaction):
111             sentry_span.name = otel_span.name
112             sentry_span.set_context(
113                 OPEN_TELEMETRY_CONTEXT, self._get_otel_context(otel_span)
114             )
115 
116         else:
117             self._update_span_with_otel_data(sentry_span, otel_span)
118 
119         sentry_span.finish(
120             end_timestamp=datetime.fromtimestamp(otel_span.end_time / 1e9)
121         )
122 
123     def _is_sentry_span(self, hub, otel_span):
124         # type: (Hub, OTelSpan) -> bool
125         """
126         Break infinite loop:
127         HTTP requests to Sentry are caught by OTel and send again to Sentry.
128         """
129         otel_span_url = otel_span.attributes.get(SpanAttributes.HTTP_URL, None)
130         dsn_url = hub.client and Dsn(hub.client.dsn or "").netloc
131 
132         if otel_span_url and dsn_url in otel_span_url:
133             return True
134 
135         return False
136 
137     def _get_otel_context(self, otel_span):
138         # type: (OTelSpan) -> Dict[str, Any]
139         """
140         Returns the OTel context for Sentry.
141         See: https://develop.sentry.dev/sdk/performance/opentelemetry/#step-5-add-opentelemetry-context
142         """
143         ctx = {}
144 
145         if otel_span.attributes:
146             ctx["attributes"] = dict(otel_span.attributes)
147 
148         if otel_span.resource.attributes:
149             ctx["resource"] = dict(otel_span.resource.attributes)
150 
151         return ctx
152 
153     def _get_trace_data(self, otel_span, parent_context):
154         # type: (OTelSpan, SpanContext) -> Dict[str, Any]
155         """
156         Extracts tracing information from one OTel span and its parent OTel context.
157         """
158         trace_data = {}
159 
160         span_id = format_span_id(otel_span.context.span_id)
161         trace_data["span_id"] = span_id
162 
163         trace_id = format_trace_id(otel_span.context.trace_id)
164         trace_data["trace_id"] = trace_id
165 
166         parent_span_id = (
167             format_span_id(otel_span.parent.span_id) if otel_span.parent else None
168         )
169         trace_data["parent_span_id"] = parent_span_id
170 
171         sentry_trace_data = get_value(SENTRY_TRACE_KEY, parent_context)
172         trace_data["parent_sampled"] = (
173             sentry_trace_data[2] if sentry_trace_data else None
174         )
175 
176         baggage = get_value(SENTRY_BAGGAGE_KEY, parent_context)
177         trace_data["baggage"] = baggage
178 
179         return trace_data
180 
181     def _update_span_with_otel_data(self, sentry_span, otel_span):
182         # type: (SentrySpan, OTelSpan) -> None
183         """
184         Convert OTel span data and update the Sentry span with it.
185         This should eventually happen on the server when ingesting the spans.
186         """
187         for key, val in otel_span.attributes.items():
188             sentry_span.set_data(key, val)
189 
190         sentry_span.set_data("otel.kind", otel_span.kind)
191 
192         op = otel_span.name
193         description = otel_span.name
194 
195         http_method = otel_span.attributes.get(SpanAttributes.HTTP_METHOD, None)
196         db_query = otel_span.attributes.get(SpanAttributes.DB_SYSTEM, None)
197 
198         if http_method:
199             op = "http"
200 
201             if otel_span.kind == SpanKind.SERVER:
202                 op += ".server"
203             elif otel_span.kind == SpanKind.CLIENT:
204                 op += ".client"
205 
206             description = http_method
207 
208             peer_name = otel_span.attributes.get(SpanAttributes.NET_PEER_NAME, None)
209             if peer_name:
210                 description += " {}".format(peer_name)
211 
212             target = otel_span.attributes.get(SpanAttributes.HTTP_TARGET, None)
213             if target:
214                 description += " {}".format(target)
215 
216             if not peer_name and not target:
217                 url = otel_span.attributes.get(SpanAttributes.HTTP_URL, None)
218                 if url:
219                     parsed_url = urlparse(url)
220                     url = f"{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}"
221                     description += " {}".format(url)
222 
223             status_code = otel_span.attributes.get(
224                 SpanAttributes.HTTP_STATUS_CODE, None
225             )
226             if status_code:
227                 sentry_span.set_http_status(status_code)
228 
229         elif db_query:
230             op = "db"
231             statement = otel_span.attributes.get(SpanAttributes.DB_STATEMENT, None)
232             if statement:
233                 description = statement
234 
235         sentry_span.op = op
236         sentry_span.description = description
237
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/sentry_sdk/integrations/opentelemetry/span_processor.py b/sentry_sdk/integrations/opentelemetry/span_processor.py
--- a/sentry_sdk/integrations/opentelemetry/span_processor.py
+++ b/sentry_sdk/integrations/opentelemetry/span_processor.py
@@ -6,16 +6,22 @@
 from opentelemetry.trace import (  # type: ignore
     format_span_id,
     format_trace_id,
+    get_current_span,
     SpanContext,
     Span as OTelSpan,
     SpanKind,
 )
+from opentelemetry.trace.span import (  # type: ignore
+    INVALID_SPAN_ID,
+    INVALID_TRACE_ID,
+)
 from sentry_sdk.consts import INSTRUMENTER
 from sentry_sdk.hub import Hub
 from sentry_sdk.integrations.opentelemetry.consts import (
     SENTRY_BAGGAGE_KEY,
     SENTRY_TRACE_KEY,
 )
+from sentry_sdk.scope import add_global_event_processor
 from sentry_sdk.tracing import Transaction, Span as SentrySpan
 from sentry_sdk.utils import Dsn
 from sentry_sdk._types import MYPY
@@ -26,10 +32,44 @@
     from typing import Any
     from typing import Dict
     from typing import Union
+    from sentry_sdk._types import Event, Hint
 
 OPEN_TELEMETRY_CONTEXT = "otel"
 
 
+def link_trace_context_to_error_event(event, otel_span_map):
+    # type: (Event, Dict[str, Union[Transaction, OTelSpan]]) -> Event
+    hub = Hub.current
+    if not hub:
+        return event
+
+    if hub.client and hub.client.options["instrumenter"] != INSTRUMENTER.OTEL:
+        return event
+
+    if hasattr(event, "type") and event["type"] == "transaction":
+        return event
+
+    otel_span = get_current_span()
+    if not otel_span:
+        return event
+
+    ctx = otel_span.get_span_context()
+    trace_id = format_trace_id(ctx.trace_id)
+    span_id = format_span_id(ctx.span_id)
+
+    if trace_id == INVALID_TRACE_ID or span_id == INVALID_SPAN_ID:
+        return event
+
+    sentry_span = otel_span_map.get(span_id, None)
+    if not sentry_span:
+        return event
+
+    contexts = event.setdefault("contexts", {})
+    contexts.setdefault("trace", {}).update(sentry_span.get_trace_context())
+
+    return event
+
+
 class SentrySpanProcessor(SpanProcessor):  # type: ignore
     """
     Converts OTel spans into Sentry spans so they can be sent to the Sentry backend.
@@ -45,6 +85,13 @@
 
         return cls.instance
 
+    def __init__(self):
+        # type: () -> None
+        @add_global_event_processor
+        def global_event_processor(event, hint):
+            # type: (Event, Hint) -> Event
+            return link_trace_context_to_error_event(event, self.otel_span_map)
+
     def on_start(self, otel_span, parent_context=None):
         # type: (OTelSpan, SpanContext) -> None
         hub = Hub.current

{"golden_diff": "diff --git a/sentry_sdk/integrations/opentelemetry/span_processor.py b/sentry_sdk/integrations/opentelemetry/span_processor.py\n--- a/sentry_sdk/integrations/opentelemetry/span_processor.py\n+++ b/sentry_sdk/integrations/opentelemetry/span_processor.py\n@@ -6,16 +6,22 @@\n from opentelemetry.trace import (  # type: ignore\n     format_span_id,\n     format_trace_id,\n+    get_current_span,\n     SpanContext,\n     Span as OTelSpan,\n     SpanKind,\n )\n+from opentelemetry.trace.span import (  # type: ignore\n+    INVALID_SPAN_ID,\n+    INVALID_TRACE_ID,\n+)\n from sentry_sdk.consts import INSTRUMENTER\n from sentry_sdk.hub import Hub\n from sentry_sdk.integrations.opentelemetry.consts import (\n     SENTRY_BAGGAGE_KEY,\n     SENTRY_TRACE_KEY,\n )\n+from sentry_sdk.scope import add_global_event_processor\n from sentry_sdk.tracing import Transaction, Span as SentrySpan\n from sentry_sdk.utils import Dsn\n from sentry_sdk._types import MYPY\n@@ -26,10 +32,44 @@\n     from typing import Any\n     from typing import Dict\n     from typing import Union\n+    from sentry_sdk._types import Event, Hint\n \n OPEN_TELEMETRY_CONTEXT = \"otel\"\n \n \n+def link_trace_context_to_error_event(event, otel_span_map):\n+    # type: (Event, Dict[str, Union[Transaction, OTelSpan]]) -> Event\n+    hub = Hub.current\n+    if not hub:\n+        return event\n+\n+    if hub.client and hub.client.options[\"instrumenter\"] != INSTRUMENTER.OTEL:\n+        return event\n+\n+    if hasattr(event, \"type\") and event[\"type\"] == \"transaction\":\n+        return event\n+\n+    otel_span = get_current_span()\n+    if not otel_span:\n+        return event\n+\n+    ctx = otel_span.get_span_context()\n+    trace_id = format_trace_id(ctx.trace_id)\n+    span_id = format_span_id(ctx.span_id)\n+\n+    if trace_id == INVALID_TRACE_ID or span_id == INVALID_SPAN_ID:\n+        return event\n+\n+    sentry_span = otel_span_map.get(span_id, None)\n+    if not sentry_span:\n+        return event\n+\n+    contexts = event.setdefault(\"contexts\", {})\n+    contexts.setdefault(\"trace\", {}).update(sentry_span.get_trace_context())\n+\n+    return event\n+\n+\n class SentrySpanProcessor(SpanProcessor):  # type: ignore\n     \"\"\"\n     Converts OTel spans into Sentry spans so they can be sent to the Sentry backend.\n@@ -45,6 +85,13 @@\n \n         return cls.instance\n \n+    def __init__(self):\n+        # type: () -> None\n+        @add_global_event_processor\n+        def global_event_processor(event, hint):\n+            # type: (Event, Hint) -> Event\n+            return link_trace_context_to_error_event(event, self.otel_span_map)\n+\n     def on_start(self, otel_span, parent_context=None):\n         # type: (OTelSpan, SpanContext) -> None\n         hub = Hub.current\n", "issue": "Link errors to OTel spans\n### Problem Statement\n\nIf you use the Otel instrumentation caught errors are not associated with performance spans coming from otel\n\n### Solution Brainstorm\n\nMake sure errors are associated with the current otel span. See Java implementation for inspiration.\n", "before_files": [{"content": "from datetime import datetime\n\nfrom opentelemetry.context import get_value  # type: ignore\nfrom opentelemetry.sdk.trace import SpanProcessor  # type: ignore\nfrom opentelemetry.semconv.trace import SpanAttributes  # type: ignore\nfrom opentelemetry.trace import (  # type: ignore\n    format_span_id,\n    format_trace_id,\n    SpanContext,\n    Span as OTelSpan,\n    SpanKind,\n)\nfrom sentry_sdk.consts import INSTRUMENTER\nfrom sentry_sdk.hub import Hub\nfrom sentry_sdk.integrations.opentelemetry.consts import (\n    SENTRY_BAGGAGE_KEY,\n    SENTRY_TRACE_KEY,\n)\nfrom sentry_sdk.tracing import Transaction, Span as SentrySpan\nfrom sentry_sdk.utils import Dsn\nfrom sentry_sdk._types import MYPY\n\nfrom urllib3.util import parse_url as urlparse  # type: ignore\n\nif MYPY:\n    from typing import Any\n    from typing import Dict\n    from typing import Union\n\nOPEN_TELEMETRY_CONTEXT = \"otel\"\n\n\nclass SentrySpanProcessor(SpanProcessor):  # type: ignore\n    \"\"\"\n    Converts OTel spans into Sentry spans so they can be sent to the Sentry backend.\n    \"\"\"\n\n    # The mapping from otel span ids to sentry spans\n    otel_span_map = {}  # type: Dict[str, Union[Transaction, OTelSpan]]\n\n    def __new__(cls):\n        # type: () -> SentrySpanProcessor\n        if not hasattr(cls, \"instance\"):\n            cls.instance = super(SentrySpanProcessor, cls).__new__(cls)\n\n        return cls.instance\n\n    def on_start(self, otel_span, parent_context=None):\n        # type: (OTelSpan, SpanContext) -> None\n        hub = Hub.current\n        if not hub:\n            return\n\n        if hub.client and hub.client.options[\"instrumenter\"] != INSTRUMENTER.OTEL:\n            return\n\n        if not otel_span.context.is_valid:\n            return\n\n        if self._is_sentry_span(hub, otel_span):\n            return\n\n        trace_data = self._get_trace_data(otel_span, parent_context)\n\n        parent_span_id = trace_data[\"parent_span_id\"]\n        sentry_parent_span = (\n            self.otel_span_map.get(parent_span_id, None) if parent_span_id else None\n        )\n\n        sentry_span = None\n        if sentry_parent_span:\n            sentry_span = sentry_parent_span.start_child(\n                span_id=trace_data[\"span_id\"],\n                description=otel_span.name,\n                start_timestamp=datetime.fromtimestamp(otel_span.start_time / 1e9),\n                instrumenter=INSTRUMENTER.OTEL,\n            )\n        else:\n            sentry_span = hub.start_transaction(\n                name=otel_span.name,\n                span_id=trace_data[\"span_id\"],\n                parent_span_id=parent_span_id,\n                trace_id=trace_data[\"trace_id\"],\n                baggage=trace_data[\"baggage\"],\n                start_timestamp=datetime.fromtimestamp(otel_span.start_time / 1e9),\n                instrumenter=INSTRUMENTER.OTEL,\n            )\n\n        self.otel_span_map[trace_data[\"span_id\"]] = sentry_span\n\n    def on_end(self, otel_span):\n        # type: (OTelSpan) -> None\n        hub = Hub.current\n        if not hub:\n            return\n\n        if hub.client and hub.client.options[\"instrumenter\"] != INSTRUMENTER.OTEL:\n            return\n\n        if not otel_span.context.is_valid:\n            return\n\n        span_id = format_span_id(otel_span.context.span_id)\n        sentry_span = self.otel_span_map.pop(span_id, None)\n        if not sentry_span:\n            return\n\n        sentry_span.op = otel_span.name\n\n        if isinstance(sentry_span, Transaction):\n            sentry_span.name = otel_span.name\n            sentry_span.set_context(\n                OPEN_TELEMETRY_CONTEXT, self._get_otel_context(otel_span)\n            )\n\n        else:\n            self._update_span_with_otel_data(sentry_span, otel_span)\n\n        sentry_span.finish(\n            end_timestamp=datetime.fromtimestamp(otel_span.end_time / 1e9)\n        )\n\n    def _is_sentry_span(self, hub, otel_span):\n        # type: (Hub, OTelSpan) -> bool\n        \"\"\"\n        Break infinite loop:\n        HTTP requests to Sentry are caught by OTel and send again to Sentry.\n        \"\"\"\n        otel_span_url = otel_span.attributes.get(SpanAttributes.HTTP_URL, None)\n        dsn_url = hub.client and Dsn(hub.client.dsn or \"\").netloc\n\n        if otel_span_url and dsn_url in otel_span_url:\n            return True\n\n        return False\n\n    def _get_otel_context(self, otel_span):\n        # type: (OTelSpan) -> Dict[str, Any]\n        \"\"\"\n        Returns the OTel context for Sentry.\n        See: https://develop.sentry.dev/sdk/performance/opentelemetry/#step-5-add-opentelemetry-context\n        \"\"\"\n        ctx = {}\n\n        if otel_span.attributes:\n            ctx[\"attributes\"] = dict(otel_span.attributes)\n\n        if otel_span.resource.attributes:\n            ctx[\"resource\"] = dict(otel_span.resource.attributes)\n\n        return ctx\n\n    def _get_trace_data(self, otel_span, parent_context):\n        # type: (OTelSpan, SpanContext) -> Dict[str, Any]\n        \"\"\"\n        Extracts tracing information from one OTel span and its parent OTel context.\n        \"\"\"\n        trace_data = {}\n\n        span_id = format_span_id(otel_span.context.span_id)\n        trace_data[\"span_id\"] = span_id\n\n        trace_id = format_trace_id(otel_span.context.trace_id)\n        trace_data[\"trace_id\"] = trace_id\n\n        parent_span_id = (\n            format_span_id(otel_span.parent.span_id) if otel_span.parent else None\n        )\n        trace_data[\"parent_span_id\"] = parent_span_id\n\n        sentry_trace_data = get_value(SENTRY_TRACE_KEY, parent_context)\n        trace_data[\"parent_sampled\"] = (\n            sentry_trace_data[2] if sentry_trace_data else None\n        )\n\n        baggage = get_value(SENTRY_BAGGAGE_KEY, parent_context)\n        trace_data[\"baggage\"] = baggage\n\n        return trace_data\n\n    def _update_span_with_otel_data(self, sentry_span, otel_span):\n        # type: (SentrySpan, OTelSpan) -> None\n        \"\"\"\n        Convert OTel span data and update the Sentry span with it.\n        This should eventually happen on the server when ingesting the spans.\n        \"\"\"\n        for key, val in otel_span.attributes.items():\n            sentry_span.set_data(key, val)\n\n        sentry_span.set_data(\"otel.kind\", otel_span.kind)\n\n        op = otel_span.name\n        description = otel_span.name\n\n        http_method = otel_span.attributes.get(SpanAttributes.HTTP_METHOD, None)\n        db_query = otel_span.attributes.get(SpanAttributes.DB_SYSTEM, None)\n\n        if http_method:\n            op = \"http\"\n\n            if otel_span.kind == SpanKind.SERVER:\n                op += \".server\"\n            elif otel_span.kind == SpanKind.CLIENT:\n                op += \".client\"\n\n            description = http_method\n\n            peer_name = otel_span.attributes.get(SpanAttributes.NET_PEER_NAME, None)\n            if peer_name:\n                description += \" {}\".format(peer_name)\n\n            target = otel_span.attributes.get(SpanAttributes.HTTP_TARGET, None)\n            if target:\n                description += \" {}\".format(target)\n\n            if not peer_name and not target:\n                url = otel_span.attributes.get(SpanAttributes.HTTP_URL, None)\n                if url:\n                    parsed_url = urlparse(url)\n                    url = f\"{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}\"\n                    description += \" {}\".format(url)\n\n            status_code = otel_span.attributes.get(\n                SpanAttributes.HTTP_STATUS_CODE, None\n            )\n            if status_code:\n                sentry_span.set_http_status(status_code)\n\n        elif db_query:\n            op = \"db\"\n            statement = otel_span.attributes.get(SpanAttributes.DB_STATEMENT, None)\n            if statement:\n                description = statement\n\n        sentry_span.op = op\n        sentry_span.description = description\n", "path": "sentry_sdk/integrations/opentelemetry/span_processor.py"}], "after_files": [{"content": "from datetime import datetime\n\nfrom opentelemetry.context import get_value  # type: ignore\nfrom opentelemetry.sdk.trace import SpanProcessor  # type: ignore\nfrom opentelemetry.semconv.trace import SpanAttributes  # type: ignore\nfrom opentelemetry.trace import (  # type: ignore\n    format_span_id,\n    format_trace_id,\n    get_current_span,\n    SpanContext,\n    Span as OTelSpan,\n    SpanKind,\n)\nfrom opentelemetry.trace.span import (  # type: ignore\n    INVALID_SPAN_ID,\n    INVALID_TRACE_ID,\n)\nfrom sentry_sdk.consts import INSTRUMENTER\nfrom sentry_sdk.hub import Hub\nfrom sentry_sdk.integrations.opentelemetry.consts import (\n    SENTRY_BAGGAGE_KEY,\n    SENTRY_TRACE_KEY,\n)\nfrom sentry_sdk.scope import add_global_event_processor\nfrom sentry_sdk.tracing import Transaction, Span as SentrySpan\nfrom sentry_sdk.utils import Dsn\nfrom sentry_sdk._types import MYPY\n\nfrom urllib3.util import parse_url as urlparse  # type: ignore\n\nif MYPY:\n    from typing import Any\n    from typing import Dict\n    from typing import Union\n    from sentry_sdk._types import Event, Hint\n\nOPEN_TELEMETRY_CONTEXT = \"otel\"\n\n\ndef link_trace_context_to_error_event(event, otel_span_map):\n    # type: (Event, Dict[str, Union[Transaction, OTelSpan]]) -> Event\n    hub = Hub.current\n    if not hub:\n        return event\n\n    if hub.client and hub.client.options[\"instrumenter\"] != INSTRUMENTER.OTEL:\n        return event\n\n    if hasattr(event, \"type\") and event[\"type\"] == \"transaction\":\n        return event\n\n    otel_span = get_current_span()\n    if not otel_span:\n        return event\n\n    ctx = otel_span.get_span_context()\n    trace_id = format_trace_id(ctx.trace_id)\n    span_id = format_span_id(ctx.span_id)\n\n    if trace_id == INVALID_TRACE_ID or span_id == INVALID_SPAN_ID:\n        return event\n\n    sentry_span = otel_span_map.get(span_id, None)\n    if not sentry_span:\n        return event\n\n    contexts = event.setdefault(\"contexts\", {})\n    contexts.setdefault(\"trace\", {}).update(sentry_span.get_trace_context())\n\n    return event\n\n\nclass SentrySpanProcessor(SpanProcessor):  # type: ignore\n    \"\"\"\n    Converts OTel spans into Sentry spans so they can be sent to the Sentry backend.\n    \"\"\"\n\n    # The mapping from otel span ids to sentry spans\n    otel_span_map = {}  # type: Dict[str, Union[Transaction, OTelSpan]]\n\n    def __new__(cls):\n        # type: () -> SentrySpanProcessor\n        if not hasattr(cls, \"instance\"):\n            cls.instance = super(SentrySpanProcessor, cls).__new__(cls)\n\n        return cls.instance\n\n    def __init__(self):\n        # type: () -> None\n        @add_global_event_processor\n        def global_event_processor(event, hint):\n            # type: (Event, Hint) -> Event\n            return link_trace_context_to_error_event(event, self.otel_span_map)\n\n    def on_start(self, otel_span, parent_context=None):\n        # type: (OTelSpan, SpanContext) -> None\n        hub = Hub.current\n        if not hub:\n            return\n\n        if hub.client and hub.client.options[\"instrumenter\"] != INSTRUMENTER.OTEL:\n            return\n\n        if not otel_span.context.is_valid:\n            return\n\n        if self._is_sentry_span(hub, otel_span):\n            return\n\n        trace_data = self._get_trace_data(otel_span, parent_context)\n\n        parent_span_id = trace_data[\"parent_span_id\"]\n        sentry_parent_span = (\n            self.otel_span_map.get(parent_span_id, None) if parent_span_id else None\n        )\n\n        sentry_span = None\n        if sentry_parent_span:\n            sentry_span = sentry_parent_span.start_child(\n                span_id=trace_data[\"span_id\"],\n                description=otel_span.name,\n                start_timestamp=datetime.fromtimestamp(otel_span.start_time / 1e9),\n                instrumenter=INSTRUMENTER.OTEL,\n            )\n        else:\n            sentry_span = hub.start_transaction(\n                name=otel_span.name,\n                span_id=trace_data[\"span_id\"],\n                parent_span_id=parent_span_id,\n                trace_id=trace_data[\"trace_id\"],\n                baggage=trace_data[\"baggage\"],\n                start_timestamp=datetime.fromtimestamp(otel_span.start_time / 1e9),\n                instrumenter=INSTRUMENTER.OTEL,\n            )\n\n        self.otel_span_map[trace_data[\"span_id\"]] = sentry_span\n\n    def on_end(self, otel_span):\n        # type: (OTelSpan) -> None\n        hub = Hub.current\n        if not hub:\n            return\n\n        if hub.client and hub.client.options[\"instrumenter\"] != INSTRUMENTER.OTEL:\n            return\n\n        if not otel_span.context.is_valid:\n            return\n\n        span_id = format_span_id(otel_span.context.span_id)\n        sentry_span = self.otel_span_map.pop(span_id, None)\n        if not sentry_span:\n            return\n\n        sentry_span.op = otel_span.name\n\n        if isinstance(sentry_span, Transaction):\n            sentry_span.name = otel_span.name\n            sentry_span.set_context(\n                OPEN_TELEMETRY_CONTEXT, self._get_otel_context(otel_span)\n            )\n\n        else:\n            self._update_span_with_otel_data(sentry_span, otel_span)\n\n        sentry_span.finish(\n            end_timestamp=datetime.fromtimestamp(otel_span.end_time / 1e9)\n        )\n\n    def _is_sentry_span(self, hub, otel_span):\n        # type: (Hub, OTelSpan) -> bool\n        \"\"\"\n        Break infinite loop:\n        HTTP requests to Sentry are caught by OTel and send again to Sentry.\n        \"\"\"\n        otel_span_url = otel_span.attributes.get(SpanAttributes.HTTP_URL, None)\n        dsn_url = hub.client and Dsn(hub.client.dsn or \"\").netloc\n\n        if otel_span_url and dsn_url in otel_span_url:\n            return True\n\n        return False\n\n    def _get_otel_context(self, otel_span):\n        # type: (OTelSpan) -> Dict[str, Any]\n        \"\"\"\n        Returns the OTel context for Sentry.\n        See: https://develop.sentry.dev/sdk/performance/opentelemetry/#step-5-add-opentelemetry-context\n        \"\"\"\n        ctx = {}\n\n        if otel_span.attributes:\n            ctx[\"attributes\"] = dict(otel_span.attributes)\n\n        if otel_span.resource.attributes:\n            ctx[\"resource\"] = dict(otel_span.resource.attributes)\n\n        return ctx\n\n    def _get_trace_data(self, otel_span, parent_context):\n        # type: (OTelSpan, SpanContext) -> Dict[str, Any]\n        \"\"\"\n        Extracts tracing information from one OTel span and its parent OTel context.\n        \"\"\"\n        trace_data = {}\n\n        span_id = format_span_id(otel_span.context.span_id)\n        trace_data[\"span_id\"] = span_id\n\n        trace_id = format_trace_id(otel_span.context.trace_id)\n        trace_data[\"trace_id\"] = trace_id\n\n        parent_span_id = (\n            format_span_id(otel_span.parent.span_id) if otel_span.parent else None\n        )\n        trace_data[\"parent_span_id\"] = parent_span_id\n\n        sentry_trace_data = get_value(SENTRY_TRACE_KEY, parent_context)\n        trace_data[\"parent_sampled\"] = (\n            sentry_trace_data[2] if sentry_trace_data else None\n        )\n\n        baggage = get_value(SENTRY_BAGGAGE_KEY, parent_context)\n        trace_data[\"baggage\"] = baggage\n\n        return trace_data\n\n    def _update_span_with_otel_data(self, sentry_span, otel_span):\n        # type: (SentrySpan, OTelSpan) -> None\n        \"\"\"\n        Convert OTel span data and update the Sentry span with it.\n        This should eventually happen on the server when ingesting the spans.\n        \"\"\"\n        for key, val in otel_span.attributes.items():\n            sentry_span.set_data(key, val)\n\n        sentry_span.set_data(\"otel.kind\", otel_span.kind)\n\n        op = otel_span.name\n        description = otel_span.name\n\n        http_method = otel_span.attributes.get(SpanAttributes.HTTP_METHOD, None)\n        db_query = otel_span.attributes.get(SpanAttributes.DB_SYSTEM, None)\n\n        if http_method:\n            op = \"http\"\n\n            if otel_span.kind == SpanKind.SERVER:\n                op += \".server\"\n            elif otel_span.kind == SpanKind.CLIENT:\n                op += \".client\"\n\n            description = http_method\n\n            peer_name = otel_span.attributes.get(SpanAttributes.NET_PEER_NAME, None)\n            if peer_name:\n                description += \" {}\".format(peer_name)\n\n            target = otel_span.attributes.get(SpanAttributes.HTTP_TARGET, None)\n            if target:\n                description += \" {}\".format(target)\n\n            if not peer_name and not target:\n                url = otel_span.attributes.get(SpanAttributes.HTTP_URL, None)\n                if url:\n                    parsed_url = urlparse(url)\n                    url = f\"{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}\"\n                    description += \" {}\".format(url)\n\n            status_code = otel_span.attributes.get(\n                SpanAttributes.HTTP_STATUS_CODE, None\n            )\n            if status_code:\n                sentry_span.set_http_status(status_code)\n\n        elif db_query:\n            op = \"db\"\n            statement = otel_span.attributes.get(SpanAttributes.DB_STATEMENT, None)\n            if statement:\n                description = statement\n\n        sentry_span.op = op\n        sentry_span.description = description\n", "path": "sentry_sdk/integrations/opentelemetry/span_processor.py"}]}