ioa-observe-sdk 1.0.29__tar.gz → 1.0.30__tar.gz
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.
- {ioa_observe_sdk-1.0.29/ioa_observe_sdk.egg-info → ioa_observe_sdk-1.0.30}/PKG-INFO +1 -1
- ioa_observe_sdk-1.0.30/ioa_observe/sdk/instrumentations/slim.py +496 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30/ioa_observe_sdk.egg-info}/PKG-INFO +1 -1
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/pyproject.toml +1 -1
- ioa_observe_sdk-1.0.29/ioa_observe/sdk/instrumentations/slim.py +0 -973
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/LICENSE.md +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/README.md +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/__init__.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/__init__.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/client/__init__.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/client/client.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/client/http.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/config/__init__.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/connectors/__init__.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/connectors/slim.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/decorators/__init__.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/decorators/base.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/decorators/helpers.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/decorators/util.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/instrumentations/__init__.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/instrumentations/a2a.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/instrumentations/mcp.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/instrumentations/nats.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/instruments.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/logging/__init__.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/logging/logging.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/metrics/__init__.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/metrics/agent.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/metrics/agents/__init__.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/metrics/agents/agent_connections.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/metrics/agents/availability.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/metrics/agents/heuristics.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/metrics/agents/recovery_tracker.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/metrics/agents/tool_call_tracker.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/metrics/agents/tracker.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/metrics/metrics.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/telemetry.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/tracing/__init__.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/tracing/content_allow_list.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/tracing/context_manager.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/tracing/context_utils.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/tracing/manual.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/tracing/tracing.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/tracing/transform_span.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/utils/__init__.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/utils/const.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/utils/in_memory_span_exporter.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/utils/json_encoder.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/utils/package_check.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe/sdk/version.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe_sdk.egg-info/SOURCES.txt +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe_sdk.egg-info/dependency_links.txt +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe_sdk.egg-info/requires.txt +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/ioa_observe_sdk.egg-info/top_level.txt +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/setup.cfg +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/tests/test_client.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/tests/test_instrumentor.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/tests/test_manual_instrumentation.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/tests/test_transform_span.py +0 -0
- {ioa_observe_sdk-1.0.29 → ioa_observe_sdk-1.0.30}/tests/test_version.py +0 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
# Copyright AGNTCY Contributors (https://github.com/agntcy)
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""SLIM v1.x Instrumentation for OpenTelemetry tracing."""
|
|
5
|
+
|
|
6
|
+
from typing import Collection
|
|
7
|
+
import functools
|
|
8
|
+
import json
|
|
9
|
+
import base64
|
|
10
|
+
import threading
|
|
11
|
+
|
|
12
|
+
from opentelemetry import baggage, context
|
|
13
|
+
from opentelemetry.baggage.propagation import W3CBaggagePropagator
|
|
14
|
+
from opentelemetry.context import get_value
|
|
15
|
+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|
16
|
+
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
|
|
17
|
+
|
|
18
|
+
from ioa_observe.sdk import TracerWrapper
|
|
19
|
+
from ioa_observe.sdk.client import kv_store
|
|
20
|
+
from ioa_observe.sdk.tracing import set_session_id, get_current_traceparent
|
|
21
|
+
|
|
22
|
+
_instruments = ("slim-bindings >= 1.0.0",)
|
|
23
|
+
_global_tracer = None
|
|
24
|
+
_kv_lock = threading.RLock()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _get_session_id(session):
|
|
28
|
+
"""Get session ID from session object (v1.x uses .session_id() method)."""
|
|
29
|
+
if hasattr(session, "session_id") and callable(session.session_id):
|
|
30
|
+
try:
|
|
31
|
+
return str(session.session_id())
|
|
32
|
+
except Exception:
|
|
33
|
+
pass
|
|
34
|
+
return str(session.id) if hasattr(session, "id") else None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _process_received_message(raw_message):
|
|
38
|
+
"""Process received message, extract tracing context, return cleaned payload."""
|
|
39
|
+
if raw_message is None:
|
|
40
|
+
return raw_message
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
if isinstance(raw_message, bytes):
|
|
44
|
+
message_dict = json.loads(raw_message.decode())
|
|
45
|
+
elif isinstance(raw_message, str):
|
|
46
|
+
message_dict = json.loads(raw_message)
|
|
47
|
+
elif isinstance(raw_message, dict):
|
|
48
|
+
message_dict = raw_message
|
|
49
|
+
else:
|
|
50
|
+
return raw_message
|
|
51
|
+
|
|
52
|
+
headers = message_dict.get("headers", {})
|
|
53
|
+
traceparent = headers.get("traceparent")
|
|
54
|
+
session_id = headers.get("session_id")
|
|
55
|
+
|
|
56
|
+
# Restore trace context
|
|
57
|
+
if traceparent:
|
|
58
|
+
carrier = {
|
|
59
|
+
k.lower(): v
|
|
60
|
+
for k, v in headers.items()
|
|
61
|
+
if k.lower() in ["traceparent", "baggage"]
|
|
62
|
+
}
|
|
63
|
+
if carrier:
|
|
64
|
+
ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
|
|
65
|
+
ctx = W3CBaggagePropagator().extract(carrier=carrier, context=ctx)
|
|
66
|
+
context.attach(ctx)
|
|
67
|
+
|
|
68
|
+
if session_id and session_id != "None":
|
|
69
|
+
set_session_id(session_id, traceparent=traceparent)
|
|
70
|
+
with _kv_lock:
|
|
71
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
72
|
+
|
|
73
|
+
# Clean headers
|
|
74
|
+
cleaned = message_dict.copy()
|
|
75
|
+
if "headers" in cleaned:
|
|
76
|
+
h = cleaned["headers"].copy()
|
|
77
|
+
for k in ["traceparent", "session_id", "slim_session_id"]:
|
|
78
|
+
h.pop(k, None)
|
|
79
|
+
if h:
|
|
80
|
+
cleaned["headers"] = h
|
|
81
|
+
else:
|
|
82
|
+
cleaned.pop("headers", None)
|
|
83
|
+
|
|
84
|
+
# Extract payload
|
|
85
|
+
if len(cleaned) == 1 and "payload" in cleaned:
|
|
86
|
+
payload = cleaned["payload"]
|
|
87
|
+
if isinstance(payload, str):
|
|
88
|
+
try:
|
|
89
|
+
return json.dumps(json.loads(payload)).encode("utf-8")
|
|
90
|
+
except json.JSONDecodeError:
|
|
91
|
+
return payload.encode("utf-8")
|
|
92
|
+
elif isinstance(payload, (dict, list)):
|
|
93
|
+
return json.dumps(payload).encode("utf-8")
|
|
94
|
+
return payload
|
|
95
|
+
return json.dumps(cleaned).encode("utf-8")
|
|
96
|
+
|
|
97
|
+
except Exception:
|
|
98
|
+
return raw_message
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _wrap_message_with_headers(message, headers):
|
|
102
|
+
"""Wrap message with tracing headers."""
|
|
103
|
+
if isinstance(message, bytes):
|
|
104
|
+
try:
|
|
105
|
+
decoded = message.decode("utf-8")
|
|
106
|
+
try:
|
|
107
|
+
original = json.loads(decoded)
|
|
108
|
+
if isinstance(original, dict):
|
|
109
|
+
wrapped = original.copy()
|
|
110
|
+
wrapped["headers"] = {**wrapped.get("headers", {}), **headers}
|
|
111
|
+
else:
|
|
112
|
+
wrapped = {"headers": headers, "payload": original}
|
|
113
|
+
except json.JSONDecodeError:
|
|
114
|
+
wrapped = {"headers": headers, "payload": decoded}
|
|
115
|
+
except UnicodeDecodeError:
|
|
116
|
+
wrapped = {
|
|
117
|
+
"headers": headers,
|
|
118
|
+
"payload": base64.b64encode(message).decode("utf-8"),
|
|
119
|
+
}
|
|
120
|
+
elif isinstance(message, str):
|
|
121
|
+
try:
|
|
122
|
+
original = json.loads(message)
|
|
123
|
+
if isinstance(original, dict):
|
|
124
|
+
wrapped = original.copy()
|
|
125
|
+
wrapped["headers"] = {**wrapped.get("headers", {}), **headers}
|
|
126
|
+
else:
|
|
127
|
+
wrapped = {"headers": headers, "payload": original}
|
|
128
|
+
except json.JSONDecodeError:
|
|
129
|
+
wrapped = {"headers": headers, "payload": message}
|
|
130
|
+
elif isinstance(message, dict):
|
|
131
|
+
wrapped = message.copy()
|
|
132
|
+
wrapped["headers"] = {**wrapped.get("headers", {}), **headers}
|
|
133
|
+
else:
|
|
134
|
+
wrapped = {"headers": headers, "payload": json.dumps(message)}
|
|
135
|
+
|
|
136
|
+
return wrapped
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class SLIMInstrumentor(BaseInstrumentor):
|
|
140
|
+
def __init__(self):
|
|
141
|
+
super().__init__()
|
|
142
|
+
global _global_tracer
|
|
143
|
+
_global_tracer = TracerWrapper().get_tracer()
|
|
144
|
+
|
|
145
|
+
def instrumentation_dependencies(self) -> Collection[str]:
|
|
146
|
+
return _instruments
|
|
147
|
+
|
|
148
|
+
def _instrument(self, **kwargs):
|
|
149
|
+
try:
|
|
150
|
+
import slim_bindings
|
|
151
|
+
except ImportError:
|
|
152
|
+
raise ImportError(
|
|
153
|
+
"No module named 'slim_bindings'. Please install it first."
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
self._instrument_service(slim_bindings)
|
|
157
|
+
self._instrument_app(slim_bindings)
|
|
158
|
+
self._instrument_sessions(slim_bindings)
|
|
159
|
+
|
|
160
|
+
def _instrument_service(self, slim_bindings):
|
|
161
|
+
"""Instrument SLIM v1.x Service class."""
|
|
162
|
+
if not hasattr(slim_bindings, "Service"):
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
Service = slim_bindings.Service
|
|
166
|
+
|
|
167
|
+
# connect_async
|
|
168
|
+
if hasattr(Service, "connect_async"):
|
|
169
|
+
original_connect_async = Service.connect_async
|
|
170
|
+
|
|
171
|
+
@functools.wraps(original_connect_async)
|
|
172
|
+
async def wrapped_connect_async(self, config, *args, **kwargs):
|
|
173
|
+
if _global_tracer:
|
|
174
|
+
with _global_tracer.start_as_current_span(
|
|
175
|
+
"slim.service.connect"
|
|
176
|
+
) as span:
|
|
177
|
+
result = await original_connect_async(
|
|
178
|
+
self, config, *args, **kwargs
|
|
179
|
+
)
|
|
180
|
+
span.set_attribute(
|
|
181
|
+
"slim.connection.id", str(result) if result else "unknown"
|
|
182
|
+
)
|
|
183
|
+
return result
|
|
184
|
+
return await original_connect_async(self, config, *args, **kwargs)
|
|
185
|
+
|
|
186
|
+
Service.connect_async = wrapped_connect_async
|
|
187
|
+
|
|
188
|
+
# run_server_async
|
|
189
|
+
if hasattr(Service, "run_server_async"):
|
|
190
|
+
original_run_server_async = Service.run_server_async
|
|
191
|
+
|
|
192
|
+
@functools.wraps(original_run_server_async)
|
|
193
|
+
async def wrapped_run_server_async(self, config, *args, **kwargs):
|
|
194
|
+
if _global_tracer:
|
|
195
|
+
with _global_tracer.start_as_current_span(
|
|
196
|
+
"slim.service.run_server"
|
|
197
|
+
) as span:
|
|
198
|
+
if hasattr(config, "endpoint"):
|
|
199
|
+
span.set_attribute("slim.server.endpoint", config.endpoint)
|
|
200
|
+
result = await original_run_server_async(
|
|
201
|
+
self, config, *args, **kwargs
|
|
202
|
+
)
|
|
203
|
+
return result
|
|
204
|
+
return await original_run_server_async(self, config, *args, **kwargs)
|
|
205
|
+
|
|
206
|
+
Service.run_server_async = wrapped_run_server_async
|
|
207
|
+
|
|
208
|
+
def _instrument_app(self, slim_bindings):
|
|
209
|
+
"""Instrument SLIM v1.x App class."""
|
|
210
|
+
if not hasattr(slim_bindings, "App"):
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
App = slim_bindings.App
|
|
214
|
+
|
|
215
|
+
# create_session_async
|
|
216
|
+
if hasattr(App, "create_session_async"):
|
|
217
|
+
original_create_session_async = App.create_session_async
|
|
218
|
+
|
|
219
|
+
@functools.wraps(original_create_session_async)
|
|
220
|
+
async def wrapped_create_session_async(
|
|
221
|
+
self, config, dest=None, *args, **kwargs
|
|
222
|
+
):
|
|
223
|
+
if _global_tracer:
|
|
224
|
+
with _global_tracer.start_as_current_span(
|
|
225
|
+
"slim.app.create_session"
|
|
226
|
+
) as span:
|
|
227
|
+
ctx = await original_create_session_async(
|
|
228
|
+
self, config, dest, *args, **kwargs
|
|
229
|
+
)
|
|
230
|
+
if hasattr(ctx, "session"):
|
|
231
|
+
sid = _get_session_id(ctx.session)
|
|
232
|
+
if sid:
|
|
233
|
+
span.set_attribute("slim.session.id", sid)
|
|
234
|
+
return ctx
|
|
235
|
+
return await original_create_session_async(
|
|
236
|
+
self, config, dest, *args, **kwargs
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
App.create_session_async = wrapped_create_session_async
|
|
240
|
+
|
|
241
|
+
# create_session_and_wait_async
|
|
242
|
+
if hasattr(App, "create_session_and_wait_async"):
|
|
243
|
+
original_create_session_and_wait_async = App.create_session_and_wait_async
|
|
244
|
+
|
|
245
|
+
@functools.wraps(original_create_session_and_wait_async)
|
|
246
|
+
async def wrapped_create_session_and_wait_async(
|
|
247
|
+
self, config, dest=None, *args, **kwargs
|
|
248
|
+
):
|
|
249
|
+
if _global_tracer:
|
|
250
|
+
with _global_tracer.start_as_current_span(
|
|
251
|
+
"slim.app.create_session"
|
|
252
|
+
) as span:
|
|
253
|
+
session = await original_create_session_and_wait_async(
|
|
254
|
+
self, config, dest, *args, **kwargs
|
|
255
|
+
)
|
|
256
|
+
sid = _get_session_id(session)
|
|
257
|
+
if sid:
|
|
258
|
+
span.set_attribute("slim.session.id", sid)
|
|
259
|
+
return session
|
|
260
|
+
return await original_create_session_and_wait_async(
|
|
261
|
+
self, config, dest, *args, **kwargs
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
App.create_session_and_wait_async = wrapped_create_session_and_wait_async
|
|
265
|
+
|
|
266
|
+
# subscribe_async
|
|
267
|
+
if hasattr(App, "subscribe_async"):
|
|
268
|
+
original_subscribe_async = App.subscribe_async
|
|
269
|
+
|
|
270
|
+
@functools.wraps(original_subscribe_async)
|
|
271
|
+
async def wrapped_subscribe_async(self, name, conn_id, *args, **kwargs):
|
|
272
|
+
if _global_tracer:
|
|
273
|
+
with _global_tracer.start_as_current_span(
|
|
274
|
+
"slim.app.subscribe"
|
|
275
|
+
) as span:
|
|
276
|
+
if hasattr(name, "organization"):
|
|
277
|
+
span.set_attribute(
|
|
278
|
+
"slim.name",
|
|
279
|
+
f"{name.organization}/{name.namespace}/{name.app}",
|
|
280
|
+
)
|
|
281
|
+
return await original_subscribe_async(
|
|
282
|
+
self, name, conn_id, *args, **kwargs
|
|
283
|
+
)
|
|
284
|
+
return await original_subscribe_async(
|
|
285
|
+
self, name, conn_id, *args, **kwargs
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
App.subscribe_async = wrapped_subscribe_async
|
|
289
|
+
|
|
290
|
+
# set_route_async
|
|
291
|
+
if hasattr(App, "set_route_async"):
|
|
292
|
+
original_set_route_async = App.set_route_async
|
|
293
|
+
|
|
294
|
+
@functools.wraps(original_set_route_async)
|
|
295
|
+
async def wrapped_set_route_async(self, name, conn_id, *args, **kwargs):
|
|
296
|
+
if _global_tracer:
|
|
297
|
+
with _global_tracer.start_as_current_span(
|
|
298
|
+
"slim.app.set_route"
|
|
299
|
+
) as span:
|
|
300
|
+
if hasattr(name, "organization"):
|
|
301
|
+
span.set_attribute(
|
|
302
|
+
"slim.route",
|
|
303
|
+
f"{name.organization}/{name.namespace}/{name.app}",
|
|
304
|
+
)
|
|
305
|
+
return await original_set_route_async(
|
|
306
|
+
self, name, conn_id, *args, **kwargs
|
|
307
|
+
)
|
|
308
|
+
return await original_set_route_async(
|
|
309
|
+
self, name, conn_id, *args, **kwargs
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
App.set_route_async = wrapped_set_route_async
|
|
313
|
+
|
|
314
|
+
# listen_for_session - this is a blocking call, not async
|
|
315
|
+
if hasattr(App, "listen_for_session"):
|
|
316
|
+
original_listen_for_session = App.listen_for_session
|
|
317
|
+
|
|
318
|
+
@functools.wraps(original_listen_for_session)
|
|
319
|
+
def wrapped_listen_for_session(self, *args, **kwargs):
|
|
320
|
+
if _global_tracer:
|
|
321
|
+
with _global_tracer.start_as_current_span(
|
|
322
|
+
"slim.app.listen_for_session"
|
|
323
|
+
):
|
|
324
|
+
return original_listen_for_session(self, *args, **kwargs)
|
|
325
|
+
return original_listen_for_session(self, *args, **kwargs)
|
|
326
|
+
|
|
327
|
+
App.listen_for_session = wrapped_listen_for_session
|
|
328
|
+
|
|
329
|
+
def _instrument_sessions(self, slim_bindings):
|
|
330
|
+
"""Instrument session classes for v1.x."""
|
|
331
|
+
session_classes = set()
|
|
332
|
+
|
|
333
|
+
# Find session classes
|
|
334
|
+
for name in ["Session", "P2PSession", "GroupSession"]:
|
|
335
|
+
if hasattr(slim_bindings, name):
|
|
336
|
+
session_classes.add(getattr(slim_bindings, name))
|
|
337
|
+
|
|
338
|
+
# Find any class with session-like methods
|
|
339
|
+
for name in dir(slim_bindings):
|
|
340
|
+
cls = getattr(slim_bindings, name)
|
|
341
|
+
if isinstance(cls, type) and any(
|
|
342
|
+
hasattr(cls, m) for m in ["get_message_async", "publish_async"]
|
|
343
|
+
):
|
|
344
|
+
session_classes.add(cls)
|
|
345
|
+
|
|
346
|
+
for session_class in session_classes:
|
|
347
|
+
if hasattr(session_class, "get_message_async"):
|
|
348
|
+
self._wrap_get_message(session_class)
|
|
349
|
+
|
|
350
|
+
if hasattr(session_class, "publish_async"):
|
|
351
|
+
self._wrap_publish(session_class, "publish_async")
|
|
352
|
+
|
|
353
|
+
if hasattr(session_class, "publish_to_async"):
|
|
354
|
+
self._wrap_publish(session_class, "publish_to_async", msg_idx=1)
|
|
355
|
+
|
|
356
|
+
def _wrap_get_message(self, session_class):
|
|
357
|
+
"""Wrap get_message_async to extract tracing context."""
|
|
358
|
+
orig = session_class.get_message_async
|
|
359
|
+
|
|
360
|
+
@functools.wraps(orig)
|
|
361
|
+
async def wrapped(self, *args, **kwargs):
|
|
362
|
+
result = await orig(self, *args, **kwargs)
|
|
363
|
+
if result is None:
|
|
364
|
+
return result
|
|
365
|
+
|
|
366
|
+
# v1.x returns ReceivedMessage with .context and .payload
|
|
367
|
+
if hasattr(result, "payload"):
|
|
368
|
+
raw = result.payload
|
|
369
|
+
elif isinstance(result, tuple) and len(result) == 2:
|
|
370
|
+
raw = result[1]
|
|
371
|
+
else:
|
|
372
|
+
raw = result
|
|
373
|
+
|
|
374
|
+
processed = _process_received_message(raw)
|
|
375
|
+
|
|
376
|
+
if hasattr(result, "payload"):
|
|
377
|
+
try:
|
|
378
|
+
result.payload = processed
|
|
379
|
+
except AttributeError:
|
|
380
|
+
|
|
381
|
+
class Processed:
|
|
382
|
+
def __init__(self, ctx, payload):
|
|
383
|
+
self.context = ctx
|
|
384
|
+
self.payload = payload
|
|
385
|
+
|
|
386
|
+
return Processed(result.context, processed)
|
|
387
|
+
return result
|
|
388
|
+
elif isinstance(result, tuple) and len(result) == 2:
|
|
389
|
+
return (result[0], processed)
|
|
390
|
+
return processed
|
|
391
|
+
|
|
392
|
+
session_class.get_message_async = wrapped
|
|
393
|
+
|
|
394
|
+
def _wrap_publish(self, session_class, method_name, msg_idx=0):
|
|
395
|
+
"""Wrap publish methods to inject tracing headers."""
|
|
396
|
+
orig = getattr(session_class, method_name)
|
|
397
|
+
|
|
398
|
+
@functools.wraps(orig)
|
|
399
|
+
async def wrapped(self, *args, **kwargs):
|
|
400
|
+
traceparent = get_current_traceparent()
|
|
401
|
+
session_id = None
|
|
402
|
+
|
|
403
|
+
if traceparent:
|
|
404
|
+
with _kv_lock:
|
|
405
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
406
|
+
if not session_id:
|
|
407
|
+
session_id = get_value("session.id")
|
|
408
|
+
if session_id:
|
|
409
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
410
|
+
|
|
411
|
+
slim_session_id = _get_session_id(self)
|
|
412
|
+
|
|
413
|
+
if _global_tracer:
|
|
414
|
+
with _global_tracer.start_as_current_span(
|
|
415
|
+
f"session.{method_name}"
|
|
416
|
+
) as span:
|
|
417
|
+
if slim_session_id:
|
|
418
|
+
span.set_attribute("slim.session.id", slim_session_id)
|
|
419
|
+
|
|
420
|
+
if args and len(args) > msg_idx and (traceparent or session_id):
|
|
421
|
+
headers = {
|
|
422
|
+
"session_id": session_id,
|
|
423
|
+
"traceparent": traceparent,
|
|
424
|
+
"slim_session_id": slim_session_id,
|
|
425
|
+
}
|
|
426
|
+
if traceparent and session_id:
|
|
427
|
+
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
428
|
+
|
|
429
|
+
args_list = list(args)
|
|
430
|
+
wrapped_msg = _wrap_message_with_headers(
|
|
431
|
+
args_list[msg_idx], headers
|
|
432
|
+
)
|
|
433
|
+
args_list[msg_idx] = (
|
|
434
|
+
json.dumps(wrapped_msg).encode("utf-8")
|
|
435
|
+
if isinstance(wrapped_msg, dict)
|
|
436
|
+
else wrapped_msg
|
|
437
|
+
)
|
|
438
|
+
args = tuple(args_list)
|
|
439
|
+
|
|
440
|
+
return await orig(self, *args, **kwargs)
|
|
441
|
+
else:
|
|
442
|
+
if args and len(args) > msg_idx and (traceparent or session_id):
|
|
443
|
+
headers = {
|
|
444
|
+
"session_id": session_id,
|
|
445
|
+
"traceparent": traceparent,
|
|
446
|
+
"slim_session_id": slim_session_id,
|
|
447
|
+
}
|
|
448
|
+
args_list = list(args)
|
|
449
|
+
wrapped_msg = _wrap_message_with_headers(
|
|
450
|
+
args_list[msg_idx], headers
|
|
451
|
+
)
|
|
452
|
+
args_list[msg_idx] = (
|
|
453
|
+
json.dumps(wrapped_msg).encode("utf-8")
|
|
454
|
+
if isinstance(wrapped_msg, dict)
|
|
455
|
+
else wrapped_msg
|
|
456
|
+
)
|
|
457
|
+
args = tuple(args_list)
|
|
458
|
+
|
|
459
|
+
return await orig(self, *args, **kwargs)
|
|
460
|
+
|
|
461
|
+
setattr(session_class, method_name, wrapped)
|
|
462
|
+
|
|
463
|
+
def _uninstrument(self, **kwargs):
|
|
464
|
+
try:
|
|
465
|
+
import slim_bindings
|
|
466
|
+
except ImportError:
|
|
467
|
+
return
|
|
468
|
+
|
|
469
|
+
def restore(obj, methods):
|
|
470
|
+
for m in methods:
|
|
471
|
+
if hasattr(obj, m):
|
|
472
|
+
orig = getattr(obj, m)
|
|
473
|
+
if hasattr(orig, "__wrapped__"):
|
|
474
|
+
setattr(obj, m, orig.__wrapped__)
|
|
475
|
+
|
|
476
|
+
if hasattr(slim_bindings, "Service"):
|
|
477
|
+
restore(slim_bindings.Service, ["connect_async", "run_server_async"])
|
|
478
|
+
|
|
479
|
+
if hasattr(slim_bindings, "App"):
|
|
480
|
+
restore(
|
|
481
|
+
slim_bindings.App,
|
|
482
|
+
[
|
|
483
|
+
"create_session_async",
|
|
484
|
+
"create_session_and_wait_async",
|
|
485
|
+
"subscribe_async",
|
|
486
|
+
"set_route_async",
|
|
487
|
+
"listen_for_session",
|
|
488
|
+
],
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
# Restore session methods
|
|
492
|
+
session_methods = ["publish_async", "publish_to_async", "get_message_async"]
|
|
493
|
+
for name in dir(slim_bindings):
|
|
494
|
+
cls = getattr(slim_bindings, name)
|
|
495
|
+
if isinstance(cls, type):
|
|
496
|
+
restore(cls, session_methods)
|