monocle-apptrace 0.3.0b5__py3-none-any.whl → 0.3.0b7__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 (34) hide show
  1. monocle_apptrace/__init__.py +1 -0
  2. monocle_apptrace/exporters/aws/s3_exporter.py +20 -6
  3. monocle_apptrace/exporters/aws/s3_exporter_opendal.py +22 -11
  4. monocle_apptrace/exporters/azure/blob_exporter.py +29 -8
  5. monocle_apptrace/exporters/azure/blob_exporter_opendal.py +23 -8
  6. monocle_apptrace/exporters/exporter_processor.py +128 -3
  7. monocle_apptrace/exporters/file_exporter.py +16 -0
  8. monocle_apptrace/exporters/monocle_exporters.py +10 -1
  9. monocle_apptrace/exporters/okahu/okahu_exporter.py +8 -6
  10. monocle_apptrace/instrumentation/__init__.py +1 -0
  11. monocle_apptrace/instrumentation/common/__init__.py +2 -0
  12. monocle_apptrace/instrumentation/common/constants.py +17 -0
  13. monocle_apptrace/instrumentation/common/instrumentor.py +136 -53
  14. monocle_apptrace/instrumentation/common/span_handler.py +92 -41
  15. monocle_apptrace/instrumentation/common/utils.py +84 -6
  16. monocle_apptrace/instrumentation/common/wrapper.py +43 -45
  17. monocle_apptrace/instrumentation/common/wrapper_method.py +8 -4
  18. monocle_apptrace/instrumentation/metamodel/botocore/handlers/botocore_span_handler.py +2 -1
  19. monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +1 -1
  20. monocle_apptrace/instrumentation/metamodel/haystack/methods.py +2 -1
  21. monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +3 -2
  22. monocle_apptrace/instrumentation/metamodel/langchain/methods.py +12 -6
  23. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +5 -3
  24. monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +6 -3
  25. monocle_apptrace/instrumentation/metamodel/openai/_helper.py +31 -7
  26. monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +1 -1
  27. monocle_apptrace/instrumentation/metamodel/openai/entities/retrieval.py +20 -1
  28. monocle_apptrace/instrumentation/metamodel/openai/methods.py +21 -1
  29. monocle_apptrace/instrumentation/metamodel/requests/__init__.py +3 -1
  30. {monocle_apptrace-0.3.0b5.dist-info → monocle_apptrace-0.3.0b7.dist-info}/METADATA +1 -1
  31. {monocle_apptrace-0.3.0b5.dist-info → monocle_apptrace-0.3.0b7.dist-info}/RECORD +34 -34
  32. {monocle_apptrace-0.3.0b5.dist-info → monocle_apptrace-0.3.0b7.dist-info}/WHEEL +0 -0
  33. {monocle_apptrace-0.3.0b5.dist-info → monocle_apptrace-0.3.0b7.dist-info}/licenses/LICENSE +0 -0
  34. {monocle_apptrace-0.3.0b5.dist-info → monocle_apptrace-0.3.0b7.dist-info}/licenses/NOTICE +0 -0
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import inspect
2
3
  from typing import Collection, Dict, List, Union
3
4
  import random
4
5
  import uuid
@@ -14,19 +15,19 @@ from opentelemetry.sdk.trace import Span, TracerProvider
14
15
  from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanProcessor
15
16
  from opentelemetry.trace import get_tracer
16
17
  from wrapt import wrap_function_wrapper
17
- from opentelemetry.trace.propagation import set_span_in_context
18
+ from opentelemetry.trace.propagation import set_span_in_context, _SPAN_KEY
18
19
  from monocle_apptrace.exporters.monocle_exporters import get_monocle_exporter
19
- from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
20
+ from monocle_apptrace.instrumentation.common.span_handler import SpanHandler, NonFrameworkSpanHandler
20
21
  from monocle_apptrace.instrumentation.common.wrapper_method import (
21
22
  DEFAULT_METHODS_LIST,
22
23
  WrapperMethod,
23
24
  MONOCLE_SPAN_HANDLERS
24
25
  )
