sf-veritas 0.10.3__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.

Potentially problematic release.


This version of sf-veritas might be problematic. Click here for more details.

Files changed (132) hide show
  1. sf_veritas/__init__.py +20 -0
  2. sf_veritas/_sffastlog.c +889 -0
  3. sf_veritas/_sffastlog.cpython-314-x86_64-linux-gnu.so +0 -0
  4. sf_veritas/_sffastnet.c +924 -0
  5. sf_veritas/_sffastnet.cpython-314-x86_64-linux-gnu.so +0 -0
  6. sf_veritas/_sffastnetworkrequest.c +730 -0
  7. sf_veritas/_sffastnetworkrequest.cpython-314-x86_64-linux-gnu.so +0 -0
  8. sf_veritas/_sffuncspan.c +2155 -0
  9. sf_veritas/_sffuncspan.cpython-314-x86_64-linux-gnu.so +0 -0
  10. sf_veritas/_sffuncspan_config.c +617 -0
  11. sf_veritas/_sffuncspan_config.cpython-314-x86_64-linux-gnu.so +0 -0
  12. sf_veritas/_sfheadercheck.c +341 -0
  13. sf_veritas/_sfheadercheck.cpython-314-x86_64-linux-gnu.so +0 -0
  14. sf_veritas/_sfnetworkhop.c +1451 -0
  15. sf_veritas/_sfnetworkhop.cpython-314-x86_64-linux-gnu.so +0 -0
  16. sf_veritas/_sfservice.c +1175 -0
  17. sf_veritas/_sfservice.cpython-314-x86_64-linux-gnu.so +0 -0
  18. sf_veritas/_sfteepreload.c +5167 -0
  19. sf_veritas/app_config.py +49 -0
  20. sf_veritas/cli.py +336 -0
  21. sf_veritas/constants.py +10 -0
  22. sf_veritas/custom_excepthook.py +304 -0
  23. sf_veritas/custom_log_handler.py +129 -0
  24. sf_veritas/custom_output_wrapper.py +144 -0
  25. sf_veritas/custom_print.py +146 -0
  26. sf_veritas/django_app.py +5 -0
  27. sf_veritas/env_vars.py +186 -0
  28. sf_veritas/exception_handling_middleware.py +18 -0
  29. sf_veritas/exception_metaclass.py +69 -0
  30. sf_veritas/fast_frame_info.py +116 -0
  31. sf_veritas/fast_network_hop.py +293 -0
  32. sf_veritas/frame_tools.py +112 -0
  33. sf_veritas/funcspan_config_loader.py +556 -0
  34. sf_veritas/function_span_profiler.py +1174 -0
  35. sf_veritas/import_hook.py +62 -0
  36. sf_veritas/infra_details/__init__.py +3 -0
  37. sf_veritas/infra_details/get_infra_details.py +24 -0
  38. sf_veritas/infra_details/kubernetes/__init__.py +3 -0
  39. sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
  40. sf_veritas/infra_details/kubernetes/get_details.py +7 -0
  41. sf_veritas/infra_details/running_on/__init__.py +17 -0
  42. sf_veritas/infra_details/running_on/kubernetes.py +11 -0
  43. sf_veritas/interceptors.py +497 -0
  44. sf_veritas/libsfnettee.so +0 -0
  45. sf_veritas/local_env_detect.py +118 -0
  46. sf_veritas/package_metadata.py +6 -0
  47. sf_veritas/patches/__init__.py +0 -0
  48. sf_veritas/patches/concurrent_futures.py +19 -0
  49. sf_veritas/patches/constants.py +1 -0
  50. sf_veritas/patches/exceptions.py +82 -0
  51. sf_veritas/patches/multiprocessing.py +32 -0
  52. sf_veritas/patches/network_libraries/__init__.py +76 -0
  53. sf_veritas/patches/network_libraries/aiohttp.py +281 -0
  54. sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
  55. sf_veritas/patches/network_libraries/http_client.py +419 -0
  56. sf_veritas/patches/network_libraries/httpcore.py +515 -0
  57. sf_veritas/patches/network_libraries/httplib2.py +204 -0
  58. sf_veritas/patches/network_libraries/httpx.py +515 -0
  59. sf_veritas/patches/network_libraries/niquests.py +211 -0
  60. sf_veritas/patches/network_libraries/pycurl.py +385 -0
  61. sf_veritas/patches/network_libraries/requests.py +633 -0
  62. sf_veritas/patches/network_libraries/tornado.py +341 -0
  63. sf_veritas/patches/network_libraries/treq.py +270 -0
  64. sf_veritas/patches/network_libraries/urllib_request.py +468 -0
  65. sf_veritas/patches/network_libraries/utils.py +398 -0
  66. sf_veritas/patches/os.py +17 -0
  67. sf_veritas/patches/threading.py +218 -0
  68. sf_veritas/patches/web_frameworks/__init__.py +54 -0
  69. sf_veritas/patches/web_frameworks/aiohttp.py +793 -0
  70. sf_veritas/patches/web_frameworks/async_websocket_consumer.py +317 -0
  71. sf_veritas/patches/web_frameworks/blacksheep.py +527 -0
  72. sf_veritas/patches/web_frameworks/bottle.py +502 -0
  73. sf_veritas/patches/web_frameworks/cherrypy.py +678 -0
  74. sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
  75. sf_veritas/patches/web_frameworks/django.py +944 -0
  76. sf_veritas/patches/web_frameworks/eve.py +395 -0
  77. sf_veritas/patches/web_frameworks/falcon.py +926 -0
  78. sf_veritas/patches/web_frameworks/fastapi.py +724 -0
  79. sf_veritas/patches/web_frameworks/flask.py +520 -0
  80. sf_veritas/patches/web_frameworks/klein.py +501 -0
  81. sf_veritas/patches/web_frameworks/litestar.py +551 -0
  82. sf_veritas/patches/web_frameworks/pyramid.py +428 -0
  83. sf_veritas/patches/web_frameworks/quart.py +824 -0
  84. sf_veritas/patches/web_frameworks/robyn.py +697 -0
  85. sf_veritas/patches/web_frameworks/sanic.py +857 -0
  86. sf_veritas/patches/web_frameworks/starlette.py +723 -0
  87. sf_veritas/patches/web_frameworks/strawberry.py +813 -0
  88. sf_veritas/patches/web_frameworks/tornado.py +481 -0
  89. sf_veritas/patches/web_frameworks/utils.py +91 -0
  90. sf_veritas/print_override.py +13 -0
  91. sf_veritas/regular_data_transmitter.py +409 -0
  92. sf_veritas/request_interceptor.py +401 -0
  93. sf_veritas/request_utils.py +550 -0
  94. sf_veritas/server_status.py +1 -0
  95. sf_veritas/shutdown_flag.py +11 -0
  96. sf_veritas/subprocess_startup.py +3 -0
  97. sf_veritas/test_cli.py +145 -0
  98. sf_veritas/thread_local.py +970 -0
  99. sf_veritas/timeutil.py +114 -0
  100. sf_veritas/transmit_exception_to_sailfish.py +28 -0
  101. sf_veritas/transmitter.py +132 -0
  102. sf_veritas/types.py +47 -0
  103. sf_veritas/unified_interceptor.py +1580 -0
  104. sf_veritas/utils.py +39 -0
  105. sf_veritas-0.10.3.dist-info/METADATA +97 -0
  106. sf_veritas-0.10.3.dist-info/RECORD +132 -0
  107. sf_veritas-0.10.3.dist-info/WHEEL +5 -0
  108. sf_veritas-0.10.3.dist-info/entry_points.txt +2 -0
  109. sf_veritas-0.10.3.dist-info/top_level.txt +1 -0
  110. sf_veritas.libs/libbrotlicommon-6ce2a53c.so.1.0.6 +0 -0
  111. sf_veritas.libs/libbrotlidec-811d1be3.so.1.0.6 +0 -0
  112. sf_veritas.libs/libcom_err-730ca923.so.2.1 +0 -0
  113. sf_veritas.libs/libcrypt-52aca757.so.1.1.0 +0 -0
  114. sf_veritas.libs/libcrypto-bdaed0ea.so.1.1.1k +0 -0
  115. sf_veritas.libs/libcurl-eaa3cf66.so.4.5.0 +0 -0
  116. sf_veritas.libs/libgssapi_krb5-323bbd21.so.2.2 +0 -0
  117. sf_veritas.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
  118. sf_veritas.libs/libk5crypto-9a74ff38.so.3.1 +0 -0
  119. sf_veritas.libs/libkeyutils-2777d33d.so.1.6 +0 -0
  120. sf_veritas.libs/libkrb5-a55300e8.so.3.3 +0 -0
  121. sf_veritas.libs/libkrb5support-e6594cfc.so.0.1 +0 -0
  122. sf_veritas.libs/liblber-2-d20824ef.4.so.2.10.9 +0 -0
  123. sf_veritas.libs/libldap-2-cea2a960.4.so.2.10.9 +0 -0
  124. sf_veritas.libs/libnghttp2-39367a22.so.14.17.0 +0 -0
  125. sf_veritas.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
  126. sf_veritas.libs/libpsl-99becdd3.so.5.3.1 +0 -0
  127. sf_veritas.libs/libsasl2-7de4d792.so.3.0.0 +0 -0
  128. sf_veritas.libs/libselinux-d0805dcb.so.1 +0 -0
  129. sf_veritas.libs/libssh-c11d285b.so.4.8.7 +0 -0
  130. sf_veritas.libs/libssl-60250281.so.1.1.1k +0 -0
  131. sf_veritas.libs/libunistring-05abdd40.so.2.1.0 +0 -0
  132. 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