sf-veritas 0.10.3__cp310-cp310-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-310-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +924 -0
- sf_veritas/_sffastnet.cpython-310-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +730 -0
- sf_veritas/_sffastnetworkrequest.cpython-310-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2155 -0
- sf_veritas/_sffuncspan.cpython-310-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +617 -0
- sf_veritas/_sffuncspan_config.cpython-310-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfheadercheck.c +341 -0
- sf_veritas/_sfheadercheck.cpython-310-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfnetworkhop.c +1451 -0
- sf_veritas/_sfnetworkhop.cpython-310-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1175 -0
- sf_veritas/_sfservice.cpython-310-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,551 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Litestar ASGI framework patch for OTEL-style network hop capture.
|
|
3
|
+
Captures request/response headers and bodies when enabled via env vars.
|
|
4
|
+
Uses sys.setprofile tracer to reliably capture endpoint metadata.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from functools import lru_cache
|
|
9
|
+
from typing import Any, Callable, List, Optional, Set
|
|
10
|
+
|
|
11
|
+
from ... import app_config
|
|
12
|
+
from ...constants import (
|
|
13
|
+
FUNCSPAN_OVERRIDE_HEADER_BYTES,
|
|
14
|
+
SAILFISH_TRACING_HEADER,
|
|
15
|
+
SAILFISH_TRACING_HEADER_BYTES,
|
|
16
|
+
)
|
|
17
|
+
from ...custom_excepthook import custom_excepthook
|
|
18
|
+
from ...env_vars import (
|
|
19
|
+
SF_DEBUG,
|
|
20
|
+
SF_NETWORKHOP_CAPTURE_ENABLED,
|
|
21
|
+
SF_NETWORKHOP_CAPTURE_REQUEST_BODY,
|
|
22
|
+
SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS,
|
|
23
|
+
SF_NETWORKHOP_CAPTURE_RESPONSE_BODY,
|
|
24
|
+
SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS,
|
|
25
|
+
SF_NETWORKHOP_REQUEST_LIMIT_MB,
|
|
26
|
+
SF_NETWORKHOP_RESPONSE_LIMIT_MB,
|
|
27
|
+
)
|
|
28
|
+
from ...fast_network_hop import fast_send_network_hop_fast, register_endpoint
|
|
29
|
+
from ...thread_local import (
|
|
30
|
+
clear_c_tls_parent_trace_id,
|
|
31
|
+
clear_current_request_path,
|
|
32
|
+
clear_outbound_header_base,
|
|
33
|
+
clear_trace_id,
|
|
34
|
+
generate_new_trace_id,
|
|
35
|
+
get_or_set_sf_trace_id,
|
|
36
|
+
get_sf_trace_id,
|
|
37
|
+
set_current_request_path,
|
|
38
|
+
set_funcspan_override,
|
|
39
|
+
set_outbound_header_base,
|
|
40
|
+
)
|
|
41
|
+
from .cors_utils import inject_sailfish_headers, should_inject_headers
|
|
42
|
+
from .utils import _is_user_code, should_skip_route
|
|
43
|
+
|
|
44
|
+
# Size limits in bytes
|
|
45
|
+
_REQUEST_LIMIT_BYTES = SF_NETWORKHOP_REQUEST_LIMIT_MB * 1024 * 1024
|
|
46
|
+
_RESPONSE_LIMIT_BYTES = SF_NETWORKHOP_RESPONSE_LIMIT_MB * 1024 * 1024
|
|
47
|
+
|
|
48
|
+
# Pre-registered endpoint IDs
|
|
49
|
+
_ENDPOINT_REGISTRY: dict[tuple, int] = {}
|
|
50
|
+
|
|
51
|
+
# Routes to skip (set by patch_litestar)
|
|
52
|
+
_ROUTES_TO_SKIP = []
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _sf_tracing_factory(app: Callable) -> Callable:
|
|
56
|
+
"""
|
|
57
|
+
OTEL-STYLE ASGI middleware that:
|
|
58
|
+
• propagates the inbound SAILFISH_TRACING_HEADER header
|
|
59
|
+
• captures request headers/body if enabled
|
|
60
|
+
• uses sys.setprofile tracer to capture first user code frame
|
|
61
|
+
• emits NetworkHop AFTER response sent (zero overhead)
|
|
62
|
+
• captures response headers/body if enabled
|
|
63
|
+
• reports any unhandled exception via `custom_excepthook`
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
async def _middleware(scope, receive, send):
|
|
67
|
+
if scope.get("type") != "http":
|
|
68
|
+
await app(scope, receive, send)
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
# Set current request path for route-based suppression (SF_DISABLE_INBOUND_NETWORK_TRACING_ON_ROUTES)
|
|
72
|
+
request_path = scope.get("path", "")
|
|
73
|
+
set_current_request_path(request_path)
|
|
74
|
+
|
|
75
|
+
# Always print to verify middleware is being called
|
|
76
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
77
|
+
print(
|
|
78
|
+
f"[[Litestar._middleware]] HTTP request to {request_path}, type={scope.get('type')}",
|
|
79
|
+
log=False,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# PERFORMANCE: Single-pass bytes-level header scan (no dict allocation until needed)
|
|
83
|
+
# Scan headers once on bytes, only decode what we need, use latin-1 (fast 1:1 byte map)
|
|
84
|
+
hdr_tuples = scope.get("headers") or ()
|
|
85
|
+
incoming_trace_raw = None # bytes
|
|
86
|
+
funcspan_raw = None # bytes
|
|
87
|
+
req_headers = None # dict[str,str] only if capture enabled
|
|
88
|
+
|
|
89
|
+
capture_req_headers = SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS # local cache
|
|
90
|
+
|
|
91
|
+
if capture_req_headers:
|
|
92
|
+
# decode once using latin-1 (1:1 bytes, faster than utf-8 and never throws)
|
|
93
|
+
tmp = {}
|
|
94
|
+
for k, v in hdr_tuples:
|
|
95
|
+
kl = k.lower()
|
|
96
|
+
if kl == SAILFISH_TRACING_HEADER_BYTES:
|
|
97
|
+
incoming_trace_raw = v
|
|
98
|
+
elif kl == FUNCSPAN_OVERRIDE_HEADER_BYTES:
|
|
99
|
+
funcspan_raw = v
|
|
100
|
+
# build the dict while we're here
|
|
101
|
+
tmp[k.decode("latin-1")] = v.decode("latin-1")
|
|
102
|
+
req_headers = tmp
|
|
103
|
+
else:
|
|
104
|
+
for k, v in hdr_tuples:
|
|
105
|
+
kl = k.lower()
|
|
106
|
+
if kl == SAILFISH_TRACING_HEADER_BYTES:
|
|
107
|
+
incoming_trace_raw = v
|
|
108
|
+
elif kl == FUNCSPAN_OVERRIDE_HEADER_BYTES:
|
|
109
|
+
funcspan_raw = v
|
|
110
|
+
# no dict build
|
|
111
|
+
|
|
112
|
+
# CRITICAL: Seed/ensure trace_id immediately (BEFORE any outbound work)
|
|
113
|
+
if incoming_trace_raw:
|
|
114
|
+
# Incoming X-Sf3-Rid header provided - use it
|
|
115
|
+
incoming_trace = incoming_trace_raw.decode("latin-1")
|
|
116
|
+
get_or_set_sf_trace_id(
|
|
117
|
+
incoming_trace, is_associated_with_inbound_request=True
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
# No incoming X-Sf3-Rid header - generate fresh trace_id for this request
|
|
121
|
+
generate_new_trace_id()
|
|
122
|
+
|
|
123
|
+
# Optional funcspan override (decode only if present)
|
|
124
|
+
funcspan_override_header = (
|
|
125
|
+
funcspan_raw.decode("latin-1") if funcspan_raw else None
|
|
126
|
+
)
|
|
127
|
+
if funcspan_override_header:
|
|
128
|
+
try:
|
|
129
|
+
set_funcspan_override(funcspan_override_header)
|
|
130
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
131
|
+
print(
|
|
132
|
+
f"[[Litestar._middleware]] Set function span override from header: {funcspan_override_header}",
|
|
133
|
+
log=False,
|
|
134
|
+
)
|
|
135
|
+
except Exception as e:
|
|
136
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
137
|
+
print(
|
|
138
|
+
f"[[Litestar._middleware]] Failed to set function span override: {e}",
|
|
139
|
+
log=False,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Initialize outbound base without list/allocs from split()
|
|
143
|
+
try:
|
|
144
|
+
trace_id = get_sf_trace_id()
|
|
145
|
+
if trace_id:
|
|
146
|
+
s = str(trace_id)
|
|
147
|
+
i = s.find("/") # session
|
|
148
|
+
j = s.find("/", i + 1) if i != -1 else -1 # page
|
|
149
|
+
if j != -1:
|
|
150
|
+
base_trace = s[:j] # "session/page"
|
|
151
|
+
set_outbound_header_base(
|
|
152
|
+
base_trace=base_trace,
|
|
153
|
+
parent_trace_id=s, # "session/page/uuid"
|
|
154
|
+
funcspan=funcspan_override_header,
|
|
155
|
+
)
|
|
156
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
157
|
+
print(
|
|
158
|
+
f"[[Litestar._middleware]] Initialized outbound header base (base={base_trace[:16]}...)",
|
|
159
|
+
log=False,
|
|
160
|
+
)
|
|
161
|
+
except Exception as e:
|
|
162
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
163
|
+
print(
|
|
164
|
+
f"[[Litestar._middleware]] Failed to initialize outbound header base: {e}",
|
|
165
|
+
log=False,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# OPTIMIZATION: Skip ALL capture infrastructure if not capturing network hops
|
|
169
|
+
# We still needed to set up trace_id and outbound header base above (for outbound call tracing)
|
|
170
|
+
# but we can skip all request/response capture overhead
|
|
171
|
+
if not SF_NETWORKHOP_CAPTURE_ENABLED:
|
|
172
|
+
try:
|
|
173
|
+
await app(scope, receive, send)
|
|
174
|
+
except Exception as exc: # noqa: BLE001
|
|
175
|
+
custom_excepthook(type(exc), exc, exc.__traceback__)
|
|
176
|
+
raise
|
|
177
|
+
finally:
|
|
178
|
+
# CRITICAL: Clear C TLS to prevent stale data in thread pools
|
|
179
|
+
clear_c_tls_parent_trace_id()
|
|
180
|
+
|
|
181
|
+
# CRITICAL: Clear outbound header base to prevent stale cached headers
|
|
182
|
+
clear_outbound_header_base()
|
|
183
|
+
|
|
184
|
+
# CRITICAL: Clear trace_id to ensure fresh generation for next request
|
|
185
|
+
clear_trace_id()
|
|
186
|
+
|
|
187
|
+
# CRITICAL: Clear current request path to prevent stale data in thread pools
|
|
188
|
+
clear_current_request_path()
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
# NOTE: req_headers already captured in single-pass scan above (if enabled)
|
|
192
|
+
|
|
193
|
+
# 2. Capture request body if enabled (must intercept receive)
|
|
194
|
+
req_body_chunks = []
|
|
195
|
+
|
|
196
|
+
# OPTIMIZATION: Only wrap receive if we need to capture request body
|
|
197
|
+
if SF_NETWORKHOP_CAPTURE_REQUEST_BODY:
|
|
198
|
+
|
|
199
|
+
async def wrapped_receive():
|
|
200
|
+
message = await receive()
|
|
201
|
+
if message["type"] == "http.request":
|
|
202
|
+
body = message.get("body", b"")
|
|
203
|
+
if body:
|
|
204
|
+
req_body_chunks.append(body)
|
|
205
|
+
return message
|
|
206
|
+
|
|
207
|
+
else:
|
|
208
|
+
wrapped_receive = receive
|
|
209
|
+
|
|
210
|
+
# 3. Use sys.setprofile to capture first user code frame
|
|
211
|
+
endpoint_info = {}
|
|
212
|
+
previous_profiler = sys.getprofile()
|
|
213
|
+
|
|
214
|
+
def _tracer(frame, event, arg):
|
|
215
|
+
# Chain to previous profiler first
|
|
216
|
+
if previous_profiler is not None:
|
|
217
|
+
try:
|
|
218
|
+
previous_profiler(frame, event, arg)
|
|
219
|
+
except Exception: # noqa: BLE001 S110
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
# Capture first user code frame (skip Strawberry GraphQL handlers)
|
|
223
|
+
if event == "call" and _is_user_code(frame.f_code.co_filename):
|
|
224
|
+
if not endpoint_info: # Only capture once
|
|
225
|
+
# Skip Strawberry GraphQL handlers - they're handled by separate Strawberry extension
|
|
226
|
+
module_name = frame.f_globals.get("__name__", "")
|
|
227
|
+
if module_name.startswith("strawberry"):
|
|
228
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
229
|
+
print(
|
|
230
|
+
f"[[Litestar]] Skipping Strawberry GraphQL handler: {frame.f_code.co_name}",
|
|
231
|
+
log=False,
|
|
232
|
+
)
|
|
233
|
+
sys.setprofile(previous_profiler)
|
|
234
|
+
return _tracer
|
|
235
|
+
|
|
236
|
+
endpoint_info["filename"] = frame.f_code.co_filename
|
|
237
|
+
endpoint_info["line"] = frame.f_lineno
|
|
238
|
+
endpoint_info["name"] = frame.f_code.co_name
|
|
239
|
+
|
|
240
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
241
|
+
print(
|
|
242
|
+
f"[[Litestar]] Tracer captured endpoint: {frame.f_code.co_name} "
|
|
243
|
+
f"({frame.f_code.co_filename}:{frame.f_lineno})",
|
|
244
|
+
log=False,
|
|
245
|
+
)
|
|
246
|
+
# Restore previous profiler
|
|
247
|
+
sys.setprofile(previous_profiler)
|
|
248
|
+
return _tracer
|
|
249
|
+
|
|
250
|
+
sys.setprofile(_tracer)
|
|
251
|
+
|
|
252
|
+
# 4. Capture response headers and body if enabled
|
|
253
|
+
resp_headers = None
|
|
254
|
+
resp_body_chunks = []
|
|
255
|
+
|
|
256
|
+
# OPTIMIZATION: Cache debug flag check (avoid repeated lookups)
|
|
257
|
+
_debug_enabled = SF_DEBUG and app_config._interceptors_initialized
|
|
258
|
+
|
|
259
|
+
# OPTIMIZATION: Cache capture flags (avoid repeated global lookups in hot path)
|
|
260
|
+
_capture_resp_headers = SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS
|
|
261
|
+
_capture_resp_body = SF_NETWORKHOP_CAPTURE_RESPONSE_BODY
|
|
262
|
+
|
|
263
|
+
async def wrapped_send(message):
|
|
264
|
+
nonlocal resp_headers
|
|
265
|
+
|
|
266
|
+
# ULTRA-FAST PATH: Most messages just pass through without any processing
|
|
267
|
+
# Only http.response.body (final) triggers network hop collection
|
|
268
|
+
msg_type = message.get("type")
|
|
269
|
+
|
|
270
|
+
# FAST PATH: Early exit for non-body messages (http.response.start, etc.)
|
|
271
|
+
if msg_type != "http.response.body":
|
|
272
|
+
# Capture response headers if needed (only on http.response.start)
|
|
273
|
+
if _capture_resp_headers and msg_type == "http.response.start":
|
|
274
|
+
try:
|
|
275
|
+
resp_headers = {
|
|
276
|
+
k.decode(): v.decode()
|
|
277
|
+
for k, v in message.get("headers", [])
|
|
278
|
+
}
|
|
279
|
+
except Exception:
|
|
280
|
+
pass
|
|
281
|
+
await send(message)
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
# BODY PATH: Capture body chunks if needed
|
|
285
|
+
if _capture_resp_body:
|
|
286
|
+
body = message.get("body", b"")
|
|
287
|
+
if body:
|
|
288
|
+
resp_body_chunks.append(body)
|
|
289
|
+
|
|
290
|
+
# Send the actual message first
|
|
291
|
+
await send(message)
|
|
292
|
+
|
|
293
|
+
# OPTIMIZATION: Early exit if there's more body chunks coming
|
|
294
|
+
if message.get("more_body", False):
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
# 5. OTEL-STYLE: Emit network hop AFTER final response body sent
|
|
298
|
+
# Ensure profiler is restored
|
|
299
|
+
sys.setprofile(previous_profiler)
|
|
300
|
+
|
|
301
|
+
if endpoint_info:
|
|
302
|
+
try:
|
|
303
|
+
filename = endpoint_info["filename"]
|
|
304
|
+
line_no = endpoint_info["line"]
|
|
305
|
+
fn_name = endpoint_info["name"]
|
|
306
|
+
hop_key = (filename, line_no)
|
|
307
|
+
|
|
308
|
+
# Get route pattern if available
|
|
309
|
+
route_pattern = scope["path"]
|
|
310
|
+
|
|
311
|
+
# Check if route should be skipped
|
|
312
|
+
if route_pattern and should_skip_route(
|
|
313
|
+
route_pattern, _ROUTES_TO_SKIP
|
|
314
|
+
):
|
|
315
|
+
if _debug_enabled:
|
|
316
|
+
print(
|
|
317
|
+
f"[[Litestar]] Skipping endpoint (route matches skip pattern): {route_pattern}",
|
|
318
|
+
log=False,
|
|
319
|
+
)
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
# Get or register endpoint
|
|
323
|
+
endpoint_id = _ENDPOINT_REGISTRY.get(hop_key)
|
|
324
|
+
if endpoint_id is None:
|
|
325
|
+
endpoint_id = register_endpoint(
|
|
326
|
+
line=str(line_no),
|
|
327
|
+
column="0",
|
|
328
|
+
name=fn_name,
|
|
329
|
+
entrypoint=filename,
|
|
330
|
+
route=route_pattern,
|
|
331
|
+
)
|
|
332
|
+
if endpoint_id >= 0:
|
|
333
|
+
_ENDPOINT_REGISTRY[hop_key] = endpoint_id
|
|
334
|
+
if _debug_enabled:
|
|
335
|
+
print(
|
|
336
|
+
f"[[Litestar]] Registered endpoint: {fn_name} @ {filename}:{line_no} (id={endpoint_id})",
|
|
337
|
+
log=False,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
if endpoint_id is not None and endpoint_id >= 0:
|
|
341
|
+
# OPTIMIZATION: Use get_sf_trace_id() directly instead of get_or_set_sf_trace_id()
|
|
342
|
+
# Trace ID is GUARANTEED to be set at request start (lines 105-112)
|
|
343
|
+
# This saves ~11-12μs by avoiding tuple unpacking and conditional logic
|
|
344
|
+
session_id = get_sf_trace_id()
|
|
345
|
+
|
|
346
|
+
# OPTIMIZATION: Consolidate body chunks efficiently
|
|
347
|
+
req_body = None
|
|
348
|
+
if req_body_chunks:
|
|
349
|
+
joined = b"".join(req_body_chunks)
|
|
350
|
+
req_body = (
|
|
351
|
+
joined
|
|
352
|
+
if len(joined) <= _REQUEST_LIMIT_BYTES
|
|
353
|
+
else joined[:_REQUEST_LIMIT_BYTES]
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
resp_body = None
|
|
357
|
+
if resp_body_chunks:
|
|
358
|
+
joined = b"".join(resp_body_chunks)
|
|
359
|
+
resp_body = (
|
|
360
|
+
joined
|
|
361
|
+
if len(joined) <= _RESPONSE_LIMIT_BYTES
|
|
362
|
+
else joined[:_RESPONSE_LIMIT_BYTES]
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Direct C call - it queues to background worker, returns instantly
|
|
366
|
+
fast_send_network_hop_fast(
|
|
367
|
+
session_id=session_id,
|
|
368
|
+
endpoint_id=endpoint_id,
|
|
369
|
+
raw_path=scope["path"],
|
|
370
|
+
raw_query_string=scope["query_string"],
|
|
371
|
+
request_headers=req_headers,
|
|
372
|
+
request_body=req_body,
|
|
373
|
+
response_headers=resp_headers,
|
|
374
|
+
response_body=resp_body,
|
|
375
|
+
)
|
|
376
|
+
if _debug_enabled:
|
|
377
|
+
print(
|
|
378
|
+
f"[[Litestar]] Emitted NetworkHop for endpoint_id={endpoint_id}",
|
|
379
|
+
log=False,
|
|
380
|
+
)
|
|
381
|
+
except Exception as e: # noqa: BLE001 S110
|
|
382
|
+
if _debug_enabled:
|
|
383
|
+
print(
|
|
384
|
+
f"[[Litestar]] Failed to emit NetworkHop: {e}",
|
|
385
|
+
log=False,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# Exception capture
|
|
389
|
+
try:
|
|
390
|
+
await app(scope, wrapped_receive, wrapped_send)
|
|
391
|
+
except Exception as exc: # noqa: BLE001
|
|
392
|
+
# Ensure profiler is restored even on exception
|
|
393
|
+
sys.setprofile(previous_profiler)
|
|
394
|
+
custom_excepthook(type(exc), exc, exc.__traceback__)
|
|
395
|
+
raise
|
|
396
|
+
finally:
|
|
397
|
+
# Ensure profiler is always restored
|
|
398
|
+
sys.setprofile(previous_profiler)
|
|
399
|
+
|
|
400
|
+
# CRITICAL: Clear C TLS to prevent stale data in thread pools
|
|
401
|
+
clear_c_tls_parent_trace_id()
|
|
402
|
+
|
|
403
|
+
# CRITICAL: Clear outbound header base to prevent stale cached headers
|
|
404
|
+
# ContextVar does NOT automatically clean up in thread pools - must clear explicitly
|
|
405
|
+
clear_outbound_header_base()
|
|
406
|
+
|
|
407
|
+
# CRITICAL: Clear trace_id to ensure fresh generation for next request
|
|
408
|
+
# Without this, get_or_set_sf_trace_id() reuses trace_id from previous request
|
|
409
|
+
# causing X-Sf4-Prid to stay constant when no incoming X-Sf3-Rid header
|
|
410
|
+
clear_trace_id()
|
|
411
|
+
|
|
412
|
+
# CRITICAL: Clear current request path to prevent stale data in thread pools
|
|
413
|
+
clear_current_request_path()
|
|
414
|
+
|
|
415
|
+
return _middleware
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def patch_litestar(routes_to_skip: Optional[List[str]] = None) -> None:
|
|
419
|
+
"""
|
|
420
|
+
OTEL-STYLE Litestar patch:
|
|
421
|
+
• Uses sys.setprofile tracer to capture endpoint metadata
|
|
422
|
+
• Captures request headers/body if enabled
|
|
423
|
+
• Emits network hop AFTER handler completes (zero-overhead)
|
|
424
|
+
• Captures response headers/body if enabled
|
|
425
|
+
• Universal exception handler for all exceptions
|
|
426
|
+
Safe no-op if Litestar is not installed.
|
|
427
|
+
"""
|
|
428
|
+
global _ROUTES_TO_SKIP
|
|
429
|
+
_ROUTES_TO_SKIP = routes_to_skip or []
|
|
430
|
+
|
|
431
|
+
try:
|
|
432
|
+
import litestar
|
|
433
|
+
from litestar import Litestar
|
|
434
|
+
from litestar.middleware import DefineMiddleware
|
|
435
|
+
except ImportError:
|
|
436
|
+
return
|
|
437
|
+
|
|
438
|
+
original_init = Litestar.__init__
|
|
439
|
+
|
|
440
|
+
def patched_init(self, *args, **kwargs):
|
|
441
|
+
"""
|
|
442
|
+
Injects Sailfish into every Litestar app instance by:
|
|
443
|
+
1. Pre-pending ASGI middleware for header propagation + network hop capture
|
|
444
|
+
2. Adding a generic exception handler for all exceptions
|
|
445
|
+
"""
|
|
446
|
+
|
|
447
|
+
# 1. Middleware injection
|
|
448
|
+
mw = list(kwargs.get("middleware", []))
|
|
449
|
+
mw.insert(0, DefineMiddleware(_sf_tracing_factory))
|
|
450
|
+
kwargs["middleware"] = mw
|
|
451
|
+
|
|
452
|
+
# 2. Universal exception handler
|
|
453
|
+
def _sf_exception_handler(request, exc): # type: ignore[valid-type]
|
|
454
|
+
"""
|
|
455
|
+
Litestar calls this for any Exception once routing/dep-resolution is done.
|
|
456
|
+
Forward to custom_excepthook and re-raise so builtin handler produces response.
|
|
457
|
+
"""
|
|
458
|
+
custom_excepthook(type(exc), exc, exc.__traceback__)
|
|
459
|
+
raise exc # let Litestar fall back to its default logic
|
|
460
|
+
|
|
461
|
+
# Merge with user-supplied handlers
|
|
462
|
+
existing_handlers = kwargs.get("exception_handlers", {})
|
|
463
|
+
if isinstance(existing_handlers, dict):
|
|
464
|
+
existing_handlers.setdefault(Exception, _sf_exception_handler)
|
|
465
|
+
else: # Litestar also accepts list[tuple[Exception, Handler]]
|
|
466
|
+
existing_handlers = list(existing_handlers) # type: ignore[arg-type]
|
|
467
|
+
existing_handlers.append((Exception, _sf_exception_handler))
|
|
468
|
+
kwargs["exception_handlers"] = existing_handlers
|
|
469
|
+
|
|
470
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
471
|
+
print(
|
|
472
|
+
"[[patch_litestar]] OTEL-style middleware + exception handler installed",
|
|
473
|
+
log=False,
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
return original_init(self, *args, **kwargs)
|
|
477
|
+
|
|
478
|
+
Litestar.__init__ = patched_init
|
|
479
|
+
|
|
480
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
481
|
+
print("[[patch_litestar]] OTEL-style patch applied", log=False)
|
|
482
|
+
|
|
483
|
+
# ── CORS patching ──────────────────────────────────────────────────
|
|
484
|
+
patch_litestar_cors()
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def patch_litestar_cors():
|
|
488
|
+
"""
|
|
489
|
+
Patch Litestar's CORSConfig to automatically inject Sailfish headers.
|
|
490
|
+
|
|
491
|
+
SAFE: Only modifies allow_headers if CORS is already configured.
|
|
492
|
+
Litestar uses CORSConfig dataclass for CORS configuration.
|
|
493
|
+
"""
|
|
494
|
+
try:
|
|
495
|
+
from litestar.config.cors import CORSConfig
|
|
496
|
+
except ImportError:
|
|
497
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
498
|
+
print(
|
|
499
|
+
"[[patch_litestar_cors]] Litestar CORSConfig not available, skipping",
|
|
500
|
+
log=False,
|
|
501
|
+
)
|
|
502
|
+
return
|
|
503
|
+
|
|
504
|
+
# Check if already patched
|
|
505
|
+
if hasattr(CORSConfig, "_sf_cors_patched"):
|
|
506
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
507
|
+
print("[[patch_litestar_cors]] Already patched, skipping", log=False)
|
|
508
|
+
return
|
|
509
|
+
|
|
510
|
+
# Patch CORSConfig.__init__ to intercept and modify allow_headers
|
|
511
|
+
original_init = CORSConfig.__init__
|
|
512
|
+
|
|
513
|
+
def patched_init(
|
|
514
|
+
self,
|
|
515
|
+
allow_origins=("*",),
|
|
516
|
+
allow_methods=("*",),
|
|
517
|
+
allow_headers=("*",),
|
|
518
|
+
allow_credentials=False,
|
|
519
|
+
allow_origin_regex=None,
|
|
520
|
+
expose_headers=(),
|
|
521
|
+
max_age=600,
|
|
522
|
+
):
|
|
523
|
+
# Intercept allow_headers parameter
|
|
524
|
+
if should_inject_headers(allow_headers):
|
|
525
|
+
allow_headers = inject_sailfish_headers(allow_headers)
|
|
526
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
527
|
+
print(
|
|
528
|
+
"[[patch_litestar_cors]] Injected Sailfish headers into CORSConfig",
|
|
529
|
+
log=False,
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
# Call original init with potentially modified headers
|
|
533
|
+
original_init(
|
|
534
|
+
self,
|
|
535
|
+
allow_origins=allow_origins,
|
|
536
|
+
allow_methods=allow_methods,
|
|
537
|
+
allow_headers=allow_headers,
|
|
538
|
+
allow_credentials=allow_credentials,
|
|
539
|
+
allow_origin_regex=allow_origin_regex,
|
|
540
|
+
expose_headers=expose_headers,
|
|
541
|
+
max_age=max_age,
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
CORSConfig.__init__ = patched_init
|
|
545
|
+
CORSConfig._sf_cors_patched = True
|
|
546
|
+
|
|
547
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
548
|
+
print(
|
|
549
|
+
"[[patch_litestar_cors]] Successfully patched Litestar CORSConfig",
|
|
550
|
+
log=False,
|
|
551
|
+
)
|