25
- from monocle_apptrace.instrumentation.common.wrapper import scope_wrapper
26
+ from monocle_apptrace.instrumentation.common.wrapper import scope_wrapper, ascope_wrapper, wrapper_processor
26
27
  from monocle_apptrace.instrumentation.common.utils import (
27
- set_scope, remove_scope, http_route_handler, load_scopes
28
+ set_scope, remove_scope, http_route_handler, load_scopes, async_wrapper, http_async_route_handler
28
29
  )
29
- from monocle_apptrace.instrumentation.common.constants import MONOCLE_INSTRUMENTOR
30
+ from monocle_apptrace.instrumentation.common.constants import MONOCLE_INSTRUMENTOR, WORKFLOW_TYPE_KEY
30
31
  from functools import wraps
31
32
  logger = logging.getLogger(__name__)
32
33
 
@@ -64,13 +65,11 @@ class MonocleInstrumentor(BaseInstrumentor):
64
65
  def instrumented_endpoint_invoke(to_wrap,wrapped, span_name, instance,fn):
65
66
  @wraps(fn)
66
67
  def with_instrumentation(*args, **kwargs):
67
- handler = SpanHandler()
68
- with tracer.start_as_current_span(span_name) as span:
69
- response = fn(*args, **kwargs)
70
- handler.hydrate_span(to_wrap, span=span, wrapped=wrapped, instance=instance, args=args, kwargs=kwargs,
71
- result=response)
72
- return response
73
-
68
+ async_task = inspect.iscoroutinefunction(fn)
69
+ boto_method_to_wrap = to_wrap.copy()
70
+ boto_method_to_wrap['skip_span'] = False
71
+ return wrapper_processor(async_task, tracer, NonFrameworkSpanHandler(),
72
+ boto_method_to_wrap, fn, instance, args, kwargs)
74
73
  return with_instrumentation
75
74
  return instrumented_endpoint_invoke
76
75
 
@@ -93,7 +92,10 @@ class MonocleInstrumentor(BaseInstrumentor):
93
92
  final_method_list.append(method.to_dict())
94
93
 
95
94
  for method in load_scopes():
96
- method['wrapper_method'] = scope_wrapper
95
+ if method.get('async', False):
96
+ method['wrapper_method'] = ascope_wrapper
97
+ else:
98
+ method['wrapper_method'] = scope_wrapper
97
99
  final_method_list.append(method)
98
100
 
99
101
  for method_config in final_method_list:
@@ -154,6 +156,24 @@ def setup_monocle_telemetry(
154
156
  span_handlers: Dict[str,SpanHandler] = None,
155
157
  wrapper_methods: List[Union[dict,WrapperMethod]] = None,
156
158
  union_with_default_methods: bool = True) -> None:
159
+ """
160
+ Set up Monocle telemetry for the application.
161
+
162
+ Parameters
163
+ ----------
164
+ workflow_name : str
165
+ The name of the workflow to be used as the service name in telemetry.
166
+ span_processors : List[SpanProcessor], optional
167
+ Custom span processors to use instead of the default ones. If None,
168
+ BatchSpanProcessors with Monocle exporters will be used.
169
+ span_handlers : Dict[str, SpanHandler], optional
170
+ Dictionary of span handlers to be used by the instrumentor, mapping handler names to handler objects.
171
+ wrapper_methods : List[Union[dict, WrapperMethod]], optional
172
+ Custom wrapper methods for instrumentation. If None, default methods will be used.
173
+ union_with_default_methods : bool, default=True
174
+ If True, combine the provided wrapper_methods with the default methods.
175
+ If False, only use the provided wrapper_methods.
176
+ """
157
177
  resource = Resource(attributes={
158
178
  SERVICE_NAME: workflow_name
159
179
  })
