monocle-apptrace 0.4.2__py3-none-any.whl → 0.5.0b1__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.

Potentially problematic release.


This version of monocle-apptrace might be problematic. Click here for more details.

Files changed (72) hide show
  1. monocle_apptrace/__main__.py +1 -1
  2. monocle_apptrace/exporters/file_exporter.py +123 -36
  3. monocle_apptrace/instrumentation/common/__init__.py +16 -1
  4. monocle_apptrace/instrumentation/common/constants.py +6 -1
  5. monocle_apptrace/instrumentation/common/instrumentor.py +19 -152
  6. monocle_apptrace/instrumentation/common/method_wrappers.py +380 -0
  7. monocle_apptrace/instrumentation/common/span_handler.py +39 -24
  8. monocle_apptrace/instrumentation/common/utils.py +20 -14
  9. monocle_apptrace/instrumentation/common/wrapper.py +10 -9
  10. monocle_apptrace/instrumentation/common/wrapper_method.py +39 -1
  11. monocle_apptrace/instrumentation/metamodel/a2a/__init__.py +0 -0
  12. monocle_apptrace/instrumentation/metamodel/a2a/_helper.py +37 -0
  13. monocle_apptrace/instrumentation/metamodel/a2a/entities/__init__.py +0 -0
  14. monocle_apptrace/instrumentation/metamodel/a2a/entities/inference.py +112 -0
  15. monocle_apptrace/instrumentation/metamodel/a2a/methods.py +22 -0
  16. monocle_apptrace/instrumentation/metamodel/aiohttp/_helper.py +6 -11
  17. monocle_apptrace/instrumentation/metamodel/anthropic/_helper.py +35 -18
  18. monocle_apptrace/instrumentation/metamodel/anthropic/entities/inference.py +14 -10
  19. monocle_apptrace/instrumentation/metamodel/azfunc/_helper.py +13 -11
  20. monocle_apptrace/instrumentation/metamodel/azfunc/entities/http.py +5 -0
  21. monocle_apptrace/instrumentation/metamodel/azureaiinference/_helper.py +88 -8
  22. monocle_apptrace/instrumentation/metamodel/azureaiinference/entities/inference.py +22 -8
  23. monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +92 -16
  24. monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +13 -8
  25. monocle_apptrace/instrumentation/metamodel/botocore/handlers/botocore_span_handler.py +1 -1
  26. monocle_apptrace/instrumentation/metamodel/fastapi/__init__.py +0 -0
  27. monocle_apptrace/instrumentation/metamodel/fastapi/_helper.py +82 -0
  28. monocle_apptrace/instrumentation/metamodel/fastapi/entities/__init__.py +0 -0
  29. monocle_apptrace/instrumentation/metamodel/fastapi/entities/http.py +44 -0
  30. monocle_apptrace/instrumentation/metamodel/fastapi/methods.py +23 -0
  31. monocle_apptrace/instrumentation/metamodel/finish_types.py +387 -0
  32. monocle_apptrace/instrumentation/metamodel/flask/_helper.py +6 -11
  33. monocle_apptrace/instrumentation/metamodel/gemini/_helper.py +51 -7
  34. monocle_apptrace/instrumentation/metamodel/gemini/entities/inference.py +17 -9
  35. monocle_apptrace/instrumentation/metamodel/gemini/entities/retrieval.py +43 -0
  36. monocle_apptrace/instrumentation/metamodel/gemini/methods.py +10 -0
  37. monocle_apptrace/instrumentation/metamodel/haystack/_helper.py +15 -8
  38. monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +5 -10
  39. monocle_apptrace/instrumentation/metamodel/haystack/methods.py +7 -0
  40. monocle_apptrace/instrumentation/metamodel/lambdafunc/_helper.py +78 -0
  41. monocle_apptrace/instrumentation/metamodel/lambdafunc/entities/http.py +51 -0
  42. monocle_apptrace/instrumentation/metamodel/lambdafunc/methods.py +23 -0
  43. monocle_apptrace/instrumentation/metamodel/lambdafunc/wrapper.py +23 -0
  44. monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +127 -19
  45. monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +15 -10
  46. monocle_apptrace/instrumentation/metamodel/langgraph/_helper.py +67 -10
  47. monocle_apptrace/instrumentation/metamodel/langgraph/entities/inference.py +127 -20
  48. monocle_apptrace/instrumentation/metamodel/langgraph/langgraph_processor.py +43 -0
  49. monocle_apptrace/instrumentation/metamodel/langgraph/methods.py +29 -5
  50. monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +227 -16
  51. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/agent.py +127 -10
  52. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +13 -8
  53. monocle_apptrace/instrumentation/metamodel/llamaindex/llamaindex_processor.py +51 -0
  54. monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +68 -1
  55. monocle_apptrace/instrumentation/metamodel/mcp/__init__.py +0 -0
  56. monocle_apptrace/instrumentation/metamodel/mcp/_helper.py +118 -0
  57. monocle_apptrace/instrumentation/metamodel/mcp/entities/__init__.py +0 -0
  58. monocle_apptrace/instrumentation/metamodel/mcp/entities/inference.py +48 -0
  59. monocle_apptrace/instrumentation/metamodel/mcp/mcp_processor.py +13 -0
  60. monocle_apptrace/instrumentation/metamodel/mcp/methods.py +21 -0
  61. monocle_apptrace/instrumentation/metamodel/openai/_helper.py +83 -16
  62. monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +103 -92
  63. monocle_apptrace/instrumentation/metamodel/openai/entities/retrieval.py +1 -1
  64. monocle_apptrace/instrumentation/metamodel/teamsai/_helper.py +41 -22
  65. monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/actionplanner_output_processor.py +1 -1
  66. monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/teamsai_output_processor.py +5 -9
  67. monocle_apptrace/instrumentation/metamodel/teamsai/sample.json +0 -4
  68. {monocle_apptrace-0.4.2.dist-info → monocle_apptrace-0.5.0b1.dist-info}/METADATA +14 -3
  69. {monocle_apptrace-0.4.2.dist-info → monocle_apptrace-0.5.0b1.dist-info}/RECORD +72 -47
  70. {monocle_apptrace-0.4.2.dist-info → monocle_apptrace-0.5.0b1.dist-info}/WHEEL +0 -0
  71. {monocle_apptrace-0.4.2.dist-info → monocle_apptrace-0.5.0b1.dist-info}/licenses/LICENSE +0 -0
  72. {monocle_apptrace-0.4.2.dist-info → monocle_apptrace-0.5.0b1.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,380 @@
1
+ import logging
2
+ import inspect
3
+ from typing import Dict, List, Optional, Any
4
+ from functools import wraps
5
+ import inspect
6
+ from opentelemetry.context import attach, get_current, detach
7
+ from opentelemetry.sdk.trace import Span
8
+ from opentelemetry.sdk.trace import Span
9
+ from opentelemetry.trace import get_tracer
10
+ from opentelemetry.trace.propagation import set_span_in_context, _SPAN_KEY
11
+ from contextlib import contextmanager, asynccontextmanager
12
+ from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
13
+ from monocle_apptrace.instrumentation.common.wrapper import atask_wrapper, task_wrapper
14
+ from monocle_apptrace.instrumentation.common.utils import (
15
+ set_scope, remove_scope, http_route_handler, http_async_route_handler
16
+ )
17
+ from monocle_apptrace.instrumentation.common.constants import MONOCLE_INSTRUMENTOR
18
+ from monocle_apptrace.instrumentation.common.instrumentor import get_tracer_provider
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+
24
+
25
+ def start_trace(
26
+ span_name: Optional[str] = None,
27
+ attributes: Optional[Dict[str, Any]] = None,
28
+ events: Optional[List[Dict[str, Any]]] = None
29
+ ):
30
+ """
31
+ Starts a new trace. All the spans created after this call will be part of the same trace.
32
+
33
+ Args:
34
+ span_name: Optional custom span name. If None, uses the default span name.
35
+ attributes: Optional dictionary of custom attributes to set on the span.
36
+ events: Optional list of events to add to the span. Each event should be a dict with
37
+ 'name' and optionally 'attributes' keys.
38
+
39
+ Returns:
40
+ Token: A token representing the attached context for the span.
41
+ This token is to be used later to stop the current trace.
42
+ Returns None if tracing fails.
43
+
44
+ Raises:
45
+ Exception: The function catches all exceptions internally and logs a warning.
46
+ """
47
+ try:
48
+ tracer = get_tracer(instrumenting_module_name= MONOCLE_INSTRUMENTOR, tracer_provider= get_tracer_provider())
49
+ span_name = span_name or "custom_span"
50
+ span = tracer.start_span(name=span_name)
51
+ updated_span_context = set_span_in_context(span=span)
52
+
53
+ # Set default monocle attributes
54
+ SpanHandler.set_default_monocle_attributes(span)
55
+ if SpanHandler.is_root_span(span):
56
+ SpanHandler.set_workflow_properties(span)
57
+
58
+ # Set custom attributes and events using common method
59
+ _setup_span_attributes_and_events(span, attributes, events)
60
+
61
+ token = attach(updated_span_context)
62
+ return token
63
+ except Exception as e:
64
+ logger.warning(f"Failed to start trace: {e}")
65
+ return None
66
+
67
+ def stop_trace(
68
+ token,
69
+ final_attributes: Optional[Dict[str, Any]] = None,
70
+ final_events: Optional[List[Dict[str, Any]]] = None
71
+ ) -> None:
72
+ """
73
+ Stop the active trace. All the spans created after this will not be part of the trace.
74
+
75
+ Args:
76
+ token: The token that was returned when the trace was started. Can be None in which case only the span is ended.
77
+ final_attributes: Optional dictionary of final attributes to set on the span before ending.
78
+ final_events: Optional list of final events to add to the span before ending.
79
+
80
+ Returns:
81
+ None
82
+ """
83
+ try:
84
+ _parent_span_context = get_current()
85
+ if _parent_span_context is not None:
86
+ parent_span: Span = _parent_span_context.get(_SPAN_KEY, None)
87
+ if parent_span is not None:
88
+ # Set final attributes and events using common method
89
+ _setup_span_attributes_and_events(parent_span, final_attributes, final_events)
90
+
91
+ parent_span.end()
92
+ if token is not None:
93
+ detach(token)
94
+ except Exception as e:
95
+ logger.warning(f"Failed to stop trace: {e}")
96
+
97
+ def start_scope(
98
+ scope_name: str,
99
+ scope_value: Optional[str] = None
100
+ ) -> object:
101
+ """
102
+ Start a new scope with the given name and optional value. If no value is provided, a random UUID will be generated.
103
+ All the spans, across traces created after this call will have the scope attached until the scope is stopped.
104
+
105
+ Args:
106
+ scope_name: The name of the scope.
107
+ scope_value: Optional value of the scope. If None, a random UUID will be generated.
108
+
109
+ Returns:
110
+ Token: A token representing the attached context for the scope. This token is to be used later to stop the current scope.
111
+ """
112
+ try:
113
+ # Set the scope using existing utility
114
+ token = set_scope(scope_name, scope_value)
115
+ return token
116
+ except Exception as e:
117
+ logger.warning(f"Failed to start scope: {e}")
118
+ return None
119
+
120
+ def stop_scope(
121
+ token: object
122
+ ) -> None:
123
+ """
124
+ Stop the active scope. All the spans created after this will not have the scope attached.
125
+
126
+ Args:
127
+ token: The token that was returned when the scope was started.
128
+
129
+ Returns:
130
+ None
131
+ """
132
+ try:
133
+ # Remove the scope
134
+ remove_scope(token)
135
+ except Exception as e:
136
+ logger.warning(f"Failed to stop scope: {e}")
137
+ return
138
+
139
+
140
+ @contextmanager
141
+ def monocle_trace(
142
+ span_name: Optional[str] = None,
143
+ attributes: Optional[Dict[str, Any]] = None,
144
+ events: Optional[List[Dict[str, Any]]] = None
145
+ ):
146
+ """
147
+ Context manager to start and stop a trace. All the spans, across traces created within the encapsulated code will have same trace ID
148
+
149
+ Args:
150
+ span_name: Optional custom span name.
151
+ attributes: Optional dictionary of custom attributes to set on the span.
152
+ events: Optional list of events to add to the span at start.
153
+ """
154
+ try:
155
+ tracer = get_tracer(instrumenting_module_name=MONOCLE_INSTRUMENTOR, tracer_provider=get_tracer_provider())
156
+ span_name = span_name or "custom_span"
157
+
158
+ with tracer.start_as_current_span(span_name) as span:
159
+ # Set default monocle attributes
160
+ SpanHandler.set_default_monocle_attributes(span)
161
+ if SpanHandler.is_root_span(span):
162
+ SpanHandler.set_workflow_properties(span)
163
+
164
+ # Set custom attributes and events using common method
165
+ _setup_span_attributes_and_events(span, attributes, events)
166
+
167
+ try:
168
+ yield
169
+ finally:
170
+ pass
171
+
172
+
173
+ except Exception as e:
174
+ logger.warning(f"Failed in monocle_trace: {e}")
175
+ yield # Still yield to not break the context manager
176
+
177
+ @asynccontextmanager
178
+ async def amonocle_trace(
179
+ span_name: Optional[str] = None,
180
+ attributes: Optional[Dict[str, Any]] = None,
181
+ events: Optional[List[Dict[str, Any]]] = None
182
+ ):
183
+ """
184
+ Async context manager to start and stop a trace. All the spans, across traces created within the encapsulated code will have same trace ID
185
+
186
+ Args:
187
+ span_name: Optional custom span name.
188
+ attributes: Optional dictionary of custom attributes to set on the span.
189
+ events: Optional list of events to add to the span at start.
190
+ """
191
+ try:
192
+ tracer = get_tracer(instrumenting_module_name=MONOCLE_INSTRUMENTOR, tracer_provider=get_tracer_provider())
193
+ span_name = span_name or "custom_span"
194
+
195
+ with tracer.start_as_current_span(span_name) as span:
196
+ # Set default monocle attributes
197
+ SpanHandler.set_default_monocle_attributes(span)
198
+ if SpanHandler.is_root_span(span):
199
+ SpanHandler.set_workflow_properties(span)
200
+
201
+ # Set custom attributes and events using common method
202
+ _setup_span_attributes_and_events(span, attributes, events)
203
+
204
+ try:
205
+ yield
206
+ finally:
207
+ pass
208
+
209
+
210
+ except Exception as e:
211
+ logger.warning(f"Failed in amonocle_trace: {e}")
212
+ yield # Still yield to not break the context manager
213
+
214
+ @contextmanager
215
+ def monocle_trace_scope(
216
+ scope_name: str,
217
+ scope_value: Optional[str] = None
218
+ ):
219
+ """
220
+ Context manager to start and stop a scope. All the spans, across traces created within the encapsulated code will have the scope attached.
221
+
222
+ Args:
223
+ scope_name: The name of the scope.
224
+ scope_value: Optional value of the scope. If None, a random UUID will be generated.
225
+ """
226
+ token = start_scope(scope_name, scope_value)
227
+ try:
228
+ yield
229
+ finally:
230
+ stop_scope(token)
231
+
232
+ @asynccontextmanager
233
+ async def amonocle_trace_scope(
234
+ scope_name: str,
235
+ scope_value: Optional[str] = None
236
+ ):
237
+ """
238
+ Async context manager to start and stop a scope. All the spans, across traces created within the encapsulated code will have the scope attached.
239
+
240
+ Args:
241
+ scope_name: The name of the scope.
242
+ scope_value: Optional value of the scope. If None, a random UUID will be generated.
243
+ """
244
+ token = start_scope(scope_name, scope_value)
245
+ try:
246
+ yield
247
+ finally:
248
+ stop_scope(token)
249
+
250
+ def monocle_trace_scope_method(
251
+ scope_name: str,
252
+ scope_value: Optional[str] = None
253
+ ):
254
+ """
255
+ Decorator to start and stop a scope for a method. All the spans, across traces created in the method will have the scope attached.
256
+
257
+ Args:
258
+ scope_name: The name of the scope.
259
+ scope_value: Optional value of the scope. If None, a random UUID will be generated.
260
+ """
261
+ def decorator(func):
262
+ if inspect.iscoroutinefunction(func):
263
+ @wraps(func)
264
+ async def wrapper(*args, **kwargs):
265
+ async with amonocle_trace_scope(
266
+ scope_name, scope_value
267
+ ):
268
+ result = await func(*args, **kwargs)
269
+ return result
270
+ return wrapper
271
+ else:
272
+ @wraps(func)
273
+ def wrapper(*args, **kwargs):
274
+ with monocle_trace_scope(
275
+ scope_name, scope_value
276
+ ):
277
+ result = func(*args, **kwargs)
278
+ return result
279
+ return wrapper
280
+ return decorator
281
+
282
+ def monocle_trace_method(
283
+ span_name: Optional[str] = None
284
+ ):
285
+ """
286
+ Decorator to start and stop a trace for a method. All the spans created in the method will be part of the same trace.
287
+
288
+ Args:
289
+ span_name: Optional custom span name. If None, uses the decorated function's name.
290
+ """
291
+
292
+ def decorator(func):
293
+ tracer = get_tracer(instrumenting_module_name=MONOCLE_INSTRUMENTOR, tracer_provider=get_tracer_provider())
294
+ handler = SpanHandler()
295
+ source_path= func.__code__.co_filename + ":" + str(func.__code__.co_firstlineno)
296
+ # Use function name as span name if not provided
297
+ effective_span_name = span_name or func.__name__ or "custom_span"
298
+
299
+ if inspect.iscoroutinefunction(func):
300
+ @wraps(func)
301
+ async def wrapper(*args, **kwargs):
302
+ return await atask_wrapper(
303
+ tracer=tracer,
304
+ handler=handler,
305
+ to_wrap={
306
+ "span_name": effective_span_name,
307
+ "output_processor":{
308
+ "type": "custom",
309
+ }
310
+ }
311
+ )( wrapped=func,
312
+ instance=None,
313
+ source_path=source_path,
314
+ args=args,
315
+ kwargs=kwargs)
316
+ return wrapper
317
+ else:
318
+ @wraps(func)
319
+ def wrapper(*args, **kwargs):
320
+ return task_wrapper(
321
+ tracer=tracer,
322
+ handler=handler,
323
+ to_wrap={
324
+ "span_name": effective_span_name,
325
+ "output_processor":{
326
+ "type": "custom",
327
+ }
328
+ }
329
+ )( wrapped=func,
330
+ instance=None,
331
+ source_path=source_path,
332
+ args=args,
333
+ kwargs=kwargs)
334
+ return wrapper
335
+ return decorator
336
+
337
+ def monocle_trace_http_route(func):
338
+ """
339
+ Decorator to start and stop a continue traces and scope for a http route. It will also initiate new scopes from the http headers if configured in ``monocle_scopes.json``
340
+ All the spans, across traces created in the route will have the scope attached.
341
+ """
342
+ if inspect.iscoroutinefunction(func):
343
+ @wraps(func)
344
+ async def wrapper(*args, **kwargs):
345
+ return await http_async_route_handler(func, *args, **kwargs)
346
+ return wrapper
347
+ else:
348
+ @wraps(func)
349
+ def wrapper(*args, **kwargs):
350
+ return http_route_handler(func, *args, **kwargs)
351
+ return wrapper
352
+
353
+
354
+ def _setup_span_attributes_and_events(
355
+ span,
356
+ attributes: Optional[Dict[str, Any]] = None,
357
+ events: Optional[List[Dict[str, Any]]] = None
358
+ ) -> None:
359
+ """
360
+ Common method to set attributes and events on a span.
361
+
362
+ Args:
363
+ span: The span to configure
364
+ attributes: Optional dictionary of custom attributes to set on the span
365
+ events: Optional list of events to add to the span
366
+ """
367
+ # Set custom attributes if provided
368
+ if attributes:
369
+ for key, value in attributes.items():
370
+ if value is not None:
371
+ span.set_attribute(key, value)
372
+
373
+ # Add custom events if provided
374
+ if events:
375
+ for event in events:
376
+ event_name = event.get('name')
377
+ event_attributes = event.get('attributes', {})
378
+ if event_name:
379
+ span.add_event(event_name, event_attributes)
380
+
@@ -11,17 +11,28 @@ from monocle_apptrace.instrumentation.common.constants import (
11
11
  MONOCLE_SDK_VERSION, MONOCLE_SDK_LANGUAGE, MONOCLE_DETECTED_SPAN_ERROR
12
12
  )
13
13
  from monocle_apptrace.instrumentation.common.utils import set_attribute, get_scopes, MonocleSpanException, get_monocle_version
14
- from monocle_apptrace.instrumentation.common.constants import WORKFLOW_TYPE_KEY, WORKFLOW_TYPE_GENERIC
14
+ from monocle_apptrace.instrumentation.common.constants import WORKFLOW_TYPE_KEY, WORKFLOW_TYPE_GENERIC, CHILD_ERROR_CODE
15
15
 
16
16
  logger = logging.getLogger(__name__)
17
17
 
18
18
  WORKFLOW_TYPE_MAP = {
19
+ "llama_index.core.agent.workflow": WORKFLOW_TYPE_GENERIC,
19
20
  "llama_index": "workflow.llamaindex",
20
21
  "langchain": "workflow.langchain",
21
22
  "haystack": "workflow.haystack",
22
23
  "teams.ai": "workflow.teams_ai",
24
+ "langgraph": "workflow.langgraph",
25
+ "openai": "workflow.openai",
26
+ "anthropic": "workflow.anthropic",
27
+ "gemini": "workflow.gemini",
23
28
  }
24
29
 
30
+ FRAMEWORK_WORKFLOW_LIST = [
31
+ "workflow.llamaindex",
32
+ "workflow.langchain",
33
+ "workflow.haystack",
34
+ "workflow.teams_ai",
35
+ ]
25
36
  class SpanHandler:
26
37
 
27
38
  def __init__(self,instrumentor=None):
@@ -36,7 +47,7 @@ class SpanHandler:
36
47
  def pre_tracing(self, to_wrap, wrapped, instance, args, kwargs):
37
48
  pass
38
49
 
39
- def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value):
50
+ def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value, token=None):
40
51
  pass
