monocle-apptrace 0.3.0b4__py3-none-any.whl → 0.3.0b5__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 +19 -0
- monocle_apptrace/exporters/monocle_exporters.py +5 -4
- monocle_apptrace/instrumentation/common/constants.py +5 -0
- monocle_apptrace/instrumentation/common/instrumentor.py +82 -11
- monocle_apptrace/instrumentation/common/span_handler.py +25 -10
- monocle_apptrace/instrumentation/common/utils.py +112 -5
- monocle_apptrace/instrumentation/common/wrapper.py +48 -23
- monocle_apptrace/instrumentation/common/wrapper_method.py +30 -7
- monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +0 -31
- monocle_apptrace/instrumentation/metamodel/botocore/handlers/botocore_span_handler.py +25 -0
- monocle_apptrace/instrumentation/metamodel/botocore/methods.py +6 -6
- 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/langchain/_helper.py +4 -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/_helper.py +37 -19
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/agent.py +47 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +9 -0
- monocle_apptrace/instrumentation/metamodel/openai/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/openai/_helper.py +88 -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 +24 -0
- monocle_apptrace/instrumentation/metamodel/openai/methods.py +25 -0
- monocle_apptrace/instrumentation/metamodel/requests/__init__.py +2 -0
- monocle_apptrace/instrumentation/metamodel/requests/_helper.py +31 -0
- monocle_apptrace/instrumentation/metamodel/requests/methods.py +12 -0
- {monocle_apptrace-0.3.0b4.dist-info → monocle_apptrace-0.3.0b5.dist-info}/METADATA +2 -1
- {monocle_apptrace-0.3.0b4.dist-info → monocle_apptrace-0.3.0b5.dist-info}/RECORD +37 -17
- {monocle_apptrace-0.3.0b4.dist-info → monocle_apptrace-0.3.0b5.dist-info}/WHEEL +0 -0
- {monocle_apptrace-0.3.0b4.dist-info → monocle_apptrace-0.3.0b5.dist-info}/licenses/LICENSE +0 -0
- {monocle_apptrace-0.3.0b4.dist-info → monocle_apptrace-0.3.0b5.dist-info}/licenses/NOTICE +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import sys, os
|
|
2
|
+
import runpy
|
|
3
|
+
from monocle_apptrace.instrumentation.common.instrumentor import setup_monocle_telemetry
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
if len(sys.argv) < 2 or not sys.argv[1].endswith(".py"):
|
|
7
|
+
print("Usage: python -m monocle_apptrace <your-main-module-file> <args>")
|
|
8
|
+
sys.exit(1)
|
|
9
|
+
file_name = os.path.basename(sys.argv[1])
|
|
10
|
+
workflow_name = file_name[:-3]
|
|
11
|
+
setup_monocle_telemetry(workflow_name=workflow_name)
|
|
12
|
+
sys.argv.pop(0)
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
runpy.run_path(path_name=sys.argv[0], run_name="__main__")
|
|
16
|
+
except Exception as e:
|
|
17
|
+
print(e)
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
main()
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from typing import Dict, Any, List
|
|
2
2
|
import os
|
|
3
|
-
import
|
|
3
|
+
import logging
|
|
4
4
|
from importlib import import_module
|
|
5
5
|
from opentelemetry.sdk.trace.export import SpanExporter, ConsoleSpanExporter
|
|
6
6
|
from monocle_apptrace.exporters.file_exporter import FileSpanExporter
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
7
8
|
|
|
8
9
|
monocle_exporters: Dict[str, Any] = {
|
|
9
10
|
"s3": {"module": "monocle_apptrace.exporters.aws.s3_exporter", "class": "S3SpanExporter"},
|
|
@@ -25,21 +26,21 @@ def get_monocle_exporter() -> List[SpanExporter]:
|
|
|
25
26
|
try:
|
|
26
27
|
exporter_class_path = monocle_exporters[exporter_name]
|
|
27
28
|
except KeyError:
|
|
28
|
-
|
|
29
|
+
logger.debug(f"Unsupported Monocle span exporter '{exporter_name}', skipping.")
|
|
29
30
|
continue
|
|
30
31
|
try:
|
|
31
32
|
exporter_module = import_module(exporter_class_path["module"])
|
|
32
33
|
exporter_class = getattr(exporter_module, exporter_class_path["class"])
|
|
33
34
|
exporters.append(exporter_class())
|
|
34
35
|
except Exception as ex:
|
|
35
|
-
|
|
36
|
+
logger.debug(
|
|
36
37
|
f"Unable to initialize Monocle span exporter '{exporter_name}', error: {ex}. Using ConsoleSpanExporter as a fallback.")
|
|
37
38
|
exporters.append(ConsoleSpanExporter())
|
|
38
39
|
continue
|
|
39
40
|
|
|
40
41
|
# If no exporters were created, default to FileSpanExporter
|
|
41
42
|
if not exporters:
|
|
42
|
-
|
|
43
|
+
logger.debug("No valid Monocle span exporters configured. Defaulting to FileSpanExporter.")
|
|
43
44
|
exporters.append(FileSpanExporter())
|
|
44
45
|
|
|
45
46
|
return exporters
|
|
@@ -37,6 +37,7 @@ service_name_map = {
|
|
|
37
37
|
GITHUB_CODESPACE_SERVICE_NAME: GITHUB_CODESPACE_IDENTIFIER_ENV_NAME
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
MONOCLE_INSTRUMENTOR = "monocle_apptrace"
|
|
40
41
|
WORKFLOW_TYPE_KEY = "workflow_type"
|
|
41
42
|
DATA_INPUT_KEY = "data.input"
|
|
42
43
|
DATA_OUTPUT_KEY = "data.output"
|
|
@@ -47,3 +48,7 @@ RESPONSE = "response"
|
|
|
47
48
|
SESSION_PROPERTIES_KEY = "session"
|
|
48
49
|
INFRA_SERVICE_KEY = "infra_service_name"
|
|
49
50
|
META_DATA = 'metadata'
|
|
51
|
+
MONOCLE_SCOPE_NAME_PREFIX = "monocle.scope."
|
|
52
|
+
SCOPE_METHOD_LIST = 'MONOCLE_SCOPE_METHODS'
|
|
53
|
+
SCOPE_METHOD_FILE = 'monocle_scopes.json'
|
|
54
|
+
SCOPE_CONFIG_PATH = 'MONOCLE_SCOPE_CONFIG_PATH'
|
|
@@ -3,6 +3,7 @@ from typing import Collection, Dict, List, Union
|
|
|
3
3
|
import random
|
|
4
4
|
import uuid
|
|
5
5
|
from opentelemetry import trace
|
|
6
|
+
from contextlib import contextmanager
|
|
6
7
|
from opentelemetry.context import attach, get_value, set_value, get_current, detach
|
|
7
8
|
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|
8
9
|
from opentelemetry.instrumentation.utils import unwrap
|
|
@@ -19,8 +20,14 @@ from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
|
|
|
19
20
|
from monocle_apptrace.instrumentation.common.wrapper_method import (
|
|
20
21
|
DEFAULT_METHODS_LIST,
|
|
21
22
|
WrapperMethod,
|
|
23
|
+
MONOCLE_SPAN_HANDLERS
|
|
22
24
|
)
|
|
23
|
-
|
|
25
|
+
from monocle_apptrace.instrumentation.common.wrapper import scope_wrapper
|
|
26
|
+
from monocle_apptrace.instrumentation.common.utils import (
|
|
27
|
+
set_scope, remove_scope, http_route_handler, load_scopes
|
|
28
|
+
)
|
|
29
|
+
from monocle_apptrace.instrumentation.common.constants import MONOCLE_INSTRUMENTOR
|
|
30
|
+
from functools import wraps
|
|
24
31
|
logger = logging.getLogger(__name__)
|
|
25
32
|
|
|
26
33
|
SESSION_PROPERTIES_KEY = "session"
|
|
@@ -29,8 +36,6 @@ _instruments = ()
|
|
|
29
36
|
|
|
30
37
|
monocle_tracer_provider: TracerProvider = None
|
|
31
38
|
|
|
32
|
-
MONOCLE_INSTRUMENTOR = "monocle_apptrace"
|
|
33
|
-
|
|
34
39
|
class MonocleInstrumentor(BaseInstrumentor):
|
|
35
40
|
workflow_name: str = ""
|
|
36
41
|
user_wrapper_methods: list[Union[dict,WrapperMethod]] = []
|
|
@@ -45,17 +50,36 @@ class MonocleInstrumentor(BaseInstrumentor):
|
|
|
45
50
|
union_with_default_methods: bool = True
|
|
46
51
|
) -> None:
|
|
47
52
|
self.user_wrapper_methods = user_wrapper_methods or []
|
|
48
|
-
self.handlers = handlers
|
|
53
|
+
self.handlers = handlers
|
|
54
|
+
if self.handlers is not None:
|
|
55
|
+
for key, val in MONOCLE_SPAN_HANDLERS.items():
|
|
56
|
+
if key not in self.handlers:
|
|
57
|
+
self.handlers[key] = val
|
|
58
|
+
else:
|
|
59
|
+
self.handlers = MONOCLE_SPAN_HANDLERS
|
|
49
60
|
self.union_with_default_methods = union_with_default_methods
|
|
50
61
|
super().__init__()
|
|
51
62
|
|
|
63
|
+
def get_instrumentor(self, tracer):
|
|
64
|
+
def instrumented_endpoint_invoke(to_wrap,wrapped, span_name, instance,fn):
|
|
65
|
+
@wraps(fn)
|
|
66
|
+
def with_instrumentation(*args, **kwargs):
|
|
67
|
+
handler = SpanHandler()
|
|
68
|
+
with tracer.start_as_current_span(span_name) as span:
|
|
69
|
+
response = fn(*args, **kwargs)
|
|
70
|
+
handler.hydrate_span(to_wrap, span=span, wrapped=wrapped, instance=instance, args=args, kwargs=kwargs,
|
|
71
|
+
result=response)
|
|
72
|
+
return response
|
|
73
|
+
|
|
74
|
+
return with_instrumentation
|
|
75
|
+
return instrumented_endpoint_invoke
|
|
76
|
+
|
|
52
77
|
def instrumentation_dependencies(self) -> Collection[str]:
|
|
53
78
|
return _instruments
|
|
54
79
|
|
|
55
80
|
def _instrument(self, **kwargs):
|
|
56
81
|
tracer_provider: TracerProvider = kwargs.get("tracer_provider")
|
|
57
|
-
|
|
58
|
-
monocle_tracer_provider = tracer_provider
|
|
82
|
+
set_tracer_provider(tracer_provider)
|
|
59
83
|
tracer = get_tracer(instrumenting_module_name=MONOCLE_INSTRUMENTOR, tracer_provider=tracer_provider)
|
|
60
84
|
|
|
61
85
|
final_method_list = []
|
|
@@ -67,6 +91,10 @@ class MonocleInstrumentor(BaseInstrumentor):
|
|
|
67
91
|
final_method_list.append(method)
|
|
68
92
|
elif isinstance(method, WrapperMethod):
|
|
69
93
|
final_method_list.append(method.to_dict())
|
|
94
|
+
|
|
95
|
+
for method in load_scopes():
|
|
96
|
+
method['wrapper_method'] = scope_wrapper
|
|
97
|
+
final_method_list.append(method)
|
|
70
98
|
|
|
71
99
|
for method_config in final_method_list:
|
|
72
100
|
target_package = method_config.get("package", None)
|
|
@@ -77,6 +105,10 @@ class MonocleInstrumentor(BaseInstrumentor):
|
|
|
77
105
|
handler_key = method_config.get("span_handler",'default')
|
|
78
106
|
try:
|
|
79
107
|
handler = self.handlers.get(handler_key)
|
|
108
|
+
if not handler:
|
|
109
|
+
logger.warning("incorrect or empty handler falling back to default handler")
|
|
110
|
+
handler = self.handlers.get('default')
|
|
111
|
+
handler.set_instrumentor(self.get_instrumentor(tracer))
|
|
80
112
|
wrap_function_wrapper(
|
|
81
113
|
target_package,
|
|
82
114
|
f"{target_object}.{target_method}" if target_object else target_method,
|
|
@@ -108,6 +140,14 @@ class MonocleInstrumentor(BaseInstrumentor):
|
|
|
108
140
|
object:{wrap_object},
|
|
109
141
|
method:{wrap_method}""")
|
|
110
142
|
|
|
143
|
+
def set_tracer_provider(tracer_provider: TracerProvider):
|
|
144
|
+
global monocle_tracer_provider
|
|
145
|
+
monocle_tracer_provider = tracer_provider
|
|
146
|
+
|
|
147
|
+
def get_tracer_provider() -> TracerProvider:
|
|
148
|
+
global monocle_tracer_provider
|
|
149
|
+
return monocle_tracer_provider
|
|
150
|
+
|
|
111
151
|
def setup_monocle_telemetry(
|
|
112
152
|
workflow_name: str,
|
|
113
153
|
span_processors: List[SpanProcessor] = None,
|
|
@@ -119,7 +159,7 @@ def setup_monocle_telemetry(
|
|
|
119
159
|
})
|
|
120
160
|
exporters = get_monocle_exporter()
|
|
121
161
|
span_processors = span_processors or [BatchSpanProcessor(exporter) for exporter in exporters]
|
|
122
|
-
|
|
162
|
+
set_tracer_provider(TracerProvider(resource=resource))
|
|
123
163
|
attach(set_value("workflow_name", workflow_name))
|
|
124
164
|
tracer_provider_default = trace.get_tracer_provider()
|
|
125
165
|
provider_type = type(tracer_provider_default).__name__
|
|
@@ -129,14 +169,14 @@ def setup_monocle_telemetry(
|
|
|
129
169
|
if not is_proxy_provider:
|
|
130
170
|
tracer_provider_default.add_span_processor(processor)
|
|
131
171
|
else:
|
|
132
|
-
|
|
172
|
+
get_tracer_provider().add_span_processor(processor)
|
|
133
173
|
if is_proxy_provider:
|
|
134
|
-
trace.set_tracer_provider(
|
|
174
|
+
trace.set_tracer_provider(get_tracer_provider())
|
|
135
175
|
instrumentor = MonocleInstrumentor(user_wrapper_methods=wrapper_methods or [],
|
|
136
176
|
handlers=span_handlers, union_with_default_methods = union_with_default_methods)
|
|
137
177
|
# instrumentor.app_name = workflow_name
|
|
138
178
|
if not instrumentor.is_instrumented_by_opentelemetry:
|
|
139
|
-
instrumentor.instrument(trace_provider=
|
|
179
|
+
instrumentor.instrument(trace_provider=get_tracer_provider())
|
|
140
180
|
|
|
141
181
|
return instrumentor
|
|
142
182
|
|
|
@@ -156,7 +196,7 @@ def propagate_trace_id(traceId = "", use_trace_context = False):
|
|
|
156
196
|
try:
|
|
157
197
|
if traceId.startswith("0x"):
|
|
158
198
|
traceId = traceId.lstrip("0x")
|
|
159
|
-
tracer = get_tracer(instrumenting_module_name= MONOCLE_INSTRUMENTOR, tracer_provider=
|
|
199
|
+
tracer = get_tracer(instrumenting_module_name= MONOCLE_INSTRUMENTOR, tracer_provider= get_tracer_provider())
|
|
160
200
|
initial_id_generator = tracer.id_generator
|
|
161
201
|
_parent_span_context = get_current() if use_trace_context else None
|
|
162
202
|
if traceId and is_valid_trace_id_uuid(traceId):
|
|
@@ -194,6 +234,37 @@ def is_valid_trace_id_uuid(traceId: str) -> bool:
|
|
|
194
234
|
pass
|
|
195
235
|
return False
|
|
196
236
|
|
|
237
|
+
def start_scope(scope_name: str, scope_value:str = None) -> object:
|
|
238
|
+
return set_scope(scope_name, scope_value)
|
|
239
|
+
|
|
240
|
+
def stop_scope(token:object) -> None:
|
|
241
|
+
remove_scope(token)
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
@contextmanager
|
|
245
|
+
def monocle_trace_scope(scope_name: str, scope_value:str = None):
|
|
246
|
+
token = start_scope(scope_name, scope_value)
|
|
247
|
+
try:
|
|
248
|
+
yield
|
|
249
|
+
finally:
|
|
250
|
+
stop_scope(token)
|
|
251
|
+
|
|
252
|
+
def monocle_trace_scope_method(scope_name: str):
|
|
253
|
+
def decorator(func):
|
|
254
|
+
def wrapper(*args, **kwargs):
|
|
255
|
+
token = start_scope(scope_name)
|
|
256
|
+
try:
|
|
257
|
+
result = func(*args, **kwargs)
|
|
258
|
+
return result
|
|
259
|
+
finally:
|
|
260
|
+
stop_scope(token)
|
|
261
|
+
return wrapper
|
|
262
|
+
return decorator
|
|
263
|
+
|
|
264
|
+
def monocle_trace_http_route(func):
|
|
265
|
+
def wrapper(req):
|
|
266
|
+
return http_route_handler(req.headers, func, req)
|
|
267
|
+
return wrapper
|
|
197
268
|
|
|
198
269
|
class FixedIdGenerator(id_generator.IdGenerator):
|
|
199
270
|
def __init__(
|
|
@@ -10,7 +10,7 @@ from monocle_apptrace.instrumentation.common.constants import (
|
|
|
10
10
|
service_name_map,
|
|
11
11
|
service_type_map,
|
|
12
12
|
)
|
|
13
|
-
from monocle_apptrace.instrumentation.common.utils import set_attribute
|
|
13
|
+
from monocle_apptrace.instrumentation.common.utils import set_attribute, get_scopes
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
@@ -24,10 +24,25 @@ WORKFLOW_TYPE_MAP = {
|
|
|
24
24
|
|
|
25
25
|
class SpanHandler:
|
|
26
26
|
|
|
27
|
+
def __init__(self,instrumentor=None):
|
|
28
|
+
self.instrumentor=instrumentor
|
|
29
|
+
|
|
30
|
+
def set_instrumentor(self, instrumentor):
|
|
31
|
+
self.instrumentor = instrumentor
|
|
32
|
+
|
|
27
33
|
def validate(self, to_wrap, wrapped, instance, args, kwargs):
|
|
28
34
|
pass
|
|
29
35
|
|
|
30
|
-
def
|
|
36
|
+
def pre_tracing(self, to_wrap, wrapped, instance, args, kwargs):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
def skip_span(self, to_wrap, wrapped, instance, args, kwargs) -> bool:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
def pre_task_processing(self, to_wrap, wrapped, instance, args,kwargs, span):
|
|
31
46
|
if self.__is_root_span(span):
|
|
32
47
|
try:
|
|
33
48
|
sdk_version = version("monocle_apptrace")
|
|
@@ -37,14 +52,9 @@ class SpanHandler:
|
|
|
37
52
|
if "pipeline" in to_wrap['package']:
|
|
38
53
|
set_attribute(QUERY, args[0]['prompt_builder']['question'])
|
|
39
54
|
|
|
40
|
-
|
|
41
|
-
|
|
42
55
|
def post_task_processing(self, to_wrap, wrapped, instance, args, kwargs, result, span):
|
|
43
56
|
pass
|
|
44
57
|
|
|
45
|
-
def set_context_properties(self, to_wrap, wrapped, instance, args, kwargs):
|
|
46
|
-
pass
|
|
47
|
-
|
|
48
58
|
def hydrate_span(self, to_wrap, wrapped, instance, args, kwargs, result, span):
|
|
49
59
|
self.hydrate_attributes(to_wrap, wrapped, instance, args, kwargs, result, span)
|
|
50
60
|
self.hydrate_events(to_wrap, wrapped, instance, args, kwargs, result, span)
|
|
@@ -72,16 +82,21 @@ class SpanHandler:
|
|
|
72
82
|
try:
|
|
73
83
|
arguments = {"instance":instance, "args":args, "kwargs":kwargs, "result":result}
|
|
74
84
|
result = accessor(arguments)
|
|
75
|
-
if result and isinstance(result, str):
|
|
85
|
+
if result and isinstance(result, (str, list)):
|
|
76
86
|
span.set_attribute(attribute_name, result)
|
|
77
87
|
except Exception as e:
|
|
78
88
|
logger.debug(f"Error processing accessor: {e}")
|
|
79
89
|
else:
|
|
80
|
-
logger.
|
|
90
|
+
logger.debug(f"{' and '.join([key for key in ['attribute', 'accessor'] if not processor.get(key)])} not found or incorrect in entity JSON")
|
|
81
91
|
span_index += 1
|
|
82
92
|
else:
|
|
83
|
-
logger.
|
|
93
|
+
logger.debug("attributes not found or incorrect written in entity json")
|
|
84
94
|
|
|
95
|
+
# set scopes as attributes by calling get_scopes()
|
|
96
|
+
# scopes is a Mapping[str:object], iterate directly with .items()
|
|
97
|
+
for scope_key, scope_value in get_scopes().items():
|
|
98
|
+
span.set_attribute(f"scope.{scope_key}", scope_value)
|
|
99
|
+
|
|
85
100
|
if span_index > 0:
|
|
86
101
|
span.set_attribute("entity.count", span_index)
|
|
87
102
|
|
|
@@ -1,16 +1,33 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
1
|
+
import logging, json
|
|
2
|
+
import os
|
|
3
|
+
from typing import Callable, Generic, Optional, TypeVar, Mapping
|
|
4
|
+
from threading import local
|
|
3
5
|
|
|
4
|
-
from opentelemetry.context import attach, detach, get_current, get_value, set_value
|
|
5
|
-
from opentelemetry.trace import NonRecordingSpan, Span
|
|
6
|
+
from opentelemetry.context import attach, detach, get_current, get_value, set_value, Context
|
|
7
|
+
from opentelemetry.trace import NonRecordingSpan, Span, get_tracer
|
|
6
8
|
from opentelemetry.trace.propagation import _SPAN_KEY
|
|
9
|
+
from opentelemetry.sdk.trace import id_generator, TracerProvider
|
|
10
|
+
from opentelemetry.propagate import inject, extract
|
|
11
|
+
from opentelemetry import baggage
|
|
12
|
+
from monocle_apptrace.instrumentation.common.constants import MONOCLE_SCOPE_NAME_PREFIX, SCOPE_METHOD_FILE, SCOPE_CONFIG_PATH
|
|
7
13
|
|
|
8
14
|
T = TypeVar('T')
|
|
9
15
|
U = TypeVar('U')
|
|
10
16
|
|
|
11
17
|
logger = logging.getLogger(__name__)
|
|
12
18
|
|
|
19
|
+
monocle_tracer_provider: TracerProvider = None
|
|
13
20
|
embedding_model_context = {}
|
|
21
|
+
scope_id_generator = id_generator.RandomIdGenerator()
|
|
22
|
+
http_scopes:dict[str:str] = {}
|
|
23
|
+
|
|
24
|
+
def set_tracer_provider(tracer_provider: TracerProvider):
|
|
25
|
+
global monocle_tracer_provider
|
|
26
|
+
monocle_tracer_provider = tracer_provider
|
|
27
|
+
|
|
28
|
+
def get_tracer_provider() -> TracerProvider:
|
|
29
|
+
global monocle_tracer_provider
|
|
30
|
+
return monocle_tracer_provider
|
|
14
31
|
|
|
15
32
|
def set_span_attribute(span, name, value):
|
|
16
33
|
if value is not None:
|
|
@@ -139,6 +156,89 @@ def get_nested_value(data, keys):
|
|
|
139
156
|
def get_keys_as_tuple(dictionary, *keys):
|
|
140
157
|
return tuple(next((value for key, value in dictionary.items() if key.endswith(k) and value is not None), None) for k in keys)
|
|
141
158
|
|
|
159
|
+
def load_scopes() -> dict:
|
|
160
|
+
methods_data = []
|
|
161
|
+
scope_methods = []
|
|
162
|
+
try:
|
|
163
|
+
scope_config_file_path = os.environ.get(SCOPE_CONFIG_PATH,
|
|
164
|
+
os.path.join(os.getcwd(), SCOPE_METHOD_FILE))
|
|
165
|
+
with open(scope_config_file_path) as f:
|
|
166
|
+
methods_data = json.load(f)
|
|
167
|
+
for method in methods_data:
|
|
168
|
+
if method.get('http_header'):
|
|
169
|
+
http_scopes[method.get('http_header')] = method.get('scope_name')
|
|
170
|
+
else:
|
|
171
|
+
scope_methods.append(method)
|
|
172
|
+
except Exception as e:
|
|
173
|
+
logger.debug(f"Error loading scope methods from file: {e}")
|
|
174
|
+
return scope_methods
|
|
175
|
+
|
|
176
|
+
def __generate_scope_id() -> str:
|
|
177
|
+
global scope_id_generator
|
|
178
|
+
return f"{hex(scope_id_generator.generate_trace_id())}"
|
|
179
|
+
|
|
180
|
+
def set_scope(scope_name: str, scope_value:str = None) -> object:
|
|
181
|
+
return set_scopes({scope_name: scope_value})
|
|
182
|
+
|
|
183
|
+
def set_scopes(scopes:dict[str, object], baggage_context:Context = None) -> object:
|
|
184
|
+
if baggage_context is None:
|
|
185
|
+
baggage_context:Context = get_current()
|
|
186
|
+
for scope_name, scope_value in scopes.items():
|
|
187
|
+
if scope_value is None:
|
|
188
|
+
scope_value = __generate_scope_id()
|
|
189
|
+
baggage_context = baggage.set_baggage(f"{MONOCLE_SCOPE_NAME_PREFIX}{scope_name}", scope_value, baggage_context)
|
|
190
|
+
|
|
191
|
+
token:object = attach(baggage_context)
|
|
192
|
+
return token
|
|
193
|
+
|
|
194
|
+
def remove_scope(token:object) -> None:
|
|
195
|
+
remove_scopes(token)
|
|
196
|
+
|
|
197
|
+
def remove_scopes(token:object) -> None:
|
|
198
|
+
if token is not None:
|
|
199
|
+
detach(token)
|
|
200
|
+
|
|
201
|
+
def get_scopes() -> dict[str, object]:
|
|
202
|
+
monocle_scopes:dict[str, object] = {}
|
|
203
|
+
for key, val in baggage.get_all().items():
|
|
204
|
+
if key.startswith(MONOCLE_SCOPE_NAME_PREFIX):
|
|
205
|
+
monocle_scopes[key[len(MONOCLE_SCOPE_NAME_PREFIX):]] = val
|
|
206
|
+
return monocle_scopes
|
|
207
|
+
|
|
208
|
+
def get_baggage_for_scopes():
|
|
209
|
+
baggage_context:Context = None
|
|
210
|
+
for scope_key, scope_value in get_scopes():
|
|
211
|
+
monocle_scope_name = f"{MONOCLE_SCOPE_NAME_PREFIX}{scope_key}"
|
|
212
|
+
baggage_context = baggage.set_baggage(monocle_scope_name, scope_value, context=baggage_context)
|
|
213
|
+
return baggage_context
|
|
214
|
+
|
|
215
|
+
def set_scopes_from_baggage(baggage_context:Context):
|
|
216
|
+
for scope_key, scope_value in baggage.get_all(baggage_context):
|
|
217
|
+
if scope_key.startswith(MONOCLE_SCOPE_NAME_PREFIX):
|
|
218
|
+
scope_name = scope_key[len(MONOCLE_SCOPE_NAME_PREFIX):]
|
|
219
|
+
set_scope(scope_name, scope_value)
|
|
220
|
+
|
|
221
|
+
def extract_http_headers(headers) -> object:
|
|
222
|
+
global http_scopes
|
|
223
|
+
trace_context:Context = extract(headers, context=get_current())
|
|
224
|
+
imported_scope:dict[str, object] = {}
|
|
225
|
+
for http_header, http_scope in http_scopes.items():
|
|
226
|
+
if http_header in headers:
|
|
227
|
+
imported_scope[http_scope] = f"{http_header}: {headers[http_header]}"
|
|
228
|
+
token = set_scopes(imported_scope, trace_context)
|
|
229
|
+
return token
|
|
230
|
+
|
|
231
|
+
def clear_http_scopes(token:object) -> None:
|
|
232
|
+
global http_scopes
|
|
233
|
+
remove_scopes(token)
|
|
234
|
+
|
|
235
|
+
def http_route_handler(headers, func, req):
|
|
236
|
+
token = extract_http_headers(headers)
|
|
237
|
+
try:
|
|
238
|
+
result = func(req)
|
|
239
|
+
return result
|
|
240
|
+
finally:
|
|
241
|
+
clear_http_scopes(token)
|
|
142
242
|
|
|
143
243
|
class Option(Generic[T]):
|
|
144
244
|
def __init__(self, value: Optional[T]):
|
|
@@ -168,4 +268,11 @@ def try_option(func: Callable[..., T], *args, **kwargs) -> Option[T]:
|
|
|
168
268
|
try:
|
|
169
269
|
return Option(func(*args, **kwargs))
|
|
170
270
|
except Exception:
|
|
171
|
-
return Option(None)
|
|
271
|
+
return Option(None)
|
|
272
|
+
|
|
273
|
+
def resolve_from_alias(my_map, alias):
|
|
274
|
+
"""Find a alias that is not none from list of aliases"""
|
|
275
|
+
for i in alias:
|
|
276
|
+
if i in my_map.keys():
|
|
277
|
+
return my_map[i]
|
|
278
|
+
return None
|
|
@@ -7,8 +7,9 @@ from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
|
|
|
7
7
|
from monocle_apptrace.instrumentation.common.utils import (
|
|
8
8
|
get_fully_qualified_class_name,
|
|
9
9
|
with_tracer_wrapper,
|
|
10
|
+
set_scope,
|
|
11
|
+
remove_scope
|
|
10
12
|
)
|
|
11
|
-
from monocle_apptrace.instrumentation.metamodel.botocore import _helper
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
13
14
|
|
|
14
15
|
|
|
@@ -26,22 +27,20 @@ def task_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instanc
|
|
|
26
27
|
else:
|
|
27
28
|
name = get_fully_qualified_class_name(instance)
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
return_value = None
|
|
31
|
+
try:
|
|
32
|
+
handler.pre_tracing(to_wrap, wrapped, instance, args, kwargs)
|
|
33
|
+
if to_wrap.get('skip_span') or handler.skip_span(to_wrap, wrapped, instance, args, kwargs):
|
|
34
|
+
return_value = wrapped(*args, **kwargs)
|
|
35
|
+
else:
|
|
36
|
+
with tracer.start_as_current_span(name) as span:
|
|
37
|
+
handler.pre_task_processing(to_wrap, wrapped, instance, args, kwargs, span)
|
|
38
|
+
return_value = wrapped(*args, **kwargs)
|
|
39
|
+
handler.hydrate_span(to_wrap, wrapped, instance, args, kwargs, return_value, span)
|
|
40
|
+
handler.post_task_processing(to_wrap, wrapped, instance, args, kwargs, return_value, span)
|
|
35
41
|
return return_value
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
handler.pre_task_processing(to_wrap, wrapped, instance, args, span)
|
|
39
|
-
return_value = wrapped(*args, **kwargs)
|
|
40
|
-
handler.hydrate_span(to_wrap, wrapped, instance, args, kwargs, return_value, span)
|
|
41
|
-
handler.post_task_processing(to_wrap, wrapped, instance, args, kwargs, return_value, span)
|
|
42
|
-
|
|
43
|
-
return return_value
|
|
44
|
-
|
|
42
|
+
finally:
|
|
43
|
+
handler.post_tracing(to_wrap, wrapped, instance, args, kwargs, return_value)
|
|
45
44
|
|
|
46
45
|
@with_tracer_wrapper
|
|
47
46
|
async def atask_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, args, kwargs):
|
|
@@ -58,12 +57,38 @@ async def atask_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped,
|
|
|
58
57
|
else:
|
|
59
58
|
name = get_fully_qualified_class_name(instance)
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
handler.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
return_value = None
|
|
61
|
+
try:
|
|
62
|
+
handler.pre_tracing(to_wrap, wrapped, instance, args, kwargs)
|
|
63
|
+
if to_wrap.get('skip_span') or handler.skip_span(to_wrap, wrapped, instance, args, kwargs):
|
|
64
|
+
return_value = wrapped(*args, **kwargs)
|
|
65
|
+
else:
|
|
66
|
+
with tracer.start_as_current_span(name) as span:
|
|
67
|
+
handler.pre_task_processing(to_wrap, wrapped, instance, args, kwargs, span)
|
|
68
|
+
return_value = wrapped(*args, **kwargs)
|
|
69
|
+
handler.hydrate_span(to_wrap, wrapped, instance, args, kwargs, return_value, span)
|
|
70
|
+
handler.post_task_processing(to_wrap, wrapped, instance, args, kwargs, return_value, span)
|
|
71
|
+
return return_value
|
|
72
|
+
finally:
|
|
73
|
+
handler.post_tracing(to_wrap, wrapped, instance, args, kwargs, return_value)
|
|
68
74
|
|
|
75
|
+
@with_tracer_wrapper
|
|
76
|
+
def scope_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, args, kwargs):
|
|
77
|
+
scope_name = to_wrap.get('scope_name', None)
|
|
78
|
+
if scope_name:
|
|
79
|
+
token = set_scope(scope_name)
|
|
80
|
+
return_value = wrapped(*args, **kwargs)
|
|
81
|
+
if scope_name:
|
|
82
|
+
remove_scope(token)
|
|
69
83
|
return return_value
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@with_tracer_wrapper
|
|
87
|
+
async def ascope_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, args, kwargs):
|
|
88
|
+
scope_name = to_wrap.get('scope_name', None)
|
|
89
|
+
if scope_name:
|
|
90
|
+
token = set_scope(scope_name)
|
|
91
|
+
return_value = wrapped(*args, **kwargs)
|
|
92
|
+
if scope_name:
|
|
93
|
+
remove_scope(token)
|
|
94
|
+
return return_value
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
# pylint: disable=too-few-public-methods
|
|
2
|
-
from
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
from monocle_apptrace.instrumentation.common.wrapper import task_wrapper, scope_wrapper
|
|
4
|
+
from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
|
|
3
5
|
from monocle_apptrace.instrumentation.metamodel.botocore.methods import BOTOCORE_METHODS
|
|
6
|
+
from monocle_apptrace.instrumentation.metamodel.botocore.handlers.botocore_span_handler import BotoCoreSpanHandler
|
|
4
7
|
from monocle_apptrace.instrumentation.metamodel.langchain.methods import (
|
|
5
8
|
LANGCHAIN_METHODS,
|
|
6
9
|
)
|
|
7
10
|
from monocle_apptrace.instrumentation.metamodel.llamaindex.methods import (LLAMAINDEX_METHODS, )
|
|
8
11
|
from monocle_apptrace.instrumentation.metamodel.haystack.methods import (HAYSTACK_METHODS, )
|
|
9
|
-
|
|
12
|
+
from monocle_apptrace.instrumentation.metamodel.openai.methods import (OPENAI_METHODS,)
|
|
13
|
+
from monocle_apptrace.instrumentation.metamodel.langgraph.methods import LANGGRAPH_METHODS
|
|
14
|
+
from monocle_apptrace.instrumentation.metamodel.flask.methods import (FLASK_METHODS, )
|
|
15
|
+
from monocle_apptrace.instrumentation.metamodel.flask._helper import FlaskSpanHandler
|
|
16
|
+
from monocle_apptrace.instrumentation.metamodel.requests.methods import (REQUESTS_METHODS, )
|
|
17
|
+
from monocle_apptrace.instrumentation.metamodel.requests._helper import RequestSpanHandler
|
|
10
18
|
|
|
11
19
|
class WrapperMethod:
|
|
12
20
|
def __init__(
|
|
@@ -17,16 +25,21 @@ class WrapperMethod:
|
|
|
17
25
|
span_name: str = None,
|
|
18
26
|
output_processor : str = None,
|
|
19
27
|
wrapper_method = task_wrapper,
|
|
20
|
-
span_handler = 'default'
|
|
28
|
+
span_handler = 'default',
|
|
29
|
+
scope_name: str = None
|
|
21
30
|
):
|
|
22
31
|
self.package = package
|
|
23
32
|
self.object = object_name
|
|
24
33
|
self.method = method
|
|
25
34
|
self.span_name = span_name
|
|
26
35
|
self.output_processor=output_processor
|
|
27
|
-
self.span_handler = span_handler
|
|
28
36
|
|
|
29
|
-
self.
|
|
37
|
+
self.span_handler:SpanHandler.__class__ = span_handler
|
|
38
|
+
self.scope_name = scope_name
|
|
39
|
+
if scope_name:
|
|
40
|
+
self.wrapper_method = scope_wrapper
|
|
41
|
+
else:
|
|
42
|
+
self.wrapper_method = wrapper_method
|
|
30
43
|
|
|
31
44
|
def to_dict(self) -> dict:
|
|
32
45
|
# Create a dictionary representation of the instance
|
|
@@ -37,9 +50,19 @@ class WrapperMethod:
|
|
|
37
50
|
'span_name': self.span_name,
|
|
38
51
|
'output_processor': self.output_processor,
|
|
39
52
|
'wrapper_method': self.wrapper_method,
|
|
40
|
-
'span_handler': self.span_handler
|
|
53
|
+
'span_handler': self.span_handler,
|
|
54
|
+
'scope_name': self.scope_name
|
|
41
55
|
}
|
|
42
56
|
return instance_dict
|
|
43
57
|
|
|
58
|
+
def get_span_handler(self) -> SpanHandler:
|
|
59
|
+
return self.span_handler()
|
|
60
|
+
|
|
61
|
+
DEFAULT_METHODS_LIST = LANGCHAIN_METHODS + LLAMAINDEX_METHODS + HAYSTACK_METHODS + BOTOCORE_METHODS + FLASK_METHODS + REQUESTS_METHODS + LANGGRAPH_METHODS + OPENAI_METHODS
|
|
44
62
|
|
|
45
|
-
|
|
63
|
+
MONOCLE_SPAN_HANDLERS: Dict[str, SpanHandler] = {
|
|
64
|
+
"default": SpanHandler(),
|
|
65
|
+
"botocore_handler": BotoCoreSpanHandler(),
|
|
66
|
+
"flask_handler": FlaskSpanHandler(),
|
|
67
|
+
"request_handler": RequestSpanHandler()
|
|
68
|
+
}
|
|
@@ -81,37 +81,6 @@ def resolve_from_alias(my_map, alias):
|
|
|
81
81
|
return my_map[i]
|
|
82
82
|
return None
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
def botocore_processor(tracer, to_wrap, wrapped, instance, args, kwargs,return_value):
|
|
86
|
-
service_name = kwargs.get("service_name")
|
|
87
|
-
service_method_mapping = {
|
|
88
|
-
"sagemaker-runtime": "invoke_endpoint",
|
|
89
|
-
"bedrock-runtime": "converse",
|
|
90
|
-
}
|
|
91
|
-
if service_name in service_method_mapping:
|
|
92
|
-
method_name = service_method_mapping[service_name]
|
|
93
|
-
original_method = getattr(return_value, method_name, None)
|
|
94
|
-
|
|
95
|
-
if original_method:
|
|
96
|
-
instrumented_method = _instrumented_endpoint_invoke(
|
|
97
|
-
to_wrap, wrapped,return_value, original_method, tracer, service_name
|
|
98
|
-
)
|
|
99
|
-
setattr(return_value, method_name, instrumented_method)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def _instrumented_endpoint_invoke(to_wrap,wrapped, instance, fn, tracer,service_name):
|
|
103
|
-
@wraps(fn)
|
|
104
|
-
def with_instrumentation(*args, **kwargs):
|
|
105
|
-
span_name="botocore-"+service_name+"-invoke-endpoint"
|
|
106
|
-
handler = SpanHandler()
|
|
107
|
-
with tracer.start_as_current_span(span_name) as span:
|
|
108
|
-
response = fn(*args, **kwargs)
|
|
109
|
-
handler.hydrate_span(to_wrap, span=span,wrapped=wrapped, instance=instance,args=args, kwargs=kwargs, result=response)
|
|
110
|
-
return response
|
|
111
|
-
|
|
112
|
-
return with_instrumentation
|
|
113
|
-
|
|
114
|
-
|
|
115
84
|
def update_span_from_llm_response(response, instance):
|
|
116
85
|
meta_dict = {}
|
|
117
86
|
if response is not None and isinstance(response, dict) and "usage" in response:
|