sf-veritas 0.11.10__cp314-cp314-manylinux_2_28_x86_64.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.
- sf_veritas/__init__.py +46 -0
- sf_veritas/_auto_preload.py +73 -0
- sf_veritas/_sfconfig.c +162 -0
- sf_veritas/_sfconfig.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfcrashhandler.c +267 -0
- sf_veritas/_sfcrashhandler.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastlog.c +953 -0
- sf_veritas/_sffastlog.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +994 -0
- sf_veritas/_sffastnet.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +727 -0
- sf_veritas/_sffastnetworkrequest.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2791 -0
- sf_veritas/_sffuncspan.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +730 -0
- sf_veritas/_sffuncspan_config.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfheadercheck.c +341 -0
- sf_veritas/_sfheadercheck.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfnetworkhop.c +1454 -0
- sf_veritas/_sfnetworkhop.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1223 -0
- sf_veritas/_sfservice.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfteepreload.c +6227 -0
- sf_veritas/app_config.py +57 -0
- sf_veritas/cli.py +336 -0
- sf_veritas/constants.py +10 -0
- sf_veritas/custom_excepthook.py +304 -0
- sf_veritas/custom_log_handler.py +146 -0
- sf_veritas/custom_output_wrapper.py +153 -0
- sf_veritas/custom_print.py +153 -0
- sf_veritas/django_app.py +5 -0
- sf_veritas/env_vars.py +186 -0
- sf_veritas/exception_handling_middleware.py +18 -0
- sf_veritas/exception_metaclass.py +69 -0
- sf_veritas/fast_frame_info.py +116 -0
- sf_veritas/fast_network_hop.py +293 -0
- sf_veritas/frame_tools.py +112 -0
- sf_veritas/funcspan_config_loader.py +693 -0
- sf_veritas/function_span_profiler.py +1313 -0
- sf_veritas/get_preload_path.py +34 -0
- sf_veritas/import_hook.py +62 -0
- sf_veritas/infra_details/__init__.py +3 -0
- sf_veritas/infra_details/get_infra_details.py +24 -0
- sf_veritas/infra_details/kubernetes/__init__.py +3 -0
- sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
- sf_veritas/infra_details/kubernetes/get_details.py +7 -0
- sf_veritas/infra_details/running_on/__init__.py +17 -0
- sf_veritas/infra_details/running_on/kubernetes.py +11 -0
- sf_veritas/interceptors.py +543 -0
- sf_veritas/libsfnettee.so +0 -0
- sf_veritas/local_env_detect.py +118 -0
- sf_veritas/package_metadata.py +6 -0
- sf_veritas/patches/__init__.py +0 -0
- sf_veritas/patches/_patch_tracker.py +74 -0
- sf_veritas/patches/concurrent_futures.py +19 -0
- sf_veritas/patches/constants.py +1 -0
- sf_veritas/patches/exceptions.py +82 -0
- sf_veritas/patches/multiprocessing.py +32 -0
- sf_veritas/patches/network_libraries/__init__.py +99 -0
- sf_veritas/patches/network_libraries/aiohttp.py +294 -0
- sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
- sf_veritas/patches/network_libraries/http_client.py +670 -0
- sf_veritas/patches/network_libraries/httpcore.py +580 -0
- sf_veritas/patches/network_libraries/httplib2.py +315 -0
- sf_veritas/patches/network_libraries/httpx.py +557 -0
- sf_veritas/patches/network_libraries/niquests.py +218 -0
- sf_veritas/patches/network_libraries/pycurl.py +399 -0
- sf_veritas/patches/network_libraries/requests.py +595 -0
- sf_veritas/patches/network_libraries/ssl_socket.py +822 -0
- sf_veritas/patches/network_libraries/tornado.py +360 -0
- sf_veritas/patches/network_libraries/treq.py +270 -0
- sf_veritas/patches/network_libraries/urllib_request.py +483 -0
- sf_veritas/patches/network_libraries/utils.py +598 -0
- sf_veritas/patches/os.py +17 -0
- sf_veritas/patches/threading.py +231 -0
- sf_veritas/patches/web_frameworks/__init__.py +54 -0
- sf_veritas/patches/web_frameworks/aiohttp.py +798 -0
- sf_veritas/patches/web_frameworks/async_websocket_consumer.py +337 -0
- sf_veritas/patches/web_frameworks/blacksheep.py +532 -0
- sf_veritas/patches/web_frameworks/bottle.py +513 -0
- sf_veritas/patches/web_frameworks/cherrypy.py +683 -0
- sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
- sf_veritas/patches/web_frameworks/django.py +963 -0
- sf_veritas/patches/web_frameworks/eve.py +401 -0
- sf_veritas/patches/web_frameworks/falcon.py +931 -0
- sf_veritas/patches/web_frameworks/fastapi.py +738 -0
- sf_veritas/patches/web_frameworks/flask.py +526 -0
- sf_veritas/patches/web_frameworks/klein.py +501 -0
- sf_veritas/patches/web_frameworks/litestar.py +616 -0
- sf_veritas/patches/web_frameworks/pyramid.py +440 -0
- sf_veritas/patches/web_frameworks/quart.py +841 -0
- sf_veritas/patches/web_frameworks/robyn.py +708 -0
- sf_veritas/patches/web_frameworks/sanic.py +874 -0
- sf_veritas/patches/web_frameworks/starlette.py +742 -0
- sf_veritas/patches/web_frameworks/strawberry.py +1446 -0
- sf_veritas/patches/web_frameworks/tornado.py +485 -0
- sf_veritas/patches/web_frameworks/utils.py +170 -0
- sf_veritas/print_override.py +13 -0
- sf_veritas/regular_data_transmitter.py +444 -0
- sf_veritas/request_interceptor.py +401 -0
- sf_veritas/request_utils.py +550 -0
- sf_veritas/segfault_handler.py +116 -0
- sf_veritas/server_status.py +1 -0
- sf_veritas/shutdown_flag.py +11 -0
- sf_veritas/subprocess_startup.py +3 -0
- sf_veritas/test_cli.py +145 -0
- sf_veritas/thread_local.py +1319 -0
- sf_veritas/timeutil.py +114 -0
- sf_veritas/transmit_exception_to_sailfish.py +28 -0
- sf_veritas/transmitter.py +132 -0
- sf_veritas/types.py +47 -0
- sf_veritas/unified_interceptor.py +1678 -0
- sf_veritas/utils.py +39 -0
- sf_veritas-0.11.10.dist-info/METADATA +97 -0
- sf_veritas-0.11.10.dist-info/RECORD +141 -0
- sf_veritas-0.11.10.dist-info/WHEEL +5 -0
- sf_veritas-0.11.10.dist-info/entry_points.txt +2 -0
- sf_veritas-0.11.10.dist-info/top_level.txt +1 -0
- sf_veritas.libs/libbrotlicommon-6ce2a53c.so.1.0.6 +0 -0
- sf_veritas.libs/libbrotlidec-811d1be3.so.1.0.6 +0 -0
- sf_veritas.libs/libcom_err-730ca923.so.2.1 +0 -0
- sf_veritas.libs/libcrypt-52aca757.so.1.1.0 +0 -0
- sf_veritas.libs/libcrypto-bdaed0ea.so.1.1.1k +0 -0
- sf_veritas.libs/libcurl-eaa3cf66.so.4.5.0 +0 -0
- sf_veritas.libs/libgssapi_krb5-323bbd21.so.2.2 +0 -0
- sf_veritas.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
- sf_veritas.libs/libk5crypto-9a74ff38.so.3.1 +0 -0
- sf_veritas.libs/libkeyutils-2777d33d.so.1.6 +0 -0
- sf_veritas.libs/libkrb5-a55300e8.so.3.3 +0 -0
- sf_veritas.libs/libkrb5support-e6594cfc.so.0.1 +0 -0
- sf_veritas.libs/liblber-2-d20824ef.4.so.2.10.9 +0 -0
- sf_veritas.libs/libldap-2-cea2a960.4.so.2.10.9 +0 -0
- sf_veritas.libs/libnghttp2-39367a22.so.14.17.0 +0 -0
- sf_veritas.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
- sf_veritas.libs/libpsl-99becdd3.so.5.3.1 +0 -0
- sf_veritas.libs/libsasl2-7de4d792.so.3.0.0 +0 -0
- sf_veritas.libs/libselinux-d0805dcb.so.1 +0 -0
- sf_veritas.libs/libssh-c11d285b.so.4.8.7 +0 -0
- sf_veritas.libs/libssl-60250281.so.1.1.1k +0 -0
- sf_veritas.libs/libunistring-05abdd40.so.2.1.0 +0 -0
- sf_veritas.libs/libuuid-95b83d40.so.1.3.0 +0 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
|
|
3
|
+
# import linecache
|
|
4
|
+
import logging
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
import threading
|
|
8
|
+
from types import FrameType, TracebackType
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
from . import app_config
|
|
12
|
+
from .env_vars import (
|
|
13
|
+
SAILFISH_EXCEPTION_FETCH_BEYOND_OFFENDER_DEPTH,
|
|
14
|
+
SAILFISH_EXCEPTION_FETCH_LOCALS_BEYOND_OFFENDER_DEPTH,
|
|
15
|
+
SF_DEBUG,
|
|
16
|
+
SF_DEBUG_TRACES,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# from .frame_tools import get_locals
|
|
20
|
+
from .interceptors import ExceptionInterceptor
|
|
21
|
+
from .local_env_detect import sf_is_local_dev_environment
|
|
22
|
+
from .thread_local import (
|
|
23
|
+
get_or_set_sf_trace_id,
|
|
24
|
+
has_handled_exception,
|
|
25
|
+
mark_exception_handled,
|
|
26
|
+
)
|
|
27
|
+
from .types import FrameInfo
|
|
28
|
+
|
|
29
|
+
REGEX_EVERYTHING_BEFORE_PYTHON_PACKAGE_NAME_WHEN_EXAMINING_FILENAME = (
|
|
30
|
+
r".*\/(site|dist)-packages\/"
|
|
31
|
+
)
|
|
32
|
+
REGEX_EVERYTHING_AFTER_NEXT_SLASH = r"\/.*"
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
# Profiling data structure to collect function call information
|
|
37
|
+
call_hierarchy = []
|
|
38
|
+
|
|
39
|
+
# Lock to manage thread-safe access to profiling data
|
|
40
|
+
call_hierarchy_lock = threading.Lock()
|
|
41
|
+
|
|
42
|
+
_original_excepthook = sys.excepthook
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def custom_thread_excepthook(args):
|
|
46
|
+
sys.excepthook(args.exc_type, args.exc_value, args.exc_traceback)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def process_traceback_only(
|
|
50
|
+
exc_value: Exception, exc_traceback: TracebackType, was_caught: bool = True
|
|
51
|
+
):
|
|
52
|
+
"""
|
|
53
|
+
Processes only the traceback information for an exception.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
exc_value (Exception): The exception value.
|
|
57
|
+
exc_traceback (TracebackType): The traceback object for the exception.
|
|
58
|
+
"""
|
|
59
|
+
trace = extract_trace(exc_traceback)
|
|
60
|
+
except_interceptor = ExceptionInterceptor()
|
|
61
|
+
_, trace_id = get_or_set_sf_trace_id()
|
|
62
|
+
if SF_DEBUG:
|
|
63
|
+
print(
|
|
64
|
+
"process_traceback_only...SENDING DATA...do_send args=",
|
|
65
|
+
(str(exc_value), trace, trace_id, was_caught),
|
|
66
|
+
trace_id,
|
|
67
|
+
log=False,
|
|
68
|
+
)
|
|
69
|
+
except_interceptor.do_send(
|
|
70
|
+
(str(exc_value), trace, trace_id, was_caught, sf_is_local_dev_environment()[0]),
|
|
71
|
+
trace_id,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# Usage example in the custom_excepthook function:
|
|
76
|
+
def custom_excepthook(
|
|
77
|
+
exc_type, exc_value, exc_traceback: TracebackType, raise_original: bool = True
|
|
78
|
+
):
|
|
79
|
+
if has_handled_exception(exc_value):
|
|
80
|
+
return
|
|
81
|
+
mark_exception_handled(exc_value)
|
|
82
|
+
|
|
83
|
+
if SF_DEBUG:
|
|
84
|
+
print(f"custom_excepthook -> raise_original={raise_original}", log=False)
|
|
85
|
+
|
|
86
|
+
transmit_exception(exc_type, exc_value, exc_traceback)
|
|
87
|
+
|
|
88
|
+
if raise_original:
|
|
89
|
+
_original_excepthook(exc_type, exc_value, exc_traceback)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def transmit_exception(exc_type, exc_value, exc_traceback, was_caught: bool = True):
|
|
93
|
+
"""
|
|
94
|
+
Handles transmitting exception information based on profiling mode.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
exc_type: The type of the exception.
|
|
98
|
+
exc_value: The exception instance.
|
|
99
|
+
exc_traceback: The traceback object for the exception.
|
|
100
|
+
"""
|
|
101
|
+
if app_config._profiling_mode_enabled:
|
|
102
|
+
threading.Thread(
|
|
103
|
+
target=process_profiling_data,
|
|
104
|
+
args=(exc_type, exc_value, exc_traceback, was_caught),
|
|
105
|
+
).start()
|
|
106
|
+
return
|
|
107
|
+
process_traceback_only(exc_value, exc_traceback, was_caught)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def profiling_function(frame: FrameType, event: str, arg):
|
|
111
|
+
if not app_config._profiling_mode_enabled:
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
if event in ["call", "return"]:
|
|
115
|
+
code = frame.f_code
|
|
116
|
+
function_name = code.co_name
|
|
117
|
+
filename = code.co_filename
|
|
118
|
+
line_no = frame.f_lineno
|
|
119
|
+
class_name = None
|
|
120
|
+
|
|
121
|
+
# Try to determine if we're inside a method of a class
|
|
122
|
+
if "self" in frame.f_locals:
|
|
123
|
+
class_name = type(frame.f_locals["self"]).__name__
|
|
124
|
+
|
|
125
|
+
call_info = {
|
|
126
|
+
"event": event,
|
|
127
|
+
"function": function_name,
|
|
128
|
+
"class": class_name,
|
|
129
|
+
"file": filename,
|
|
130
|
+
"line_no": line_no,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Add profiling data to call hierarchy with a lock for thread safety
|
|
134
|
+
with call_hierarchy_lock:
|
|
135
|
+
if len(call_hierarchy) < app_config._profiling_max_depth:
|
|
136
|
+
call_hierarchy.append(call_info)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def process_profiling_data(exc_type, exc_value, exc_traceback, was_caught: bool = True):
|
|
140
|
+
# Extract the traceback and process detailed information
|
|
141
|
+
trace = extract_trace(exc_traceback)
|
|
142
|
+
|
|
143
|
+
# Process profiling data
|
|
144
|
+
with call_hierarchy_lock:
|
|
145
|
+
if SF_DEBUG and SF_DEBUG_TRACES:
|
|
146
|
+
print("=" * 40, log=False)
|
|
147
|
+
print("Printing the complete call hierarchy after exception...", log=False)
|
|
148
|
+
print("=" * 40, log=False)
|
|
149
|
+
|
|
150
|
+
for entry in call_hierarchy:
|
|
151
|
+
if entry["event"] == "call":
|
|
152
|
+
class_name = f"{entry['class']}." if entry["class"] else ""
|
|
153
|
+
if SF_DEBUG and SF_DEBUG_TRACES:
|
|
154
|
+
print(
|
|
155
|
+
f"Called: {class_name}{entry['function']} (File: {entry['file']}, Line: {entry['line_no']})",
|
|
156
|
+
log=False,
|
|
157
|
+
)
|
|
158
|
+
elif entry["event"] == "return":
|
|
159
|
+
if SF_DEBUG and SF_DEBUG_TRACES:
|
|
160
|
+
print(f"Return from: {entry['function']}", log=False)
|
|
161
|
+
|
|
162
|
+
if SF_DEBUG and SF_DEBUG_TRACES:
|
|
163
|
+
print("-" * 40, log=False)
|
|
164
|
+
|
|
165
|
+
# Clear profiling data after processing
|
|
166
|
+
call_hierarchy.clear()
|
|
167
|
+
|
|
168
|
+
# Send the extracted traceback data to the exception interceptor
|
|
169
|
+
except_interceptor = ExceptionInterceptor()
|
|
170
|
+
_, trace_id = get_or_set_sf_trace_id()
|
|
171
|
+
if SF_DEBUG:
|
|
172
|
+
print(
|
|
173
|
+
"process_traceback_only...SENDING DATA...do_send args=",
|
|
174
|
+
(str(exc_value), trace, trace_id, was_caught),
|
|
175
|
+
trace_id,
|
|
176
|
+
log=False,
|
|
177
|
+
)
|
|
178
|
+
except_interceptor.do_send(
|
|
179
|
+
(str(exc_value), trace, trace_id, was_caught, sf_is_local_dev_environment()[0]),
|
|
180
|
+
trace_id,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def safe_repr(value):
|
|
185
|
+
try:
|
|
186
|
+
return repr(value)
|
|
187
|
+
except Exception:
|
|
188
|
+
return f"<unrepresentable: {type(value).__name__}>"
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def extract_trace(tb: Optional[TracebackType] = None) -> List[FrameInfo]:
|
|
192
|
+
tb_list = []
|
|
193
|
+
|
|
194
|
+
# Collect all traceback frames in natural order (most recent last)
|
|
195
|
+
while tb is not None:
|
|
196
|
+
tb_list.append(tb)
|
|
197
|
+
tb = tb.tb_next
|
|
198
|
+
|
|
199
|
+
if not tb_list:
|
|
200
|
+
return []
|
|
201
|
+
|
|
202
|
+
frame_stack = []
|
|
203
|
+
|
|
204
|
+
# Iterate through the traceback, capturing frame information
|
|
205
|
+
for tb in tb_list:
|
|
206
|
+
frame = tb.tb_frame
|
|
207
|
+
frame_info = inspect.getframeinfo(frame)
|
|
208
|
+
filename = frame_info.filename
|
|
209
|
+
function_name = frame_info.function
|
|
210
|
+
line_no = frame_info.lineno
|
|
211
|
+
code_context = (
|
|
212
|
+
frame_info.code_context[0].strip()
|
|
213
|
+
if frame_info.code_context
|
|
214
|
+
else "No code context"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
frame_info_obj = FrameInfo(
|
|
218
|
+
file=filename,
|
|
219
|
+
line=line_no,
|
|
220
|
+
function=function_name,
|
|
221
|
+
code=code_context,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Capture ALL local variables at each frame - no depth or package restrictions
|
|
225
|
+
frame_info_obj.locals = {k: safe_repr(v) for k, v in frame.f_locals.items()}
|
|
226
|
+
|
|
227
|
+
frame_stack.append(frame_info_obj)
|
|
228
|
+
|
|
229
|
+
frame_stack[-1].offender = True
|
|
230
|
+
return frame_stack
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def start_profiling():
|
|
234
|
+
"""Start profiling to collect call hierarchy information if profiling is enabled."""
|
|
235
|
+
from .env_vars import SF_DEBUG
|
|
236
|
+
|
|
237
|
+
if SF_DEBUG:
|
|
238
|
+
try:
|
|
239
|
+
print(
|
|
240
|
+
f"[[DEBUG]] start_profiling() called, _profiling_mode_enabled={app_config._profiling_mode_enabled}",
|
|
241
|
+
log=False,
|
|
242
|
+
)
|
|
243
|
+
except:
|
|
244
|
+
print(
|
|
245
|
+
f"[[DEBUG]] start_profiling() called, _profiling_mode_enabled={app_config._profiling_mode_enabled}"
|
|
246
|
+
)
|
|
247
|
+
if app_config._profiling_mode_enabled:
|
|
248
|
+
try:
|
|
249
|
+
print(
|
|
250
|
+
f"[[DEBUG]] Setting sys.setprofile to profiling_function, current={sys.getprofile()}",
|
|
251
|
+
log=False,
|
|
252
|
+
)
|
|
253
|
+
except:
|
|
254
|
+
print(
|
|
255
|
+
f"[[DEBUG]] Setting sys.setprofile to profiling_function, current={sys.getprofile()}"
|
|
256
|
+
)
|
|
257
|
+
sys.setprofile(profiling_function)
|
|
258
|
+
try:
|
|
259
|
+
print(
|
|
260
|
+
f"[[DEBUG]] After setting, sys.getprofile()={sys.getprofile()}",
|
|
261
|
+
log=False,
|
|
262
|
+
)
|
|
263
|
+
except:
|
|
264
|
+
print(f"[[DEBUG]] After setting, sys.getprofile()={sys.getprofile()}")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def stop_profiling():
|
|
268
|
+
"""Stop profiling to prevent collecting more call hierarchy data."""
|
|
269
|
+
sys.setprofile(None)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def should_collect_local_variables_for_stack_item(filename: str) -> bool:
|
|
273
|
+
if "__all__" in app_config._site_and_dist_packages_to_collect_local_variables_on:
|
|
274
|
+
return True
|
|
275
|
+
return is_allowed_package(filename)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def is_installed_package(filename: str) -> bool:
|
|
279
|
+
return bool(
|
|
280
|
+
re.search(
|
|
281
|
+
REGEX_EVERYTHING_BEFORE_PYTHON_PACKAGE_NAME_WHEN_EXAMINING_FILENAME,
|
|
282
|
+
filename,
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def get_package_name_from_site_or_dist_package(filename: str):
|
|
288
|
+
site_dist_packages_removed = re.sub(
|
|
289
|
+
REGEX_EVERYTHING_BEFORE_PYTHON_PACKAGE_NAME_WHEN_EXAMINING_FILENAME,
|
|
290
|
+
"",
|
|
291
|
+
filename,
|
|
292
|
+
)
|
|
293
|
+
package_installed_name = re.sub(
|
|
294
|
+
REGEX_EVERYTHING_AFTER_NEXT_SLASH, "", site_dist_packages_removed
|
|
295
|
+
)
|
|
296
|
+
return package_installed_name
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def is_allowed_package(filename: str) -> bool:
|
|
300
|
+
if not is_installed_package(filename):
|
|
301
|
+
return True
|
|
302
|
+
|
|
303
|
+
package = get_package_name_from_site_or_dist_package(filename)
|
|
304
|
+
return package in app_config._site_and_dist_packages_to_collect_local_variables_on
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from . import app_config
|
|
5
|
+
from .env_vars import PRINT_CONFIGURATION_STATUSES, SF_DEBUG
|
|
6
|
+
from .interceptors import LogInterceptor
|
|
7
|
+
from .thread_local import (
|
|
8
|
+
get_current_function_span_id,
|
|
9
|
+
get_or_set_sf_trace_id,
|
|
10
|
+
get_reentrancy_guard_logging_preactive,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
# Try native fast path (compiled C extension). If missing, we fall back.
|
|
14
|
+
try:
|
|
15
|
+
from . import _sffastlog as _FASTMOD
|
|
16
|
+
|
|
17
|
+
if SF_DEBUG:
|
|
18
|
+
print("[[custom_log_handler]] _FASTMOD is okay")
|
|
19
|
+
|
|
20
|
+
_FAST_OK = True
|
|
21
|
+
except Exception:
|
|
22
|
+
if SF_DEBUG:
|
|
23
|
+
print("[[custom_log_handler]] _FASTMOD != OK")
|
|
24
|
+
_FASTMOD = None
|
|
25
|
+
_FAST_OK = False
|
|
26
|
+
|
|
27
|
+
# Small constant table avoids attribute access for levelname on hot path
|
|
28
|
+
_LEVEL_NAME = {
|
|
29
|
+
50: "CRITICAL",
|
|
30
|
+
40: "ERROR",
|
|
31
|
+
30: "WARNING",
|
|
32
|
+
20: "INFO",
|
|
33
|
+
10: "DEBUG",
|
|
34
|
+
0: "NOTSET",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CustomLogHandler(logging.Handler, LogInterceptor):
|
|
39
|
+
"""
|
|
40
|
+
Ultra-light log handler:
|
|
41
|
+
- Avoid Formatter unless explicitly set.
|
|
42
|
+
- Avoid %-formatting if record.args is empty.
|
|
43
|
+
- Prefer levelno→name map (micro faster than attribute lookup).
|
|
44
|
+
- Fetch/generate trace_id once per emit (unchanged).
|
|
45
|
+
- DIRECT native send via _sffastlog when available; Python fallback otherwise.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self):
|
|
49
|
+
logging.Handler.__init__(self)
|
|
50
|
+
LogInterceptor.__init__(self, api_key=app_config._sailfish_api_key)
|
|
51
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
52
|
+
print("Intercepting log statements")
|
|
53
|
+
|
|
54
|
+
def emit(self, record: logging.LogRecord, trace_id: Optional[str] = None):
|
|
55
|
+
# Bind frequently used symbols to locals to reduce attribute/global lookups
|
|
56
|
+
_formatter = self.formatter
|
|
57
|
+
_get_tid = get_or_set_sf_trace_id
|
|
58
|
+
_get_preactive = get_reentrancy_guard_logging_preactive
|
|
59
|
+
_debug = SF_DEBUG
|
|
60
|
+
_level_table = _LEVEL_NAME
|
|
61
|
+
_fast = _FASTMOD
|
|
62
|
+
_fast_ok = _FAST_OK
|
|
63
|
+
_do_send = self.do_send # Python fallback
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
# FAST PATH message extraction:
|
|
67
|
+
# - If a formatter is attached, defer to it (caller explicitly wants decorations)
|
|
68
|
+
# - Otherwise:
|
|
69
|
+
# - If there are args, we must perform %-format (getMessage)
|
|
70
|
+
# - If no args and msg is already a str, reuse it directly (no str() allocation)
|
|
71
|
+
# - Else fall back to str(msg)
|
|
72
|
+
if _formatter is None:
|
|
73
|
+
if record.args:
|
|
74
|
+
log_entry = record.getMessage()
|
|
75
|
+
else:
|
|
76
|
+
msg = record.msg
|
|
77
|
+
log_entry = msg if (msg.__class__ is str) else str(msg)
|
|
78
|
+
else:
|
|
79
|
+
log_entry = self.format(record)
|
|
80
|
+
|
|
81
|
+
# Cheap ignore check (lives on LogInterceptor); skip everything if ignored
|
|
82
|
+
# Keeps semantics identical while saving work on hot path
|
|
83
|
+
try:
|
|
84
|
+
if self.check_if_contents_should_be_ignored(log_entry):
|
|
85
|
+
return
|
|
86
|
+
except AttributeError:
|
|
87
|
+
# If not defined for some reason, proceed (older builds)
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
# CHEAP LEVEL NAME:
|
|
91
|
+
lvlno = record.levelno
|
|
92
|
+
log_level = _level_table.get(lvlno)
|
|
93
|
+
if log_level is None: # uncommon/3rd-party custom levels
|
|
94
|
+
log_level = record.levelname
|
|
95
|
+
|
|
96
|
+
# TRACE ID ONCE (unchanged contract)
|
|
97
|
+
if trace_id is None:
|
|
98
|
+
_, trace_id = _get_tid(None)
|
|
99
|
+
|
|
100
|
+
# Debug printing (rarely enabled)
|
|
101
|
+
if _debug:
|
|
102
|
+
# Avoid f-strings to skip extra work when disabled
|
|
103
|
+
print(
|
|
104
|
+
"[[DEBUG custom_log_handler]]",
|
|
105
|
+
"trace_id=" + str(trace_id),
|
|
106
|
+
"[[" + log_level + "]]",
|
|
107
|
+
log_entry,
|
|
108
|
+
"[[/DEBUG]]",
|
|
109
|
+
log=False,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# ---------- Native fast path ----------
|
|
113
|
+
if _fast_ok and _fast is not None:
|
|
114
|
+
try:
|
|
115
|
+
preactive = bool(_get_preactive())
|
|
116
|
+
# Capture parent_span_id IMMEDIATELY for async-safety
|
|
117
|
+
parent_span_id = get_current_function_span_id()
|
|
118
|
+
|
|
119
|
+
# DEBUG: Log what parent_span_id we captured (only for TEST_PARENT_SPAN logs)
|
|
120
|
+
if "TEST_PARENT_SPAN" in log_entry:
|
|
121
|
+
print(f"[LOG_HANDLER_DEBUG] parent_span_id={parent_span_id} for log: {log_entry[:80]}", log=False)
|
|
122
|
+
|
|
123
|
+
# Single C call → copy → enqueue → return; HTTP happens on native thread
|
|
124
|
+
_fast.log(
|
|
125
|
+
level=log_level or "UNKNOWN",
|
|
126
|
+
contents=log_entry,
|
|
127
|
+
session_id=str(trace_id),
|
|
128
|
+
preactive=preactive,
|
|
129
|
+
parent_span_id=parent_span_id,
|
|
130
|
+
)
|
|
131
|
+
return
|
|
132
|
+
except Exception as _e:
|
|
133
|
+
print(
|
|
134
|
+
"[_sffastlog] fast-path failed; falling back:",
|
|
135
|
+
_e,
|
|
136
|
+
log=False,
|
|
137
|
+
)
|
|
138
|
+
# fall through to Python path
|
|
139
|
+
|
|
140
|
+
# ---------- Python fallback (deferred batching/async) ----------
|
|
141
|
+
_do_send((log_level, log_entry, trace_id), trace_id)
|
|
142
|
+
|
|
143
|
+
except Exception as e: # keep lean; avoid storms on storms
|
|
144
|
+
if _debug:
|
|
145
|
+
print("CustomLogHandler.emit error:", e)
|
|
146
|
+
self.handleError(record)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# sf_veritas/custom_output_wrapper.py
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from . import app_config
|
|
6
|
+
from .env_vars import PRINT_CONFIGURATION_STATUSES
|
|
7
|
+
from .interceptors import PrintInterceptor
|
|
8
|
+
from .thread_local import (
|
|
9
|
+
_thread_locals,
|
|
10
|
+
get_current_function_span_id,
|
|
11
|
+
get_or_set_sf_trace_id,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# ---- optional native fast path (C extension) ----
|
|
15
|
+
try:
|
|
16
|
+
from . import _sffastlog # compiled extension with ring + libcurl sender
|
|
17
|
+
|
|
18
|
+
_FAST_OK = True
|
|
19
|
+
except Exception: # pragma: no cover
|
|
20
|
+
_sffastlog = None
|
|
21
|
+
_FAST_OK = False
|
|
22
|
+
|
|
23
|
+
_FAST_PRINT_READY = False # one-time init guard
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _ensure_fast_print_initialized() -> bool:
|
|
27
|
+
"""Idempotent, cheap check to init native print path once."""
|
|
28
|
+
global _FAST_PRINT_READY
|
|
29
|
+
if not _FAST_OK or _FAST_PRINT_READY:
|
|
30
|
+
return _FAST_PRINT_READY
|
|
31
|
+
|
|
32
|
+
endpoint = getattr(app_config, "_sailfish_graphql_endpoint", None)
|
|
33
|
+
api_key = getattr(app_config, "_sailfish_api_key", None)
|
|
34
|
+
service_uuid = getattr(app_config, "_service_uuid", None)
|
|
35
|
+
library = getattr(app_config, "library", "sailfish-python")
|
|
36
|
+
version = getattr(app_config, "version", "0.0.0")
|
|
37
|
+
http2 = 1 if os.getenv("SF_NBPOST_HTTP2", "0") == "1" else 0
|
|
38
|
+
|
|
39
|
+
if not (endpoint and api_key and service_uuid):
|
|
40
|
+
return False # not configured yet
|
|
41
|
+
|
|
42
|
+
# GraphQL mutation for print statements (must match server)
|
|
43
|
+
query = (
|
|
44
|
+
"mutation CollectPrintStatements("
|
|
45
|
+
"$apiKey: String!,"
|
|
46
|
+
"$serviceUuid: String!,"
|
|
47
|
+
"$sessionId: String!,"
|
|
48
|
+
"$contents: String!,"
|
|
49
|
+
"$reentrancyGuardPreactive: Boolean!,"
|
|
50
|
+
"$library: String!,"
|
|
51
|
+
"$timestampMs: String!,"
|
|
52
|
+
"$version: String!,"
|
|
53
|
+
"$parentSpanId: String"
|
|
54
|
+
"){collectPrintStatements("
|
|
55
|
+
"apiKey:$apiKey,serviceUuid:$serviceUuid,sessionId:$sessionId,"
|
|
56
|
+
"contents:$contents,reentrancyGuardPreactive:$reentrancyGuardPreactive,"
|
|
57
|
+
"library:$library,timestampMs:$timestampMs,version:$version,"
|
|
58
|
+
"parentSpanId:$parentSpanId)}"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
ok = _sffastlog.init_print(
|
|
63
|
+
url=endpoint,
|
|
64
|
+
query=query,
|
|
65
|
+
api_key=str(api_key),
|
|
66
|
+
service_uuid=str(service_uuid),
|
|
67
|
+
library=str(library),
|
|
68
|
+
version=str(version),
|
|
69
|
+
http2=http2,
|
|
70
|
+
)
|
|
71
|
+
_FAST_PRINT_READY = bool(ok)
|
|
72
|
+
except Exception:
|
|
73
|
+
_FAST_PRINT_READY = False
|
|
74
|
+
|
|
75
|
+
return _FAST_PRINT_READY
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class CustomOutputWrapper:
|
|
79
|
+
"""
|
|
80
|
+
Ultra-thin sys.stdout wrapper:
|
|
81
|
+
- Writes straight to the real stream to preserve console output.
|
|
82
|
+
- If native fast path is ready, send to C ring immediately.
|
|
83
|
+
- Otherwise, fall back to PrintInterceptor (Python path).
|
|
84
|
+
- No locks, no regex, no unconditional flush.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
__slots__ = ("original", "print_interceptor")
|
|
88
|
+
|
|
89
|
+
def __init__(self, original):
|
|
90
|
+
self.original = original
|
|
91
|
+
self.print_interceptor = PrintInterceptor()
|
|
92
|
+
|
|
93
|
+
def write(self, msg):
|
|
94
|
+
# Respect your reentrancy guard quickly
|
|
95
|
+
if getattr(_thread_locals, "reentrancy_guard_logging_active", False):
|
|
96
|
+
self.original.write(msg)
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
# Always write to the real stream first (don’t flush unless caller asks)
|
|
100
|
+
self.original.write(msg)
|
|
101
|
+
|
|
102
|
+
# Cheap ignore cases (optional): skip totally empty or pure newline
|
|
103
|
+
if not msg or msg == "\n":
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
# Build once
|
|
107
|
+
message = msg
|
|
108
|
+
_, trace_id = get_or_set_sf_trace_id()
|
|
109
|
+
|
|
110
|
+
# Native fast path (C) — lowest overhead
|
|
111
|
+
if _ensure_fast_print_initialized():
|
|
112
|
+
try:
|
|
113
|
+
# Capture parent_span_id IMMEDIATELY for async-safety
|
|
114
|
+
parent_span_id = get_current_function_span_id()
|
|
115
|
+
|
|
116
|
+
_sffastlog.print_(
|
|
117
|
+
contents=message, session_id=str(trace_id), preactive=0, parent_span_id=parent_span_id
|
|
118
|
+
)
|
|
119
|
+
return
|
|
120
|
+
except Exception:
|
|
121
|
+
pass # fall through to Python fallback
|
|
122
|
+
|
|
123
|
+
# Python fallback path
|
|
124
|
+
self.print_interceptor.do_send((message, trace_id), trace_id)
|
|
125
|
+
|
|
126
|
+
def flush(self):
|
|
127
|
+
# Only flush when caller flushes (keeps latency down)
|
|
128
|
+
self.original.flush()
|
|
129
|
+
|
|
130
|
+
def __getattr__(self, attr):
|
|
131
|
+
return getattr(self.original, attr)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def setup_custom_output_wrappers():
|
|
135
|
+
# Import here to avoid circular dependency
|
|
136
|
+
from .print_override import override_print
|
|
137
|
+
|
|
138
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
139
|
+
sys.__stdout__.write("setup_custom_output_wrappers\n")
|
|
140
|
+
|
|
141
|
+
# First override print to support log=True/False parameter
|
|
142
|
+
override_print()
|
|
143
|
+
|
|
144
|
+
# Then wrap stdout for interception
|
|
145
|
+
sys.stdout = CustomOutputWrapper(sys.stdout)
|
|
146
|
+
|
|
147
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
148
|
+
sys.__stdout__.write("setup_custom_output_wrappers...DONE\n")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def get_custom_output_wrapper_django():
|
|
152
|
+
"""Django-specific wrapper setup function called by Django patch."""
|
|
153
|
+
setup_custom_output_wrappers()
|