ioa-observe-sdk 1.0.29__py3-none-any.whl → 1.0.30__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.
- ioa_observe/sdk/instrumentations/slim.py +367 -844
- {ioa_observe_sdk-1.0.29.dist-info → ioa_observe_sdk-1.0.30.dist-info}/METADATA +1 -1
- {ioa_observe_sdk-1.0.29.dist-info → ioa_observe_sdk-1.0.30.dist-info}/RECORD +6 -6
- {ioa_observe_sdk-1.0.29.dist-info → ioa_observe_sdk-1.0.30.dist-info}/WHEEL +0 -0
- {ioa_observe_sdk-1.0.29.dist-info → ioa_observe_sdk-1.0.30.dist-info}/licenses/LICENSE.md +0 -0
- {ioa_observe_sdk-1.0.29.dist-info → ioa_observe_sdk-1.0.30.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Copyright AGNTCY Contributors (https://github.com/agntcy)
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
+
"""SLIM v1.x Instrumentation for OpenTelemetry tracing."""
|
|
5
|
+
|
|
4
6
|
from typing import Collection
|
|
5
7
|
import functools
|
|
6
8
|
import json
|
|
@@ -17,9 +19,121 @@ from ioa_observe.sdk import TracerWrapper
|
|
|
17
19
|
from ioa_observe.sdk.client import kv_store
|
|
18
20
|
from ioa_observe.sdk.tracing import set_session_id, get_current_traceparent
|
|
19
21
|
|
|
20
|
-
_instruments = ("slim-bindings >= 0.
|
|
22
|
+
_instruments = ("slim-bindings >= 1.0.0",)
|
|
21
23
|
_global_tracer = None
|
|
22
|
-
_kv_lock = threading.RLock()
|
|
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
|
|
23
137
|
|
|
24
138
|
|
|
25
139
|
class SLIMInstrumentor(BaseInstrumentor):
|
|
@@ -39,935 +153,344 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
39
153
|
"No module named 'slim_bindings'. Please install it first."
|
|
40
154
|
)
|
|
41
155
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
# Legacy v0.5.x app-level publish method
|
|
46
|
-
original_publish = slim_bindings.Slim.publish
|
|
47
|
-
|
|
48
|
-
@functools.wraps(original_publish)
|
|
49
|
-
async def instrumented_publish(self, *args, **kwargs):
|
|
50
|
-
if _global_tracer:
|
|
51
|
-
with _global_tracer.start_as_current_span("slim.publish") as span:
|
|
52
|
-
traceparent = get_current_traceparent()
|
|
53
|
-
|
|
54
|
-
# Handle different publish signatures
|
|
55
|
-
# Definition 1: publish(session, message, topic_name) - v0.4.0+ group chat
|
|
56
|
-
# Definition 2: publish(session, message, organization, namespace, topic) - legacy
|
|
57
|
-
if len(args) >= 3:
|
|
58
|
-
session_arg = args[0] if args else None
|
|
59
|
-
if hasattr(session_arg, "id"):
|
|
60
|
-
span.set_attribute(
|
|
61
|
-
"slim.session.id", str(session_arg.id)
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
# Check if third argument is PyName (new API) or string (legacy API)
|
|
65
|
-
if len(args) >= 3 and hasattr(args[2], "organization"):
|
|
66
|
-
# New API: args[2] is PyName
|
|
67
|
-
topic_name = args[2]
|
|
68
|
-
span.set_attribute(
|
|
69
|
-
"slim.topic.organization", topic_name.organization
|
|
70
|
-
)
|
|
71
|
-
span.set_attribute(
|
|
72
|
-
"slim.topic.namespace", topic_name.namespace
|
|
73
|
-
)
|
|
74
|
-
span.set_attribute("slim.topic.app", topic_name.app)
|
|
75
|
-
else:
|
|
76
|
-
traceparent = get_current_traceparent()
|
|
77
|
-
|
|
78
|
-
# Thread-safe access to kv_store
|
|
79
|
-
session_id = None
|
|
80
|
-
if traceparent:
|
|
81
|
-
with _kv_lock:
|
|
82
|
-
session_id = kv_store.get(f"execution.{traceparent}")
|
|
83
|
-
|
|
84
|
-
headers = {
|
|
85
|
-
"session_id": session_id if session_id else None,
|
|
86
|
-
"traceparent": traceparent,
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
# Set baggage context
|
|
90
|
-
if traceparent and session_id:
|
|
91
|
-
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
92
|
-
|
|
93
|
-
# Wrap message with headers - handle different message positions
|
|
94
|
-
message_arg_index = 1 # message will typically be the second argument
|
|
95
|
-
if len(args) > message_arg_index:
|
|
96
|
-
original_args = list(args)
|
|
97
|
-
message = original_args[message_arg_index]
|
|
98
|
-
wrapped_message = SLIMInstrumentor._wrap_message_with_headers(
|
|
99
|
-
self, message, headers
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
# Convert wrapped message back to bytes if needed
|
|
103
|
-
if isinstance(wrapped_message, dict):
|
|
104
|
-
message_to_send = json.dumps(wrapped_message).encode("utf-8")
|
|
105
|
-
else:
|
|
106
|
-
message_to_send = wrapped_message
|
|
156
|
+
self._instrument_service(slim_bindings)
|
|
157
|
+
self._instrument_app(slim_bindings)
|
|
158
|
+
self._instrument_sessions(slim_bindings)
|
|
107
159
|
|
|
108
|
-
|
|
109
|
-
|
|
160
|
+
def _instrument_service(self, slim_bindings):
|
|
161
|
+
"""Instrument SLIM v1.x Service class."""
|
|
162
|
+
if not hasattr(slim_bindings, "Service"):
|
|
163
|
+
return
|
|
110
164
|
|
|
111
|
-
|
|
165
|
+
Service = slim_bindings.Service
|
|
112
166
|
|
|
113
|
-
|
|
167
|
+
# connect_async
|
|
168
|
+
if hasattr(Service, "connect_async"):
|
|
169
|
+
original_connect_async = Service.connect_async
|
|
114
170
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
original_publish_to = slim_bindings.Slim.publish_to
|
|
118
|
-
|
|
119
|
-
@functools.wraps(original_publish_to)
|
|
120
|
-
async def instrumented_publish_to(
|
|
121
|
-
self, session_info, message, *args, **kwargs
|
|
122
|
-
):
|
|
171
|
+
@functools.wraps(original_connect_async)
|
|
172
|
+
async def wrapped_connect_async(self, config, *args, **kwargs):
|
|
123
173
|
if _global_tracer:
|
|
124
174
|
with _global_tracer.start_as_current_span(
|
|
125
|
-
"slim.
|
|
175
|
+
"slim.service.connect"
|
|
126
176
|
) as span:
|
|
127
|
-
|
|
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)
|
|
128
185
|
|
|
129
|
-
|
|
130
|
-
if hasattr(session_info, "id"):
|
|
131
|
-
span.set_attribute("slim.session.id", str(session_info.id))
|
|
132
|
-
else:
|
|
133
|
-
traceparent = get_current_traceparent()
|
|
186
|
+
Service.connect_async = wrapped_connect_async
|
|
134
187
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
with _kv_lock:
|
|
139
|
-
session_id = kv_store.get(f"execution.{traceparent}")
|
|
140
|
-
if session_id:
|
|
141
|
-
kv_store.set(f"execution.{traceparent}", session_id)
|
|
188
|
+
# run_server_async
|
|
189
|
+
if hasattr(Service, "run_server_async"):
|
|
190
|
+
original_run_server_async = Service.run_server_async
|
|
142
191
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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)
|
|
154
205
|
|
|
155
|
-
|
|
156
|
-
self, message, headers
|
|
157
|
-
)
|
|
158
|
-
message_to_send = (
|
|
159
|
-
json.dumps(wrapped_message).encode("utf-8")
|
|
160
|
-
if isinstance(wrapped_message, dict)
|
|
161
|
-
else wrapped_message
|
|
162
|
-
)
|
|
206
|
+
Service.run_server_async = wrapped_run_server_async
|
|
163
207
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
208
|
+
def _instrument_app(self, slim_bindings):
|
|
209
|
+
"""Instrument SLIM v1.x App class."""
|
|
210
|
+
if not hasattr(slim_bindings, "App"):
|
|
211
|
+
return
|
|
167
212
|
|
|
168
|
-
|
|
213
|
+
App = slim_bindings.App
|
|
169
214
|
|
|
170
|
-
#
|
|
171
|
-
if hasattr(
|
|
172
|
-
|
|
215
|
+
# create_session_async
|
|
216
|
+
if hasattr(App, "create_session_async"):
|
|
217
|
+
original_create_session_async = App.create_session_async
|
|
173
218
|
|
|
174
|
-
@functools.wraps(
|
|
175
|
-
async def
|
|
176
|
-
self,
|
|
219
|
+
@functools.wraps(original_create_session_async)
|
|
220
|
+
async def wrapped_create_session_async(
|
|
221
|
+
self, config, dest=None, *args, **kwargs
|
|
177
222
|
):
|
|
178
223
|
if _global_tracer:
|
|
179
224
|
with _global_tracer.start_as_current_span(
|
|
180
|
-
"slim.
|
|
225
|
+
"slim.app.create_session"
|
|
181
226
|
) as span:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if hasattr(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
"slim.remote.namespace", remote_name.namespace
|
|
193
|
-
)
|
|
194
|
-
span.set_attribute("slim.remote.app", remote_name.app)
|
|
195
|
-
else:
|
|
196
|
-
traceparent = get_current_traceparent()
|
|
197
|
-
|
|
198
|
-
# Thread-safe access to kv_store
|
|
199
|
-
session_id = None
|
|
200
|
-
if traceparent:
|
|
201
|
-
with _kv_lock:
|
|
202
|
-
session_id = kv_store.get(f"execution.{traceparent}")
|
|
203
|
-
|
|
204
|
-
headers = {
|
|
205
|
-
"session_id": session_id if session_id else None,
|
|
206
|
-
"traceparent": traceparent,
|
|
207
|
-
"slim_session_id": str(session_info.id)
|
|
208
|
-
if hasattr(session_info, "id")
|
|
209
|
-
else None,
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
# Set baggage context
|
|
213
|
-
if traceparent and session_id:
|
|
214
|
-
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
215
|
-
|
|
216
|
-
wrapped_message = SLIMInstrumentor._wrap_message_with_headers(
|
|
217
|
-
self, message, headers
|
|
218
|
-
)
|
|
219
|
-
message_to_send = (
|
|
220
|
-
json.dumps(wrapped_message).encode("utf-8")
|
|
221
|
-
if isinstance(wrapped_message, dict)
|
|
222
|
-
else wrapped_message
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
kwargs_with_timeout = kwargs.copy()
|
|
226
|
-
if timeout is not None:
|
|
227
|
-
kwargs_with_timeout["timeout"] = timeout
|
|
228
|
-
|
|
229
|
-
return await original_request_reply(
|
|
230
|
-
self,
|
|
231
|
-
session_info,
|
|
232
|
-
message_to_send,
|
|
233
|
-
remote_name,
|
|
234
|
-
**kwargs_with_timeout,
|
|
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
|
|
235
237
|
)
|
|
236
238
|
|
|
237
|
-
|
|
239
|
+
App.create_session_async = wrapped_create_session_async
|
|
238
240
|
|
|
239
|
-
#
|
|
240
|
-
if hasattr(
|
|
241
|
-
|
|
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
|
|
242
244
|
|
|
243
|
-
@functools.wraps(
|
|
244
|
-
async def
|
|
245
|
-
self,
|
|
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
|
|
246
248
|
):
|
|
247
249
|
if _global_tracer:
|
|
248
|
-
with _global_tracer.start_as_current_span(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
span.set_attribute(
|
|
261
|
-
"slim.participant.app", participant_name.app
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
return await original_invite(
|
|
265
|
-
self, session_info, participant_name, *args, **kwargs
|
|
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
|
|
266
262
|
)
|
|
267
263
|
|
|
268
|
-
|
|
264
|
+
App.create_session_and_wait_async = wrapped_create_session_and_wait_async
|
|
269
265
|
|
|
270
|
-
#
|
|
271
|
-
if hasattr(
|
|
272
|
-
|
|
266
|
+
# subscribe_async
|
|
267
|
+
if hasattr(App, "subscribe_async"):
|
|
268
|
+
original_subscribe_async = App.subscribe_async
|
|
273
269
|
|
|
274
|
-
@functools.wraps(
|
|
275
|
-
async def
|
|
270
|
+
@functools.wraps(original_subscribe_async)
|
|
271
|
+
async def wrapped_subscribe_async(self, name, conn_id, *args, **kwargs):
|
|
276
272
|
if _global_tracer:
|
|
277
|
-
with _global_tracer.start_as_current_span(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
"slim.route.organization", remote_name.organization
|
|
282
|
-
)
|
|
273
|
+
with _global_tracer.start_as_current_span(
|
|
274
|
+
"slim.app.subscribe"
|
|
275
|
+
) as span:
|
|
276
|
+
if hasattr(name, "organization"):
|
|
283
277
|
span.set_attribute(
|
|
284
|
-
"slim.
|
|
278
|
+
"slim.name",
|
|
279
|
+
f"{name.organization}/{name.namespace}/{name.app}",
|
|
285
280
|
)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
return await original_set_route(self, remote_name, *args, **kwargs)
|
|
289
|
-
|
|
290
|
-
slim_bindings.Slim.set_route = instrumented_set_route
|
|
291
|
-
|
|
292
|
-
# Instrument `receive` - only if it exists (removed in v0.6.0)
|
|
293
|
-
if hasattr(slim_bindings.Slim, "receive"):
|
|
294
|
-
original_receive = slim_bindings.Slim.receive
|
|
295
|
-
|
|
296
|
-
@functools.wraps(original_receive)
|
|
297
|
-
async def instrumented_receive(
|
|
298
|
-
self, session=None, timeout=None, *args, **kwargs
|
|
299
|
-
):
|
|
300
|
-
# Handle both old and new API patterns
|
|
301
|
-
if session is not None or timeout is not None:
|
|
302
|
-
# New API pattern with session parameter
|
|
303
|
-
kwargs_with_params = kwargs.copy()
|
|
304
|
-
if session is not None:
|
|
305
|
-
kwargs_with_params["session"] = session
|
|
306
|
-
if timeout is not None:
|
|
307
|
-
kwargs_with_params["timeout"] = timeout
|
|
308
|
-
recv_session, raw_message = await original_receive(
|
|
309
|
-
self, **kwargs_with_params
|
|
310
|
-
)
|
|
311
|
-
else:
|
|
312
|
-
# Legacy API pattern
|
|
313
|
-
recv_session, raw_message = await original_receive(
|
|
314
|
-
self, *args, **kwargs
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
if raw_message is None:
|
|
318
|
-
return recv_session, raw_message
|
|
319
|
-
|
|
320
|
-
try:
|
|
321
|
-
message_dict = json.loads(raw_message.decode())
|
|
322
|
-
headers = message_dict.get("headers", {})
|
|
323
|
-
|
|
324
|
-
# Extract traceparent and session info from headers
|
|
325
|
-
traceparent = headers.get("traceparent")
|
|
326
|
-
session_id = headers.get("session_id")
|
|
327
|
-
|
|
328
|
-
# Create carrier for context propagation
|
|
329
|
-
carrier = {}
|
|
330
|
-
for key in ["traceparent", "Traceparent", "baggage", "Baggage"]:
|
|
331
|
-
if key.lower() in [k.lower() for k in headers.keys()]:
|
|
332
|
-
for k in headers.keys():
|
|
333
|
-
if k.lower() == key.lower():
|
|
334
|
-
carrier[key.lower()] = headers[k]
|
|
335
|
-
|
|
336
|
-
# Restore trace context
|
|
337
|
-
if carrier and traceparent:
|
|
338
|
-
ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
|
|
339
|
-
ctx = W3CBaggagePropagator().extract(
|
|
340
|
-
carrier=carrier, context=ctx
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
# Activate the restored context
|
|
344
|
-
token = context.attach(ctx)
|
|
345
|
-
|
|
346
|
-
try:
|
|
347
|
-
# Set execution ID with the restored context
|
|
348
|
-
if session_id and session_id != "None":
|
|
349
|
-
set_session_id(session_id, traceparent=traceparent)
|
|
350
|
-
|
|
351
|
-
# Store in kv_store with thread safety
|
|
352
|
-
with _kv_lock:
|
|
353
|
-
kv_store.set(f"execution.{traceparent}", session_id)
|
|
354
|
-
|
|
355
|
-
# DON'T detach the context yet - we need it to persist for the callback
|
|
356
|
-
# The context will be cleaned up later or by the garbage collector
|
|
357
|
-
|
|
358
|
-
except Exception as e:
|
|
359
|
-
# Only detach on error
|
|
360
|
-
context.detach(token)
|
|
361
|
-
raise e
|
|
362
|
-
elif traceparent and session_id and session_id != "None":
|
|
363
|
-
# Even without carrier context, set session ID if we have the data
|
|
364
|
-
set_session_id(session_id, traceparent=traceparent)
|
|
365
|
-
|
|
366
|
-
# Fallback: check stored execution ID if not found in headers
|
|
367
|
-
if traceparent and (not session_id or session_id == "None"):
|
|
368
|
-
with _kv_lock:
|
|
369
|
-
stored_session_id = kv_store.get(f"execution.{traceparent}")
|
|
370
|
-
if stored_session_id:
|
|
371
|
-
session_id = stored_session_id
|
|
372
|
-
set_session_id(session_id, traceparent=traceparent)
|
|
373
|
-
|
|
374
|
-
# Process and clean the message
|
|
375
|
-
message_to_return = message_dict.copy()
|
|
376
|
-
if "headers" in message_to_return:
|
|
377
|
-
headers_copy = message_to_return["headers"].copy()
|
|
378
|
-
# Remove tracing-specific headers but keep other headers
|
|
379
|
-
headers_copy.pop("traceparent", None)
|
|
380
|
-
headers_copy.pop("session_id", None)
|
|
381
|
-
headers_copy.pop("slim_session_id", None)
|
|
382
|
-
if headers_copy:
|
|
383
|
-
message_to_return["headers"] = headers_copy
|
|
384
|
-
else:
|
|
385
|
-
message_to_return.pop("headers", None)
|
|
386
|
-
|
|
387
|
-
# Return processed message
|
|
388
|
-
if len(message_to_return) == 1 and "payload" in message_to_return:
|
|
389
|
-
payload = message_to_return["payload"]
|
|
390
|
-
if isinstance(payload, str):
|
|
391
|
-
try:
|
|
392
|
-
payload_dict = json.loads(payload)
|
|
393
|
-
return recv_session, json.dumps(payload_dict).encode(
|
|
394
|
-
"utf-8"
|
|
395
|
-
)
|
|
396
|
-
except json.JSONDecodeError:
|
|
397
|
-
return recv_session, payload.encode(
|
|
398
|
-
"utf-8"
|
|
399
|
-
) if isinstance(payload, str) else payload
|
|
400
|
-
return recv_session, json.dumps(payload).encode(
|
|
401
|
-
"utf-8"
|
|
402
|
-
) if isinstance(payload, (dict, list)) else payload
|
|
403
|
-
else:
|
|
404
|
-
return recv_session, json.dumps(message_to_return).encode(
|
|
405
|
-
"utf-8"
|
|
281
|
+
return await original_subscribe_async(
|
|
282
|
+
self, name, conn_id, *args, **kwargs
|
|
406
283
|
)
|
|
284
|
+
return await original_subscribe_async(
|
|
285
|
+
self, name, conn_id, *args, **kwargs
|
|
286
|
+
)
|
|
407
287
|
|
|
408
|
-
|
|
409
|
-
print(f"Error processing message: {e}")
|
|
410
|
-
return recv_session, raw_message
|
|
411
|
-
|
|
412
|
-
slim_bindings.Slim.receive = instrumented_receive
|
|
413
|
-
|
|
414
|
-
# Instrument `connect` - only if it exists
|
|
415
|
-
if hasattr(slim_bindings.Slim, "connect"):
|
|
416
|
-
original_connect = slim_bindings.Slim.connect
|
|
417
|
-
|
|
418
|
-
@functools.wraps(original_connect)
|
|
419
|
-
async def instrumented_connect(self, *args, **kwargs):
|
|
420
|
-
if _global_tracer:
|
|
421
|
-
with _global_tracer.start_as_current_span("slim.connect"):
|
|
422
|
-
return await original_connect(self, *args, **kwargs)
|
|
423
|
-
else:
|
|
424
|
-
return await original_connect(self, *args, **kwargs)
|
|
425
|
-
|
|
426
|
-
slim_bindings.Slim.connect = instrumented_connect
|
|
288
|
+
App.subscribe_async = wrapped_subscribe_async
|
|
427
289
|
|
|
428
|
-
#
|
|
429
|
-
if hasattr(
|
|
430
|
-
|
|
290
|
+
# set_route_async
|
|
291
|
+
if hasattr(App, "set_route_async"):
|
|
292
|
+
original_set_route_async = App.set_route_async
|
|
431
293
|
|
|
432
|
-
@functools.wraps(
|
|
433
|
-
async def
|
|
294
|
+
@functools.wraps(original_set_route_async)
|
|
295
|
+
async def wrapped_set_route_async(self, name, conn_id, *args, **kwargs):
|
|
434
296
|
if _global_tracer:
|
|
435
297
|
with _global_tracer.start_as_current_span(
|
|
436
|
-
"slim.
|
|
298
|
+
"slim.app.set_route"
|
|
437
299
|
) as span:
|
|
438
|
-
|
|
439
|
-
|
|
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
|
|
440
307
|
)
|
|
308
|
+
return await original_set_route_async(
|
|
309
|
+
self, name, conn_id, *args, **kwargs
|
|
310
|
+
)
|
|
441
311
|
|
|
442
|
-
|
|
443
|
-
if hasattr(session_info, "id"):
|
|
444
|
-
span.set_attribute("slim.session.id", str(session_info.id))
|
|
445
|
-
|
|
446
|
-
return session_info
|
|
447
|
-
else:
|
|
448
|
-
return await original_create_session(self, config, *args, **kwargs)
|
|
449
|
-
|
|
450
|
-
slim_bindings.Slim.create_session = instrumented_create_session
|
|
451
|
-
|
|
452
|
-
# Instrument new v0.6.0+ session-level methods
|
|
453
|
-
# These methods are available on Session objects, not the Slim app
|
|
454
|
-
self._instrument_session_methods(slim_bindings)
|
|
312
|
+
App.set_route_async = wrapped_set_route_async
|
|
455
313
|
|
|
456
|
-
#
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
original_listen_for_session = slim_bindings.Slim.listen_for_session
|
|
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
|
|
460
317
|
|
|
461
318
|
@functools.wraps(original_listen_for_session)
|
|
462
|
-
|
|
319
|
+
def wrapped_listen_for_session(self, *args, **kwargs):
|
|
463
320
|
if _global_tracer:
|
|
464
321
|
with _global_tracer.start_as_current_span(
|
|
465
|
-
"slim.listen_for_session"
|
|
322
|
+
"slim.app.listen_for_session"
|
|
466
323
|
):
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
)
|
|
324
|
+
return original_listen_for_session(self, *args, **kwargs)
|
|
325
|
+
return original_listen_for_session(self, *args, **kwargs)
|
|
470
326
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
def _instrument_session_methods(self, slim_bindings):
|
|
478
|
-
# Try to find session-related classes in the slim_bindings module
|
|
479
|
-
session_classes = []
|
|
480
|
-
|
|
481
|
-
# Check for v0.6.0+ Session classes
|
|
482
|
-
if hasattr(slim_bindings, "Session"):
|
|
483
|
-
for attr_name in ["Session", "P2PSession", "GroupSession"]:
|
|
484
|
-
if hasattr(slim_bindings, attr_name):
|
|
485
|
-
session_class = getattr(slim_bindings, attr_name)
|
|
486
|
-
session_classes.append((attr_name, session_class))
|
|
487
|
-
|
|
488
|
-
# Check for older PySession class (pre-v0.6.0)
|
|
489
|
-
if hasattr(slim_bindings, "PySession"):
|
|
490
|
-
session_classes.append(("PySession", slim_bindings.PySession))
|
|
491
|
-
|
|
492
|
-
# Also look for any class that has session-like methods
|
|
493
|
-
for attr_name in dir(slim_bindings):
|
|
494
|
-
attr = getattr(slim_bindings, attr_name)
|
|
495
|
-
if isinstance(attr, type) and (
|
|
496
|
-
hasattr(attr, "get_message") or hasattr(attr, "publish")
|
|
497
|
-
):
|
|
498
|
-
session_classes.append((attr_name, attr))
|
|
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()
|
|
499
332
|
|
|
500
|
-
#
|
|
501
|
-
for
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
self._instrument_session_get_message(session_class)
|
|
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))
|
|
505
337
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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)
|
|
509
345
|
|
|
510
|
-
|
|
511
|
-
|
|
346
|
+
for session_class in session_classes:
|
|
347
|
+
if hasattr(session_class, "get_message_async"):
|
|
348
|
+
self._wrap_get_message(session_class)
|
|
512
349
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
original_get_message = session_class.get_message
|
|
350
|
+
if hasattr(session_class, "publish_async"):
|
|
351
|
+
self._wrap_publish(session_class, "publish_async")
|
|
516
352
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
# Handle the message reception similar to the old receive method
|
|
520
|
-
if timeout is not None:
|
|
521
|
-
kwargs["timeout"] = timeout
|
|
353
|
+
if hasattr(session_class, "publish_to_async"):
|
|
354
|
+
self._wrap_publish(session_class, "publish_to_async", msg_idx=1)
|
|
522
355
|
|
|
523
|
-
|
|
356
|
+
def _wrap_get_message(self, session_class):
|
|
357
|
+
"""Wrap get_message_async to extract tracing context."""
|
|
358
|
+
orig = session_class.get_message_async
|
|
524
359
|
|
|
525
|
-
|
|
360
|
+
@functools.wraps(orig)
|
|
361
|
+
async def wrapped(self, *args, **kwargs):
|
|
362
|
+
result = await orig(self, *args, **kwargs)
|
|
526
363
|
if result is None:
|
|
527
364
|
return result
|
|
528
365
|
|
|
529
|
-
#
|
|
530
|
-
if
|
|
531
|
-
|
|
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]
|
|
532
371
|
else:
|
|
533
|
-
|
|
534
|
-
message_context = None
|
|
372
|
+
raw = result
|
|
535
373
|
|
|
536
|
-
|
|
537
|
-
return result
|
|
374
|
+
processed = _process_received_message(raw)
|
|
538
375
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
elif isinstance(raw_message, str):
|
|
544
|
-
message_dict = json.loads(raw_message)
|
|
545
|
-
elif isinstance(raw_message, dict):
|
|
546
|
-
message_dict = raw_message
|
|
547
|
-
else:
|
|
548
|
-
# Unknown type, return as-is
|
|
549
|
-
return result
|
|
550
|
-
|
|
551
|
-
headers = message_dict.get("headers", {})
|
|
552
|
-
|
|
553
|
-
# Extract traceparent and session info from headers
|
|
554
|
-
traceparent = headers.get("traceparent")
|
|
555
|
-
session_id = headers.get("session_id")
|
|
556
|
-
|
|
557
|
-
# Create carrier for context propagation
|
|
558
|
-
carrier = {}
|
|
559
|
-
for key in ["traceparent", "Traceparent", "baggage", "Baggage"]:
|
|
560
|
-
if key.lower() in [k.lower() for k in headers.keys()]:
|
|
561
|
-
for k in headers.keys():
|
|
562
|
-
if k.lower() == key.lower():
|
|
563
|
-
carrier[key.lower()] = headers[k]
|
|
564
|
-
|
|
565
|
-
# Restore trace context
|
|
566
|
-
if carrier and traceparent:
|
|
567
|
-
ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
|
|
568
|
-
ctx = W3CBaggagePropagator().extract(carrier=carrier, context=ctx)
|
|
569
|
-
|
|
570
|
-
# Activate the restored context
|
|
571
|
-
token = context.attach(ctx)
|
|
572
|
-
|
|
573
|
-
try:
|
|
574
|
-
# Set execution ID with the restored context
|
|
575
|
-
if session_id and session_id != "None":
|
|
576
|
-
set_session_id(session_id, traceparent=traceparent)
|
|
577
|
-
|
|
578
|
-
# Store in kv_store with thread safety
|
|
579
|
-
with _kv_lock:
|
|
580
|
-
kv_store.set(f"execution.{traceparent}", session_id)
|
|
581
|
-
|
|
582
|
-
# DON'T detach the context yet - we need it to persist for the callback
|
|
583
|
-
|
|
584
|
-
except Exception as e:
|
|
585
|
-
# Only detach on error
|
|
586
|
-
context.detach(token)
|
|
587
|
-
raise e
|
|
588
|
-
elif traceparent and session_id and session_id != "None":
|
|
589
|
-
# Even without carrier context, set session ID if we have the data
|
|
590
|
-
set_session_id(session_id, traceparent=traceparent)
|
|
591
|
-
|
|
592
|
-
# Fallback: check stored execution ID if not found in headers
|
|
593
|
-
if traceparent and (not session_id or session_id == "None"):
|
|
594
|
-
with _kv_lock:
|
|
595
|
-
stored_session_id = kv_store.get(f"execution.{traceparent}")
|
|
596
|
-
if stored_session_id:
|
|
597
|
-
session_id = stored_session_id
|
|
598
|
-
set_session_id(session_id, traceparent=traceparent)
|
|
599
|
-
|
|
600
|
-
# Process and clean the message
|
|
601
|
-
message_to_return = message_dict.copy()
|
|
602
|
-
if "headers" in message_to_return:
|
|
603
|
-
headers_copy = message_to_return["headers"].copy()
|
|
604
|
-
# Remove tracing-specific headers but keep other headers
|
|
605
|
-
headers_copy.pop("traceparent", None)
|
|
606
|
-
headers_copy.pop("session_id", None)
|
|
607
|
-
headers_copy.pop("slim_session_id", None)
|
|
608
|
-
if headers_copy:
|
|
609
|
-
message_to_return["headers"] = headers_copy
|
|
610
|
-
else:
|
|
611
|
-
message_to_return.pop("headers", None)
|
|
612
|
-
|
|
613
|
-
# Return processed message, maintaining original return format
|
|
614
|
-
if len(message_to_return) == 1 and "payload" in message_to_return:
|
|
615
|
-
payload = message_to_return["payload"]
|
|
616
|
-
if isinstance(payload, str):
|
|
617
|
-
try:
|
|
618
|
-
payload_dict = json.loads(payload)
|
|
619
|
-
processed_message = json.dumps(payload_dict).encode("utf-8")
|
|
620
|
-
except json.JSONDecodeError:
|
|
621
|
-
processed_message = (
|
|
622
|
-
payload.encode("utf-8")
|
|
623
|
-
if isinstance(payload, str)
|
|
624
|
-
else payload
|
|
625
|
-
)
|
|
626
|
-
else:
|
|
627
|
-
processed_message = (
|
|
628
|
-
json.dumps(payload).encode("utf-8")
|
|
629
|
-
if isinstance(payload, (dict, list))
|
|
630
|
-
else payload
|
|
631
|
-
)
|
|
632
|
-
else:
|
|
633
|
-
processed_message = json.dumps(message_to_return).encode("utf-8")
|
|
376
|
+
if hasattr(result, "payload"):
|
|
377
|
+
try:
|
|
378
|
+
result.payload = processed
|
|
379
|
+
except AttributeError:
|
|
634
380
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
return processed_message
|
|
381
|
+
class Processed:
|
|
382
|
+
def __init__(self, ctx, payload):
|
|
383
|
+
self.context = ctx
|
|
384
|
+
self.payload = payload
|
|
640
385
|
|
|
641
|
-
|
|
642
|
-
print(f"Error processing message: {e}")
|
|
386
|
+
return Processed(result.context, processed)
|
|
643
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)
|
|
644
410
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
def _instrument_session_publish(self, session_class, method_name):
|
|
648
|
-
"""Instrument session publish methods"""
|
|
649
|
-
original_method = getattr(session_class, method_name)
|
|
411
|
+
slim_session_id = _get_session_id(self)
|
|
650
412
|
|
|
651
|
-
@functools.wraps(original_method)
|
|
652
|
-
async def instrumented_session_publish(self, *args, **kwargs):
|
|
653
413
|
if _global_tracer:
|
|
654
414
|
with _global_tracer.start_as_current_span(
|
|
655
415
|
f"session.{method_name}"
|
|
656
416
|
) as span:
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
# Add session context to span
|
|
660
|
-
if hasattr(self, "id"):
|
|
661
|
-
span.set_attribute("slim.session.id", str(self.id))
|
|
662
|
-
|
|
663
|
-
# Handle message wrapping
|
|
664
|
-
if args:
|
|
665
|
-
# Thread-safe access to kv_store
|
|
666
|
-
session_id = None
|
|
667
|
-
if traceparent:
|
|
668
|
-
with _kv_lock:
|
|
669
|
-
session_id = kv_store.get(f"execution.{traceparent}")
|
|
670
|
-
if not session_id:
|
|
671
|
-
session_id = get_value("session.id")
|
|
672
|
-
if session_id:
|
|
673
|
-
kv_store.set(
|
|
674
|
-
f"execution.{traceparent}", session_id
|
|
675
|
-
)
|
|
417
|
+
if slim_session_id:
|
|
418
|
+
span.set_attribute("slim.session.id", slim_session_id)
|
|
676
419
|
|
|
420
|
+
if args and len(args) > msg_idx and (traceparent or session_id):
|
|
677
421
|
headers = {
|
|
678
|
-
"session_id": session_id
|
|
422
|
+
"session_id": session_id,
|
|
679
423
|
"traceparent": traceparent,
|
|
680
|
-
"slim_session_id":
|
|
681
|
-
if hasattr(self, "id")
|
|
682
|
-
else None,
|
|
424
|
+
"slim_session_id": slim_session_id,
|
|
683
425
|
}
|
|
684
|
-
|
|
685
|
-
# Set baggage context
|
|
686
426
|
if traceparent and session_id:
|
|
687
427
|
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
688
428
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
1
|
|
693
|
-
if method_name == "publish_to"
|
|
694
|
-
or session_class.__name__ == "SessionContext"
|
|
695
|
-
else 0
|
|
429
|
+
args_list = list(args)
|
|
430
|
+
wrapped_msg = _wrap_message_with_headers(
|
|
431
|
+
args_list[msg_idx], headers
|
|
696
432
|
)
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
)
|
|
704
|
-
)
|
|
705
|
-
|
|
706
|
-
# Convert wrapped message back to bytes if needed
|
|
707
|
-
if isinstance(wrapped_message, dict):
|
|
708
|
-
message_to_send = json.dumps(wrapped_message).encode(
|
|
709
|
-
"utf-8"
|
|
710
|
-
)
|
|
711
|
-
else:
|
|
712
|
-
message_to_send = wrapped_message
|
|
713
|
-
|
|
714
|
-
args_list[message_idx] = message_to_send
|
|
715
|
-
args = tuple(args_list)
|
|
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)
|
|
716
439
|
|
|
717
|
-
return await
|
|
440
|
+
return await orig(self, *args, **kwargs)
|
|
718
441
|
else:
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
if session_id:
|
|
729
|
-
kv_store.set(f"execution.{traceparent}", session_id)
|
|
730
|
-
|
|
731
|
-
if traceparent or session_id:
|
|
732
|
-
headers = {
|
|
733
|
-
"session_id": session_id if session_id else None,
|
|
734
|
-
"traceparent": traceparent,
|
|
735
|
-
"slim_session_id": str(self.id)
|
|
736
|
-
if hasattr(self, "id")
|
|
737
|
-
else None,
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
# Wrap the message (first argument for publish, second for publish_to)
|
|
741
|
-
message_idx = 1 if method_name == "publish_to" else 0
|
|
742
|
-
if len(args) > message_idx:
|
|
743
|
-
args_list = list(args)
|
|
744
|
-
message = args_list[message_idx]
|
|
745
|
-
wrapped_message = (
|
|
746
|
-
SLIMInstrumentor._wrap_message_with_headers(
|
|
747
|
-
None, message, headers
|
|
748
|
-
)
|
|
749
|
-
)
|
|
750
|
-
|
|
751
|
-
if isinstance(wrapped_message, dict):
|
|
752
|
-
message_to_send = json.dumps(wrapped_message).encode(
|
|
753
|
-
"utf-8"
|
|
754
|
-
)
|
|
755
|
-
else:
|
|
756
|
-
message_to_send = wrapped_message
|
|
757
|
-
|
|
758
|
-
args_list[message_idx] = message_to_send
|
|
759
|
-
args = tuple(args_list)
|
|
760
|
-
|
|
761
|
-
return await original_method(self, *args, **kwargs)
|
|
762
|
-
|
|
763
|
-
setattr(session_class, method_name, instrumented_session_publish)
|
|
764
|
-
|
|
765
|
-
def _instrument_session_method_if_exists(self, slim_bindings, method_name):
|
|
766
|
-
"""Helper to instrument a session method if it exists"""
|
|
767
|
-
|
|
768
|
-
# Look for session classes that might have this method
|
|
769
|
-
for attr_name in dir(slim_bindings):
|
|
770
|
-
attr = getattr(slim_bindings, attr_name)
|
|
771
|
-
if hasattr(attr, method_name):
|
|
772
|
-
original_method = getattr(attr, method_name)
|
|
773
|
-
|
|
774
|
-
if callable(original_method):
|
|
775
|
-
instrumented_method = self._create_session_method_wrapper(
|
|
776
|
-
method_name, original_method
|
|
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
|
|
777
451
|
)
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
async def instrumented_session_method(self, *args, **kwargs):
|
|
785
|
-
if _global_tracer:
|
|
786
|
-
with _global_tracer.start_as_current_span(f"session.{method_name}"):
|
|
787
|
-
traceparent = get_current_traceparent()
|
|
788
|
-
|
|
789
|
-
# Handle message wrapping for publish methods
|
|
790
|
-
if method_name in ["publish", "publish_to"] and args:
|
|
791
|
-
# Thread-safe access to kv_store
|
|
792
|
-
session_id = None
|
|
793
|
-
if traceparent:
|
|
794
|
-
with _kv_lock:
|
|
795
|
-
session_id = kv_store.get(f"execution.{traceparent}")
|
|
796
|
-
if session_id:
|
|
797
|
-
kv_store.set(f"execution.{traceparent}", session_id)
|
|
798
|
-
|
|
799
|
-
headers = {
|
|
800
|
-
"session_id": session_id if session_id else None,
|
|
801
|
-
"traceparent": traceparent,
|
|
802
|
-
"slim_session_id": str(self.id)
|
|
803
|
-
if hasattr(self, "id")
|
|
804
|
-
else None,
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
# Set baggage context
|
|
808
|
-
if traceparent and session_id:
|
|
809
|
-
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
810
|
-
|
|
811
|
-
# Wrap the message (first argument for publish, second for publish_to)
|
|
812
|
-
message_idx = 1 if method_name == "publish_to" else 0
|
|
813
|
-
if len(args) > message_idx:
|
|
814
|
-
args_list = list(args)
|
|
815
|
-
message = args_list[message_idx]
|
|
816
|
-
wrapped_message = SLIMInstrumentor._wrap_message_with_headers(
|
|
817
|
-
None,
|
|
818
|
-
message,
|
|
819
|
-
headers, # Pass None for self since it's a static method
|
|
820
|
-
)
|
|
821
|
-
|
|
822
|
-
# Convert wrapped message back to bytes if needed
|
|
823
|
-
if isinstance(wrapped_message, dict):
|
|
824
|
-
message_to_send = json.dumps(wrapped_message).encode(
|
|
825
|
-
"utf-8"
|
|
826
|
-
)
|
|
827
|
-
else:
|
|
828
|
-
message_to_send = wrapped_message
|
|
829
|
-
|
|
830
|
-
args_list[message_idx] = message_to_send
|
|
831
|
-
args = tuple(args_list)
|
|
832
|
-
|
|
833
|
-
return await original_method(self, *args, **kwargs)
|
|
834
|
-
else:
|
|
835
|
-
# Handle message wrapping even without tracing
|
|
836
|
-
if method_name in ["publish", "publish_to"] and args:
|
|
837
|
-
traceparent = get_current_traceparent()
|
|
838
|
-
session_id = None
|
|
839
|
-
if traceparent:
|
|
840
|
-
with _kv_lock:
|
|
841
|
-
session_id = kv_store.get(f"execution.{traceparent}")
|
|
842
|
-
|
|
843
|
-
if traceparent or session_id:
|
|
844
|
-
headers = {
|
|
845
|
-
"session_id": session_id if session_id else None,
|
|
846
|
-
"traceparent": traceparent,
|
|
847
|
-
"slim_session_id": str(self.id)
|
|
848
|
-
if hasattr(self, "id")
|
|
849
|
-
else None,
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
# Wrap the message
|
|
853
|
-
message_idx = 1 if method_name == "publish_to" else 0
|
|
854
|
-
if len(args) > message_idx:
|
|
855
|
-
args_list = list(args)
|
|
856
|
-
message = args_list[message_idx]
|
|
857
|
-
wrapped_message = (
|
|
858
|
-
SLIMInstrumentor._wrap_message_with_headers(
|
|
859
|
-
None, message, headers
|
|
860
|
-
)
|
|
861
|
-
)
|
|
862
|
-
|
|
863
|
-
if isinstance(wrapped_message, dict):
|
|
864
|
-
message_to_send = json.dumps(wrapped_message).encode(
|
|
865
|
-
"utf-8"
|
|
866
|
-
)
|
|
867
|
-
else:
|
|
868
|
-
message_to_send = wrapped_message
|
|
869
|
-
|
|
870
|
-
args_list[message_idx] = message_to_send
|
|
871
|
-
args = tuple(args_list)
|
|
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)
|
|
872
458
|
|
|
873
|
-
return await
|
|
459
|
+
return await orig(self, *args, **kwargs)
|
|
874
460
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
@staticmethod
|
|
878
|
-
def _wrap_message_with_headers(self, message, headers):
|
|
879
|
-
"""Helper method to wrap messages with headers consistently"""
|
|
880
|
-
if isinstance(message, bytes):
|
|
881
|
-
try:
|
|
882
|
-
decoded_message = message.decode("utf-8")
|
|
883
|
-
try:
|
|
884
|
-
original_message = json.loads(decoded_message)
|
|
885
|
-
if isinstance(original_message, dict):
|
|
886
|
-
wrapped_message = original_message.copy()
|
|
887
|
-
existing_headers = wrapped_message.get("headers", {})
|
|
888
|
-
existing_headers.update(headers)
|
|
889
|
-
wrapped_message["headers"] = existing_headers
|
|
890
|
-
else:
|
|
891
|
-
wrapped_message = {
|
|
892
|
-
"headers": headers,
|
|
893
|
-
"payload": original_message,
|
|
894
|
-
}
|
|
895
|
-
except json.JSONDecodeError:
|
|
896
|
-
wrapped_message = {"headers": headers, "payload": decoded_message}
|
|
897
|
-
except UnicodeDecodeError:
|
|
898
|
-
# Fix type annotation issue by ensuring message is bytes
|
|
899
|
-
encoded_message = (
|
|
900
|
-
message if isinstance(message, bytes) else message.encode("utf-8")
|
|
901
|
-
)
|
|
902
|
-
wrapped_message = {
|
|
903
|
-
"headers": headers,
|
|
904
|
-
"payload": base64.b64encode(encoded_message).decode("utf-8"),
|
|
905
|
-
}
|
|
906
|
-
elif isinstance(message, str):
|
|
907
|
-
try:
|
|
908
|
-
original_message = json.loads(message)
|
|
909
|
-
if isinstance(original_message, dict):
|
|
910
|
-
wrapped_message = original_message.copy()
|
|
911
|
-
existing_headers = wrapped_message.get("headers", {})
|
|
912
|
-
existing_headers.update(headers)
|
|
913
|
-
wrapped_message["headers"] = existing_headers
|
|
914
|
-
else:
|
|
915
|
-
wrapped_message = {"headers": headers, "payload": original_message}
|
|
916
|
-
except json.JSONDecodeError:
|
|
917
|
-
wrapped_message = {"headers": headers, "payload": message}
|
|
918
|
-
elif isinstance(message, dict):
|
|
919
|
-
wrapped_message = message.copy()
|
|
920
|
-
existing_headers = wrapped_message.get("headers", {})
|
|
921
|
-
existing_headers.update(headers)
|
|
922
|
-
wrapped_message["headers"] = existing_headers
|
|
923
|
-
else:
|
|
924
|
-
wrapped_message = {"headers": headers, "payload": json.dumps(message)}
|
|
925
|
-
|
|
926
|
-
return wrapped_message
|
|
461
|
+
setattr(session_class, method_name, wrapped)
|
|
927
462
|
|
|
928
463
|
def _uninstrument(self, **kwargs):
|
|
929
464
|
try:
|
|
930
465
|
import slim_bindings
|
|
931
466
|
except ImportError:
|
|
932
|
-
|
|
933
|
-
|
|
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
|
+
],
|
|
934
489
|
)
|
|
935
490
|
|
|
936
|
-
# Restore
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
"connect",
|
|
943
|
-
"create_session",
|
|
944
|
-
"invite",
|
|
945
|
-
"set_route",
|
|
946
|
-
"listen_for_session", # v0.6.0+
|
|
947
|
-
]
|
|
948
|
-
|
|
949
|
-
for method_name in methods_to_restore:
|
|
950
|
-
if hasattr(slim_bindings.Slim, method_name):
|
|
951
|
-
original_method = getattr(slim_bindings.Slim, method_name)
|
|
952
|
-
if hasattr(original_method, "__wrapped__"):
|
|
953
|
-
setattr(
|
|
954
|
-
slim_bindings.Slim, method_name, original_method.__wrapped__
|
|
955
|
-
)
|
|
956
|
-
|
|
957
|
-
# Also try to restore session-level methods (v0.6.0+)
|
|
958
|
-
# This is best-effort since session classes may vary
|
|
959
|
-
session_methods_to_restore = [
|
|
960
|
-
"publish",
|
|
961
|
-
"publish_to",
|
|
962
|
-
"get_message",
|
|
963
|
-
"invite",
|
|
964
|
-
"remove",
|
|
965
|
-
]
|
|
966
|
-
|
|
967
|
-
for attr_name in dir(slim_bindings):
|
|
968
|
-
attr = getattr(slim_bindings, attr_name)
|
|
969
|
-
for method_name in session_methods_to_restore:
|
|
970
|
-
if hasattr(attr, method_name):
|
|
971
|
-
original_method = getattr(attr, method_name)
|
|
972
|
-
if hasattr(original_method, "__wrapped__"):
|
|
973
|
-
setattr(attr, method_name, original_method.__wrapped__)
|
|
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)
|