41
52
 
42
53
  def skip_span(self, to_wrap, wrapped, instance, args, kwargs) -> bool:
@@ -66,6 +77,9 @@ class SpanHandler:
66
77
  span.set_attribute("span_source", source_path)
67
78
  for scope_key, scope_value in get_scopes().items():
68
79
  span.set_attribute(f"scope.{scope_key}", scope_value)
80
+ workflow_name = SpanHandler.get_workflow_name(span=span)
81
+ if workflow_name:
82
+ span.set_attribute("workflow.name", workflow_name)
69
83
 
70
84
  @staticmethod
71
85
  def set_workflow_properties(span: Span, to_wrap = None):
@@ -75,17 +89,14 @@ class SpanHandler:
75
89
 
76
90
  @staticmethod
77
91
  def set_non_workflow_properties(span: Span, to_wrap = None):
78
- workflow_name = SpanHandler.get_workflow_name(span=span)
79
- if workflow_name:
80
- span.set_attribute("workflow.name", workflow_name)
81
92
  span.set_attribute("span.type", "generic")
82
93
 
83
- def post_task_processing(self, to_wrap, wrapped, instance, args, kwargs, result, span:Span):
94
+ def post_task_processing(self, to_wrap, wrapped, instance, args, kwargs, result, ex, span:Span, parent_span:Span):
84
95
  pass
