ioa-observe-sdk 1.0.20__py3-none-any.whl → 1.0.21__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 +503 -68
- {ioa_observe_sdk-1.0.20.dist-info → ioa_observe_sdk-1.0.21.dist-info}/METADATA +15 -14
- {ioa_observe_sdk-1.0.20.dist-info → ioa_observe_sdk-1.0.21.dist-info}/RECORD +6 -6
- {ioa_observe_sdk-1.0.20.dist-info → ioa_observe_sdk-1.0.21.dist-info}/WHEEL +0 -0
- {ioa_observe_sdk-1.0.20.dist-info → ioa_observe_sdk-1.0.21.dist-info}/licenses/LICENSE.md +0 -0
- {ioa_observe_sdk-1.0.20.dist-info → ioa_observe_sdk-1.0.21.dist-info}/top_level.txt +0 -0
|
@@ -16,7 +16,7 @@ from ioa_observe.sdk import TracerWrapper
|
|
|
16
16
|
from ioa_observe.sdk.client import kv_store
|
|
17
17
|
from ioa_observe.sdk.tracing import set_session_id, get_current_traceparent
|
|
18
18
|
|
|
19
|
-
_instruments = ("slim-bindings >= 0.4",)
|
|
19
|
+
_instruments = ("slim-bindings >= 0.4.0",)
|
|
20
20
|
_global_tracer = None
|
|
21
21
|
_kv_lock = threading.RLock() # Add thread-safety for kv_store operations
|
|
22
22
|
|
|
@@ -39,7 +39,9 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
# Instrument `publish` method - handles multiple signatures
|
|
42
|
+
# In v0.6.0+, publish moved from Slim class to Session objects
|
|
42
43
|
if hasattr(slim_bindings.Slim, "publish"):
|
|
44
|
+
# Legacy v0.5.x app-level publish method
|
|
43
45
|
original_publish = slim_bindings.Slim.publish
|
|
44
46
|
|
|
45
47
|
@functools.wraps(original_publish)
|
|
@@ -166,7 +168,7 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
166
168
|
|
|
167
169
|
slim_bindings.Slim.publish_to = instrumented_publish_to
|
|
168
170
|
|
|
169
|
-
# Instrument `request_reply` (
|
|
171
|
+
# Instrument `request_reply` (v0.4.0+ to v0.5.x method, removed in v0.6.0)
|
|
170
172
|
if hasattr(slim_bindings.Slim, "request_reply"):
|
|
171
173
|
original_request_reply = slim_bindings.Slim.request_reply
|
|
172
174
|
|
|
@@ -290,35 +292,261 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
290
292
|
|
|
291
293
|
slim_bindings.Slim.set_route = instrumented_set_route
|
|
292
294
|
|
|
293
|
-
# Instrument `receive`
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
295
|
+
# Instrument `receive` - only if it exists (removed in v0.6.0)
|
|
296
|
+
if hasattr(slim_bindings.Slim, "receive"):
|
|
297
|
+
original_receive = slim_bindings.Slim.receive
|
|
298
|
+
|
|
299
|
+
@functools.wraps(original_receive)
|
|
300
|
+
async def instrumented_receive(
|
|
301
|
+
self, session=None, timeout=None, *args, **kwargs
|
|
302
|
+
):
|
|
303
|
+
# Handle both old and new API patterns
|
|
304
|
+
if session is not None or timeout is not None:
|
|
305
|
+
# New API pattern with session parameter
|
|
306
|
+
kwargs_with_params = kwargs.copy()
|
|
307
|
+
if session is not None:
|
|
308
|
+
kwargs_with_params["session"] = session
|
|
309
|
+
if timeout is not None:
|
|
310
|
+
kwargs_with_params["timeout"] = timeout
|
|
311
|
+
recv_session, raw_message = await original_receive(
|
|
312
|
+
self, **kwargs_with_params
|
|
313
|
+
)
|
|
314
|
+
else:
|
|
315
|
+
# Legacy API pattern
|
|
316
|
+
recv_session, raw_message = await original_receive(
|
|
317
|
+
self, *args, **kwargs
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
if raw_message is None:
|
|
321
|
+
return recv_session, raw_message
|
|
322
|
+
|
|
323
|
+
try:
|
|
324
|
+
message_dict = json.loads(raw_message.decode())
|
|
325
|
+
headers = message_dict.get("headers", {})
|
|
326
|
+
|
|
327
|
+
# Extract traceparent and session info from headers
|
|
328
|
+
traceparent = headers.get("traceparent")
|
|
329
|
+
session_id = headers.get("session_id")
|
|
330
|
+
|
|
331
|
+
# Create carrier for context propagation
|
|
332
|
+
carrier = {}
|
|
333
|
+
for key in ["traceparent", "Traceparent", "baggage", "Baggage"]:
|
|
334
|
+
if key.lower() in [k.lower() for k in headers.keys()]:
|
|
335
|
+
for k in headers.keys():
|
|
336
|
+
if k.lower() == key.lower():
|
|
337
|
+
carrier[key.lower()] = headers[k]
|
|
338
|
+
|
|
339
|
+
# Restore trace context
|
|
340
|
+
if carrier and traceparent:
|
|
341
|
+
ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
|
|
342
|
+
ctx = W3CBaggagePropagator().extract(
|
|
343
|
+
carrier=carrier, context=ctx
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# Activate the restored context
|
|
347
|
+
token = context.attach(ctx)
|
|
348
|
+
|
|
349
|
+
try:
|
|
350
|
+
# Set execution ID with the restored context
|
|
351
|
+
if session_id and session_id != "None":
|
|
352
|
+
set_session_id(session_id, traceparent=traceparent)
|
|
353
|
+
|
|
354
|
+
# Store in kv_store with thread safety
|
|
355
|
+
with _kv_lock:
|
|
356
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
357
|
+
|
|
358
|
+
# DON'T detach the context yet - we need it to persist for the callback
|
|
359
|
+
# The context will be cleaned up later or by the garbage collector
|
|
360
|
+
|
|
361
|
+
except Exception as e:
|
|
362
|
+
# Only detach on error
|
|
363
|
+
context.detach(token)
|
|
364
|
+
raise e
|
|
365
|
+
elif traceparent and session_id and session_id != "None":
|
|
366
|
+
# Even without carrier context, set session ID if we have the data
|
|
367
|
+
set_session_id(session_id, traceparent=traceparent)
|
|
368
|
+
|
|
369
|
+
# Fallback: check stored execution ID if not found in headers
|
|
370
|
+
if traceparent and (not session_id or session_id == "None"):
|
|
371
|
+
with _kv_lock:
|
|
372
|
+
stored_session_id = kv_store.get(f"execution.{traceparent}")
|
|
373
|
+
if stored_session_id:
|
|
374
|
+
session_id = stored_session_id
|
|
375
|
+
set_session_id(session_id, traceparent=traceparent)
|
|
376
|
+
|
|
377
|
+
# Process and clean the message
|
|
378
|
+
message_to_return = message_dict.copy()
|
|
379
|
+
if "headers" in message_to_return:
|
|
380
|
+
headers_copy = message_to_return["headers"].copy()
|
|
381
|
+
# Remove tracing-specific headers but keep other headers
|
|
382
|
+
headers_copy.pop("traceparent", None)
|
|
383
|
+
headers_copy.pop("session_id", None)
|
|
384
|
+
headers_copy.pop("slim_session_id", None)
|
|
385
|
+
if headers_copy:
|
|
386
|
+
message_to_return["headers"] = headers_copy
|
|
387
|
+
else:
|
|
388
|
+
message_to_return.pop("headers", None)
|
|
389
|
+
|
|
390
|
+
# Return processed message
|
|
391
|
+
if len(message_to_return) == 1 and "payload" in message_to_return:
|
|
392
|
+
payload = message_to_return["payload"]
|
|
393
|
+
if isinstance(payload, str):
|
|
394
|
+
try:
|
|
395
|
+
payload_dict = json.loads(payload)
|
|
396
|
+
return recv_session, json.dumps(payload_dict).encode(
|
|
397
|
+
"utf-8"
|
|
398
|
+
)
|
|
399
|
+
except json.JSONDecodeError:
|
|
400
|
+
return recv_session, payload.encode(
|
|
401
|
+
"utf-8"
|
|
402
|
+
) if isinstance(payload, str) else payload
|
|
403
|
+
return recv_session, json.dumps(payload).encode(
|
|
404
|
+
"utf-8"
|
|
405
|
+
) if isinstance(payload, (dict, list)) else payload
|
|
406
|
+
else:
|
|
407
|
+
return recv_session, json.dumps(message_to_return).encode(
|
|
408
|
+
"utf-8"
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
except Exception as e:
|
|
412
|
+
print(f"Error processing message: {e}")
|
|
413
|
+
return recv_session, raw_message
|
|
414
|
+
|
|
415
|
+
slim_bindings.Slim.receive = instrumented_receive
|
|
416
|
+
|
|
417
|
+
# Instrument `connect` - only if it exists
|
|
418
|
+
if hasattr(slim_bindings.Slim, "connect"):
|
|
419
|
+
original_connect = slim_bindings.Slim.connect
|
|
420
|
+
|
|
421
|
+
@functools.wraps(original_connect)
|
|
422
|
+
async def instrumented_connect(self, *args, **kwargs):
|
|
423
|
+
if _global_tracer:
|
|
424
|
+
with _global_tracer.start_as_current_span("slim.connect"):
|
|
425
|
+
return await original_connect(self, *args, **kwargs)
|
|
426
|
+
else:
|
|
427
|
+
return await original_connect(self, *args, **kwargs)
|
|
428
|
+
|
|
429
|
+
slim_bindings.Slim.connect = instrumented_connect
|
|
430
|
+
|
|
431
|
+
# Instrument `create_session` (new v0.4.0+ method)
|
|
432
|
+
if hasattr(slim_bindings.Slim, "create_session"):
|
|
433
|
+
original_create_session = slim_bindings.Slim.create_session
|
|
434
|
+
|
|
435
|
+
@functools.wraps(original_create_session)
|
|
436
|
+
async def instrumented_create_session(self, config, *args, **kwargs):
|
|
437
|
+
if _global_tracer:
|
|
438
|
+
with _global_tracer.start_as_current_span(
|
|
439
|
+
"slim.create_session"
|
|
440
|
+
) as span:
|
|
441
|
+
session_info = await original_create_session(
|
|
442
|
+
self, config, *args, **kwargs
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
# Add session attributes to span
|
|
446
|
+
if hasattr(session_info, "id"):
|
|
447
|
+
span.set_attribute("slim.session.id", str(session_info.id))
|
|
448
|
+
|
|
449
|
+
return session_info
|
|
450
|
+
else:
|
|
451
|
+
return await original_create_session(self, config, *args, **kwargs)
|
|
452
|
+
|
|
453
|
+
slim_bindings.Slim.create_session = instrumented_create_session
|
|
454
|
+
|
|
455
|
+
# Instrument new v0.6.0+ session-level methods
|
|
456
|
+
# These methods are available on Session objects, not the Slim app
|
|
457
|
+
self._instrument_session_methods(slim_bindings)
|
|
458
|
+
|
|
459
|
+
# Instrument new v0.6.0+ app-level methods
|
|
460
|
+
# listen_for_session replaces app.receive() for new sessions in v0.6.0+
|
|
461
|
+
if hasattr(slim_bindings.Slim, "listen_for_session"):
|
|
462
|
+
original_listen_for_session = slim_bindings.Slim.listen_for_session
|
|
463
|
+
|
|
464
|
+
@functools.wraps(original_listen_for_session)
|
|
465
|
+
async def instrumented_listen_for_session(self, *args, **kwargs):
|
|
466
|
+
if _global_tracer:
|
|
467
|
+
with _global_tracer.start_as_current_span(
|
|
468
|
+
"slim.listen_for_session"
|
|
469
|
+
):
|
|
470
|
+
session = await original_listen_for_session(
|
|
471
|
+
self, *args, **kwargs
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
return session
|
|
475
|
+
else:
|
|
476
|
+
return await original_listen_for_session(self, *args, **kwargs)
|
|
477
|
+
|
|
478
|
+
slim_bindings.Slim.listen_for_session = instrumented_listen_for_session
|
|
479
|
+
|
|
480
|
+
def _instrument_session_methods(self, slim_bindings):
|
|
481
|
+
# In v0.6.0+, we need to instrument session classes dynamically
|
|
482
|
+
# Try to find session-related classes in the slim_bindings module
|
|
483
|
+
session_classes = []
|
|
484
|
+
|
|
485
|
+
# Look for common session class names
|
|
486
|
+
for attr_name in ["Session", "P2PSession", "GroupSession"]:
|
|
487
|
+
if hasattr(slim_bindings, attr_name):
|
|
488
|
+
session_class = getattr(slim_bindings, attr_name)
|
|
489
|
+
session_classes.append((attr_name, session_class))
|
|
490
|
+
|
|
491
|
+
# Also look for any class that has session-like methods
|
|
492
|
+
for attr_name in dir(slim_bindings):
|
|
493
|
+
attr = getattr(slim_bindings, attr_name)
|
|
494
|
+
if isinstance(attr, type) and (
|
|
495
|
+
hasattr(attr, "get_message") or hasattr(attr, "publish")
|
|
496
|
+
):
|
|
497
|
+
session_classes.append((attr_name, attr))
|
|
498
|
+
|
|
499
|
+
# Instrument session methods for found classes
|
|
500
|
+
for _, session_class in session_classes:
|
|
501
|
+
# Instrument get_message (v0.6.0+ replacement for receive)
|
|
502
|
+
if hasattr(session_class, "get_message"):
|
|
503
|
+
self._instrument_session_get_message(session_class)
|
|
504
|
+
|
|
505
|
+
# Instrument session publish methods
|
|
506
|
+
if hasattr(session_class, "publish"):
|
|
507
|
+
self._instrument_session_publish(session_class, "publish")
|
|
508
|
+
|
|
509
|
+
if hasattr(session_class, "publish_to"):
|
|
510
|
+
self._instrument_session_publish(session_class, "publish_to")
|
|
511
|
+
|
|
512
|
+
def _instrument_session_get_message(self, session_class):
|
|
513
|
+
"""Instrument session.get_message method (v0.6.0+)"""
|
|
514
|
+
original_get_message = session_class.get_message
|
|
515
|
+
|
|
516
|
+
@functools.wraps(original_get_message)
|
|
517
|
+
async def instrumented_get_message(self, timeout=None, *args, **kwargs):
|
|
518
|
+
# Handle the message reception similar to the old receive method
|
|
519
|
+
if timeout is not None:
|
|
520
|
+
kwargs["timeout"] = timeout
|
|
521
|
+
|
|
522
|
+
result = await original_get_message(self, **kwargs)
|
|
523
|
+
|
|
524
|
+
# Handle different return types from get_message
|
|
525
|
+
if result is None:
|
|
526
|
+
return result
|
|
527
|
+
|
|
528
|
+
# Check if get_message returns a tuple (context, message) or just message
|
|
529
|
+
if isinstance(result, tuple) and len(result) == 2:
|
|
530
|
+
message_context, raw_message = result
|
|
311
531
|
else:
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
self, *args, **kwargs
|
|
315
|
-
)
|
|
532
|
+
raw_message = result
|
|
533
|
+
message_context = None
|
|
316
534
|
|
|
317
535
|
if raw_message is None:
|
|
318
|
-
return
|
|
536
|
+
return result
|
|
319
537
|
|
|
320
538
|
try:
|
|
321
|
-
|
|
539
|
+
# Handle different message types
|
|
540
|
+
if isinstance(raw_message, bytes):
|
|
541
|
+
message_dict = json.loads(raw_message.decode())
|
|
542
|
+
elif isinstance(raw_message, str):
|
|
543
|
+
message_dict = json.loads(raw_message)
|
|
544
|
+
elif isinstance(raw_message, dict):
|
|
545
|
+
message_dict = raw_message
|
|
546
|
+
else:
|
|
547
|
+
# Unknown type, return as-is
|
|
548
|
+
return result
|
|
549
|
+
|
|
322
550
|
headers = message_dict.get("headers", {})
|
|
323
551
|
|
|
324
552
|
# Extract traceparent and session info from headers
|
|
@@ -351,7 +579,6 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
351
579
|
kv_store.set(f"execution.{traceparent}", session_id)
|
|
352
580
|
|
|
353
581
|
# DON'T detach the context yet - we need it to persist for the callback
|
|
354
|
-
# The context will be cleaned up later or by the garbage collector
|
|
355
582
|
|
|
356
583
|
except Exception as e:
|
|
357
584
|
# Only detach on error
|
|
@@ -382,68 +609,257 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
382
609
|
else:
|
|
383
610
|
message_to_return.pop("headers", None)
|
|
384
611
|
|
|
385
|
-
# Return processed message
|
|
612
|
+
# Return processed message, maintaining original return format
|
|
386
613
|
if len(message_to_return) == 1 and "payload" in message_to_return:
|
|
387
614
|
payload = message_to_return["payload"]
|
|
388
615
|
if isinstance(payload, str):
|
|
389
616
|
try:
|
|
390
617
|
payload_dict = json.loads(payload)
|
|
391
|
-
|
|
392
|
-
"utf-8"
|
|
393
|
-
)
|
|
618
|
+
processed_message = json.dumps(payload_dict).encode("utf-8")
|
|
394
619
|
except json.JSONDecodeError:
|
|
395
|
-
|
|
396
|
-
payload
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
620
|
+
processed_message = (
|
|
621
|
+
payload.encode("utf-8")
|
|
622
|
+
if isinstance(payload, str)
|
|
623
|
+
else payload
|
|
624
|
+
)
|
|
625
|
+
else:
|
|
626
|
+
processed_message = (
|
|
627
|
+
json.dumps(payload).encode("utf-8")
|
|
628
|
+
if isinstance(payload, (dict, list))
|
|
629
|
+
else payload
|
|
630
|
+
)
|
|
401
631
|
else:
|
|
402
|
-
|
|
632
|
+
processed_message = json.dumps(message_to_return).encode("utf-8")
|
|
633
|
+
|
|
634
|
+
# Return in the same format as received
|
|
635
|
+
if isinstance(result, tuple) and len(result) == 2:
|
|
636
|
+
return (message_context, processed_message)
|
|
637
|
+
else:
|
|
638
|
+
return processed_message
|
|
403
639
|
|
|
404
640
|
except Exception as e:
|
|
405
641
|
print(f"Error processing message: {e}")
|
|
406
|
-
return
|
|
642
|
+
return result
|
|
407
643
|
|
|
408
|
-
|
|
644
|
+
session_class.get_message = instrumented_get_message
|
|
409
645
|
|
|
410
|
-
|
|
411
|
-
|
|
646
|
+
def _instrument_session_publish(self, session_class, method_name):
|
|
647
|
+
"""Instrument session publish methods"""
|
|
648
|
+
original_method = getattr(session_class, method_name)
|
|
412
649
|
|
|
413
|
-
@functools.wraps(
|
|
414
|
-
async def
|
|
650
|
+
@functools.wraps(original_method)
|
|
651
|
+
async def instrumented_session_publish(self, *args, **kwargs):
|
|
415
652
|
if _global_tracer:
|
|
416
|
-
with _global_tracer.start_as_current_span(
|
|
417
|
-
|
|
653
|
+
with _global_tracer.start_as_current_span(
|
|
654
|
+
f"session.{method_name}"
|
|
655
|
+
) as span:
|
|
656
|
+
traceparent = get_current_traceparent()
|
|
657
|
+
|
|
658
|
+
# Add session context to span
|
|
659
|
+
if hasattr(self, "id"):
|
|
660
|
+
span.set_attribute("slim.session.id", str(self.id))
|
|
661
|
+
|
|
662
|
+
# Handle message wrapping
|
|
663
|
+
if args:
|
|
664
|
+
# Thread-safe access to kv_store
|
|
665
|
+
session_id = None
|
|
666
|
+
if traceparent:
|
|
667
|
+
with _kv_lock:
|
|
668
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
669
|
+
if session_id:
|
|
670
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
671
|
+
|
|
672
|
+
headers = {
|
|
673
|
+
"session_id": session_id if session_id else None,
|
|
674
|
+
"traceparent": traceparent,
|
|
675
|
+
"slim_session_id": str(self.id)
|
|
676
|
+
if hasattr(self, "id")
|
|
677
|
+
else None,
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
# Set baggage context
|
|
681
|
+
if traceparent and session_id:
|
|
682
|
+
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
683
|
+
|
|
684
|
+
# Wrap the message (first argument for publish, second for publish_to)
|
|
685
|
+
message_idx = 1 if method_name == "publish_to" else 0
|
|
686
|
+
if len(args) > message_idx:
|
|
687
|
+
args_list = list(args)
|
|
688
|
+
message = args_list[message_idx]
|
|
689
|
+
wrapped_message = (
|
|
690
|
+
SLIMInstrumentor._wrap_message_with_headers(
|
|
691
|
+
None, message, headers
|
|
692
|
+
)
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
# Convert wrapped message back to bytes if needed
|
|
696
|
+
if isinstance(wrapped_message, dict):
|
|
697
|
+
message_to_send = json.dumps(wrapped_message).encode(
|
|
698
|
+
"utf-8"
|
|
699
|
+
)
|
|
700
|
+
else:
|
|
701
|
+
message_to_send = wrapped_message
|
|
702
|
+
|
|
703
|
+
args_list[message_idx] = message_to_send
|
|
704
|
+
args = tuple(args_list)
|
|
705
|
+
|
|
706
|
+
return await original_method(self, *args, **kwargs)
|
|
418
707
|
else:
|
|
419
|
-
|
|
708
|
+
# Handle message wrapping even without tracing
|
|
709
|
+
if args:
|
|
710
|
+
traceparent = get_current_traceparent()
|
|
711
|
+
session_id = None
|
|
712
|
+
if traceparent:
|
|
713
|
+
with _kv_lock:
|
|
714
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
715
|
+
|
|
716
|
+
if traceparent or session_id:
|
|
717
|
+
headers = {
|
|
718
|
+
"session_id": session_id if session_id else None,
|
|
719
|
+
"traceparent": traceparent,
|
|
720
|
+
"slim_session_id": str(self.id)
|
|
721
|
+
if hasattr(self, "id")
|
|
722
|
+
else None,
|
|
723
|
+
}
|
|
420
724
|
|
|
421
|
-
|
|
725
|
+
# Wrap the message (first argument for publish, second for publish_to)
|
|
726
|
+
message_idx = 1 if method_name == "publish_to" else 0
|
|
727
|
+
if len(args) > message_idx:
|
|
728
|
+
args_list = list(args)
|
|
729
|
+
message = args_list[message_idx]
|
|
730
|
+
wrapped_message = (
|
|
731
|
+
SLIMInstrumentor._wrap_message_with_headers(
|
|
732
|
+
None, message, headers
|
|
733
|
+
)
|
|
734
|
+
)
|
|
422
735
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
736
|
+
if isinstance(wrapped_message, dict):
|
|
737
|
+
message_to_send = json.dumps(wrapped_message).encode(
|
|
738
|
+
"utf-8"
|
|
739
|
+
)
|
|
740
|
+
else:
|
|
741
|
+
message_to_send = wrapped_message
|
|
426
742
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
if _global_tracer:
|
|
430
|
-
with _global_tracer.start_as_current_span(
|
|
431
|
-
"slim.create_session"
|
|
432
|
-
) as span:
|
|
433
|
-
session_info = await original_create_session(
|
|
434
|
-
self, config, *args, **kwargs
|
|
435
|
-
)
|
|
743
|
+
args_list[message_idx] = message_to_send
|
|
744
|
+
args = tuple(args_list)
|
|
436
745
|
|
|
437
|
-
|
|
438
|
-
if hasattr(session_info, "id"):
|
|
439
|
-
span.set_attribute("slim.session.id", str(session_info.id))
|
|
746
|
+
return await original_method(self, *args, **kwargs)
|
|
440
747
|
|
|
441
|
-
|
|
442
|
-
else:
|
|
443
|
-
return await original_create_session(self, config, *args, **kwargs)
|
|
748
|
+
setattr(session_class, method_name, instrumented_session_publish)
|
|
444
749
|
|
|
445
|
-
|
|
750
|
+
def _instrument_session_method_if_exists(self, slim_bindings, method_name):
|
|
751
|
+
"""Helper to instrument a session method if it exists"""
|
|
752
|
+
|
|
753
|
+
# Look for session classes that might have this method
|
|
754
|
+
for attr_name in dir(slim_bindings):
|
|
755
|
+
attr = getattr(slim_bindings, attr_name)
|
|
756
|
+
if hasattr(attr, method_name):
|
|
757
|
+
original_method = getattr(attr, method_name)
|
|
758
|
+
|
|
759
|
+
if callable(original_method):
|
|
760
|
+
instrumented_method = self._create_session_method_wrapper(
|
|
761
|
+
method_name, original_method
|
|
762
|
+
)
|
|
763
|
+
setattr(attr, method_name, instrumented_method)
|
|
764
|
+
|
|
765
|
+
def _create_session_method_wrapper(self, method_name, original_method):
|
|
766
|
+
"""Create an instrumented wrapper for session methods"""
|
|
767
|
+
|
|
768
|
+
@functools.wraps(original_method)
|
|
769
|
+
async def instrumented_session_method(self, *args, **kwargs):
|
|
770
|
+
if _global_tracer:
|
|
771
|
+
with _global_tracer.start_as_current_span(f"session.{method_name}"):
|
|
772
|
+
traceparent = get_current_traceparent()
|
|
773
|
+
|
|
774
|
+
# Handle message wrapping for publish methods
|
|
775
|
+
if method_name in ["publish", "publish_to"] and args:
|
|
776
|
+
# Thread-safe access to kv_store
|
|
777
|
+
session_id = None
|
|
778
|
+
if traceparent:
|
|
779
|
+
with _kv_lock:
|
|
780
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
781
|
+
if session_id:
|
|
782
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
783
|
+
|
|
784
|
+
headers = {
|
|
785
|
+
"session_id": session_id if session_id else None,
|
|
786
|
+
"traceparent": traceparent,
|
|
787
|
+
"slim_session_id": str(self.id)
|
|
788
|
+
if hasattr(self, "id")
|
|
789
|
+
else None,
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
# Set baggage context
|
|
793
|
+
if traceparent and session_id:
|
|
794
|
+
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
795
|
+
|
|
796
|
+
# Wrap the message (first argument for publish, second for publish_to)
|
|
797
|
+
message_idx = 1 if method_name == "publish_to" else 0
|
|
798
|
+
if len(args) > message_idx:
|
|
799
|
+
args_list = list(args)
|
|
800
|
+
message = args_list[message_idx]
|
|
801
|
+
wrapped_message = SLIMInstrumentor._wrap_message_with_headers(
|
|
802
|
+
None,
|
|
803
|
+
message,
|
|
804
|
+
headers, # Pass None for self since it's a static method
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
# Convert wrapped message back to bytes if needed
|
|
808
|
+
if isinstance(wrapped_message, dict):
|
|
809
|
+
message_to_send = json.dumps(wrapped_message).encode(
|
|
810
|
+
"utf-8"
|
|
811
|
+
)
|
|
812
|
+
else:
|
|
813
|
+
message_to_send = wrapped_message
|
|
814
|
+
|
|
815
|
+
args_list[message_idx] = message_to_send
|
|
816
|
+
args = tuple(args_list)
|
|
446
817
|
|
|
818
|
+
return await original_method(self, *args, **kwargs)
|
|
819
|
+
else:
|
|
820
|
+
# Handle message wrapping even without tracing
|
|
821
|
+
if method_name in ["publish", "publish_to"] and args:
|
|
822
|
+
traceparent = get_current_traceparent()
|
|
823
|
+
session_id = None
|
|
824
|
+
if traceparent:
|
|
825
|
+
with _kv_lock:
|
|
826
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
827
|
+
|
|
828
|
+
if traceparent or session_id:
|
|
829
|
+
headers = {
|
|
830
|
+
"session_id": session_id if session_id else None,
|
|
831
|
+
"traceparent": traceparent,
|
|
832
|
+
"slim_session_id": str(self.id)
|
|
833
|
+
if hasattr(self, "id")
|
|
834
|
+
else None,
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
# Wrap the message
|
|
838
|
+
message_idx = 1 if method_name == "publish_to" else 0
|
|
839
|
+
if len(args) > message_idx:
|
|
840
|
+
args_list = list(args)
|
|
841
|
+
message = args_list[message_idx]
|
|
842
|
+
wrapped_message = (
|
|
843
|
+
SLIMInstrumentor._wrap_message_with_headers(
|
|
844
|
+
None, message, headers
|
|
845
|
+
)
|
|
846
|
+
)
|
|
847
|
+
|
|
848
|
+
if isinstance(wrapped_message, dict):
|
|
849
|
+
message_to_send = json.dumps(wrapped_message).encode(
|
|
850
|
+
"utf-8"
|
|
851
|
+
)
|
|
852
|
+
else:
|
|
853
|
+
message_to_send = wrapped_message
|
|
854
|
+
|
|
855
|
+
args_list[message_idx] = message_to_send
|
|
856
|
+
args = tuple(args_list)
|
|
857
|
+
|
|
858
|
+
return await original_method(self, *args, **kwargs)
|
|
859
|
+
|
|
860
|
+
return instrumented_session_method
|
|
861
|
+
|
|
862
|
+
@staticmethod
|
|
447
863
|
def _wrap_message_with_headers(self, message, headers):
|
|
448
864
|
"""Helper method to wrap messages with headers consistently"""
|
|
449
865
|
if isinstance(message, bytes):
|
|
@@ -506,12 +922,13 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
506
922
|
methods_to_restore = [
|
|
507
923
|
"publish",
|
|
508
924
|
"publish_to",
|
|
509
|
-
"request_reply",
|
|
925
|
+
"request_reply", # v0.4.0-v0.5.x only
|
|
510
926
|
"receive",
|
|
511
927
|
"connect",
|
|
512
928
|
"create_session",
|
|
513
929
|
"invite",
|
|
514
930
|
"set_route",
|
|
931
|
+
"listen_for_session", # v0.6.0+
|
|
515
932
|
]
|
|
516
933
|
|
|
517
934
|
for method_name in methods_to_restore:
|
|
@@ -521,3 +938,21 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
521
938
|
setattr(
|
|
522
939
|
slim_bindings.Slim, method_name, original_method.__wrapped__
|
|
523
940
|
)
|
|
941
|
+
|
|
942
|
+
# Also try to restore session-level methods (v0.6.0+)
|
|
943
|
+
# This is best-effort since session classes may vary
|
|
944
|
+
session_methods_to_restore = [
|
|
945
|
+
"publish",
|
|
946
|
+
"publish_to",
|
|
947
|
+
"get_message",
|
|
948
|
+
"invite",
|
|
949
|
+
"remove",
|
|
950
|
+
]
|
|
951
|
+
|
|
952
|
+
for attr_name in dir(slim_bindings):
|
|
953
|
+
attr = getattr(slim_bindings, attr_name)
|
|
954
|
+
for method_name in session_methods_to_restore:
|
|
955
|
+
if hasattr(attr, method_name):
|
|
956
|
+
original_method = getattr(attr, method_name)
|
|
957
|
+
if hasattr(original_method, "__wrapped__"):
|
|
958
|
+
setattr(attr, method_name, original_method.__wrapped__)
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ioa-observe-sdk
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.21
|
|
4
4
|
Summary: IOA Observability SDK
|
|
5
|
+
License-Expression: Apache-2.0
|
|
5
6
|
Requires-Python: >=3.10
|
|
6
7
|
Description-Content-Type: text/markdown
|
|
7
8
|
License-File: LICENSE.md
|
|
8
9
|
Requires-Dist: colorama==0.4.6
|
|
9
10
|
Requires-Dist: requests>=2.32.3
|
|
10
|
-
Requires-Dist: opentelemetry-api==1.
|
|
11
|
+
Requires-Dist: opentelemetry-api==1.37.0
|
|
11
12
|
Requires-Dist: opentelemetry-distro
|
|
12
|
-
Requires-Dist: opentelemetry-exporter-otlp==1.
|
|
13
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-common==1.
|
|
14
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc==1.
|
|
15
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.
|
|
13
|
+
Requires-Dist: opentelemetry-exporter-otlp==1.37.0
|
|
14
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-common==1.37.0
|
|
15
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc==1.37.0
|
|
16
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.37.0
|
|
16
17
|
Requires-Dist: opentelemetry-instrumentation
|
|
17
|
-
Requires-Dist: opentelemetry-instrumentation-logging==0.
|
|
18
|
+
Requires-Dist: opentelemetry-instrumentation-logging==0.58b0
|
|
18
19
|
Requires-Dist: opentelemetry-instrumentation-openai==0.43.1
|
|
19
20
|
Requires-Dist: opentelemetry-instrumentation-llamaindex==0.43.1
|
|
20
21
|
Requires-Dist: opentelemetry-instrumentation-ollama==0.43.1
|
|
@@ -26,18 +27,18 @@ Requires-Dist: opentelemetry-instrumentation-crewai==0.43.1
|
|
|
26
27
|
Requires-Dist: opentelemetry-instrumentation-google-generativeai==0.43.1
|
|
27
28
|
Requires-Dist: opentelemetry-instrumentation-groq==0.43.1
|
|
28
29
|
Requires-Dist: opentelemetry-instrumentation-mistralai==0.43.1
|
|
29
|
-
Requires-Dist: opentelemetry-instrumentation-requests==0.
|
|
30
|
+
Requires-Dist: opentelemetry-instrumentation-requests==0.58b0
|
|
30
31
|
Requires-Dist: opentelemetry-instrumentation-sagemaker==0.43.1
|
|
31
|
-
Requires-Dist: opentelemetry-instrumentation-threading==0.
|
|
32
|
+
Requires-Dist: opentelemetry-instrumentation-threading==0.58b0
|
|
32
33
|
Requires-Dist: opentelemetry-instrumentation-together==0.43.1
|
|
33
34
|
Requires-Dist: opentelemetry-instrumentation-transformers==0.43.1
|
|
34
|
-
Requires-Dist: opentelemetry-instrumentation-urllib3==0.
|
|
35
|
+
Requires-Dist: opentelemetry-instrumentation-urllib3==0.58b0
|
|
35
36
|
Requires-Dist: opentelemetry-instrumentation-vertexai==0.43.1
|
|
36
|
-
Requires-Dist: opentelemetry-proto==1.
|
|
37
|
-
Requires-Dist: opentelemetry-sdk==1.
|
|
38
|
-
Requires-Dist: opentelemetry-semantic-conventions==0.
|
|
37
|
+
Requires-Dist: opentelemetry-proto==1.37.0
|
|
38
|
+
Requires-Dist: opentelemetry-sdk==1.37.0
|
|
39
|
+
Requires-Dist: opentelemetry-semantic-conventions==0.58b0
|
|
39
40
|
Requires-Dist: opentelemetry-semantic-conventions-ai>=0.4.11
|
|
40
|
-
Requires-Dist: opentelemetry-util-http==0.
|
|
41
|
+
Requires-Dist: opentelemetry-util-http==0.58b0
|
|
41
42
|
Requires-Dist: langgraph>=0.3.2
|
|
42
43
|
Requires-Dist: langchain>=0.3.19
|
|
43
44
|
Requires-Dist: langchain-openai>=0.3.8
|
|
@@ -16,7 +16,7 @@ ioa_observe/sdk/decorators/util.py,sha256=IebvH9gwZN1en3LblYJUh4bAV2STl6xmp8WpZz
|
|
|
16
16
|
ioa_observe/sdk/instrumentations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
ioa_observe/sdk/instrumentations/a2a.py,sha256=ZpqvPl4u-yheQzSdBfxnZhWFZ8ntbKni_uaW3IDyjqw,6309
|
|
18
18
|
ioa_observe/sdk/instrumentations/mcp.py,sha256=vRM3ofnn7AMmry2RrfyZnZVPEutLWiDMghx2TSnm0Wk,18569
|
|
19
|
-
ioa_observe/sdk/instrumentations/slim.py,sha256=
|
|
19
|
+
ioa_observe/sdk/instrumentations/slim.py,sha256=r_vIuYUo-IhSRuYWymqfS26swDZ8_CFDH6p_QbduLKU,43757
|
|
20
20
|
ioa_observe/sdk/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
ioa_observe/sdk/logging/logging.py,sha256=HZxW9s8Due7jgiNkdI38cIjv5rC9D-Flta3RQMOnpow,2891
|
|
22
22
|
ioa_observe/sdk/metrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -41,8 +41,8 @@ ioa_observe/sdk/utils/const.py,sha256=d67dUTAH9UpWvUV9GLBUqn1Sc2knJ55dy-e6YoLrvS
|
|
|
41
41
|
ioa_observe/sdk/utils/in_memory_span_exporter.py,sha256=H_4TRaThMO1H6vUQ0OpQvzJk_fZH0OOsRAM1iZQXsR8,2112
|
|
42
42
|
ioa_observe/sdk/utils/json_encoder.py,sha256=g4NQ0tTqgWssY6I1D7r4zo0G6PiUo61jhofTAw5-jno,639
|
|
43
43
|
ioa_observe/sdk/utils/package_check.py,sha256=1d1MjxhwoEZIx9dumirT2pRsEWgn-m-SI4npDeEalew,576
|
|
44
|
-
ioa_observe_sdk-1.0.
|
|
45
|
-
ioa_observe_sdk-1.0.
|
|
46
|
-
ioa_observe_sdk-1.0.
|
|
47
|
-
ioa_observe_sdk-1.0.
|
|
48
|
-
ioa_observe_sdk-1.0.
|
|
44
|
+
ioa_observe_sdk-1.0.21.dist-info/licenses/LICENSE.md,sha256=55VjUfgjWOS4vv3Cf55gfq-RxjPgRIO2vlgYPUuC5lA,11362
|
|
45
|
+
ioa_observe_sdk-1.0.21.dist-info/METADATA,sha256=M6yTeHdBIWvp72-hLTpv8AztbhAxUwA8hVoduyRqxHU,7058
|
|
46
|
+
ioa_observe_sdk-1.0.21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
47
|
+
ioa_observe_sdk-1.0.21.dist-info/top_level.txt,sha256=Yt-6Y1olZEDqCs2REeqI30WjYx0pLGQSVqzYmDd67N8,12
|
|
48
|
+
ioa_observe_sdk-1.0.21.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|