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