85
96
 
86
97
  def hydrate_span(self, to_wrap, wrapped, instance, args, kwargs, result, span, parent_span = None, ex:Exception = None) -> bool:
87
98
  try:
88
- detected_error_in_attribute = self.hydrate_attributes(to_wrap, wrapped, instance, args, kwargs, result, span)
99
+ detected_error_in_attribute = self.hydrate_attributes(to_wrap, wrapped, instance, args, kwargs, result, span, parent_span)
89
100
  detected_error_in_event = self.hydrate_events(to_wrap, wrapped, instance, args, kwargs, result, span, parent_span, ex)
90
101
  if detected_error_in_attribute or detected_error_in_event:
91
102
  span.set_attribute(MONOCLE_DETECTED_SPAN_ERROR, True)
@@ -93,7 +104,7 @@ class SpanHandler:
93
104
  if span.status.status_code == StatusCode.UNSET and ex is None:
94
105
  span.set_status(StatusCode.OK)
95
106
 
96
- def hydrate_attributes(self, to_wrap, wrapped, instance, args, kwargs, result, span:Span) -> bool:
107
+ def hydrate_attributes(self, to_wrap, wrapped, instance, args, kwargs, result, span:Span, parent_span:Span) -> bool:
97
108
  detected_error:bool = False