@@ -191,40 +211,48 @@ def on_processor_start(span: Span, parent_context):
191
211
  def set_context_properties(properties: dict) -> None:
192
212
  attach(set_value(SESSION_PROPERTIES_KEY, properties))
193
213
 
194
-
195
- def propagate_trace_id(traceId = "", use_trace_context = False):
214
+ def start_trace():
215
+ """
216
+ Starts a new trace. All the spans created after this call will be part of the same trace.
217
+ Returns:
218
+ Token: A token representing the attached context for the workflow span.
219
+ This token is to be used later to stop the current trace.
220
+ Returns None if tracing fails.
221
+
222
+ Raises:
223
+ Exception: The function catches all exceptions internally and logs a warning.
224
+ """
196
225
  try:
197
- if traceId.startswith("0x"):
198
- traceId = traceId.lstrip("0x")
199
226
  tracer = get_tracer(instrumenting_module_name= MONOCLE_INSTRUMENTOR, tracer_provider= get_tracer_provider())
200
- initial_id_generator = tracer.id_generator
201
- _parent_span_context = get_current() if use_trace_context else None
202
- if traceId and is_valid_trace_id_uuid(traceId):
203
- tracer.id_generator = FixedIdGenerator(uuid.UUID(traceId).int)
204
-
205
- span = tracer.start_span(name = "parent_placeholder_span", context= _parent_span_context)
206
- updated_span_context = set_span_in_context(span=span, context= _parent_span_context)
207
- updated_span_context = set_value("root_span_id", span.get_span_context().span_id, updated_span_context)
208
- token = attach(updated_span_context)
209
-
210
- span.end()
211
- tracer.id_generator = initial_id_generator
227
+ span = tracer.start_span(name = "workflow")
228
+ updated_span_context = set_span_in_context(span=span)
229
+ SpanHandler.set_default_monocle_attributes(span)
230
+ SpanHandler.set_workflow_properties(span)
231
+ token = SpanHandler.attach_workflow_type(context=updated_span_context)
212
232
  return token
213
233
  except:
214
- logger.warning("Failed to propagate trace id")
215
- return
216
-
217
-
218
- def propagate_trace_id_from_traceparent():
219
- propagate_trace_id(use_trace_context = True)
220
-
221
-
222
- def stop_propagate_trace_id(token) -> None:
234
+ logger.warning("Failed to start trace")
235
+ return None
236
+
237
+ def stop_trace(token) -> None:
238
+ """
239
+ Stop the active trace and detach workflow type if token is provided. All the spans created after this will not be part of the trace.
240
+ Args:
241
+ token: The token that was returned when the trace was started. Used to detach
242
+ workflow type. Can be None in which case only the span is ended.
243
+ Returns:
244
+ None
245
+ """
223
246
  try:
224
- detach(token)
247
+ _parent_span_context = get_current()
248
+ if _parent_span_context is not None:
249
+ parent_span: Span = _parent_span_context.get(_SPAN_KEY, None)
250
+ if parent_span is not None:
251
+ parent_span.end()
252
+ if token is not None:
253
+ SpanHandler.detach_workflow_type(token)
225
254
  except:
226
- logger.warning("Failed to stop propagating trace id")
227
-
255
+ logger.warning("Failed to stop trace")
228
256
 
229
257
  def is_valid_trace_id_uuid(traceId: str) -> bool:
230
258
  try:
@@ -235,36 +263,90 @@ def is_valid_trace_id_uuid(traceId: str) -> bool:
235
263
  return False
236
264
 
237
265
  def start_scope(scope_name: str, scope_value:str = None) -> object:
266
+ """
267
+ Start a new scope with the given name and and optional value. If no value is provided, a random UUID will be generated.
268
+ All the spans, across traces created after this call will have the scope attached until the scope is stopped.
269
+ Args:
270
+ scope_name: The name of the scope.
271
+ scope_value: Optional value of the scope. If None, a random UUID will be generated.
272
+ Returns:
273
+ Token: A token representing the attached context for the scope. This token is to be used later to stop the current scope.
274
+ """
238
275
  return set_scope(scope_name, scope_value)
