monocle-apptrace 0.1.1__py3-none-any.whl → 0.3.0__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 (88) hide show
  1. monocle_apptrace/__init__.py +1 -0
  2. monocle_apptrace/__main__.py +19 -0
  3. monocle_apptrace/exporters/aws/s3_exporter.py +181 -0
  4. monocle_apptrace/exporters/aws/s3_exporter_opendal.py +137 -0
  5. monocle_apptrace/exporters/azure/blob_exporter.py +146 -0
  6. monocle_apptrace/exporters/azure/blob_exporter_opendal.py +162 -0
  7. monocle_apptrace/exporters/base_exporter.py +48 -0
  8. monocle_apptrace/exporters/exporter_processor.py +144 -0
  9. monocle_apptrace/exporters/file_exporter.py +16 -0
  10. monocle_apptrace/exporters/monocle_exporters.py +55 -0
  11. monocle_apptrace/exporters/okahu/okahu_exporter.py +117 -0
  12. monocle_apptrace/instrumentation/__init__.py +1 -0
  13. monocle_apptrace/instrumentation/common/__init__.py +2 -0
  14. monocle_apptrace/instrumentation/common/constants.py +70 -0
  15. monocle_apptrace/instrumentation/common/instrumentor.py +362 -0
  16. monocle_apptrace/instrumentation/common/span_handler.py +220 -0
  17. monocle_apptrace/instrumentation/common/utils.py +356 -0
  18. monocle_apptrace/instrumentation/common/wrapper.py +92 -0
  19. monocle_apptrace/instrumentation/common/wrapper_method.py +72 -0
  20. monocle_apptrace/instrumentation/metamodel/__init__.py +0 -0
  21. monocle_apptrace/instrumentation/metamodel/botocore/__init__.py +0 -0
  22. monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +95 -0
  23. monocle_apptrace/instrumentation/metamodel/botocore/entities/__init__.py +0 -0
  24. monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +65 -0
  25. monocle_apptrace/instrumentation/metamodel/botocore/handlers/botocore_span_handler.py +26 -0
  26. monocle_apptrace/instrumentation/metamodel/botocore/methods.py +16 -0
  27. monocle_apptrace/instrumentation/metamodel/flask/__init__.py +0 -0
  28. monocle_apptrace/instrumentation/metamodel/flask/_helper.py +29 -0
  29. monocle_apptrace/instrumentation/metamodel/flask/methods.py +13 -0
  30. monocle_apptrace/instrumentation/metamodel/haystack/__init__.py +0 -0
  31. monocle_apptrace/instrumentation/metamodel/haystack/_helper.py +127 -0
  32. monocle_apptrace/instrumentation/metamodel/haystack/entities/__init__.py +0 -0
  33. monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +76 -0
  34. monocle_apptrace/instrumentation/metamodel/haystack/entities/retrieval.py +61 -0
  35. monocle_apptrace/instrumentation/metamodel/haystack/methods.py +43 -0
  36. monocle_apptrace/instrumentation/metamodel/langchain/__init__.py +0 -0
  37. monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +127 -0
  38. monocle_apptrace/instrumentation/metamodel/langchain/entities/__init__.py +0 -0
  39. monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +72 -0
  40. monocle_apptrace/instrumentation/metamodel/langchain/entities/retrieval.py +58 -0
  41. monocle_apptrace/{metamodel/maps/lang_chain_methods.json → instrumentation/metamodel/langchain/methods.py} +48 -43
  42. monocle_apptrace/instrumentation/metamodel/langgraph/__init__.py +0 -0
  43. monocle_apptrace/instrumentation/metamodel/langgraph/_helper.py +48 -0
  44. monocle_apptrace/instrumentation/metamodel/langgraph/entities/__init__.py +0 -0
  45. monocle_apptrace/instrumentation/metamodel/langgraph/entities/inference.py +56 -0
  46. monocle_apptrace/instrumentation/metamodel/langgraph/methods.py +14 -0
  47. monocle_apptrace/instrumentation/metamodel/llamaindex/__init__.py +0 -0
  48. monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +172 -0
  49. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/__init__.py +0 -0
  50. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/agent.py +47 -0
  51. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +73 -0
  52. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/retrieval.py +57 -0
  53. monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +101 -0
  54. monocle_apptrace/instrumentation/metamodel/openai/__init__.py +0 -0
  55. monocle_apptrace/instrumentation/metamodel/openai/_helper.py +112 -0
  56. monocle_apptrace/instrumentation/metamodel/openai/entities/__init__.py +0 -0
  57. monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +71 -0
  58. monocle_apptrace/instrumentation/metamodel/openai/entities/retrieval.py +43 -0
  59. monocle_apptrace/instrumentation/metamodel/openai/methods.py +45 -0
  60. monocle_apptrace/instrumentation/metamodel/requests/__init__.py +4 -0
  61. monocle_apptrace/instrumentation/metamodel/requests/_helper.py +31 -0
  62. monocle_apptrace/instrumentation/metamodel/requests/methods.py +12 -0
  63. {monocle_apptrace-0.1.1.dist-info → monocle_apptrace-0.3.0.dist-info}/METADATA +23 -2
  64. monocle_apptrace-0.3.0.dist-info/RECORD +68 -0
  65. {monocle_apptrace-0.1.1.dist-info → monocle_apptrace-0.3.0.dist-info}/WHEEL +1 -1
  66. monocle_apptrace/constants.py +0 -22
  67. monocle_apptrace/haystack/__init__.py +0 -9
  68. monocle_apptrace/haystack/wrap_node.py +0 -27
  69. monocle_apptrace/haystack/wrap_openai.py +0 -44
  70. monocle_apptrace/haystack/wrap_pipeline.py +0 -62
  71. monocle_apptrace/instrumentor.py +0 -124
  72. monocle_apptrace/langchain/__init__.py +0 -6
  73. monocle_apptrace/llamaindex/__init__.py +0 -15
  74. monocle_apptrace/metamodel/README.md +0 -47
  75. monocle_apptrace/metamodel/entities/README.md +0 -54
  76. monocle_apptrace/metamodel/entities/entity_types.json +0 -157
  77. monocle_apptrace/metamodel/entities/entity_types.py +0 -51
  78. monocle_apptrace/metamodel/maps/haystack_methods.json +0 -25
  79. monocle_apptrace/metamodel/maps/llama_index_methods.json +0 -70
  80. monocle_apptrace/metamodel/spans/README.md +0 -121
  81. monocle_apptrace/metamodel/spans/span_example.json +0 -140
  82. monocle_apptrace/metamodel/spans/span_format.json +0 -55
  83. monocle_apptrace/utils.py +0 -93
  84. monocle_apptrace/wrap_common.py +0 -311
  85. monocle_apptrace/wrapper.py +0 -24
  86. monocle_apptrace-0.1.1.dist-info/RECORD +0 -29
  87. {monocle_apptrace-0.1.1.dist-info → monocle_apptrace-0.3.0.dist-info}/licenses/LICENSE +0 -0
  88. {monocle_apptrace-0.1.1.dist-info → monocle_apptrace-0.3.0.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,362 @@
1
+ import logging
2
+ import inspect
3
+ from typing import Collection, Dict, List, Union
4
+ import random
5
+ import uuid
6
+ from opentelemetry import trace
7
+ from contextlib import contextmanager
8
+ from opentelemetry.context import attach, get_value, set_value, get_current, detach
9
+ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
10
+ from opentelemetry.instrumentation.utils import unwrap
11
+ from opentelemetry.trace import SpanContext
12
+ from opentelemetry.sdk.trace import TracerProvider, Span, id_generator
13
+ from opentelemetry.sdk.resources import SERVICE_NAME, Resource
14
+ from opentelemetry.sdk.trace import Span, TracerProvider
15
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanProcessor
16
+ from opentelemetry.trace import get_tracer
17
+ from wrapt import wrap_function_wrapper
18
+ from opentelemetry.trace.propagation import set_span_in_context, _SPAN_KEY
19
+ from monocle_apptrace.exporters.monocle_exporters import get_monocle_exporter
20
+ from monocle_apptrace.instrumentation.common.span_handler import SpanHandler, NonFrameworkSpanHandler
21
+ from monocle_apptrace.instrumentation.common.wrapper_method import (
22
+ DEFAULT_METHODS_LIST,
23
+ WrapperMethod,
24
+ MONOCLE_SPAN_HANDLERS
25
+ )
26
+ from monocle_apptrace.instrumentation.common.wrapper import scope_wrapper, ascope_wrapper, wrapper_processor
27
+ from monocle_apptrace.instrumentation.common.utils import (
28
+ set_scope, remove_scope, http_route_handler, load_scopes, async_wrapper, http_async_route_handler
29
+ )
30
+ from monocle_apptrace.instrumentation.common.constants import MONOCLE_INSTRUMENTOR, WORKFLOW_TYPE_KEY
31
+ from functools import wraps
32
+ logger = logging.getLogger(__name__)
33
+
34
+ SESSION_PROPERTIES_KEY = "session"
35
+
36
+ _instruments = ()
37
+
38
+ monocle_tracer_provider: TracerProvider = None
39
+
40
+ class MonocleInstrumentor(BaseInstrumentor):
41
+ workflow_name: str = ""
42
+ user_wrapper_methods: list[Union[dict,WrapperMethod]] = []
43
+ instrumented_method_list: list[object] = []
44
+ handlers:Dict[str,SpanHandler] = {} # dict of handlers
45
+ union_with_default_methods: bool = False
46
+
47
+ def __init__(
48
+ self,
49
+ handlers,
50
+ user_wrapper_methods: list[Union[dict,WrapperMethod]] = None,
51
+ union_with_default_methods: bool = True
52
+ ) -> None:
53
+ self.user_wrapper_methods = user_wrapper_methods or []
54
+ self.handlers = handlers
55
+ if self.handlers is not None:
56
+ for key, val in MONOCLE_SPAN_HANDLERS.items():
57
+ if key not in self.handlers:
58
+ self.handlers[key] = val
59
+ else:
60
+ self.handlers = MONOCLE_SPAN_HANDLERS
61
+ self.union_with_default_methods = union_with_default_methods
62
+ super().__init__()
63
+
64
+ def get_instrumentor(self, tracer):
65
+ def instrumented_endpoint_invoke(to_wrap,wrapped, span_name, instance,fn):
66
+ @wraps(fn)
67
+ def with_instrumentation(*args, **kwargs):
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)
73
+ return with_instrumentation
74
+ return instrumented_endpoint_invoke
75
+
76
+ def instrumentation_dependencies(self) -> Collection[str]:
77
+ return _instruments
78
+
79
+ def _instrument(self, **kwargs):
80
+ tracer_provider: TracerProvider = kwargs.get("tracer_provider")
81
+ set_tracer_provider(tracer_provider)
82
+ tracer = get_tracer(instrumenting_module_name=MONOCLE_INSTRUMENTOR, tracer_provider=tracer_provider)
83
+
84
+ final_method_list = []
85
+ if self.union_with_default_methods is True:
86
+ final_method_list= final_method_list + DEFAULT_METHODS_LIST
87
+
88
+ for method in self.user_wrapper_methods:
89
+ if isinstance(method, dict):
90
+ final_method_list.append(method)
91
+ elif isinstance(method, WrapperMethod):
92
+ final_method_list.append(method.to_dict())
93
+
94
+ for method in load_scopes():
95
+ if method.get('async', False):
96
+ method['wrapper_method'] = ascope_wrapper
97
+ else:
98
+ method['wrapper_method'] = scope_wrapper
99
+ final_method_list.append(method)
100
+
101
+ for method_config in final_method_list:
102
+ target_package = method_config.get("package", None)
103
+ target_object = method_config.get("object", None)
104
+ target_method = method_config.get("method", None)
105
+ wrapped_by = method_config.get("wrapper_method", None)
106
+ #get the requisite handler or default one
107
+ handler_key = method_config.get("span_handler",'default')
108
+ try:
109
+ handler = self.handlers.get(handler_key)
110
+ if not handler:
111
+ logger.warning("incorrect or empty handler falling back to default handler")
112
+ handler = self.handlers.get('default')
113
+ handler.set_instrumentor(self.get_instrumentor(tracer))
114
+ wrap_function_wrapper(
115
+ target_package,
116
+ f"{target_object}.{target_method}" if target_object else target_method,
117
+ wrapped_by(tracer, handler, method_config),
118
+ )
119
+ self.instrumented_method_list.append(method_config)
120
+ except ModuleNotFoundError as e:
121
+ logger.debug(f"ignoring module {e.name}")
122
+
123
+ except Exception as ex:
124
+ logger.error(f"""_instrument wrap exception: {str(ex)}
125
+ for package: {target_package},
126
+ object:{target_object},
127
+ method:{target_method}""")
128
+
129
+ def _uninstrument(self, **kwargs):
130
+ for wrapped_method in self.instrumented_method_list:
131
+ try:
132
+ wrap_package = wrapped_method.get("package")
133
+ wrap_object = wrapped_method.get("object")
134
+ wrap_method = wrapped_method.get("method")
135
+ unwrap(
136
+ f"{wrap_package}.{wrap_object}" if wrap_object else wrap_package,
137
+ wrap_method,
138
+ )
139
+ except Exception as ex:
140
+ logger.error(f"""_instrument unwrap exception: {str(ex)}
141
+ for package: {wrap_package},
142
+ object:{wrap_object},
143
+ method:{wrap_method}""")
144
+
145
+ def set_tracer_provider(tracer_provider: TracerProvider):
146
+ global monocle_tracer_provider
147
+ monocle_tracer_provider = tracer_provider
148
+
149
+ def get_tracer_provider() -> TracerProvider:
150
+ global monocle_tracer_provider
151
+ return monocle_tracer_provider
152
+
153
+ def setup_monocle_telemetry(
154
+ workflow_name: str,
155
+ span_processors: List[SpanProcessor] = None,
156
+ span_handlers: Dict[str,SpanHandler] = None,
157
+ wrapper_methods: List[Union[dict,WrapperMethod]] = None,
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
+ """
177
+ resource = Resource(attributes={
178
+ SERVICE_NAME: workflow_name
179
+ })
180
+ exporters = get_monocle_exporter()
181
+ span_processors = span_processors or [BatchSpanProcessor(exporter) for exporter in exporters]
182
+ set_tracer_provider(TracerProvider(resource=resource))
183
+ attach(set_value("workflow_name", workflow_name))
184
+ tracer_provider_default = trace.get_tracer_provider()
185
+ provider_type = type(tracer_provider_default).__name__
186
+ is_proxy_provider = "Proxy" in provider_type
187
+ for processor in span_processors:
188
+ processor.on_start = on_processor_start
189
+ if not is_proxy_provider:
190
+ tracer_provider_default.add_span_processor(processor)
191
+ else:
192
+ get_tracer_provider().add_span_processor(processor)
193
+ if is_proxy_provider:
194
+ trace.set_tracer_provider(get_tracer_provider())
195
+ instrumentor = MonocleInstrumentor(user_wrapper_methods=wrapper_methods or [],
196
+ handlers=span_handlers, union_with_default_methods = union_with_default_methods)
197
+ # instrumentor.app_name = workflow_name
198
+ if not instrumentor.is_instrumented_by_opentelemetry:
199
+ instrumentor.instrument(trace_provider=get_tracer_provider())
200
+
201
+ return instrumentor
202
+
203
+ def on_processor_start(span: Span, parent_context):
204
+ context_properties = get_value(SESSION_PROPERTIES_KEY)
205
+ if context_properties is not None:
206
+ for key, value in context_properties.items():
207
+ span.set_attribute(
208
+ f"{SESSION_PROPERTIES_KEY}.{key}", value
209
+ )
210
+
211
+ def set_context_properties(properties: dict) -> None:
212
+ attach(set_value(SESSION_PROPERTIES_KEY, properties))
213
+
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
+ """
225
+ try:
226
+ tracer = get_tracer(instrumenting_module_name= MONOCLE_INSTRUMENTOR, tracer_provider= get_tracer_provider())
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)
232
+ return token
233
+ except:
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
+ """
246
+ try:
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)
254
+ except:
255
+ logger.warning("Failed to stop trace")
256
+
257
+ def is_valid_trace_id_uuid(traceId: str) -> bool:
258
+ try:
259
+ uuid.UUID(traceId)
260
+ return True
261
+ except:
262
+ pass
263
+ return False
264
+
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
+ """
275
+ return set_scope(scope_name, scope_value)
276
+
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
+ """
285
+ remove_scope(token)
286
+ return
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
+
299
+ @contextmanager
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."""
306
+ token = start_scope(scope_name, scope_value)
307
+ try:
308
+ yield
309
+ finally:
310
+ stop_scope(token)
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
+ """
316
+ def decorator(func):
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)
321
+ return result
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
333
+ return decorator
334
+
335
+ def monocle_trace_http_route(func):
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
350
+
351
+ class FixedIdGenerator(id_generator.IdGenerator):
352
+ def __init__(
353
+ self,
354
+ trace_id: int) -> None:
355
+ self.trace_id = trace_id
356
+
357
+ def generate_span_id(self) -> int:
358
+ return random.getrandbits(64)
359
+
360
+ def generate_trace_id(self) -> int:
361
+ return self.trace_id
362
+
@@ -0,0 +1,220 @@
1
+ import logging
2
+ import os
3
+ from importlib.metadata import version
4
+ from opentelemetry.context import get_value, set_value, attach, detach
5
+ from opentelemetry.sdk.trace import Span
6
+ from opentelemetry.trace.status import Status, StatusCode
7
+ from monocle_apptrace.instrumentation.common.constants import (
8
+ QUERY,
9
+ service_name_map,
10
+ service_type_map,
11
+ MONOCLE_SDK_VERSION
12
+ )
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
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ WORKFLOW_TYPE_MAP = {
19
+ "llama_index": "workflow.llamaindex",
20
+ "langchain": "workflow.langchain",
21
+ "haystack": "workflow.haystack"
22
+ }
23
+
24
+ class SpanHandler:
25
+
26
+ def __init__(self,instrumentor=None):
27
+ self.instrumentor=instrumentor
28
+
29
+ def set_instrumentor(self, instrumentor):
30
+ self.instrumentor = instrumentor
31
+
32
+ def validate(self, to_wrap, wrapped, instance, args, kwargs):
33
+ pass
34
+
35
+ def pre_tracing(self, to_wrap, wrapped, instance, args, kwargs):
36
+ pass
37
+
38
+ def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value):
39
+ pass
40
+
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
45
+ return False
46
+
47
+ def pre_task_processing(self, to_wrap, wrapped, instance, args,kwargs, span):
48
+ if "pipeline" in to_wrap['package']:
49
+ set_attribute(QUERY, args[0]['prompt_builder']['question'])
50
+
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)
73
+
74
+ def hydrate_span(self, to_wrap, wrapped, instance, args, kwargs, result, span):
75
+ self.hydrate_attributes(to_wrap, wrapped, instance, args, kwargs, result, span)
76
+ self.hydrate_events(to_wrap, wrapped, instance, args, kwargs, result, span)
77
+
78
+ def hydrate_attributes(self, to_wrap, wrapped, instance, args, kwargs, result, span):
79
+ span_index = 0
80
+ if SpanHandler.is_root_span(span):
81
+ span_index = 2 # root span will have workflow and hosting entities pre-populated
82
+ if 'output_processor' in to_wrap and to_wrap["output_processor"] is not None:
83
+ output_processor=to_wrap['output_processor']
84
+ if 'type' in output_processor:
85
+ span.set_attribute("span.type", output_processor['type'])
86
+ else:
87
+ logger.warning("type of span not found or incorrect written in entity json")
88
+ if 'attributes' in output_processor:
89
+ for processors in output_processor["attributes"]:
90
+ for processor in processors:
91
+ attribute = processor.get('attribute')
92
+ accessor = processor.get('accessor')
93
+
94
+ if attribute and accessor:
95
+ attribute_name = f"entity.{span_index+1}.{attribute}"
96
+ try:
97
+ arguments = {"instance":instance, "args":args, "kwargs":kwargs, "result":result}
98
+ result = accessor(arguments)
99
+ if result and isinstance(result, (str, list)):
100
+ span.set_attribute(attribute_name, result)
101
+ except MonocleSpanException as e:
102
+ span.set_status(StatusCode.ERROR, e.message)
103
+ except Exception as e:
104
+ logger.debug(f"Error processing accessor: {e}")
105
+ else:
106
+ logger.debug(f"{' and '.join([key for key in ['attribute', 'accessor'] if not processor.get(key)])} not found or incorrect in entity JSON")
107
+ span_index += 1
108
+ else:
109
+ logger.debug("attributes not found or incorrect written in entity json")
110
+
111
+ # set scopes as attributes by calling get_scopes()
112
+ # scopes is a Mapping[str:object], iterate directly with .items()
113
+ for scope_key, scope_value in get_scopes().items():
114
+ span.set_attribute(f"scope.{scope_key}", scope_value)
115
+
116
+ if span_index > 0:
117
+ span.set_attribute("entity.count", span_index)
118
+
119
+
120
+ def hydrate_events(self, to_wrap, wrapped, instance, args, kwargs, result, span):
121
+ if 'output_processor' in to_wrap and to_wrap["output_processor"] is not None:
122
+ output_processor=to_wrap['output_processor']
123
+ arguments = {"instance": instance, "args": args, "kwargs": kwargs, "result": result}
124
+ if 'events' in output_processor:
125
+ events = output_processor['events']
126
+ for event in events:
127
+ event_name = event.get("name")
128
+ event_attributes = {}
129
+ attributes = event.get("attributes", [])
130
+ for attribute in attributes:
131
+ attribute_key = attribute.get("attribute")
132
+ accessor = attribute.get("accessor")
133
+ if accessor:
134
+ try:
135
+ if attribute_key is not None:
136
+ event_attributes[attribute_key] = accessor(arguments)
137
+ else:
138
+ event_attributes.update(accessor(arguments))
139
+ except MonocleSpanException as e:
140
+ span.set_status(StatusCode.ERROR, e.message)
141
+ except Exception as e:
142
+ logger.debug(f"Error evaluating accessor for attribute '{attribute_key}': {e}")
143
+ span.add_event(name=event_name, attributes=event_attributes)
144
+
145
+ @staticmethod
146
+ def set_workflow_attributes(to_wrap, span: Span):
147
+ span_index = 1
148
+ workflow_name = SpanHandler.get_workflow_name(span=span)
149
+ if workflow_name:
150
+ span.set_attribute("span.type", "workflow")
151
+ span.set_attribute(f"entity.{span_index}.name", workflow_name)
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
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")
172
+ for type_env, type_name in service_type_map.items():
173
+ if type_env in os.environ:
174
+ span.set_attribute(f"entity.{span_index}.type", f"app_hosting.{type_name}")
175
+ entity_name_env = service_name_map.get(type_name, "unknown")
176
+ span.set_attribute(f"entity.{span_index}.name", os.environ.get(entity_name_env, "generic"))
177
+
178
+ @staticmethod
179
+ def get_workflow_name(span: Span) -> str:
180
+ try:
181
+ return get_value("workflow_name") or span.resource.attributes.get("service.name")
182
+ except Exception as e:
183
+ logger.exception(f"Error getting workflow name: {e}")
184
+ return None
185
+
186
+ @staticmethod
187
+ def is_root_span(curr_span: Span) -> bool:
188
+ try:
189
+ if curr_span is not None and hasattr(curr_span, "parent"):
190
+ return curr_span.parent is None
191
+ except Exception as e:
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()