98
109
  span_index = 0
99
110
  if SpanHandler.is_root_span(span):
@@ -104,6 +115,7 @@ class SpanHandler:
104
115
  skip_processors:list[str] = self.skip_processor(to_wrap, wrapped, instance, span, args, kwargs) or []
105
116
 
106
117
  if 'attributes' in output_processor and 'attributes' not in skip_processors:
118
+ arguments = {"instance":instance, "args":args, "kwargs":kwargs, "result":result, "parent_span":parent_span, "span":span}
107
119
  for processors in output_processor["attributes"]:
108
120
  for processor in processors:
109
121
  attribute = processor.get('attribute')
@@ -112,10 +124,9 @@ class SpanHandler:
112
124
  if attribute and accessor:
113
125
  attribute_name = f"entity.{span_index+1}.{attribute}"
114
126
  try:
115
- arguments = {"instance":instance, "args":args, "kwargs":kwargs, "result":result}
116
- result = accessor(arguments)
117
- if result and isinstance(result, (str, list)):
118
- span.set_attribute(attribute_name, result)
127
+ processor_result = accessor(arguments)
128
+ if processor_result and isinstance(processor_result, (str, list)):
129
+ span.set_attribute(attribute_name, processor_result)
119
130
  except MonocleSpanException as e:
