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,341 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Monkey-patch Tornado's HTTP clients so that
|
|
3
|
+
|
|
4
|
+
• Every outbound request carries SAILFISH_TRACING_HEADER
|
|
5
|
+
(unless the destination host is excluded).
|
|
6
|
+
• Every request – success or failure – triggers record_network_request(…).
|
|
7
|
+
|
|
8
|
+
Covers
|
|
9
|
+
• tornado.httpclient.AsyncHTTPClient.fetch (await-able)
|
|
10
|
+
• tornado.httpclient.HTTPClient.fetch (blocking/sync)
|
|
11
|
+
Safe to call repeatedly; patches only once per process.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import time
|
|
18
|
+
from typing import List, Optional, Tuple
|
|
19
|
+
|
|
20
|
+
from ...thread_local import trace_id_ctx
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
import wrapt
|
|
24
|
+
|
|
25
|
+
HAS_WRAPT = True
|
|
26
|
+
except ImportError:
|
|
27
|
+
HAS_WRAPT = False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# JSON serialization - try fast orjson first, fallback to stdlib json
|
|
31
|
+
try:
|
|
32
|
+
import orjson
|
|
33
|
+
|
|
34
|
+
HAS_ORJSON = True
|
|
35
|
+
except ImportError:
|
|
36
|
+
import json
|
|
37
|
+
|
|
38
|
+
HAS_ORJSON = False
|
|
39
|
+
|
|
40
|
+
from .utils import (
|
|
41
|
+
init_fast_header_check,
|
|
42
|
+
inject_headers_ultrafast,
|
|
43
|
+
record_network_request,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _tee_preload_active() -> bool:
|
|
48
|
+
"""Detect if LD_PRELOAD tee is active (same logic as http_client.py)."""
|
|
49
|
+
if os.getenv("SF_TEE_PRELOAD_ONLY", "0") == "1":
|
|
50
|
+
return True
|
|
51
|
+
ld = os.getenv("LD_PRELOAD", "")
|
|
52
|
+
return "libsfnettee.so" in ld or "_sfteepreload" in ld
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def patch_tornado(domains_to_not_propagate_headers_to: Optional[List[str]] = None):
|
|
56
|
+
try:
|
|
57
|
+
# Tornado is optional; exit silently if missing
|
|
58
|
+
from tornado.httpclient import AsyncHTTPClient, HTTPClient, HTTPRequest
|
|
59
|
+
except ImportError:
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
exclude: List[str] = domains_to_not_propagate_headers_to or []
|
|
63
|
+
preload_active = _tee_preload_active()
|
|
64
|
+
|
|
65
|
+
# Initialize C extension for ultra-fast header checking (if available)
|
|
66
|
+
if preload_active:
|
|
67
|
+
init_fast_header_check(exclude)
|
|
68
|
+
|
|
69
|
+
# ------------------------------------------------------------------ #
|
|
70
|
+
# Helpers shared by sync & async wrappers
|
|
71
|
+
# ------------------------------------------------------------------ #
|
|
72
|
+
def _resolve(
|
|
73
|
+
req_or_url, kwargs
|
|
74
|
+
) -> Tuple[str, str, dict]: # → (url, METHOD, headers_dict)
|
|
75
|
+
"""
|
|
76
|
+
Handle both call styles:
|
|
77
|
+
|
|
78
|
+
client.fetch("https://foo", method="POST", headers={...})
|
|
79
|
+
client.fetch(HTTPRequest(...))
|
|
80
|
+
|
|
81
|
+
Always returns a mutable *headers* dict.
|
|
82
|
+
"""
|
|
83
|
+
if isinstance(req_or_url, HTTPRequest):
|
|
84
|
+
url = req_or_url.url
|
|
85
|
+
method = (req_or_url.method or "GET").upper()
|
|
86
|
+
hdrs = dict(req_or_url.headers or {})
|
|
87
|
+
else:
|
|
88
|
+
url = str(req_or_url)
|
|
89
|
+
method = kwargs.get("method", "GET").upper()
|
|
90
|
+
hdrs = dict(kwargs.get("headers", {}) or {})
|
|
91
|
+
return url, method, hdrs
|
|
92
|
+
|
|
93
|
+
def _inject(
|
|
94
|
+
req_or_url, kwargs, hdrs: dict
|
|
95
|
+
): # mutate request object *or* kwargs to carry hdrs
|
|
96
|
+
if isinstance(req_or_url, HTTPRequest):
|
|
97
|
+
req_or_url.headers = hdrs
|
|
98
|
+
else:
|
|
99
|
+
kwargs["headers"] = hdrs
|
|
100
|
+
return req_or_url, kwargs
|
|
101
|
+
|
|
102
|
+
# Unified _prepare function for both fast and slow paths
|
|
103
|
+
def _prepare(url: str, hdrs: dict):
|
|
104
|
+
"""Return (trace_id, merged_headers, start_ms). Ultra-fast header injection."""
|
|
105
|
+
# Make a copy for mutation
|
|
106
|
+
out = dict(hdrs)
|
|
107
|
+
|
|
108
|
+
# ULTRA-FAST: inject_headers_ultrafast does domain filtering + header injection (~100ns)
|
|
109
|
+
inject_headers_ultrafast(out, url, exclude)
|
|
110
|
+
|
|
111
|
+
# Get trace_id for capture (only used in slow path)
|
|
112
|
+
if not preload_active:
|
|
113
|
+
trace_id = trace_id_ctx.get(None) or ""
|
|
114
|
+
else:
|
|
115
|
+
trace_id = "" # Not needed for fast path
|
|
116
|
+
|
|
117
|
+
return trace_id, out, int(time.time() * 1_000)
|
|
118
|
+
|
|
119
|
+
# ------------------------------------------------------------------ #
|
|
120
|
+
# AsyncHTTPClient.fetch wrapper
|
|
121
|
+
# ------------------------------------------------------------------ #
|
|
122
|
+
if preload_active:
|
|
123
|
+
# ========== ULTRA-FAST PATH: When LD_PRELOAD is active ==========
|
|
124
|
+
if HAS_WRAPT:
|
|
125
|
+
# FASTEST: Use wrapt directly (OTEL-style for minimal overhead)
|
|
126
|
+
async def instrumented_async_fetch(wrapped, instance, args, kwargs):
|
|
127
|
+
"""Ultra-fast header injection using C extension via wrapt."""
|
|
128
|
+
req_or_url = args[0] if len(args) > 0 else kwargs.get("request", "")
|
|
129
|
+
url, method, hdrs_cur = _resolve(req_or_url, kwargs)
|
|
130
|
+
trace_id, hdrs_new, t0 = _prepare(url, hdrs_cur)
|
|
131
|
+
|
|
132
|
+
# Inject headers back into args/kwargs
|
|
133
|
+
if len(args) > 0:
|
|
134
|
+
args = (_inject(args[0], kwargs, hdrs_new)[0],) + args[1:]
|
|
135
|
+
else:
|
|
136
|
+
req_or_url, kwargs = _inject(req_or_url, kwargs, hdrs_new)
|
|
137
|
+
|
|
138
|
+
# NO capture, NO timing, NO record - immediate return!
|
|
139
|
+
return await wrapped(*args, **kwargs)
|
|
140
|
+
|
|
141
|
+
wrapt.wrap_function_wrapper(
|
|
142
|
+
"tornado.httpclient", "AsyncHTTPClient.fetch", instrumented_async_fetch
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
# Fallback: Direct patching if wrapt not available
|
|
146
|
+
original_async_fetch = AsyncHTTPClient.fetch
|
|
147
|
+
|
|
148
|
+
async def patched_async_fetch(self, req_or_url, *args, **kwargs):
|
|
149
|
+
url, method, hdrs_cur = _resolve(req_or_url, kwargs)
|
|
150
|
+
trace_id, hdrs_new, t0 = _prepare(url, hdrs_cur)
|
|
151
|
+
req_or_url, kwargs = _inject(req_or_url, kwargs, hdrs_new)
|
|
152
|
+
|
|
153
|
+
# NO capture, NO timing, NO record - immediate return!
|
|
154
|
+
return await original_async_fetch(self, req_or_url, *args, **kwargs)
|
|
155
|
+
|
|
156
|
+
AsyncHTTPClient.fetch = patched_async_fetch # type: ignore[assignment]
|
|
157
|
+
|
|
158
|
+
else:
|
|
159
|
+
# ========== FULL CAPTURE PATH: When LD_PRELOAD is NOT active ==========
|
|
160
|
+
async def patched_async_fetch(self, req_or_url, *args, **kwargs):
|
|
161
|
+
url, method, hdrs_cur = _resolve(req_or_url, kwargs)
|
|
162
|
+
trace_id, hdrs_new, t0 = _prepare(url, hdrs_cur)
|
|
163
|
+
req_or_url, kwargs = _inject(req_or_url, kwargs, hdrs_new)
|
|
164
|
+
|
|
165
|
+
# Capture request data
|
|
166
|
+
req_data = b""
|
|
167
|
+
req_headers = b""
|
|
168
|
+
try:
|
|
169
|
+
body = None
|
|
170
|
+
if isinstance(req_or_url, HTTPRequest):
|
|
171
|
+
body = req_or_url.body
|
|
172
|
+
else:
|
|
173
|
+
body = kwargs.get("body")
|
|
174
|
+
|
|
175
|
+
if body:
|
|
176
|
+
if isinstance(body, bytes):
|
|
177
|
+
req_data = body
|
|
178
|
+
elif isinstance(body, str):
|
|
179
|
+
req_data = body.encode("utf-8")
|
|
180
|
+
|
|
181
|
+
# Capture request headers
|
|
182
|
+
if HAS_ORJSON:
|
|
183
|
+
req_headers = orjson.dumps({str(k): str(v) for k, v in hdrs_new.items()})
|
|
184
|
+
else:
|
|
185
|
+
req_headers = json.dumps({str(k): str(v) for k, v in hdrs_new.items()}).encode("utf-8")
|
|
186
|
+
except Exception: # noqa: BLE001
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
status, success, err = 0, False, None
|
|
190
|
+
resp_data = b""
|
|
191
|
+
resp_headers = b""
|
|
192
|
+
try:
|
|
193
|
+
resp = await original_async_fetch(self, req_or_url, *args, **kwargs)
|
|
194
|
+
status = getattr(resp, "code", 0)
|
|
195
|
+
success = status < 400
|
|
196
|
+
|
|
197
|
+
# Capture response data
|
|
198
|
+
try:
|
|
199
|
+
resp_data = getattr(resp, "body", b"")
|
|
200
|
+
# Capture response headers
|
|
201
|
+
if HAS_ORJSON:
|
|
202
|
+
resp_headers = orjson.dumps({str(k): str(v) for k, v in resp.headers.items()})
|
|
203
|
+
else:
|
|
204
|
+
resp_headers = json.dumps({str(k): str(v) for k, v in resp.headers.items()}).encode("utf-8")
|
|
205
|
+
except Exception: # noqa: BLE001
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
return resp
|
|
209
|
+
except Exception as exc: # noqa: BLE001
|
|
210
|
+
err = str(exc)[:255]
|
|
211
|
+
raise
|
|
212
|
+
finally:
|
|
213
|
+
record_network_request(
|
|
214
|
+
trace_id,
|
|
215
|
+
url,
|
|
216
|
+
method,
|
|
217
|
+
status,
|
|
218
|
+
success,
|
|
219
|
+
err,
|
|
220
|
+
timestamp_start=t0,
|
|
221
|
+
timestamp_end=int(time.time() * 1_000),
|
|
222
|
+
request_data=req_data,
|
|
223
|
+
response_data=resp_data,
|
|
224
|
+
request_headers=req_headers,
|
|
225
|
+
response_headers=resp_headers,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if not HAS_WRAPT:
|
|
229
|
+
AsyncHTTPClient.fetch = patched_async_fetch # type: ignore[assignment]
|
|
230
|
+
|
|
231
|
+
# ------------------------------------------------------------------ #
|
|
232
|
+
# HTTPClient.fetch wrapper (blocking)
|
|
233
|
+
# ------------------------------------------------------------------ #
|
|
234
|
+
if preload_active:
|
|
235
|
+
# ========== ULTRA-FAST PATH: When LD_PRELOAD is active ==========
|
|
236
|
+
if HAS_WRAPT:
|
|
237
|
+
# FASTEST: Use wrapt directly (OTEL-style for minimal overhead)
|
|
238
|
+
def instrumented_sync_fetch(wrapped, instance, args, kwargs):
|
|
239
|
+
"""Ultra-fast header injection using C extension via wrapt."""
|
|
240
|
+
req_or_url = args[0] if len(args) > 0 else kwargs.get("request", "")
|
|
241
|
+
url, method, hdrs_cur = _resolve(req_or_url, kwargs)
|
|
242
|
+
trace_id, hdrs_new, t0 = _prepare(url, hdrs_cur)
|
|
243
|
+
|
|
244
|
+
# Inject headers back into args/kwargs
|
|
245
|
+
if len(args) > 0:
|
|
246
|
+
args = (_inject(args[0], kwargs, hdrs_new)[0],) + args[1:]
|
|
247
|
+
else:
|
|
248
|
+
req_or_url, kwargs = _inject(req_or_url, kwargs, hdrs_new)
|
|
249
|
+
|
|
250
|
+
# NO capture, NO timing, NO record - immediate return!
|
|
251
|
+
return wrapped(*args, **kwargs)
|
|
252
|
+
|
|
253
|
+
wrapt.wrap_function_wrapper(
|
|
254
|
+
"tornado.httpclient", "HTTPClient.fetch", instrumented_sync_fetch
|
|
255
|
+
)
|
|
256
|
+
else:
|
|
257
|
+
# Fallback: Direct patching if wrapt not available
|
|
258
|
+
original_sync_fetch = HTTPClient.fetch
|
|
259
|
+
|
|
260
|
+
def patched_sync_fetch(self, req_or_url, *args, **kwargs):
|
|
261
|
+
url, method, hdrs_cur = _resolve(req_or_url, kwargs)
|
|
262
|
+
trace_id, hdrs_new, t0 = _prepare(url, hdrs_cur)
|
|
263
|
+
req_or_url, kwargs = _inject(req_or_url, kwargs, hdrs_new)
|
|
264
|
+
|
|
265
|
+
# NO capture, NO timing, NO record - immediate return!
|
|
266
|
+
return original_sync_fetch(self, req_or_url, *args, **kwargs)
|
|
267
|
+
|
|
268
|
+
HTTPClient.fetch = patched_sync_fetch # type: ignore[assignment]
|
|
269
|
+
|
|
270
|
+
else:
|
|
271
|
+
# ========== FULL CAPTURE PATH: When LD_PRELOAD is NOT active ==========
|
|
272
|
+
def patched_sync_fetch(self, req_or_url, *args, **kwargs):
|
|
273
|
+
url, method, hdrs_cur = _resolve(req_or_url, kwargs)
|
|
274
|
+
trace_id, hdrs_new, t0 = _prepare(url, hdrs_cur)
|
|
275
|
+
req_or_url, kwargs = _inject(req_or_url, kwargs, hdrs_new)
|
|
276
|
+
|
|
277
|
+
# Capture request data
|
|
278
|
+
req_data = b""
|
|
279
|
+
req_headers = b""
|
|
280
|
+
try:
|
|
281
|
+
body = None
|
|
282
|
+
if isinstance(req_or_url, HTTPRequest):
|
|
283
|
+
body = req_or_url.body
|
|
284
|
+
else:
|
|
285
|
+
body = kwargs.get("body")
|
|
286
|
+
|
|
287
|
+
if body:
|
|
288
|
+
if isinstance(body, bytes):
|
|
289
|
+
req_data = body
|
|
290
|
+
elif isinstance(body, str):
|
|
291
|
+
req_data = body.encode("utf-8")
|
|
292
|
+
|
|
293
|
+
# Capture request headers
|
|
294
|
+
if HAS_ORJSON:
|
|
295
|
+
req_headers = orjson.dumps({str(k): str(v) for k, v in hdrs_new.items()})
|
|
296
|
+
else:
|
|
297
|
+
req_headers = json.dumps({str(k): str(v) for k, v in hdrs_new.items()}).encode("utf-8")
|
|
298
|
+
except Exception: # noqa: BLE001
|
|
299
|
+
pass
|
|
300
|
+
|
|
301
|
+
status, success, err = 0, False, None
|
|
302
|
+
resp_data = b""
|
|
303
|
+
resp_headers = b""
|
|
304
|
+
try:
|
|
305
|
+
resp = original_sync_fetch(self, req_or_url, *args, **kwargs)
|
|
306
|
+
status = getattr(resp, "code", 0)
|
|
307
|
+
success = status < 400
|
|
308
|
+
|
|
309
|
+
# Capture response data
|
|
310
|
+
try:
|
|
311
|
+
resp_data = getattr(resp, "body", b"")
|
|
312
|
+
# Capture response headers
|
|
313
|
+
if HAS_ORJSON:
|
|
314
|
+
resp_headers = orjson.dumps({str(k): str(v) for k, v in resp.headers.items()})
|
|
315
|
+
else:
|
|
316
|
+
resp_headers = json.dumps({str(k): str(v) for k, v in resp.headers.items()}).encode("utf-8")
|
|
317
|
+
except Exception: # noqa: BLE001
|
|
318
|
+
pass
|
|
319
|
+
|
|
320
|
+
return resp
|
|
321
|
+
except Exception as exc: # noqa: BLE001
|
|
322
|
+
err = str(exc)[:255]
|
|
323
|
+
raise
|
|
324
|
+
finally:
|
|
325
|
+
record_network_request(
|
|
326
|
+
trace_id,
|
|
327
|
+
url,
|
|
328
|
+
method,
|
|
329
|
+
status,
|
|
330
|
+
success,
|
|
331
|
+
err,
|
|
332
|
+
timestamp_start=t0,
|
|
333
|
+
timestamp_end=int(time.time() * 1_000),
|
|
334
|
+
request_data=req_data,
|
|
335
|
+
response_data=resp_data,
|
|
336
|
+
request_headers=req_headers,
|
|
337
|
+
response_headers=resp_headers,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
if not HAS_WRAPT:
|
|
341
|
+
HTTPClient.fetch = patched_sync_fetch # type: ignore[assignment]
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Header propagation + network-recording patch for **Treq**.
|
|
3
|
+
|
|
4
|
+
• Propagates SAILFISH_TRACING_HEADER (unless excluded destination).
|
|
5
|
+
• Records every outbound request via record_network_request(…).
|
|
6
|
+
|
|
7
|
+
It also guarantees that Twisted's reactor is *running*:
|
|
8
|
+
|
|
9
|
+
1. Prefer installing the asyncio reactor early.
|
|
10
|
+
2. If a different reactor is already installed, start it in a background thread
|
|
11
|
+
(if it isn't running yet), so Deferreds produced by treq will fire.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import os
|
|
18
|
+
import threading
|
|
19
|
+
import time
|
|
20
|
+
from typing import List, Optional
|
|
21
|
+
|
|
22
|
+
from ...thread_local import trace_id_ctx
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
import wrapt
|
|
26
|
+
|
|
27
|
+
HAS_WRAPT = True
|
|
28
|
+
except ImportError:
|
|
29
|
+
HAS_WRAPT = False
|
|
30
|
+
|
|
31
|
+
# JSON serialization - try fast orjson first, fallback to stdlib json
|
|
32
|
+
try:
|
|
33
|
+
import orjson
|
|
34
|
+
|
|
35
|
+
HAS_ORJSON = True
|
|
36
|
+
except ImportError:
|
|
37
|
+
import json
|
|
38
|
+
|
|
39
|
+
HAS_ORJSON = False
|
|
40
|
+
|
|
41
|
+
from ..constants import supported_network_verbs as verbs
|
|
42
|
+
from .utils import (
|
|
43
|
+
init_fast_header_check,
|
|
44
|
+
inject_headers_ultrafast,
|
|
45
|
+
record_network_request,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _tee_preload_active() -> bool:
|
|
50
|
+
"""Detect if LD_PRELOAD tee is active (same logic as http_client.py)."""
|
|
51
|
+
if os.getenv("SF_TEE_PRELOAD_ONLY", "0") == "1":
|
|
52
|
+
return True
|
|
53
|
+
ld = os.getenv("LD_PRELOAD", "")
|
|
54
|
+
return "libsfnettee.so" in ld or "_sfteepreload" in ld
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _ensure_reactor_running() -> None:
|
|
58
|
+
"""
|
|
59
|
+
• Try to replace Twisted's default reactor with the asyncio one.
|
|
60
|
+
• If that fails because a reactor is already installed, make sure the
|
|
61
|
+
existing reactor is *running* (start it in a daemon thread if needed).
|
|
62
|
+
|
|
63
|
+
NOTE: If the application has its own reactor startup logic, it should
|
|
64
|
+
start the reactor BEFORE importing treq to avoid conflicts.
|
|
65
|
+
"""
|
|
66
|
+
# Twisted import must be inside this function to avoid premature reactor load
|
|
67
|
+
from twisted.internet import reactor
|
|
68
|
+
|
|
69
|
+
# If reactor is already running, nothing to do
|
|
70
|
+
if reactor.running:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
from twisted.internet import asyncioreactor
|
|
75
|
+
|
|
76
|
+
# Already an asyncio reactor? -> nothing to do
|
|
77
|
+
if reactor.__class__.__module__ == "twisted.internet.asyncioreactor":
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
# Try upgrade to asyncio-reactor (will raise if another reactor in use)
|
|
81
|
+
asyncioreactor.install(asyncio.get_event_loop()) # type: ignore[arg-type]
|
|
82
|
+
return
|
|
83
|
+
except Exception:
|
|
84
|
+
# Could not swap reactors (already installed). Check if we need to start it.
|
|
85
|
+
# Use a more robust check to avoid race conditions
|
|
86
|
+
if not reactor.running and not getattr(reactor, "_started", False):
|
|
87
|
+
|
|
88
|
+
def _safe_reactor_run():
|
|
89
|
+
try:
|
|
90
|
+
reactor.run(installSignalHandlers=False)
|
|
91
|
+
except Exception:
|
|
92
|
+
# Reactor already running (race condition) - silently ignore
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
threading.Thread(
|
|
96
|
+
target=_safe_reactor_run,
|
|
97
|
+
daemon=True,
|
|
98
|
+
).start()
|
|
99
|
+
# Give reactor a moment to start
|
|
100
|
+
|
|
101
|
+
time.sleep(0.01)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def patch_treq(domains_to_not_propagate_headers_to: Optional[List[str]] = None):
|
|
105
|
+
try:
|
|
106
|
+
# Ensure a live reactor *before* importing treq
|
|
107
|
+
_ensure_reactor_running()
|
|
108
|
+
|
|
109
|
+
import treq
|
|
110
|
+
except ImportError:
|
|
111
|
+
return # treq is not installed; nothing to patch
|
|
112
|
+
|
|
113
|
+
exclude = domains_to_not_propagate_headers_to or []
|
|
114
|
+
preload_active = _tee_preload_active()
|
|
115
|
+
|
|
116
|
+
# Initialize C extension for ultra-fast header checking (if available)
|
|
117
|
+
if preload_active:
|
|
118
|
+
init_fast_header_check(exclude)
|
|
119
|
+
|
|
120
|
+
orig_request = treq.request
|
|
121
|
+
|
|
122
|
+
# ------------------------------------------------------------------ #
|
|
123
|
+
if preload_active:
|
|
124
|
+
# ========== ULTRA-FAST PATH: When LD_PRELOAD is active ==========
|
|
125
|
+
if HAS_WRAPT:
|
|
126
|
+
|
|
127
|
+
def instrumented_request(wrapped, instance, args, kwargs):
|
|
128
|
+
"""Ultra-fast header injection using inject_headers_ultrafast() via wrapt."""
|
|
129
|
+
# args = (method, url, ...), kwargs = {...}
|
|
130
|
+
url = args[1] if len(args) > 1 else kwargs.get("url", "")
|
|
131
|
+
|
|
132
|
+
# Get or create headers dict
|
|
133
|
+
hdrs = dict(kwargs.pop("headers", {}) or {})
|
|
134
|
+
|
|
135
|
+
# ULTRA-FAST: inject_headers_ultrafast does domain filtering + header injection (~100ns)
|
|
136
|
+
inject_headers_ultrafast(hdrs, url, exclude)
|
|
137
|
+
|
|
138
|
+
kwargs["headers"] = hdrs
|
|
139
|
+
|
|
140
|
+
# Immediately call original and return - NO timing, NO capture!
|
|
141
|
+
return wrapped(*args, **kwargs)
|
|
142
|
+
|
|
143
|
+
wrapt.wrap_function_wrapper("treq", "request", instrumented_request)
|
|
144
|
+
else:
|
|
145
|
+
# Fallback: Direct patching if wrapt not available
|
|
146
|
+
def patched_request_fast(method: str, url: str, **kwargs):
|
|
147
|
+
"""Ultra-fast header injection without wrapt."""
|
|
148
|
+
# Get or create headers dict
|
|
149
|
+
hdrs = dict(kwargs.pop("headers", {}) or {})
|
|
150
|
+
|
|
151
|
+
# ULTRA-FAST: inject_headers_ultrafast does domain filtering + header injection (~100ns)
|
|
152
|
+
inject_headers_ultrafast(hdrs, url, exclude)
|
|
153
|
+
|
|
154
|
+
kwargs["headers"] = hdrs
|
|
155
|
+
|
|
156
|
+
# Immediately call original and return - NO timing, NO capture!
|
|
157
|
+
return orig_request(method, url, **kwargs)
|
|
158
|
+
|
|
159
|
+
treq.request = patched_request_fast # type: ignore[assignment]
|
|
160
|
+
else:
|
|
161
|
+
# ========== FULL CAPTURE PATH: When LD_PRELOAD is NOT active ==========
|
|
162
|
+
def patched_request(method: str, url: str, **kwargs):
|
|
163
|
+
# -------- header injection
|
|
164
|
+
hdrs = dict(kwargs.pop("headers", {}) or {})
|
|
165
|
+
|
|
166
|
+
# ULTRA-FAST: inject_headers_ultrafast does domain filtering + header injection (~100ns)
|
|
167
|
+
inject_headers_ultrafast(hdrs, url, exclude)
|
|
168
|
+
|
|
169
|
+
kwargs["headers"] = hdrs
|
|
170
|
+
|
|
171
|
+
# Get trace_id for capture
|
|
172
|
+
trace_id = trace_id_ctx.get(None) or ""
|
|
173
|
+
|
|
174
|
+
# Capture request data
|
|
175
|
+
req_data = b""
|
|
176
|
+
req_headers = b""
|
|
177
|
+
try:
|
|
178
|
+
if "json" in kwargs:
|
|
179
|
+
if HAS_ORJSON:
|
|
180
|
+
req_data = orjson.dumps(kwargs["json"])
|
|
181
|
+
else:
|
|
182
|
+
req_data = json.dumps(kwargs["json"]).encode("utf-8")
|
|
183
|
+
elif "data" in kwargs:
|
|
184
|
+
data = kwargs["data"]
|
|
185
|
+
if isinstance(data, bytes):
|
|
186
|
+
req_data = data
|
|
187
|
+
elif isinstance(data, str):
|
|
188
|
+
req_data = data.encode("utf-8")
|
|
189
|
+
|
|
190
|
+
# Capture request headers
|
|
191
|
+
if HAS_ORJSON:
|
|
192
|
+
req_headers = orjson.dumps({str(k): str(v) for k, v in hdrs.items()})
|
|
193
|
+
else:
|
|
194
|
+
req_headers = json.dumps({str(k): str(v) for k, v in hdrs.items()}).encode("utf-8")
|
|
195
|
+
except Exception: # noqa: BLE001
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
t0 = int(time.time() * 1_000)
|
|
199
|
+
d = orig_request(method, url, **kwargs) # Deferred
|
|
200
|
+
|
|
201
|
+
# -------- record on success
|
|
202
|
+
def _ok(resp):
|
|
203
|
+
status = getattr(resp, "code", 0)
|
|
204
|
+
resp_headers = b""
|
|
205
|
+
|
|
206
|
+
# Capture response headers
|
|
207
|
+
try:
|
|
208
|
+
if HAS_ORJSON:
|
|
209
|
+
resp_headers = orjson.dumps(
|
|
210
|
+
dict(resp.headers.getAllRawHeaders())
|
|
211
|
+
)
|
|
212
|
+
else:
|
|
213
|
+
resp_headers = json.dumps(
|
|
214
|
+
dict(resp.headers.getAllRawHeaders())
|
|
215
|
+
).encode("utf-8")
|
|
216
|
+
except Exception: # noqa: BLE001
|
|
217
|
+
pass
|
|
218
|
+
|
|
219
|
+
# For treq, capturing response body is complex (async via Deferred)
|
|
220
|
+
# We'll record without body to keep it simple and non-blocking
|
|
221
|
+
# Only capture if LD_PRELOAD is NOT active (avoid duplicates)
|
|
222
|
+
if not preload_active:
|
|
223
|
+
record_network_request(
|
|
224
|
+
trace_id,
|
|
225
|
+
url,
|
|
226
|
+
method.upper(),
|
|
227
|
+
status,
|
|
228
|
+
status < 400,
|
|
229
|
+
None,
|
|
230
|
+
timestamp_start=t0,
|
|
231
|
+
timestamp_end=int(time.time() * 1_000),
|
|
232
|
+
request_data=req_data,
|
|
233
|
+
request_headers=req_headers,
|
|
234
|
+
response_headers=resp_headers,
|
|
235
|
+
)
|
|
236
|
+
return resp
|
|
237
|
+
|
|
238
|
+
# -------- record on failure
|
|
239
|
+
def _err(f):
|
|
240
|
+
# Only capture if LD_PRELOAD is NOT active (avoid duplicates)
|
|
241
|
+
if not preload_active:
|
|
242
|
+
record_network_request(
|
|
243
|
+
trace_id,
|
|
244
|
+
url,
|
|
245
|
+
method.upper(),
|
|
246
|
+
0,
|
|
247
|
+
False,
|
|
248
|
+
str(f.value)[:255],
|
|
249
|
+
timestamp_start=t0,
|
|
250
|
+
timestamp_end=int(time.time() * 1_000),
|
|
251
|
+
request_data=req_data,
|
|
252
|
+
request_headers=req_headers,
|
|
253
|
+
)
|
|
254
|
+
return f
|
|
255
|
+
|
|
256
|
+
d.addCallbacks(_ok, _err)
|
|
257
|
+
return d
|
|
258
|
+
|
|
259
|
+
treq.request = patched_request # type: ignore[assignment]
|
|
260
|
+
|
|
261
|
+
# Convenience verbs → reuse patched_request
|
|
262
|
+
def _verb_factory(v: str):
|
|
263
|
+
def _verb(url, **k):
|
|
264
|
+
return treq.request(v.upper(), url, **k)
|
|
265
|
+
|
|
266
|
+
_verb.__name__ = v
|
|
267
|
+
return _verb
|
|
268
|
+
|
|
269
|
+
for verb in verbs:
|
|
270
|
+
setattr(treq, verb, _verb_factory(verb))
|