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,82 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import sys
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
from importlib import abc
|
|
6
|
+
|
|
7
|
+
from ..env_vars import PRINT_CONFIGURATION_STATUSES
|
|
8
|
+
|
|
9
|
+
# Thread-local storage to avoid re-entry problems
|
|
10
|
+
patch_lock = threading.local()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def patch_exceptions(module):
|
|
14
|
+
if hasattr(patch_lock, "active"):
|
|
15
|
+
return
|
|
16
|
+
patch_lock.active = True
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ExceptionPatchingFinder(abc.MetaPathFinder):
|
|
20
|
+
def find_spec(self, fullname, path, target=None):
|
|
21
|
+
if hasattr(patch_lock, "loading") and patch_lock.loading:
|
|
22
|
+
return None
|
|
23
|
+
try:
|
|
24
|
+
patch_lock.loading = True
|
|
25
|
+
original_spec = importlib.util.find_spec(fullname, path)
|
|
26
|
+
if original_spec:
|
|
27
|
+
return importlib.util.spec_from_loader(
|
|
28
|
+
fullname,
|
|
29
|
+
ExceptionPatchingLoader(original_spec.loader),
|
|
30
|
+
origin=original_spec.origin,
|
|
31
|
+
)
|
|
32
|
+
return None
|
|
33
|
+
finally:
|
|
34
|
+
patch_lock.loading = False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ExceptionPatchingLoader(abc.Loader):
|
|
38
|
+
def __init__(self, loader):
|
|
39
|
+
self._original_loader = loader
|
|
40
|
+
|
|
41
|
+
def create_module(self, spec):
|
|
42
|
+
return self._original_loader.create_module(spec)
|
|
43
|
+
|
|
44
|
+
def exec_module(self, module):
|
|
45
|
+
self._original_loader.exec_module(module)
|
|
46
|
+
patch_exceptions(module)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def install_import_hook():
|
|
50
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
51
|
+
print("EXCEPTIONS - install_import_hook", log=False)
|
|
52
|
+
sys.meta_path.insert(0, ExceptionPatchingFinder())
|
|
53
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
54
|
+
print("EXCEPTIONS - install_import_hook...DONE", log=False)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Initially store the current state of sys.excepthook
|
|
58
|
+
original_excepthook = sys.excepthook
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def monitor_excepthook(interval=1):
|
|
62
|
+
global original_excepthook
|
|
63
|
+
|
|
64
|
+
while True:
|
|
65
|
+
current_hook = sys.excepthook
|
|
66
|
+
if current_hook != original_excepthook and PRINT_CONFIGURATION_STATUSES:
|
|
67
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
68
|
+
print("sys.excepthook has been modified!")
|
|
69
|
+
original_excepthook = current_hook
|
|
70
|
+
continue
|
|
71
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
72
|
+
print("No change detected in sys.excepthook.")
|
|
73
|
+
|
|
74
|
+
# Pause for the specified interval before the next check
|
|
75
|
+
time.sleep(interval)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# Function to start monitoring in a separate thread
|
|
79
|
+
def start_monitoring(interval=2):
|
|
80
|
+
thread = threading.Thread(target=monitor_excepthook, args=(interval,))
|
|
81
|
+
# thread.daemon = True # This makes the thread exit when the main program exits
|
|
82
|
+
thread.start()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import multiprocessing
|
|
2
|
+
|
|
3
|
+
from ..thread_local import get_context, set_context
|
|
4
|
+
|
|
5
|
+
_original_process_init = multiprocessing.Process.__init__
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def patched_process_init(self, *args, **kwargs):
|
|
9
|
+
current_context = get_context()
|
|
10
|
+
|
|
11
|
+
original_target = kwargs.get("target")
|
|
12
|
+
if original_target:
|
|
13
|
+
|
|
14
|
+
def wrapped_target(*targs, **tkwargs):
|
|
15
|
+
set_context(current_context)
|
|
16
|
+
original_target(*targs, **tkwargs)
|
|
17
|
+
|
|
18
|
+
kwargs["target"] = wrapped_target
|
|
19
|
+
elif args and callable(args[0]):
|
|
20
|
+
original_target = args[0]
|
|
21
|
+
|
|
22
|
+
def wrapped_target(*targs, **tkwargs):
|
|
23
|
+
set_context(current_context)
|
|
24
|
+
original_target(*targs, **tkwargs)
|
|
25
|
+
|
|
26
|
+
args = (wrapped_target,) + args[1:]
|
|
27
|
+
|
|
28
|
+
_original_process_init(self, *args, **kwargs)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def patch_multiprocessing():
|
|
32
|
+
multiprocessing.Process.__init__ = patched_process_init
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
from ...request_interceptor import get_domains_to_not_propagate_headers_to
|
|
5
|
+
from .utils import init_fast_network_tracking
|
|
6
|
+
from .requests import patch_requests
|
|
7
|
+
from .aiohttp import patch_aiohttp
|
|
8
|
+
from .httpx import patch_httpx
|
|
9
|
+
from .httpcore import patch_httpcore
|
|
10
|
+
from .http_client import patch_http_client
|
|
11
|
+
from .urllib_request import patch_urllib_request
|
|
12
|
+
from .httplib2 import patch_httplib2
|
|
13
|
+
from .pycurl import patch_pycurl
|
|
14
|
+
from .niquests import patch_niquests
|
|
15
|
+
from .curl_cffi import patch_curl_cffi
|
|
16
|
+
from .tornado import patch_tornado
|
|
17
|
+
from .treq import patch_treq
|
|
18
|
+
|
|
19
|
+
# from .aioh2 import patch_aioh2 # Asynchronous HTTP/2 client, no clear extension hooks
|
|
20
|
+
# from .http_prompt import patch_http_prompt # CLI HTTP client, minimal public API
|
|
21
|
+
# from .mureq import patch_mureq # Specialized crawler client, little documentation
|
|
22
|
+
# from .reqboost import patch_reqboost # High-performance batch client, docs scarce
|
|
23
|
+
# from .impit import (patch_impit) # Used by Crawlee's ImpitHttpClient
|
|
24
|
+
# from .h11 import patch_h11 # Low-level HTTP/1.1 protocol library
|
|
25
|
+
# from .aioquic import patch_aioquic # QUIC/HTTP-3 client, no standard headers API
|
|
26
|
+
# from .qh3 import patch_qh3 # Experimental HTTP/3 client, no docs found
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def patch_all_http_clients(
|
|
30
|
+
domains_to_not_propagate_headers_to: Optional[List[str]] = None,
|
|
31
|
+
):
|
|
32
|
+
# Enable Python-level header injection (ULTRA-FAST: <100ns)
|
|
33
|
+
# This disables C-level header injection (291µs overhead) and lets Python patches handle it
|
|
34
|
+
os.environ["SF_PYTHON_HEADER_INJECTION"] = "0"
|
|
35
|
+
|
|
36
|
+
# Initialize fast C-based network tracking
|
|
37
|
+
# NOTE: When LD_PRELOAD is active (_sfteepreload), this only initializes the Python senders
|
|
38
|
+
# The actual socket capture is done by _sfteepreload automatically
|
|
39
|
+
init_fast_network_tracking()
|
|
40
|
+
|
|
41
|
+
# Send domains mutation ONCE before patching (not from within each patch function)
|
|
42
|
+
if domains_to_not_propagate_headers_to:
|
|
43
|
+
domains_to_not_propagate_headers_to = get_domains_to_not_propagate_headers_to(
|
|
44
|
+
domains_to_not_propagate_headers_to
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# ULTRA-FAST Python-level header injection (<100ns)
|
|
48
|
+
# Enable core libraries that cover 95% of use cases:
|
|
49
|
+
|
|
50
|
+
# requests covers: requests → urllib3 → http.client (entire stack!)
|
|
51
|
+
patch_requests(domains_to_not_propagate_headers_to)
|
|
52
|
+
|
|
53
|
+
# aiohttp for async HTTP (standalone stack)
|
|
54
|
+
patch_aiohttp(domains_to_not_propagate_headers_to)
|
|
55
|
+
|
|
56
|
+
# Additional libraries (enable if needed):
|
|
57
|
+
patch_http_client(domains_to_not_propagate_headers_to) # Covered by requests
|
|
58
|
+
patch_urllib_request(domains_to_not_propagate_headers_to) # Covered by requests
|
|
59
|
+
patch_httplib2(domains_to_not_propagate_headers_to)
|
|
60
|
+
patch_httpx(domains_to_not_propagate_headers_to)
|
|
61
|
+
patch_httpcore(domains_to_not_propagate_headers_to)
|
|
62
|
+
patch_pycurl(domains_to_not_propagate_headers_to)
|
|
63
|
+
patch_treq(domains_to_not_propagate_headers_to)
|
|
64
|
+
patch_tornado(domains_to_not_propagate_headers_to)
|
|
65
|
+
patch_curl_cffi(domains_to_not_propagate_headers_to)
|
|
66
|
+
patch_niquests(domains_to_not_propagate_headers_to)
|
|
67
|
+
|
|
68
|
+
# # Lesser-used libraries
|
|
69
|
+
# patch_impit(domains_to_not_propagate_headers_to)
|
|
70
|
+
# patch_aioh2(domains_to_not_propagate_headers_to)
|
|
71
|
+
# patch_http_prompt(domains_to_not_propagate_headers_to)
|
|
72
|
+
# patch_mureq(domains_to_not_propagate_headers_to)
|
|
73
|
+
# patch_reqboost(domains_to_not_propagate_headers_to)
|
|
74
|
+
# patch_h11(domains_to_not_propagate_headers_to)
|
|
75
|
+
# patch_aioquic(domains_to_not_propagate_headers_to)
|
|
76
|
+
# patch_qh3(domains_to_not_propagate_headers_to)
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
from typing import Any, List, Optional
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
import wrapt
|
|
7
|
+
|
|
8
|
+
HAS_WRAPT = True
|
|
9
|
+
except ImportError:
|
|
10
|
+
HAS_WRAPT = False
|
|
11
|
+
|
|
12
|
+
from ...constants import FUNCSPAN_OVERRIDE_HEADER, SAILFISH_TRACING_HEADER
|
|
13
|
+
from ...thread_local import trace_id_ctx
|
|
14
|
+
from .utils import (
|
|
15
|
+
init_fast_header_check,
|
|
16
|
+
inject_headers_ultrafast,
|
|
17
|
+
record_network_request,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# JSON serialization - try fast orjson first, fallback to stdlib json
|
|
21
|
+
try:
|
|
22
|
+
import orjson
|
|
23
|
+
|
|
24
|
+
HAS_ORJSON = True
|
|
25
|
+
except ImportError:
|
|
26
|
+
import json
|
|
27
|
+
|
|
28
|
+
HAS_ORJSON = False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _tee_preload_active() -> bool:
|
|
32
|
+
"""Detect if LD_PRELOAD tee is active (same logic as http_client.py)."""
|
|
33
|
+
if os.getenv("SF_TEE_PRELOAD_ONLY", "0") == "1":
|
|
34
|
+
return True
|
|
35
|
+
ld = os.getenv("LD_PRELOAD", "")
|
|
36
|
+
return "libsfnettee.so" in ld or "_sfteepreload" in ld
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def patch_aiohttp(domains_to_not_propagate_headers_to: Optional[List[str]] = None):
|
|
40
|
+
"""
|
|
41
|
+
Monkey-patch aiohttp so that every HTTP verb:
|
|
42
|
+
1) injects SAILFISH_TRACING_HEADER + FUNCSPAN_OVERRIDE_HEADER when allowed,
|
|
43
|
+
2) measures timing (only when LD_PRELOAD not active),
|
|
44
|
+
3) calls NetworkRequestTransmitter().do_send via record_network_request (UNLESS LD_PRELOAD active).
|
|
45
|
+
|
|
46
|
+
When LD_PRELOAD is active: ULTRA-FAST path using TraceConfig with wrapt (OTEL-style, minimal overhead).
|
|
47
|
+
When LD_PRELOAD is NOT active: Full capture path with body/header recording.
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
import aiohttp
|
|
51
|
+
except:
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
skip = domains_to_not_propagate_headers_to or []
|
|
55
|
+
preload_active = _tee_preload_active()
|
|
56
|
+
|
|
57
|
+
# Initialize C extension for ultra-fast header checking (if available)
|
|
58
|
+
if preload_active:
|
|
59
|
+
init_fast_header_check(skip)
|
|
60
|
+
|
|
61
|
+
if preload_active:
|
|
62
|
+
# ========== ULTRA-FAST PATH: Direct wrapt on _request (bypass TraceConfig overhead!) ==========
|
|
63
|
+
# TraceConfig adds 15-20% overhead, so we patch _request directly like OTEL does for other libraries
|
|
64
|
+
|
|
65
|
+
if HAS_WRAPT:
|
|
66
|
+
# FASTEST: Use wrapt directly on _request method (OTEL-style for minimal overhead)
|
|
67
|
+
async def instrumented_request(wrapped, instance, args, kwargs):
|
|
68
|
+
"""
|
|
69
|
+
Ultra-fast header injection using thread-local cache.
|
|
70
|
+
Bypasses TraceConfig machinery for <5% overhead.
|
|
71
|
+
"""
|
|
72
|
+
# Extract verb and URL from args
|
|
73
|
+
verb_name = args[0] if args else kwargs.get("method", "GET")
|
|
74
|
+
url = str(args[1] if len(args) > 1 else kwargs.get("url", ""))
|
|
75
|
+
|
|
76
|
+
# Direct header mutation (no copy!)
|
|
77
|
+
headers = kwargs.get("headers")
|
|
78
|
+
if headers is None:
|
|
79
|
+
headers = {}
|
|
80
|
+
kwargs["headers"] = headers
|
|
81
|
+
|
|
82
|
+
# CRITICAL: Skip if already injected (prevents double injection)
|
|
83
|
+
if SAILFISH_TRACING_HEADER not in headers:
|
|
84
|
+
# ULTRA-FAST: Thread-local cache + direct ContextVar.get() (<100ns!)
|
|
85
|
+
inject_headers_ultrafast(headers, url, skip)
|
|
86
|
+
|
|
87
|
+
# NO timing, NO capture, NO threads - immediate return!
|
|
88
|
+
return await wrapped(*args, **kwargs)
|
|
89
|
+
|
|
90
|
+
wrapt.wrap_function_wrapper(
|
|
91
|
+
aiohttp.ClientSession, "_request", instrumented_request
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
else:
|
|
95
|
+
# Fallback: Direct patching if wrapt not available
|
|
96
|
+
orig_request = aiohttp.ClientSession._request
|
|
97
|
+
|
|
98
|
+
async def patched_request(self, verb_name: str, url: Any, **kwargs):
|
|
99
|
+
"""Ultra-fast header injection without wrapt."""
|
|
100
|
+
headers = kwargs.get("headers")
|
|
101
|
+
if headers is None:
|
|
102
|
+
headers = {}
|
|
103
|
+
kwargs["headers"] = headers
|
|
104
|
+
|
|
105
|
+
# CRITICAL: Skip if already injected (prevents double injection)
|
|
106
|
+
if SAILFISH_TRACING_HEADER not in headers:
|
|
107
|
+
# ULTRA-FAST: Thread-local cache + direct ContextVar.get() (<100ns!)
|
|
108
|
+
inject_headers_ultrafast(headers, str(url), skip)
|
|
109
|
+
|
|
110
|
+
# NO timing, NO capture - immediate return!
|
|
111
|
+
return await orig_request(self, verb_name, url, **kwargs)
|
|
112
|
+
|
|
113
|
+
aiohttp.ClientSession._request = patched_request
|
|
114
|
+
|
|
115
|
+
else:
|
|
116
|
+
# ========== FULL CAPTURE PATH: When LD_PRELOAD is NOT active ==========
|
|
117
|
+
# Uses wrapper function to capture request/response data
|
|
118
|
+
orig_request = aiohttp.ClientSession._request
|
|
119
|
+
|
|
120
|
+
async def patched_request(self, verb_name: str, url: Any, **kwargs):
|
|
121
|
+
headers = kwargs.get("headers", {}) or {}
|
|
122
|
+
if not isinstance(headers, dict):
|
|
123
|
+
headers = dict(headers)
|
|
124
|
+
|
|
125
|
+
# CRITICAL: Skip if already injected (prevents double injection)
|
|
126
|
+
if SAILFISH_TRACING_HEADER not in headers:
|
|
127
|
+
# ULTRA-FAST: Thread-local cache + direct ContextVar.get() (<100ns!)
|
|
128
|
+
inject_headers_ultrafast(headers, str(url), skip)
|
|
129
|
+
|
|
130
|
+
kwargs["headers"] = headers
|
|
131
|
+
|
|
132
|
+
# Get trace_id for network recording (after injection)
|
|
133
|
+
trace_id = trace_id_ctx.get(None) or ""
|
|
134
|
+
|
|
135
|
+
# SLOW PATH: LD_PRELOAD not active, do full Python-level capture
|
|
136
|
+
# Capture request data as bytes - BEFORE request
|
|
137
|
+
req_data = b""
|
|
138
|
+
req_headers = b""
|
|
139
|
+
try:
|
|
140
|
+
if "json" in kwargs:
|
|
141
|
+
if HAS_ORJSON:
|
|
142
|
+
req_data = orjson.dumps(kwargs["json"])
|
|
143
|
+
else:
|
|
144
|
+
req_data = json.dumps(kwargs["json"]).encode("utf-8")
|
|
145
|
+
elif "data" in kwargs:
|
|
146
|
+
data = kwargs["data"]
|
|
147
|
+
if isinstance(data, bytes):
|
|
148
|
+
req_data = data
|
|
149
|
+
elif isinstance(data, str):
|
|
150
|
+
req_data = data.encode("utf-8")
|
|
151
|
+
|
|
152
|
+
# Capture request headers
|
|
153
|
+
if HAS_ORJSON:
|
|
154
|
+
# Convert keys to str (aiohttp may use istr which orjson doesn't accept)
|
|
155
|
+
req_headers = orjson.dumps({str(k): str(v) for k, v in headers.items()})
|
|
156
|
+
else:
|
|
157
|
+
req_headers = json.dumps({str(k): str(v) for k, v in headers.items()}).encode("utf-8")
|
|
158
|
+
except Exception: # noqa: BLE001
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
# 2) Perform & time the request
|
|
162
|
+
start = int(time.time() * 1_000)
|
|
163
|
+
response = await orig_request(self, verb_name, url, **kwargs)
|
|
164
|
+
end = int(time.time() * 1_000)
|
|
165
|
+
|
|
166
|
+
# 3) Capture response metadata immediately (before response can be closed)
|
|
167
|
+
status = getattr(response, "status", 0)
|
|
168
|
+
ok = status < 400
|
|
169
|
+
|
|
170
|
+
# Capture response headers immediately (cheap and safe)
|
|
171
|
+
resp_headers = b""
|
|
172
|
+
if HAS_ORJSON:
|
|
173
|
+
# Convert keys to str (aiohttp uses istr which orjson doesn't accept)
|
|
174
|
+
resp_headers = orjson.dumps({str(k): str(v) for k, v in response.headers.items()})
|
|
175
|
+
else:
|
|
176
|
+
resp_headers = json.dumps({str(k): str(v) for k, v in response.headers.items()}).encode("utf-8")
|
|
177
|
+
|
|
178
|
+
# Send to C extension immediately - no background task needed!
|
|
179
|
+
record_network_request(
|
|
180
|
+
trace_id,
|
|
181
|
+
str(url),
|
|
182
|
+
verb_name.upper(),
|
|
183
|
+
status,
|
|
184
|
+
ok,
|
|
185
|
+
None,
|
|
186
|
+
timestamp_start=start,
|
|
187
|
+
timestamp_end=end,
|
|
188
|
+
request_data=req_data,
|
|
189
|
+
response_data=b"", # Skip body to avoid consuming stream
|
|
190
|
+
request_headers=req_headers,
|
|
191
|
+
response_headers=resp_headers,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# CRITICAL: Return response immediately!
|
|
195
|
+
return response
|
|
196
|
+
|
|
197
|
+
# Apply the wrapper to ClientSession._request
|
|
198
|
+
aiohttp.ClientSession._request = patched_request
|
|
199
|
+
|
|
200
|
+
# 2) Also patch the module-level aiohttp.request coroutine (for full-capture path)
|
|
201
|
+
orig_module_request = getattr(aiohttp, "request", None)
|
|
202
|
+
if orig_module_request:
|
|
203
|
+
|
|
204
|
+
async def patched_module_request(verb_name: str, url: str, **kwargs):
|
|
205
|
+
headers = kwargs.get("headers", {}) or {}
|
|
206
|
+
if not isinstance(headers, dict):
|
|
207
|
+
headers = dict(headers)
|
|
208
|
+
|
|
209
|
+
# CRITICAL: Skip if already injected (prevents double injection)
|
|
210
|
+
if SAILFISH_TRACING_HEADER not in headers:
|
|
211
|
+
# ULTRA-FAST: Thread-local cache + direct ContextVar.get() (<100ns!)
|
|
212
|
+
inject_headers_ultrafast(headers, str(url), skip)
|
|
213
|
+
|
|
214
|
+
kwargs["headers"] = headers
|
|
215
|
+
|
|
216
|
+
# Get trace_id for network recording (after injection)
|
|
217
|
+
trace_id = trace_id_ctx.get(None) or ""
|
|
218
|
+
|
|
219
|
+
# SLOW PATH: LD_PRELOAD not active, do full Python-level capture
|
|
220
|
+
# Capture request data as bytes - BEFORE request
|
|
221
|
+
req_data = b""
|
|
222
|
+
req_headers = b""
|
|
223
|
+
try:
|
|
224
|
+
if "json" in kwargs:
|
|
225
|
+
if HAS_ORJSON:
|
|
226
|
+
req_data = orjson.dumps(kwargs["json"])
|
|
227
|
+
else:
|
|
228
|
+
req_data = json.dumps(kwargs["json"]).encode("utf-8")
|
|
229
|
+
elif "data" in kwargs:
|
|
230
|
+
data = kwargs["data"]
|
|
231
|
+
if isinstance(data, bytes):
|
|
232
|
+
req_data = data
|
|
233
|
+
elif isinstance(data, str):
|
|
234
|
+
req_data = data.encode("utf-8")
|
|
235
|
+
|
|
236
|
+
# Capture request headers
|
|
237
|
+
if HAS_ORJSON:
|
|
238
|
+
# Convert keys to str (aiohttp may use istr which orjson doesn't accept)
|
|
239
|
+
req_headers = orjson.dumps({str(k): str(v) for k, v in headers.items()})
|
|
240
|
+
else:
|
|
241
|
+
req_headers = json.dumps({str(k): str(v) for k, v in headers.items()}).encode("utf-8")
|
|
242
|
+
except Exception: # noqa: BLE001
|
|
243
|
+
pass
|
|
244
|
+
|
|
245
|
+
start = int(time.time() * 1_000)
|
|
246
|
+
response = await orig_module_request(verb_name, url, **kwargs)
|
|
247
|
+
end = int(time.time() * 1_000)
|
|
248
|
+
|
|
249
|
+
status = getattr(
|
|
250
|
+
response, "status", getattr(response, "status_code", 0)
|
|
251
|
+
)
|
|
252
|
+
ok = status < 400
|
|
253
|
+
|
|
254
|
+
# Capture response headers immediately (cheap and safe)
|
|
255
|
+
resp_headers = b""
|
|
256
|
+
if HAS_ORJSON:
|
|
257
|
+
resp_headers = orjson.dumps(dict(response.headers))
|
|
258
|
+
else:
|
|
259
|
+
resp_headers = json.dumps(dict(response.headers)).encode("utf-8")
|
|
260
|
+
|
|
261
|
+
# Send to C extension immediately - no background task needed!
|
|
262
|
+
record_network_request(
|
|
263
|
+
trace_id,
|
|
264
|
+
str(url),
|
|
265
|
+
verb_name.upper(),
|
|
266
|
+
status,
|
|
267
|
+
ok,
|
|
268
|
+
None,
|
|
269
|
+
timestamp_start=start,
|
|
270
|
+
timestamp_end=end,
|
|
271
|
+
request_data=req_data,
|
|
272
|
+
response_data=b"", # Skip body to avoid consuming stream
|
|
273
|
+
request_headers=req_headers,
|
|
274
|
+
response_headers=resp_headers,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# CRITICAL: Return response immediately!
|
|
278
|
+
return response
|
|
279
|
+
|
|
280
|
+
# Apply the wrapper to module-level request function
|
|
281
|
+
aiohttp.request = patched_module_request
|