120
131
  span.set_status(StatusCode.ERROR, e.message)
121
132
  detected_error = True
@@ -140,7 +151,7 @@ class SpanHandler:
140
151
  output_processor=to_wrap['output_processor']
141
152
  skip_processors:list[str] = self.skip_processor(to_wrap, wrapped, instance, span, args, kwargs) or []
142
153
 
143
- arguments = {"instance": instance, "args": args, "kwargs": kwargs, "result": ret_result, "exception":ex}
154
+ arguments = {"instance": instance, "args": args, "kwargs": kwargs, "result": ret_result, "exception":ex, "parent_span":parent_span, "span": span}
144
155
  # Process events if they are defined in the output_processor.
145
156
  # In case of inference.modelapi skip the event processing unless the span has an exception
146
157
  if 'events' in output_processor and ('events' not in skip_processors or ex is not None):
@@ -187,6 +198,16 @@ class SpanHandler:
187
198
  workflow_type = SpanHandler.get_workflow_type(to_wrap)
188
199
  span.set_attribute(f"entity.{span_index}.type", workflow_type)
189
200
 
201
+ def get_workflow_name_in_progress(self) -> str:
202
+ return get_value(WORKFLOW_TYPE_KEY)
203
+
204
+ @staticmethod
205
+ def is_framework_workflow(workflow_type) -> bool:
206
+ return workflow_type in FRAMEWORK_WORKFLOW_LIST
207
+
208
+ def is_framework_span_in_progress(self) -> bool:
209
+ return SpanHandler.is_framework_workflow(self.get_workflow_name_in_progress())
210
+
190
211
  @staticmethod
