sf-veritas 0.10.3__cp311-cp311-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.
Potentially problematic release.
This version of sf-veritas might be problematic. Click here for more details.
- sf_veritas/__init__.py +20 -0
- sf_veritas/_sffastlog.c +889 -0
- sf_veritas/_sffastlog.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +924 -0
- sf_veritas/_sffastnet.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +730 -0
- sf_veritas/_sffastnetworkrequest.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2155 -0
- sf_veritas/_sffuncspan.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +617 -0
- sf_veritas/_sffuncspan_config.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfheadercheck.c +341 -0
- sf_veritas/_sfheadercheck.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfnetworkhop.c +1451 -0
- sf_veritas/_sfnetworkhop.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1175 -0
- sf_veritas/_sfservice.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfteepreload.c +5167 -0
- sf_veritas/app_config.py +49 -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 +129 -0
- sf_veritas/custom_output_wrapper.py +144 -0
- sf_veritas/custom_print.py +146 -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 +556 -0
- sf_veritas/function_span_profiler.py +1174 -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 +497 -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/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 +76 -0
- sf_veritas/patches/network_libraries/aiohttp.py +281 -0
- sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
- sf_veritas/patches/network_libraries/http_client.py +419 -0
- sf_veritas/patches/network_libraries/httpcore.py +515 -0
- sf_veritas/patches/network_libraries/httplib2.py +204 -0
- sf_veritas/patches/network_libraries/httpx.py +515 -0
- sf_veritas/patches/network_libraries/niquests.py +211 -0
- sf_veritas/patches/network_libraries/pycurl.py +385 -0
- sf_veritas/patches/network_libraries/requests.py +633 -0
- sf_veritas/patches/network_libraries/tornado.py +341 -0
- sf_veritas/patches/network_libraries/treq.py +270 -0
- sf_veritas/patches/network_libraries/urllib_request.py +468 -0
- sf_veritas/patches/network_libraries/utils.py +398 -0
- sf_veritas/patches/os.py +17 -0
- sf_veritas/patches/threading.py +218 -0
- sf_veritas/patches/web_frameworks/__init__.py +54 -0
- sf_veritas/patches/web_frameworks/aiohttp.py +793 -0
- sf_veritas/patches/web_frameworks/async_websocket_consumer.py +317 -0
- sf_veritas/patches/web_frameworks/blacksheep.py +527 -0
- sf_veritas/patches/web_frameworks/bottle.py +502 -0
- sf_veritas/patches/web_frameworks/cherrypy.py +678 -0
- sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
- sf_veritas/patches/web_frameworks/django.py +944 -0
- sf_veritas/patches/web_frameworks/eve.py +395 -0
- sf_veritas/patches/web_frameworks/falcon.py +926 -0
- sf_veritas/patches/web_frameworks/fastapi.py +724 -0
- sf_veritas/patches/web_frameworks/flask.py +520 -0
- sf_veritas/patches/web_frameworks/klein.py +501 -0
- sf_veritas/patches/web_frameworks/litestar.py +551 -0
- sf_veritas/patches/web_frameworks/pyramid.py +428 -0
- sf_veritas/patches/web_frameworks/quart.py +824 -0
- sf_veritas/patches/web_frameworks/robyn.py +697 -0
- sf_veritas/patches/web_frameworks/sanic.py +857 -0
- sf_veritas/patches/web_frameworks/starlette.py +723 -0
- sf_veritas/patches/web_frameworks/strawberry.py +813 -0
- sf_veritas/patches/web_frameworks/tornado.py +481 -0
- sf_veritas/patches/web_frameworks/utils.py +91 -0
- sf_veritas/print_override.py +13 -0
- sf_veritas/regular_data_transmitter.py +409 -0
- sf_veritas/request_interceptor.py +401 -0
- sf_veritas/request_utils.py +550 -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 +970 -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 +1580 -0
- sf_veritas/utils.py +39 -0
- sf_veritas-0.10.3.dist-info/METADATA +97 -0
- sf_veritas-0.10.3.dist-info/RECORD +132 -0
- sf_veritas-0.10.3.dist-info/WHEEL +5 -0
- sf_veritas-0.10.3.dist-info/entry_points.txt +2 -0
- sf_veritas-0.10.3.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,69 @@
|
|
|
1
|
+
from abc import ABCMeta
|
|
2
|
+
|
|
3
|
+
from .custom_excepthook import transmit_exception
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ExceptionMeta(ABCMeta):
|
|
7
|
+
"""
|
|
8
|
+
Metaclass to add `capture_even_if_caught` functionality to exceptions
|
|
9
|
+
and provide a `transmit_to_sailfish` method for all exceptions.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __new__(cls, name, bases, dct):
|
|
13
|
+
# Wrap or define the `__init__` method
|
|
14
|
+
if "__init__" in dct:
|
|
15
|
+
original_init = dct["__init__"]
|
|
16
|
+
|
|
17
|
+
def wrapped_init(self, *args, **kwargs):
|
|
18
|
+
# Add the `capture_even_if_caught` attribute
|
|
19
|
+
self.capture_even_if_caught = kwargs.pop(
|
|
20
|
+
"capture_even_if_caught", False
|
|
21
|
+
)
|
|
22
|
+
self._handled = False # Ensure `_handled` is initialized
|
|
23
|
+
original_init(self, *args, **kwargs)
|
|
24
|
+
|
|
25
|
+
dct["__init__"] = wrapped_init
|
|
26
|
+
else:
|
|
27
|
+
|
|
28
|
+
def default_init(self, *args, **kwargs):
|
|
29
|
+
# Add the `capture_even_if_caught` attribute
|
|
30
|
+
self.capture_even_if_caught = kwargs.pop(
|
|
31
|
+
"capture_even_if_caught", False
|
|
32
|
+
)
|
|
33
|
+
self._handled = False # Ensure `_handled` is initialized
|
|
34
|
+
super(Exception, self).__init__(*args, **kwargs)
|
|
35
|
+
|
|
36
|
+
dct["__init__"] = default_init
|
|
37
|
+
|
|
38
|
+
# Add `transmit_to_sailfish` method to all exceptions
|
|
39
|
+
def transmit_to_sailfish(self, was_caught: bool = False):
|
|
40
|
+
"""
|
|
41
|
+
Transmit this exception to Sailfish.
|
|
42
|
+
"""
|
|
43
|
+
if not getattr(self, "_handled", False):
|
|
44
|
+
transmit_exception(type(self), self, self.__traceback__, was_caught)
|
|
45
|
+
setattr(
|
|
46
|
+
self, "_handled", True
|
|
47
|
+
) # Mark as handled to prevent duplication
|
|
48
|
+
|
|
49
|
+
dct["transmit_to_sailfish"] = transmit_to_sailfish
|
|
50
|
+
|
|
51
|
+
return super().__new__(cls, name, bases, dct)
|
|
52
|
+
|
|
53
|
+
def __call__(cls, *args, **kwargs):
|
|
54
|
+
"""
|
|
55
|
+
Intercept exception instantiation to handle `capture_even_if_caught`.
|
|
56
|
+
"""
|
|
57
|
+
instance = super().__call__(*args, **kwargs)
|
|
58
|
+
# Automatically handle `capture_even_if_caught` exceptions
|
|
59
|
+
if getattr(instance, "capture_even_if_caught", False):
|
|
60
|
+
instance.transmit_to_sailfish()
|
|
61
|
+
return instance
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class PatchedException(Exception, metaclass=ExceptionMeta):
|
|
65
|
+
"""
|
|
66
|
+
A patched version of the built-in Exception class with universal interception capabilities.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
pass
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ultra-fast frame introspection using C extension.
|
|
3
|
+
Falls back to Python if C extension is not available.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sysconfig
|
|
7
|
+
from functools import lru_cache
|
|
8
|
+
from typing import Optional, Tuple
|
|
9
|
+
|
|
10
|
+
# Try to import C extension
|
|
11
|
+
try:
|
|
12
|
+
from . import _sfframeinfo
|
|
13
|
+
|
|
14
|
+
_FAST_FRAME_OK = True
|
|
15
|
+
except ImportError:
|
|
16
|
+
_sfframeinfo = None
|
|
17
|
+
_FAST_FRAME_OK = False
|
|
18
|
+
|
|
19
|
+
# Fallback Python implementation
|
|
20
|
+
_STDLIB = sysconfig.get_paths()["stdlib"]
|
|
21
|
+
_SITE_TAGS = ("site-packages", "dist-packages")
|
|
22
|
+
_SKIP_PREFIXES = (_STDLIB, "/usr/local/lib/python", "/usr/lib/python")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@lru_cache(maxsize=512)
|
|
26
|
+
def _is_user_code_py(path: Optional[str]) -> bool:
|
|
27
|
+
"""Python fallback for checking if path is user code."""
|
|
28
|
+
if not path or path.startswith("<"):
|
|
29
|
+
return False
|
|
30
|
+
for p in _SKIP_PREFIXES:
|
|
31
|
+
if path.startswith(p):
|
|
32
|
+
return False
|
|
33
|
+
return not any(tag in path for tag in _SITE_TAGS)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_code_info(func) -> Optional[Tuple[str, int, str]]:
|
|
37
|
+
"""
|
|
38
|
+
Extract (filename, lineno, name) from a function object.
|
|
39
|
+
Returns None if not user code or extraction fails.
|
|
40
|
+
|
|
41
|
+
Uses C extension if available for ~10x speedup.
|
|
42
|
+
"""
|
|
43
|
+
if _FAST_FRAME_OK:
|
|
44
|
+
try:
|
|
45
|
+
result = _sfframeinfo.get_code_info(func)
|
|
46
|
+
if result:
|
|
47
|
+
# Result is (filename, lineno, name, is_user)
|
|
48
|
+
return (result[0], result[1], result[2])
|
|
49
|
+
return None
|
|
50
|
+
except Exception:
|
|
51
|
+
pass # Fall back to Python
|
|
52
|
+
|
|
53
|
+
# Python fallback
|
|
54
|
+
try:
|
|
55
|
+
code = getattr(func, "__code__", None)
|
|
56
|
+
if not code:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
filename = code.co_filename
|
|
60
|
+
if not _is_user_code_py(filename):
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
return (filename, code.co_firstlineno, func.__name__)
|
|
64
|
+
except Exception:
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_frame_info(frame) -> Optional[Tuple[str, int, str]]:
|
|
69
|
+
"""
|
|
70
|
+
Extract (filename, lineno, name) from a frame object.
|
|
71
|
+
Returns None if not user code or extraction fails.
|
|
72
|
+
|
|
73
|
+
Uses C extension if available for ~10x speedup.
|
|
74
|
+
"""
|
|
75
|
+
if _FAST_FRAME_OK:
|
|
76
|
+
try:
|
|
77
|
+
result = _sfframeinfo.get_frame_info(frame)
|
|
78
|
+
if result:
|
|
79
|
+
# Result is (filename, lineno, name, is_user)
|
|
80
|
+
return (result[0], result[1], result[2])
|
|
81
|
+
return None
|
|
82
|
+
except Exception:
|
|
83
|
+
pass # Fall back to Python
|
|
84
|
+
|
|
85
|
+
# Python fallback
|
|
86
|
+
try:
|
|
87
|
+
code = frame.f_code
|
|
88
|
+
filename = code.co_filename
|
|
89
|
+
if not _is_user_code_py(filename):
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
return (filename, frame.f_lineno, code.co_name)
|
|
93
|
+
except Exception:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def is_user_code_func(func) -> bool:
|
|
98
|
+
"""
|
|
99
|
+
Quick check if function is user code.
|
|
100
|
+
|
|
101
|
+
Uses C extension if available for ~10x speedup.
|
|
102
|
+
"""
|
|
103
|
+
if _FAST_FRAME_OK:
|
|
104
|
+
try:
|
|
105
|
+
return bool(_sfframeinfo.is_user_code_func(func))
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
# Python fallback
|
|
110
|
+
try:
|
|
111
|
+
code = getattr(func, "__code__", None)
|
|
112
|
+
if not code:
|
|
113
|
+
return False
|
|
114
|
+
return _is_user_code_py(code.co_filename)
|
|
115
|
+
except Exception:
|
|
116
|
+
return False
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ultra-fast network hop sender using C extension with endpoint pre-registration.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from logging import getLogger
|
|
7
|
+
from typing import Optional, Tuple
|
|
8
|
+
|
|
9
|
+
from .regular_data_transmitter import NetworkHopsTransmitter
|
|
10
|
+
from .thread_local import is_network_recording_suppressed
|
|
11
|
+
|
|
12
|
+
logger = getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
# Optional native fast path for network hops (C extension)
|
|
15
|
+
# LAZY IMPORT: Don't import at module level to avoid circular import
|
|
16
|
+
# Import will happen in _ensure_fast_networkhop_initialized() when first needed
|
|
17
|
+
_sfnetworkhop = None
|
|
18
|
+
_NETWORKHOP_FAST_OK = None # None = not yet attempted, True = success, False = failed
|
|
19
|
+
|
|
20
|
+
_FAST_NETWORKHOP_READY = False # one-time guard for native networkhop init
|
|
21
|
+
|
|
22
|
+
# GraphQL mutation string for network hops (with optional body/header capture)
|
|
23
|
+
_COLLECT_NETWORKHOP_MUTATION = (
|
|
24
|
+
"mutation collectNetworkHops("
|
|
25
|
+
"$apiKey: String!,"
|
|
26
|
+
"$sessionId: String!,"
|
|
27
|
+
"$timestampMs: String!,"
|
|
28
|
+
"$line: String!,"
|
|
29
|
+
"$column: String!,"
|
|
30
|
+
"$name: String!,"
|
|
31
|
+
"$entrypoint: String!,"
|
|
32
|
+
"$route: String,"
|
|
33
|
+
"$queryParams: String,"
|
|
34
|
+
"$serviceUuid: String,"
|
|
35
|
+
"$requestHeaders: String,"
|
|
36
|
+
"$requestBody: String,"
|
|
37
|
+
"$responseHeaders: String,"
|
|
38
|
+
"$responseBody: String"
|
|
39
|
+
"){collectNetworkHops("
|
|
40
|
+
"apiKey:$apiKey,sessionId:$sessionId,timestampMs:$timestampMs,"
|
|
41
|
+
"line:$line,column:$column,name:$name,entrypoint:$entrypoint,"
|
|
42
|
+
"route:$route,queryParams:$queryParams,serviceUuid:$serviceUuid,requestHeaders:$requestHeaders,"
|
|
43
|
+
"requestBody:$requestBody,responseHeaders:$responseHeaders,"
|
|
44
|
+
"responseBody:$responseBody)}"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _ensure_fast_networkhop_initialized() -> bool:
|
|
49
|
+
global _FAST_NETWORKHOP_READY, _sfnetworkhop, _NETWORKHOP_FAST_OK
|
|
50
|
+
|
|
51
|
+
SF_DEBUG = os.getenv("SF_DEBUG", "false").lower() == "true"
|
|
52
|
+
|
|
53
|
+
# PERFORMANCE: Skip network hop extension when testing network library only
|
|
54
|
+
if os.getenv("TESTING_NETWORK_LIBRARY_ONLY", "0") == "1":
|
|
55
|
+
if SF_DEBUG:
|
|
56
|
+
print(
|
|
57
|
+
"[[_ensure_fast_networkhop_initialized]] Network hop extension disabled (TESTING_NETWORK_LIBRARY_ONLY=1)",
|
|
58
|
+
log=False,
|
|
59
|
+
)
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
# LAZY IMPORT: Try to import C extension on first use
|
|
63
|
+
if _NETWORKHOP_FAST_OK is None:
|
|
64
|
+
try:
|
|
65
|
+
from . import _sfnetworkhop as _sfnh_module
|
|
66
|
+
|
|
67
|
+
_sfnetworkhop = _sfnh_module
|
|
68
|
+
_NETWORKHOP_FAST_OK = True
|
|
69
|
+
if SF_DEBUG:
|
|
70
|
+
print(
|
|
71
|
+
f"[[_ensure_fast_networkhop_initialized]] Successfully imported _sfnetworkhop C extension",
|
|
72
|
+
log=False,
|
|
73
|
+
)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
if SF_DEBUG:
|
|
76
|
+
print(
|
|
77
|
+
f"[[_ensure_fast_networkhop_initialized]] Failed to import _sfnetworkhop C extension: {e}",
|
|
78
|
+
log=False,
|
|
79
|
+
)
|
|
80
|
+
_sfnetworkhop = None
|
|
81
|
+
_NETWORKHOP_FAST_OK = False
|
|
82
|
+
|
|
83
|
+
if not _NETWORKHOP_FAST_OK:
|
|
84
|
+
if SF_DEBUG:
|
|
85
|
+
print(
|
|
86
|
+
f"[[_ensure_fast_networkhop_initialized]] C extension not available (_NETWORKHOP_FAST_OK=False)",
|
|
87
|
+
log=False,
|
|
88
|
+
)
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
if _FAST_NETWORKHOP_READY:
|
|
92
|
+
if SF_DEBUG:
|
|
93
|
+
print(
|
|
94
|
+
f"[[_ensure_fast_networkhop_initialized]] Already initialized, returning True",
|
|
95
|
+
log=False,
|
|
96
|
+
)
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
from . import app_config
|
|
100
|
+
|
|
101
|
+
endpoint = getattr(app_config, "_sailfish_graphql_endpoint", None)
|
|
102
|
+
api_key = getattr(app_config, "_sailfish_api_key", None)
|
|
103
|
+
service_uuid = getattr(app_config, "_service_uuid", None)
|
|
104
|
+
http2 = 1 if os.getenv("SF_NBPOST_HTTP2", "0") == "1" else 0
|
|
105
|
+
|
|
106
|
+
if SF_DEBUG:
|
|
107
|
+
print(
|
|
108
|
+
f"[[_ensure_fast_networkhop_initialized]] Config: endpoint={bool(endpoint)}, api_key={bool(api_key)}, service_uuid={bool(service_uuid)}",
|
|
109
|
+
log=False,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if not (endpoint and api_key and service_uuid):
|
|
113
|
+
if SF_DEBUG:
|
|
114
|
+
print(
|
|
115
|
+
f"[[_ensure_fast_networkhop_initialized]] Missing required config, returning False",
|
|
116
|
+
log=False,
|
|
117
|
+
)
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
if SF_DEBUG:
|
|
122
|
+
print(
|
|
123
|
+
f"[[_ensure_fast_networkhop_initialized]] Calling _sfnetworkhop.init() with url={endpoint}",
|
|
124
|
+
log=False,
|
|
125
|
+
)
|
|
126
|
+
ok = _sfnetworkhop.init(
|
|
127
|
+
url=endpoint,
|
|
128
|
+
query=_COLLECT_NETWORKHOP_MUTATION,
|
|
129
|
+
api_key=str(api_key),
|
|
130
|
+
service_uuid=str(service_uuid),
|
|
131
|
+
http2=http2,
|
|
132
|
+
)
|
|
133
|
+
_FAST_NETWORKHOP_READY = bool(ok)
|
|
134
|
+
if SF_DEBUG:
|
|
135
|
+
print(
|
|
136
|
+
f"[[_ensure_fast_networkhop_initialized]] _sfnetworkhop.init() returned {ok}, _FAST_NETWORKHOP_READY={_FAST_NETWORKHOP_READY}",
|
|
137
|
+
log=False,
|
|
138
|
+
)
|
|
139
|
+
except Exception as e:
|
|
140
|
+
if SF_DEBUG:
|
|
141
|
+
print(
|
|
142
|
+
f"[[_ensure_fast_networkhop_initialized]] _sfnetworkhop.init() raised exception: {e}",
|
|
143
|
+
log=False,
|
|
144
|
+
)
|
|
145
|
+
_FAST_NETWORKHOP_READY = False
|
|
146
|
+
|
|
147
|
+
return _FAST_NETWORKHOP_READY
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def register_endpoint(
|
|
151
|
+
line: str, column: str, name: str, entrypoint: str, route: str = None
|
|
152
|
+
) -> int:
|
|
153
|
+
"""
|
|
154
|
+
Register a web framework endpoint's invariant fields once.
|
|
155
|
+
Returns an endpoint_id usable with fast_send_network_hop_fast(...).
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
line: Line number where the endpoint is defined
|
|
159
|
+
column: Column number (typically "0")
|
|
160
|
+
name: Function name of the endpoint
|
|
161
|
+
entrypoint: File path where the endpoint is defined
|
|
162
|
+
route: Route pattern (e.g., "/api/users/{id}") - optional
|
|
163
|
+
"""
|
|
164
|
+
SF_DEBUG = os.getenv("SF_DEBUG", "false").lower() == "true"
|
|
165
|
+
|
|
166
|
+
if SF_DEBUG:
|
|
167
|
+
print(
|
|
168
|
+
f"[[register_endpoint]] Called for {name} @ {entrypoint}:{line} (route={route})",
|
|
169
|
+
log=False,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if not _ensure_fast_networkhop_initialized():
|
|
173
|
+
if SF_DEBUG:
|
|
174
|
+
print(
|
|
175
|
+
f"[[register_endpoint]] _ensure_fast_networkhop_initialized() returned False, returning -1",
|
|
176
|
+
log=False,
|
|
177
|
+
)
|
|
178
|
+
return -1
|
|
179
|
+
try:
|
|
180
|
+
eid = _sfnetworkhop.register_endpoint(
|
|
181
|
+
line=line, column=column, name=name, entrypoint=entrypoint, route=route
|
|
182
|
+
)
|
|
183
|
+
if SF_DEBUG:
|
|
184
|
+
print(
|
|
185
|
+
f"[[register_endpoint]] _sfnetworkhop.register_endpoint() returned {eid}",
|
|
186
|
+
log=False,
|
|
187
|
+
)
|
|
188
|
+
return int(eid)
|
|
189
|
+
except Exception as e:
|
|
190
|
+
if SF_DEBUG:
|
|
191
|
+
print(
|
|
192
|
+
f"[[register_endpoint]] _sfnetworkhop.register_endpoint() raised exception: {e}",
|
|
193
|
+
log=False,
|
|
194
|
+
)
|
|
195
|
+
return -1
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def fast_send_network_hop_fast(
|
|
199
|
+
session_id: str,
|
|
200
|
+
endpoint_id: int,
|
|
201
|
+
raw_path: str = None,
|
|
202
|
+
raw_query_string: bytes = None,
|
|
203
|
+
request_headers: dict = None,
|
|
204
|
+
request_body: bytes = None,
|
|
205
|
+
response_headers: dict = None,
|
|
206
|
+
response_body: bytes = None,
|
|
207
|
+
) -> None:
|
|
208
|
+
"""
|
|
209
|
+
ULTRA-FAST PATH: Assumes initialization already happened (checked once at startup).
|
|
210
|
+
Optionally accepts request/response headers and body for capture.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
session_id: Unique session identifier
|
|
214
|
+
endpoint_id: Pre-registered endpoint ID
|
|
215
|
+
raw_path: Optional actual request path (e.g., "/log") - passed to C for JSON escaping
|
|
216
|
+
raw_query_string: Optional raw query string bytes (e.g., b"foo=5") - passed to C for decoding/escaping
|
|
217
|
+
request_headers: Optional dict of request headers
|
|
218
|
+
request_body: Optional request body (bytes, str, or list of chunks)
|
|
219
|
+
response_headers: Optional dict of response headers
|
|
220
|
+
response_body: Optional response body (bytes, str, or list of chunks)
|
|
221
|
+
"""
|
|
222
|
+
# Check if network recording is suppressed (e.g., by @skip_network_tracing decorator)
|
|
223
|
+
if is_network_recording_suppressed():
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
# HOT PATH OPTIMIZATION: Skip initialization check after first successful init
|
|
227
|
+
if not _FAST_NETWORKHOP_READY:
|
|
228
|
+
if not _ensure_fast_networkhop_initialized():
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
# Direct C call - no exception handling (C code is bulletproof)
|
|
232
|
+
if endpoint_id >= 0:
|
|
233
|
+
# Check if we need the extended path (with route/query/body/header capture)
|
|
234
|
+
has_extended_data = (
|
|
235
|
+
raw_path is not None
|
|
236
|
+
or raw_query_string is not None
|
|
237
|
+
or request_headers is not None
|
|
238
|
+
or request_body is not None
|
|
239
|
+
or response_headers is not None
|
|
240
|
+
or response_body is not None
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
if has_extended_data:
|
|
244
|
+
# Use extended capture path - C handles all decoding/escaping
|
|
245
|
+
_sfnetworkhop.networkhop_with_bodies(
|
|
246
|
+
session_id=session_id,
|
|
247
|
+
endpoint_id=endpoint_id,
|
|
248
|
+
raw_path=raw_path,
|
|
249
|
+
raw_query_string=raw_query_string,
|
|
250
|
+
request_headers=request_headers,
|
|
251
|
+
request_body=request_body,
|
|
252
|
+
response_headers=response_headers,
|
|
253
|
+
response_body=response_body,
|
|
254
|
+
)
|
|
255
|
+
else:
|
|
256
|
+
# Ultra-fast path with NO extra data
|
|
257
|
+
_sfnetworkhop.networkhop_fast(
|
|
258
|
+
session_id=session_id, endpoint_id=endpoint_id
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def fast_send_network_hop(
|
|
263
|
+
session_id: str, line: str, column: str, name: str, entrypoint: str
|
|
264
|
+
):
|
|
265
|
+
"""
|
|
266
|
+
Backward-compatible API (not used by patched FastAPI; kept for other callers).
|
|
267
|
+
"""
|
|
268
|
+
# Check if network recording is suppressed (e.g., by @skip_network_tracing decorator)
|
|
269
|
+
if is_network_recording_suppressed():
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
if _ensure_fast_networkhop_initialized():
|
|
273
|
+
try:
|
|
274
|
+
# Old slow path in C; kept for compatibility
|
|
275
|
+
_sfnetworkhop.networkhop(
|
|
276
|
+
session_id=session_id,
|
|
277
|
+
line=line,
|
|
278
|
+
column=column,
|
|
279
|
+
name=name,
|
|
280
|
+
entrypoint=entrypoint,
|
|
281
|
+
)
|
|
282
|
+
return
|
|
283
|
+
except Exception:
|
|
284
|
+
pass
|
|
285
|
+
|
|
286
|
+
# Fallback to Python transmitter
|
|
287
|
+
NetworkHopsTransmitter().send(
|
|
288
|
+
session_id=session_id,
|
|
289
|
+
line=line,
|
|
290
|
+
column=column,
|
|
291
|
+
name=name,
|
|
292
|
+
entrypoint=entrypoint,
|
|
293
|
+
)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Any, Iterable, List
|
|
3
|
+
|
|
4
|
+
from .env_vars import (
|
|
5
|
+
SAILFISH_EXCEPTION_LOCALS_HIDE_DUNDER,
|
|
6
|
+
SAILFISH_EXCEPTION_LOCALS_HIDE_SELF,
|
|
7
|
+
SAILFISH_EXCEPTION_LOCALS_HIDE_SUNDER,
|
|
8
|
+
SAILFISH_EXCEPTION_LOCALS_TYPES_TO_IGNORE,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def import_type(module_name: str, type_name: str):
|
|
13
|
+
try:
|
|
14
|
+
exec( # pylint: disable=exec-used
|
|
15
|
+
f"from {module_name} import {type_name}",
|
|
16
|
+
globals(),
|
|
17
|
+
)
|
|
18
|
+
return globals().get(type_name, None)
|
|
19
|
+
except ImportError:
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_types_from_str(types_str: str) -> List[type]:
|
|
24
|
+
types_list = []
|
|
25
|
+
if types_str:
|
|
26
|
+
for type_path in types_str.split(","):
|
|
27
|
+
type_path_fixed = type_path.replace(" ", "")
|
|
28
|
+
module_name, type_name = type_path_fixed.rsplit(".", 1)
|
|
29
|
+
type_obj = import_type(module_name, type_name)
|
|
30
|
+
if type_obj:
|
|
31
|
+
types_list.append(type_obj)
|
|
32
|
+
return types_list
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
EXCEPTION_LOCALS_TYPES_TO_IGNORE = get_types_from_str(
|
|
36
|
+
SAILFISH_EXCEPTION_LOCALS_TYPES_TO_IGNORE
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_current_frame():
|
|
41
|
+
return sys._getframe(1) # pylint: disable=protected-access
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def value_type_to_be_ignored(value: Any) -> bool:
|
|
45
|
+
return any(
|
|
46
|
+
isinstance(value, type_obj) for type_obj in EXCEPTION_LOCALS_TYPES_TO_IGNORE
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def key_is_str_to_be_ignored(
|
|
51
|
+
key: Any, locals_hide_self: bool, locals_hide_dunder: bool, locals_hide_sunder: bool
|
|
52
|
+
) -> bool:
|
|
53
|
+
return (
|
|
54
|
+
(locals_hide_self and key == "self")
|
|
55
|
+
or (locals_hide_dunder and key.startswith("__"))
|
|
56
|
+
or (locals_hide_sunder and key.startswith("_"))
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def filter_locals(
|
|
61
|
+
iter_locals: Iterable[tuple[str, object]],
|
|
62
|
+
locals_hide_self: bool,
|
|
63
|
+
locals_hide_dunder: bool,
|
|
64
|
+
locals_hide_sunder: bool,
|
|
65
|
+
) -> Iterable[tuple[str, object]]:
|
|
66
|
+
for key, value in iter_locals:
|
|
67
|
+
if not isinstance(key, str):
|
|
68
|
+
if value_type_to_be_ignored(value):
|
|
69
|
+
continue
|
|
70
|
+
else:
|
|
71
|
+
if key_is_str_to_be_ignored(
|
|
72
|
+
key, locals_hide_self, locals_hide_dunder, locals_hide_sunder
|
|
73
|
+
):
|
|
74
|
+
continue
|
|
75
|
+
if value_type_to_be_ignored(value):
|
|
76
|
+
continue
|
|
77
|
+
if key in ("args", "field_args") and isinstance(value, list):
|
|
78
|
+
value = list(
|
|
79
|
+
filter_locals(
|
|
80
|
+
enumerate(value),
|
|
81
|
+
locals_hide_self,
|
|
82
|
+
locals_hide_dunder,
|
|
83
|
+
locals_hide_sunder,
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
if key in ("kwargs", "field_kwargs") and isinstance(value, dict):
|
|
87
|
+
value = dict(
|
|
88
|
+
filter_locals(
|
|
89
|
+
value.items(),
|
|
90
|
+
locals_hide_self,
|
|
91
|
+
locals_hide_dunder,
|
|
92
|
+
locals_hide_sunder,
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
yield key, value
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_locals(
|
|
99
|
+
iter_locals: Iterable[tuple[str, object]],
|
|
100
|
+
locals_hide_self: bool = SAILFISH_EXCEPTION_LOCALS_HIDE_SELF,
|
|
101
|
+
locals_hide_dunder: bool = SAILFISH_EXCEPTION_LOCALS_HIDE_DUNDER,
|
|
102
|
+
locals_hide_sunder: bool = SAILFISH_EXCEPTION_LOCALS_HIDE_SUNDER,
|
|
103
|
+
) -> Iterable[tuple[str, object]]:
|
|
104
|
+
"""Extract locals from an iterator of key pairs."""
|
|
105
|
+
if not (locals_hide_dunder or locals_hide_sunder):
|
|
106
|
+
yield from iter_locals
|
|
107
|
+
return
|
|
108
|
+
iter_locals_filtered = filter_locals(
|
|
109
|
+
iter_locals, locals_hide_self, locals_hide_dunder, locals_hide_sunder
|
|
110
|
+
)
|
|
111
|
+
for key, value in iter_locals_filtered:
|
|
112
|
+
yield key, value
|