239
276
 
240
277
  def stop_scope(token:object) -> None:
278
+ """
279
+ Stop the active scope. All the spans created after this will not have the scope attached.
280
+ Args:
281
+ token: The token that was returned when the scope was started.
282
+ Returns:
283
+ None
284
+ """
241
285
  remove_scope(token)
242
286
  return
243
287
 
288
+ @contextmanager
289
+ def monocle_trace():
290
+ """
291
+ Context manager to start and stop a scope. All the spans, across traces created within the encapsulated code will have same trace ID
292
+ """
293
+ token = start_trace()
294
+ try:
295
+ yield
296
+ finally:
297
+ stop_trace(token)
298
+
244
299
  @contextmanager
245
300
  def monocle_trace_scope(scope_name: str, scope_value:str = None):
301
+ """
302
+ Context manager to start and stop a scope. All the spans, across traces created within the encapsulated code will have the scope attached.
303
+ Args:
304
+ scope_name: The name of the scope.
305
+ scope_value: Optional value of the scope. If None, a random UUID will be generated."""
246
306
  token = start_scope(scope_name, scope_value)
247
307
  try:
248
308
  yield
249
309
  finally:
250
310
  stop_scope(token)
251
-
252
- def monocle_trace_scope_method(scope_name: str):
311
+
312
+ def monocle_trace_scope_method(scope_name: str, scope_value:str=None):
313
+ """
314
+ Decorator to start and stop a scope for a method. All the spans, across traces created in the method will have the scope attached.
315
+ """
253
316
  def decorator(func):
254
- def wrapper(*args, **kwargs):
255
- token = start_scope(scope_name)
256
- try:
257
- result = func(*args, **kwargs)
317
+ if inspect.iscoroutinefunction(func):
318
+ @wraps(func)
319
+ async def wrapper(*args, **kwargs):
320
+ result = async_wrapper(func, scope_name, scope_value, None, *args, **kwargs)
258
321
  return result
259
- finally:
260
- stop_scope(token)
261
- return wrapper
322
+ return wrapper
323
+ else:
324
+ @wraps(func)
325
+ def wrapper(*args, **kwargs):
326
+ token = start_scope(scope_name, scope_value)
327
+ try:
328
+ result = func(*args, **kwargs)
329
+ return result
330
+ finally:
331
+ stop_scope(token)
332
+ return wrapper
262
333
  return decorator
263
334
 
264
335
  def monocle_trace_http_route(func):
265
- def wrapper(req):
266
- return http_route_handler(req.headers, func, req)
267
- return wrapper
336
+ """
337
+ 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``
338
+ All the spans, across traces created in the route will have the scope attached.
339
+ """
340
+ if inspect.iscoroutinefunction(func):
341
+ @wraps(func)
342
+ async def wrapper(*args, **kwargs):
343
+ return http_async_route_handler(func, *args, **kwargs)
344
+ return wrapper
345
+ else:
346
+ @wraps(func)
347
+ def wrapper(*args, **kwargs):
348
+ return http_route_handler(func, *args, **kwargs)
349
+ return wrapper
268
350
 
269
351
  class FixedIdGenerator(id_generator.IdGenerator):