191
212
  def get_workflow_type(to_wrap):
192
213
  # workflow type
@@ -231,7 +252,7 @@ class SpanHandler:
231
252
  token = None
232
253
  if to_wrap:
233
254
  workflow_type = SpanHandler.get_workflow_type(to_wrap)
234
- if workflow_type != WORKFLOW_TYPE_GENERIC:
255
+ if SpanHandler.is_framework_workflow(workflow_type):
235
256
  token = attach(set_value(WORKFLOW_TYPE_KEY,
236
257
  SpanHandler.get_workflow_type(to_wrap), context))
237
258
  return token
@@ -252,21 +273,15 @@ class SpanHandler:
252
273
 
253
274
 
254
275
  class NonFrameworkSpanHandler(SpanHandler):
255
-
256
- def get_workflow_name_in_progress(self) -> str:
257
- return get_value(WORKFLOW_TYPE_KEY)
258
-
259
- def is_framework_span_in_progess(self) -> bool:
260
- return self.get_workflow_name_in_progress() in WORKFLOW_TYPE_MAP.values()
261
-
262
276
  # If the language framework is being executed, then skip generating direct openAI attributes and events
263
277
  def skip_processor(self, to_wrap, wrapped, instance, span, args, kwargs) -> list[str]:
264
- if self.is_framework_span_in_progess():
278
+ if super().is_framework_span_in_progress():
265
279
  return ["attributes", "events"]
