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.
- monocle_apptrace/__init__.py +1 -0
- monocle_apptrace/exporters/aws/s3_exporter.py +20 -6
- monocle_apptrace/exporters/aws/s3_exporter_opendal.py +22 -11
- monocle_apptrace/exporters/azure/blob_exporter.py +29 -8
- monocle_apptrace/exporters/azure/blob_exporter_opendal.py +23 -8
- monocle_apptrace/exporters/exporter_processor.py +128 -3
- monocle_apptrace/exporters/file_exporter.py +16 -0
- monocle_apptrace/exporters/monocle_exporters.py +10 -1
- monocle_apptrace/exporters/okahu/okahu_exporter.py +8 -6
- monocle_apptrace/instrumentation/__init__.py +1 -0
- monocle_apptrace/instrumentation/common/__init__.py +2 -0
- monocle_apptrace/instrumentation/common/constants.py +17 -0
- monocle_apptrace/instrumentation/common/instrumentor.py +136 -53
- monocle_apptrace/instrumentation/common/span_handler.py +92 -41
- monocle_apptrace/instrumentation/common/utils.py +84 -6
- monocle_apptrace/instrumentation/common/wrapper.py +43 -45
- monocle_apptrace/instrumentation/common/wrapper_method.py +8 -4
- monocle_apptrace/instrumentation/metamodel/botocore/handlers/botocore_span_handler.py +2 -1
- monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +1 -1
- monocle_apptrace/instrumentation/metamodel/haystack/methods.py +2 -1
- monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +3 -2
- monocle_apptrace/instrumentation/metamodel/langchain/methods.py +12 -6
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +5 -3
- monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +6 -3
- monocle_apptrace/instrumentation/metamodel/openai/_helper.py +31 -7
- monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +1 -1
- monocle_apptrace/instrumentation/metamodel/openai/entities/retrieval.py +20 -1
- monocle_apptrace/instrumentation/metamodel/openai/methods.py +21 -1
- monocle_apptrace/instrumentation/metamodel/requests/__init__.py +3 -1
- {monocle_apptrace-0.3.0b5.dist-info → monocle_apptrace-0.3.0b7.dist-info}/METADATA +1 -1
- {monocle_apptrace-0.3.0b5.dist-info → monocle_apptrace-0.3.0b7.dist-info}/RECORD +34 -34
- {monocle_apptrace-0.3.0b5.dist-info → monocle_apptrace-0.3.0b7.dist-info}/WHEEL +0 -0
- {monocle_apptrace-0.3.0b5.dist-info → monocle_apptrace-0.3.0b7.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
result = 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)
|
|
258
321
|
return result
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
|
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
|
-
|
|
56
|
-
|
|
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
|
|
65
|
-
span_index
|
|
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
|
-
|
|
130
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if not
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
236
|
-
|
|
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(
|
|
259
|
+
result = func(*args, **kwargs)
|
|
239
260
|
return result
|
|
240
261
|
finally:
|
|
241
|
-
|
|
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:
|