monocle-apptrace 0.2.0__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.
- monocle_apptrace/__init__.py +1 -0
- monocle_apptrace/__main__.py +19 -0
- monocle_apptrace/exporters/aws/s3_exporter.py +50 -27
- monocle_apptrace/exporters/aws/s3_exporter_opendal.py +137 -0
- monocle_apptrace/exporters/azure/blob_exporter.py +30 -12
- monocle_apptrace/exporters/azure/blob_exporter_opendal.py +162 -0
- monocle_apptrace/exporters/base_exporter.py +19 -18
- monocle_apptrace/exporters/exporter_processor.py +128 -3
- monocle_apptrace/exporters/file_exporter.py +16 -0
- monocle_apptrace/exporters/monocle_exporters.py +48 -20
- 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 +70 -0
- monocle_apptrace/instrumentation/common/instrumentor.py +362 -0
- monocle_apptrace/instrumentation/common/span_handler.py +220 -0
- monocle_apptrace/instrumentation/common/utils.py +356 -0
- monocle_apptrace/instrumentation/common/wrapper.py +92 -0
- monocle_apptrace/instrumentation/common/wrapper_method.py +72 -0
- monocle_apptrace/instrumentation/metamodel/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/botocore/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +95 -0
- monocle_apptrace/instrumentation/metamodel/botocore/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +65 -0
- monocle_apptrace/instrumentation/metamodel/botocore/handlers/botocore_span_handler.py +26 -0
- monocle_apptrace/instrumentation/metamodel/botocore/methods.py +16 -0
- monocle_apptrace/instrumentation/metamodel/flask/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/flask/_helper.py +29 -0
- monocle_apptrace/instrumentation/metamodel/flask/methods.py +13 -0
- monocle_apptrace/instrumentation/metamodel/haystack/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/haystack/_helper.py +127 -0
- monocle_apptrace/instrumentation/metamodel/haystack/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +76 -0
- monocle_apptrace/instrumentation/metamodel/haystack/entities/retrieval.py +61 -0
- monocle_apptrace/instrumentation/metamodel/haystack/methods.py +43 -0
- monocle_apptrace/instrumentation/metamodel/langchain/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +127 -0
- monocle_apptrace/instrumentation/metamodel/langchain/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +72 -0
- monocle_apptrace/instrumentation/metamodel/langchain/entities/retrieval.py +58 -0
- monocle_apptrace/instrumentation/metamodel/langchain/methods.py +111 -0
- monocle_apptrace/instrumentation/metamodel/langgraph/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/langgraph/_helper.py +48 -0
- monocle_apptrace/instrumentation/metamodel/langgraph/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/langgraph/entities/inference.py +56 -0
- monocle_apptrace/instrumentation/metamodel/langgraph/methods.py +14 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +172 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/agent.py +47 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +73 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/retrieval.py +57 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +101 -0
- monocle_apptrace/instrumentation/metamodel/openai/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/openai/_helper.py +112 -0
- monocle_apptrace/instrumentation/metamodel/openai/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +71 -0
- monocle_apptrace/instrumentation/metamodel/openai/entities/retrieval.py +43 -0
- monocle_apptrace/instrumentation/metamodel/openai/methods.py +45 -0
- monocle_apptrace/instrumentation/metamodel/requests/__init__.py +4 -0
- monocle_apptrace/instrumentation/metamodel/requests/_helper.py +31 -0
- monocle_apptrace/instrumentation/metamodel/requests/methods.py +12 -0
- {monocle_apptrace-0.2.0.dist-info → monocle_apptrace-0.3.0.dist-info}/METADATA +19 -2
- monocle_apptrace-0.3.0.dist-info/RECORD +68 -0
- {monocle_apptrace-0.2.0.dist-info → monocle_apptrace-0.3.0.dist-info}/WHEEL +1 -1
- monocle_apptrace/constants.py +0 -22
- monocle_apptrace/haystack/__init__.py +0 -9
- monocle_apptrace/haystack/wrap_node.py +0 -27
- monocle_apptrace/haystack/wrap_openai.py +0 -44
- monocle_apptrace/haystack/wrap_pipeline.py +0 -63
- monocle_apptrace/instrumentor.py +0 -121
- monocle_apptrace/langchain/__init__.py +0 -9
- monocle_apptrace/llamaindex/__init__.py +0 -16
- monocle_apptrace/metamodel/README.md +0 -47
- monocle_apptrace/metamodel/entities/README.md +0 -77
- monocle_apptrace/metamodel/entities/app_hosting_types.json +0 -29
- monocle_apptrace/metamodel/entities/entities.json +0 -49
- monocle_apptrace/metamodel/entities/inference_types.json +0 -33
- monocle_apptrace/metamodel/entities/model_types.json +0 -41
- monocle_apptrace/metamodel/entities/vector_store_types.json +0 -25
- monocle_apptrace/metamodel/entities/workflow_types.json +0 -22
- monocle_apptrace/metamodel/maps/attributes/inference/langchain_entities.json +0 -35
- monocle_apptrace/metamodel/maps/attributes/inference/llamaindex_entities.json +0 -35
- monocle_apptrace/metamodel/maps/attributes/retrieval/langchain_entities.json +0 -27
- monocle_apptrace/metamodel/maps/attributes/retrieval/llamaindex_entities.json +0 -27
- monocle_apptrace/metamodel/maps/haystack_methods.json +0 -25
- monocle_apptrace/metamodel/maps/langchain_methods.json +0 -129
- monocle_apptrace/metamodel/maps/llamaindex_methods.json +0 -74
- monocle_apptrace/metamodel/spans/README.md +0 -121
- monocle_apptrace/metamodel/spans/span_example.json +0 -140
- monocle_apptrace/metamodel/spans/span_format.json +0 -55
- monocle_apptrace/metamodel/spans/span_types.json +0 -16
- monocle_apptrace/utils.py +0 -172
- monocle_apptrace/wrap_common.py +0 -417
- monocle_apptrace/wrapper.py +0 -26
- monocle_apptrace-0.2.0.dist-info/RECORD +0 -44
- {monocle_apptrace-0.2.0.dist-info → monocle_apptrace-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {monocle_apptrace-0.2.0.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()
|