266
280
 
267
281
  def set_span_type(self, to_wrap, wrapped, instance, output_processor, span:Span, args, kwargs) -> str:
268
282
  span_type = super().set_span_type(to_wrap, wrapped, instance, output_processor, span, args, kwargs)
269
- if self.is_framework_span_in_progess() and span_type is not None:
283
+ if self.is_framework_span_in_progress() and span_type is not None:
270
284
  span_type = span_type+".modelapi"
271
285
  span.set_attribute("span.type", span_type)
272
- return span_type
286
+ return span_type
287
+
@@ -70,7 +70,7 @@ def with_tracer_wrapper(func):
70
70
  """Helper for providing tracer for wrapper functions."""
71
71
 
72
72
  def _with_tracer(tracer, handler, to_wrap):
73
- def wrapper(wrapped, instance, args, kwargs):
73
+ def wrapper(wrapped, instance, args, kwargs, source_path=None):
74
74
  try:
75
75
  # get and log the parent span context if injected by the application
76
76
  # This is useful for debugging and tracing of Azure functions
@@ -83,12 +83,12 @@ def with_tracer_wrapper(func):
83
83
  f"Parent span is found with trace id {hex(parent_span.get_span_context().trace_id)}")
84
84
  except Exception as e:
85
85
  logger.error("Exception in attaching parent context: %s", e)
86
-
87
- if traceback.extract_stack().__len__() > 2:
88
- filename, line_number, _, _ = traceback.extract_stack()[-2]
89
- source_path = f"{filename}:{line_number}"
90
- else:
91
- source_path = ""
86
+ if not source_path:
87
+ if traceback.extract_stack().__len__() > 2:
88
+ filename, line_number, _, _ = traceback.extract_stack()[-2]
89
+ source_path = f"{filename}:{line_number}"
90
+ else:
91
+ source_path = ""
92
92
  val = func(tracer, handler, to_wrap, wrapped, instance, source_path, args, kwargs)
93
93
  return val
94
94
 
@@ -152,13 +152,6 @@ def flatten_dict(d, parent_key='', sep='_'):
152
152
  items.append((new_key, v))
153
153
  return dict(items)
154
154
 
155
- def get_fully_qualified_class_name(instance):
156
- if instance is None:
157
- return None
158
- module_name = instance.__class__.__module__
159
- qualname = instance.__class__.__qualname__
160
- return f"{module_name}.{qualname}"
161
-
162
155
  # returns json path like key probe in a dictionary
163
156
  def get_nested_value(data, keys):
164
157
  for key in keys:
@@ -330,6 +323,12 @@ def add_monocle_trace_state(headers:dict[str:str]) -> None:
330
323
  else:
331
324
  headers['tracestate'] = monocle_trace_state
332
325
 
326
+ def get_json_dumps(obj) -> str:
327
+ try:
328
+ return json.dumps(obj)
329
+ except TypeError as e:
330
+ return str(obj)
331
+
333
332
  class Option(Generic[T]):
334
333
  def __init__(self, value: Optional[T]):
335
334
  self.value = value
@@ -392,6 +391,13 @@ def get_exception_message(arguments):
392
391
  else:
393
392
  return ''
394
393
 
394
+ def get_error_message(arguments):
395
+ status_code = get_status_code(arguments)
396
+ if status_code == 'success':
397
+ return ''
398
+ else:
399
+ return status_code
400
+
395
401
  def get_status_code(arguments):
396
402
  if arguments["exception"] is not None:
397
403
  return get_exception_status_code(arguments)