270
352
  def __init__(
@@ -277,3 +359,4 @@ class FixedIdGenerator(id_generator.IdGenerator):
277
359
 
278
360
  def generate_trace_id(self) -> int:
279
361
  return self.trace_id
362
+
@@ -1,16 +1,17 @@
1
1
  import logging
2
2
  import os
3
3
  from importlib.metadata import version
4
- from opentelemetry.context import get_current
5
- from opentelemetry.context import get_value
4
+ from opentelemetry.context import get_value, set_value, attach, detach
6
5
  from opentelemetry.sdk.trace import Span
7
-
6
+ from opentelemetry.trace.status import Status, StatusCode
8
7
  from monocle_apptrace.instrumentation.common.constants import (
9
8
  QUERY,
10
9
  service_name_map,
11
10
  service_type_map,
11
+ MONOCLE_SDK_VERSION
12
12
  )
13
- from monocle_apptrace.instrumentation.common.utils import set_attribute, get_scopes
13
+ from monocle_apptrace.instrumentation.common.utils import set_attribute, get_scopes, MonocleSpanException
14
+ from monocle_apptrace.instrumentation.common.constants import WORKFLOW_TYPE_KEY, WORKFLOW_TYPE_GENERIC
14
15
 
15
16
  logger = logging.getLogger(__name__)
16
17
 
@@ -20,8 +21,6 @@ WORKFLOW_TYPE_MAP = {
20
21
  "haystack": "workflow.haystack"
21
22
  }
22
23
 
23
-
24
-
25
24
  class SpanHandler:
26
25
 
27
26
  def __init__(self,instrumentor=None):
@@ -40,20 +39,37 @@ class SpanHandler:
40
39
  pass
41
40
 
42
41
  def skip_span(self, to_wrap, wrapped, instance, args, kwargs) -> bool:
42
+ # If this is a workflow span type and a workflow span is already generated, then skip generating this span
43
+ if to_wrap.get('span_type') == "workflow" and self.is_workflow_span_active():
44
+ return True
43
45
  return False
44
46
 
45
47
  def pre_task_processing(self, to_wrap, wrapped, instance, args,kwargs, span):
46
- if self.__is_root_span(span):
47
- try:
48
- sdk_version = version("monocle_apptrace")
49
- span.set_attribute("monocle_apptrace.version", sdk_version)
50
- except Exception as e:
51
- logger.warning("Exception finding monocle-apptrace version.")
52
48
  if "pipeline" in to_wrap['package']:
53
49
  set_attribute(QUERY, args[0]['prompt_builder']['question'])
54
50
 
55
- def post_task_processing(self, to_wrap, wrapped, instance, args, kwargs, result, span):
56
- pass
51
+ @staticmethod
52
+ def set_default_monocle_attributes(span: Span):
53
+ """ Set default monocle attributes for all spans """
54
+ try:
55
+ sdk_version = version("monocle_apptrace")
56
+ span.set_attribute(MONOCLE_SDK_VERSION, sdk_version)
57
+ except Exception as e:
58
+ logger.warning("Exception finding monocle-apptrace version.")
59
+ for scope_key, scope_value in get_scopes().items():
60
+ span.set_attribute(f"scope.{scope_key}", scope_value)
61
+
62
+ @staticmethod
63
+ def set_workflow_properties(span: Span, to_wrap = None):
64
+ """ Set attributes of workflow if this is a root span"""
65
+ SpanHandler.set_workflow_attributes(to_wrap, span)
66
+ SpanHandler.set_app_hosting_identifier_attribute(span)
67
+ span.set_status(StatusCode.OK)
68
+
69
+
70
+ def post_task_processing(self, to_wrap, wrapped, instance, args, kwargs, result, span:Span):
71
+ if span.status.status_code == StatusCode.UNSET:
72
+ span.set_status(StatusCode.OK)
57
73
 
58
74
  def hydrate_span(self, to_wrap, wrapped, instance, args, kwargs, result, span):
59
75
  self.hydrate_attributes(to_wrap, wrapped, instance, args, kwargs, result, span)
@@ -61,10 +77,8 @@ class SpanHandler:
61
77
 
62
78
  def hydrate_attributes(self, to_wrap, wrapped, instance, args, kwargs, result, span):
63
79
  span_index = 0
64
- if self.__is_root_span(span):
65
- span_index += self.set_workflow_attributes(to_wrap, span, span_index+1)
66
- span_index += self.set_app_hosting_identifier_attribute(span, span_index+1)
67
-
80
+ if SpanHandler.is_root_span(span):
81
+ span_index = 2 # root span will have workflow and hosting entities pre-populated
68
82
  if 'output_processor' in to_wrap and to_wrap["output_processor"] is not None:
69
83
  output_processor=to_wrap['output_processor']
70
84
  if 'type' in output_processor:
@@ -84,6 +98,8 @@ class SpanHandler:
84
98
  result = accessor(arguments)
85
99
  if result and isinstance(result, (str, list)):
86
100
  span.set_attribute(attribute_name, result)
101
+ except MonocleSpanException as e:
102
+ span.set_status(StatusCode.ERROR, e.message)
87
103
  except Exception as e:
88
104
  logger.debug(f"Error processing accessor: {e}")
89
105
  else:
@@ -120,50 +136,85 @@ class SpanHandler:
120
136
  event_attributes[attribute_key] = accessor(arguments)
121
137
  else:
122
138
  event_attributes.update(accessor(arguments))
139
+ except MonocleSpanException as e:
140
+ span.set_status(StatusCode.ERROR, e.message)
123
141
  except Exception as e:
124
142
  logger.debug(f"Error evaluating accessor for attribute '{attribute_key}': {e}")
125
143
  span.add_event(name=event_name, attributes=event_attributes)
126
144
 
127
-
128
-
129
- def set_workflow_attributes(self, to_wrap, span: Span, span_index):
130
- return_value = 1
131
- workflow_name = self.get_workflow_name(span=span)
145
+ @staticmethod
146
+ def set_workflow_attributes(to_wrap, span: Span):
147
+ span_index = 1
148
+ workflow_name = SpanHandler.get_workflow_name(span=span)
132
149
  if workflow_name:
133
150
  span.set_attribute("span.type", "workflow")
134
151
  span.set_attribute(f"entity.{span_index}.name", workflow_name)
135
- # workflow type
136
- package_name = to_wrap.get('package')
137
- workflow_type_set = False
138
- for (package, workflow_type) in WORKFLOW_TYPE_MAP.items():
139
- if (package_name is not None and package in package_name):
140
- span.set_attribute(f"entity.{span_index}.type", workflow_type)
141
- workflow_type_set = True
142
- if not workflow_type_set:
143
- span.set_attribute(f"entity.{span_index}.type", "workflow.generic")
144
- return return_value
145
-
146
- def set_app_hosting_identifier_attribute(self, span, span_index):
147
- return_value = 0
152
+ workflow_type = SpanHandler.get_workflow_type(to_wrap)
153
+ span.set_attribute(f"entity.{span_index}.type", workflow_type)
154
+
155
+ @staticmethod
156
+ def get_workflow_type(to_wrap):
157
+ # workflow type
158
+ workflow_type = WORKFLOW_TYPE_GENERIC
159
+ if to_wrap is not None:
160
+ package_name = to_wrap.get('package')
161
+ for (package, framework_workflow_type) in WORKFLOW_TYPE_MAP.items():
162
+ if (package_name is not None and package in package_name):
163
+ workflow_type = framework_workflow_type
164
+ break
165
+ return workflow_type
166
+
167
+ def set_app_hosting_identifier_attribute(span):
168
+ span_index = 2
148
169
  # Search env to indentify the infra service type, if found check env for service name if possible
170
+ span.set_attribute(f"entity.{span_index}.type", f"app_hosting.generic")
171
+ span.set_attribute(f"entity.{span_index}.name", "generic")
149
172
  for type_env, type_name in service_type_map.items():
150
173
  if type_env in os.environ:
151
- return_value = 1
152
174
  span.set_attribute(f"entity.{span_index}.type", f"app_hosting.{type_name}")
153
175
  entity_name_env = service_name_map.get(type_name, "unknown")
154
176
  span.set_attribute(f"entity.{span_index}.name", os.environ.get(entity_name_env, "generic"))
155
- return return_value
156
177
 
157
- def get_workflow_name(self, span: Span) -> str:
178
+ @staticmethod
179
+ def get_workflow_name(span: Span) -> str:
158
180
  try:
159
181
  return get_value("workflow_name") or span.resource.attributes.get("service.name")
160
182
  except Exception as e:
161
183
  logger.exception(f"Error getting workflow name: {e}")
162
184
  return None
163
185
 
164
- def __is_root_span(self, curr_span: Span) -> bool:
186
+ @staticmethod
187
+ def is_root_span(curr_span: Span) -> bool:
165
188
  try:
166
189
  if curr_span is not None and hasattr(curr_span, "parent"):
167
- return curr_span.parent is None or get_current().get("root_span_id") == curr_span.parent.span_id
190
+ return curr_span.parent is None
168
191
  except Exception as e:
169
192
  logger.warning(f"Error finding root span: {e}")
193
+
194
+ def is_non_workflow_root_span(self, curr_span: Span, to_wrap) -> bool:
195
+ return SpanHandler.is_root_span(curr_span) and to_wrap.get("span_type") != "workflow"
196
+
197
+ def is_workflow_span_active(self):
198
+ return get_value(WORKFLOW_TYPE_KEY) is not None
199
+
200
+ @staticmethod
201
+ def attach_workflow_type(to_wrap=None, context=None):
202
+ token = None
203
+ if to_wrap:
204
+ if to_wrap.get('span_type') == "workflow":
205
+ token = attach(set_value(WORKFLOW_TYPE_KEY,
206
+ SpanHandler.get_workflow_type(to_wrap), context))
207
+ else:
208
+ token = attach(set_value(WORKFLOW_TYPE_KEY, WORKFLOW_TYPE_GENERIC, context))
209
+ return token
210
+
211
+ @staticmethod
212
+ def detach_workflow_type(token):
213
+ if token:
214
+ return detach(token)
215
+
216
+ class NonFrameworkSpanHandler(SpanHandler):
217
+
218
+ # If the language framework is being executed, then skip generating direct openAI spans
219
+ def skip_span(self, to_wrap, wrapped, instance, args, kwargs) -> bool:
220
+ return get_value(WORKFLOW_TYPE_KEY) in WORKFLOW_TYPE_MAP.values()
@@ -1,7 +1,7 @@
1
1
  import logging, json
2
2
  import os
3
3
  from typing import Callable, Generic, Optional, TypeVar, Mapping
4
- from threading import local
4
+ import threading, asyncio
5
5
 
6
6
  from opentelemetry.context import attach, detach, get_current, get_value, set_value, Context
7
7
  from opentelemetry.trace import NonRecordingSpan, Span, get_tracer
@@ -9,7 +9,7 @@ from opentelemetry.trace.propagation import _SPAN_KEY
9
9
  from opentelemetry.sdk.trace import id_generator, TracerProvider
10
10
  from opentelemetry.propagate import inject, extract
11
11
  from opentelemetry import baggage
12
- from monocle_apptrace.instrumentation.common.constants import MONOCLE_SCOPE_NAME_PREFIX, SCOPE_METHOD_FILE, SCOPE_CONFIG_PATH
12
+ from monocle_apptrace.instrumentation.common.constants import MONOCLE_SCOPE_NAME_PREFIX, SCOPE_METHOD_FILE, SCOPE_CONFIG_PATH, llm_type_map
13
13
 
14
14
  T = TypeVar('T')
15
15
  U = TypeVar('U')
@@ -21,6 +21,21 @@ embedding_model_context = {}
21
21
  scope_id_generator = id_generator.RandomIdGenerator()
22
22
  http_scopes:dict[str:str] = {}
23
23
 
24
+ class MonocleSpanException(Exception):
25
+ def __init__(self, err_message:str):
26
+ """
27
+ Monocle exeption to indicate error in span processing.
28
+ Parameters:
29
+ - err_message (str): Error message.
30
+ - status (str): Status code
31
+ """
32
+ super().__init__(err_message)
33
+ self.message = err_message
34
+
35
+ def __str__(self):
36
+ """String representation of the exception."""
37
+ return f"[Monocle Span Error: {self.message} {self.status}"
38
+
24
39
  def set_tracer_provider(tracer_provider: TracerProvider):
25
40
  global monocle_tracer_provider
26
41
  monocle_tracer_provider = tracer_provider
@@ -232,13 +247,69 @@ def clear_http_scopes(token:object) -> None:
232
247
  global http_scopes
233
248
  remove_scopes(token)
234
249
 
235
- def http_route_handler(headers, func, req):
236
- token = extract_http_headers(headers)
250
+ def http_route_handler(func, *args, **kwargs):
251
+ if 'req' in kwargs and hasattr(kwargs['req'], 'headers'):
252
+ headers = kwargs['req'].headers
253
+ else:
254
+ headers = None
255
+ token = None
256
+ if headers is not None:
257
+ token = extract_http_headers(headers)
237
258
  try:
238
- result = func(req)
259
+ result = func(*args, **kwargs)
239
260
  return result
240
261
  finally:
241
- clear_http_scopes(token)
262
+ if token is not None:
263
+ clear_http_scopes(token)
264
+
265
+ async def http_async_route_handler(func, *args, **kwargs):
266
+ if 'req' in kwargs and hasattr(kwargs['req'], 'headers'):
267
+ headers = kwargs['req'].headers
268
+ else:
269
+ headers = None
270
+ return async_wrapper(func, None, None, headers, *args, **kwargs)
271
+
272
+ def run_async_with_scope(method, current_context, exceptions, *args, **kwargs):
273
+ token = None
274
+ try:
275
+ if current_context:
276
+ token = attach(current_context)
277
+ return asyncio.run(method(*args, **kwargs))
278
+ except Exception as e:
279
+ exceptions['exception'] = e
280
+ raise e
281
+ finally:
282
+ if token:
283
+ detach(token)
284
+
285
+ def async_wrapper(method, scope_name=None, scope_value=None, headers=None, *args, **kwargs):
286
+ try:
287
+ run_loop = asyncio.get_running_loop()
288
+ except RuntimeError:
289
+ run_loop = None
290
+
291
+ token = None
292
+ exceptions = {}
293
+ if scope_name:
294
+ token = set_scope(scope_name, scope_value)
295
+ elif headers:
296
+ token = extract_http_headers(headers)
297
+ current_context = get_current()
298
+ try:
299
+ if run_loop and run_loop.is_running():
300
+ results = []
301
+ thread = threading.Thread(target=lambda: results.append(run_async_with_scope(method, current_context, exceptions, *args, **kwargs)))
302
+ thread.start()
303
+ thread.join()
304
+ if 'exception' in exceptions:
305
+ raise exceptions['exception']
306
+ return_value = results[0] if len(results) > 0 else None
307
+ return return_value
308
+ else:
309
+ return run_async_with_scope(method, None, exceptions, *args, **kwargs)
310
+ finally:
311
+ if token:
312
+ remove_scope(token)
242
313
 
243
314
  class Option(Generic[T]):
244
315
  def __init__(self, value: Optional[T]):
@@ -270,6 +341,13 @@ def try_option(func: Callable[..., T], *args, **kwargs) -> Option[T]:
270
341
  except Exception:
271
342
  return Option(None)
272
343
 
344
+ def get_llm_type(instance):
345
+ try:
346
+ llm_type = llm_type_map.get(type(instance).__name__.lower())
347
+ return llm_type
348
+ except:
349
+ pass
350
+
273
351
  def resolve_from_alias(my_map, alias):
274
352
  """Find a alias that is not none from list of aliases"""
275
353
  for i in alias: