sf-veritas 0.11.10__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.
- sf_veritas/__init__.py +46 -0
- sf_veritas/_auto_preload.py +73 -0
- sf_veritas/_sfconfig.c +162 -0
- sf_veritas/_sfconfig.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfcrashhandler.c +267 -0
- sf_veritas/_sfcrashhandler.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastlog.c +953 -0
- sf_veritas/_sffastlog.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +994 -0
- sf_veritas/_sffastnet.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +727 -0
- sf_veritas/_sffastnetworkrequest.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2791 -0
- sf_veritas/_sffuncspan.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +730 -0
- sf_veritas/_sffuncspan_config.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfheadercheck.c +341 -0
- sf_veritas/_sfheadercheck.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfnetworkhop.c +1454 -0
- sf_veritas/_sfnetworkhop.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1223 -0
- sf_veritas/_sfservice.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfteepreload.c +6227 -0
- sf_veritas/app_config.py +57 -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 +146 -0
- sf_veritas/custom_output_wrapper.py +153 -0
- sf_veritas/custom_print.py +153 -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 +693 -0
- sf_veritas/function_span_profiler.py +1313 -0
- sf_veritas/get_preload_path.py +34 -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 +543 -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/_patch_tracker.py +74 -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 +99 -0
- sf_veritas/patches/network_libraries/aiohttp.py +294 -0
- sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
- sf_veritas/patches/network_libraries/http_client.py +670 -0
- sf_veritas/patches/network_libraries/httpcore.py +580 -0
- sf_veritas/patches/network_libraries/httplib2.py +315 -0
- sf_veritas/patches/network_libraries/httpx.py +557 -0
- sf_veritas/patches/network_libraries/niquests.py +218 -0
- sf_veritas/patches/network_libraries/pycurl.py +399 -0
- sf_veritas/patches/network_libraries/requests.py +595 -0
- sf_veritas/patches/network_libraries/ssl_socket.py +822 -0
- sf_veritas/patches/network_libraries/tornado.py +360 -0
- sf_veritas/patches/network_libraries/treq.py +270 -0
- sf_veritas/patches/network_libraries/urllib_request.py +483 -0
- sf_veritas/patches/network_libraries/utils.py +598 -0
- sf_veritas/patches/os.py +17 -0
- sf_veritas/patches/threading.py +231 -0
- sf_veritas/patches/web_frameworks/__init__.py +54 -0
- sf_veritas/patches/web_frameworks/aiohttp.py +798 -0
- sf_veritas/patches/web_frameworks/async_websocket_consumer.py +337 -0
- sf_veritas/patches/web_frameworks/blacksheep.py +532 -0
- sf_veritas/patches/web_frameworks/bottle.py +513 -0
- sf_veritas/patches/web_frameworks/cherrypy.py +683 -0
- sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
- sf_veritas/patches/web_frameworks/django.py +963 -0
- sf_veritas/patches/web_frameworks/eve.py +401 -0
- sf_veritas/patches/web_frameworks/falcon.py +931 -0
- sf_veritas/patches/web_frameworks/fastapi.py +738 -0
- sf_veritas/patches/web_frameworks/flask.py +526 -0
- sf_veritas/patches/web_frameworks/klein.py +501 -0
- sf_veritas/patches/web_frameworks/litestar.py +616 -0
- sf_veritas/patches/web_frameworks/pyramid.py +440 -0
- sf_veritas/patches/web_frameworks/quart.py +841 -0
- sf_veritas/patches/web_frameworks/robyn.py +708 -0
- sf_veritas/patches/web_frameworks/sanic.py +874 -0
- sf_veritas/patches/web_frameworks/starlette.py +742 -0
- sf_veritas/patches/web_frameworks/strawberry.py +1446 -0
- sf_veritas/patches/web_frameworks/tornado.py +485 -0
- sf_veritas/patches/web_frameworks/utils.py +170 -0
- sf_veritas/print_override.py +13 -0
- sf_veritas/regular_data_transmitter.py +444 -0
- sf_veritas/request_interceptor.py +401 -0
- sf_veritas/request_utils.py +550 -0
- sf_veritas/segfault_handler.py +116 -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 +1319 -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 +1678 -0
- sf_veritas/utils.py +39 -0
- sf_veritas-0.11.10.dist-info/METADATA +97 -0
- sf_veritas-0.11.10.dist-info/RECORD +141 -0
- sf_veritas-0.11.10.dist-info/WHEEL +5 -0
- sf_veritas-0.11.10.dist-info/entry_points.txt +2 -0
- sf_veritas-0.11.10.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,532 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context-var propagation + first-hop NetworkHop emission.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# ------------------------------------------------------------------ #
|
|
6
|
+
# Shared helpers (same as Django/FastAPI utils)
|
|
7
|
+
# ------------------------------------------------------------------ #
|
|
8
|
+
import inspect
|
|
9
|
+
import os
|
|
10
|
+
import sysconfig
|
|
11
|
+
import threading
|
|
12
|
+
from functools import lru_cache
|
|
13
|
+
from typing import Any, Callable, List, Optional, Set, Tuple
|
|
14
|
+
|
|
15
|
+
from ... import _sffuncspan, _sffuncspan_config, app_config
|
|
16
|
+
from ...constants import (
|
|
17
|
+
FUNCSPAN_OVERRIDE_HEADER_BYTES,
|
|
18
|
+
SAILFISH_TRACING_HEADER,
|
|
19
|
+
SAILFISH_TRACING_HEADER_BYTES,
|
|
20
|
+
)
|
|
21
|
+
from ...custom_excepthook import custom_excepthook
|
|
22
|
+
from ...env_vars import (
|
|
23
|
+
SF_DEBUG,
|
|
24
|
+
SF_NETWORKHOP_CAPTURE_REQUEST_BODY,
|
|
25
|
+
SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS,
|
|
26
|
+
SF_NETWORKHOP_CAPTURE_RESPONSE_BODY,
|
|
27
|
+
SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS,
|
|
28
|
+
SF_NETWORKHOP_REQUEST_LIMIT_MB,
|
|
29
|
+
SF_NETWORKHOP_RESPONSE_LIMIT_MB,
|
|
30
|
+
)
|
|
31
|
+
from ...fast_network_hop import fast_send_network_hop_fast, register_endpoint
|
|
32
|
+
from ...thread_local import (
|
|
33
|
+
clear_c_tls_parent_trace_id,
|
|
34
|
+
clear_current_request_path,
|
|
35
|
+
clear_funcspan_override,
|
|
36
|
+
clear_outbound_header_base,
|
|
37
|
+
clear_trace_id,
|
|
38
|
+
generate_new_trace_id,
|
|
39
|
+
get_or_set_sf_trace_id,
|
|
40
|
+
get_sf_trace_id,
|
|
41
|
+
set_current_request_path,
|
|
42
|
+
set_funcspan_override,
|
|
43
|
+
set_outbound_header_base,
|
|
44
|
+
)
|
|
45
|
+
from .cors_utils import inject_sailfish_headers, should_inject_headers
|
|
46
|
+
from .utils import _is_user_code, _unwrap_user_func, should_skip_route, reinitialize_log_print_capture_for_worker
|
|
47
|
+
|
|
48
|
+
# Size limits in bytes
|
|
49
|
+
_REQUEST_LIMIT_BYTES = SF_NETWORKHOP_REQUEST_LIMIT_MB * 1024 * 1024
|
|
50
|
+
_RESPONSE_LIMIT_BYTES = SF_NETWORKHOP_RESPONSE_LIMIT_MB * 1024 * 1024
|
|
51
|
+
|
|
52
|
+
# Pre-registered endpoint IDs
|
|
53
|
+
_ENDPOINT_REGISTRY: dict[tuple, int] = {}
|
|
54
|
+
|
|
55
|
+
# Routes to skip (set by patch_blacksheep)
|
|
56
|
+
_ROUTES_TO_SKIP = []
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ------------------------------------------------------------------ #
|
|
60
|
+
# Middleware
|
|
61
|
+
# ------------------------------------------------------------------ #
|
|
62
|
+
async def _sf_tracing_middleware(request, handler):
|
|
63
|
+
"""
|
|
64
|
+
OTEL-STYLE BlackSheep middleware that:
|
|
65
|
+
1. Propagates trace-id from SAILFISH_TRACING_HEADER.
|
|
66
|
+
2. Captures request headers/body if enabled.
|
|
67
|
+
3. Captures endpoint metadata and registers endpoint.
|
|
68
|
+
4. Calls handler and captures exceptions.
|
|
69
|
+
5. Captures response headers/body if enabled.
|
|
70
|
+
6. Emits NetworkHop AFTER handler completes (OTEL-style zero-overhead).
|
|
71
|
+
"""
|
|
72
|
+
# Set current request path for route-based suppression (SF_DISABLE_INBOUND_NETWORK_TRACING_ON_ROUTES)
|
|
73
|
+
request_path = request.url.path if hasattr(request.url, 'path') else str(request.url)
|
|
74
|
+
set_current_request_path(request_path)
|
|
75
|
+
|
|
76
|
+
# PERFORMANCE: Single-pass bytes-level header scan (no dict allocation until needed)
|
|
77
|
+
# Scan headers once, only decode what we need, use latin-1 (fast 1:1 byte map)
|
|
78
|
+
# BlackSheep headers are tuples of (bytes, bytes)
|
|
79
|
+
hdr_items = request.headers if hasattr(request.headers, "__iter__") else []
|
|
80
|
+
incoming_trace_raw = None # bytes
|
|
81
|
+
funcspan_raw = None # bytes
|
|
82
|
+
req_headers = None # dict[str,str] only if capture enabled
|
|
83
|
+
|
|
84
|
+
capture_req_headers = SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS # local cache
|
|
85
|
+
|
|
86
|
+
if capture_req_headers:
|
|
87
|
+
# build the dict while we're scanning
|
|
88
|
+
tmp = {}
|
|
89
|
+
for k, v in hdr_items:
|
|
90
|
+
# BlackSheep headers are bytes
|
|
91
|
+
kl = k.lower() if isinstance(k, bytes) else k.encode().lower()
|
|
92
|
+
if kl == SAILFISH_TRACING_HEADER_BYTES:
|
|
93
|
+
incoming_trace_raw = v
|
|
94
|
+
elif kl == FUNCSPAN_OVERRIDE_HEADER_BYTES:
|
|
95
|
+
funcspan_raw = v
|
|
96
|
+
# decode using latin-1 for speed
|
|
97
|
+
tmp[k.decode("latin-1") if isinstance(k, bytes) else k] = (
|
|
98
|
+
v.decode("latin-1") if isinstance(v, bytes) else v
|
|
99
|
+
)
|
|
100
|
+
req_headers = tmp
|
|
101
|
+
else:
|
|
102
|
+
for k, v in hdr_items:
|
|
103
|
+
kl = k.lower() if isinstance(k, bytes) else k.encode().lower()
|
|
104
|
+
if kl == SAILFISH_TRACING_HEADER_BYTES:
|
|
105
|
+
incoming_trace_raw = v
|
|
106
|
+
elif kl == FUNCSPAN_OVERRIDE_HEADER_BYTES:
|
|
107
|
+
funcspan_raw = v
|
|
108
|
+
|
|
109
|
+
# 1. CRITICAL: Seed/ensure trace_id immediately (BEFORE any outbound work)
|
|
110
|
+
if incoming_trace_raw:
|
|
111
|
+
# Incoming X-Sf3-Rid header provided - use it
|
|
112
|
+
incoming_trace = (
|
|
113
|
+
incoming_trace_raw.decode("latin-1")
|
|
114
|
+
if isinstance(incoming_trace_raw, bytes)
|
|
115
|
+
else str(incoming_trace_raw)
|
|
116
|
+
)
|
|
117
|
+
get_or_set_sf_trace_id(incoming_trace, is_associated_with_inbound_request=True)
|
|
118
|
+
else:
|
|
119
|
+
# No incoming X-Sf3-Rid header - generate fresh trace_id for this request
|
|
120
|
+
generate_new_trace_id()
|
|
121
|
+
|
|
122
|
+
# Optional funcspan override (decode only if present)
|
|
123
|
+
funcspan_override_header = None
|
|
124
|
+
if funcspan_raw:
|
|
125
|
+
funcspan_override_header = (
|
|
126
|
+
funcspan_raw.decode("latin-1")
|
|
127
|
+
if isinstance(funcspan_raw, bytes)
|
|
128
|
+
else str(funcspan_raw)
|
|
129
|
+
)
|
|
130
|
+
try:
|
|
131
|
+
set_funcspan_override(funcspan_override_header)
|
|
132
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
133
|
+
print(
|
|
134
|
+
f"[[Blacksheep.middleware]] Set function span override from header: {funcspan_override_header}",
|
|
135
|
+
log=False,
|
|
136
|
+
)
|
|
137
|
+
except Exception as e:
|
|
138
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
139
|
+
print(
|
|
140
|
+
f"[[Blacksheep.middleware]] Failed to set function span override: {e}",
|
|
141
|
+
log=False,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Initialize outbound base without list/allocs from split()
|
|
145
|
+
try:
|
|
146
|
+
trace_id = get_sf_trace_id()
|
|
147
|
+
if trace_id:
|
|
148
|
+
s = str(trace_id)
|
|
149
|
+
i = s.find("/") # session
|
|
150
|
+
j = s.find("/", i + 1) if i != -1 else -1 # page
|
|
151
|
+
if j != -1:
|
|
152
|
+
base_trace = s[:j] # "session/page"
|
|
153
|
+
set_outbound_header_base(
|
|
154
|
+
base_trace=base_trace,
|
|
155
|
+
parent_trace_id=s, # "session/page/uuid"
|
|
156
|
+
funcspan=funcspan_override_header,
|
|
157
|
+
)
|
|
158
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
159
|
+
print(
|
|
160
|
+
f"[[Blacksheep.middleware]] Initialized outbound header base (base={base_trace[:16]}...)",
|
|
161
|
+
log=False,
|
|
162
|
+
)
|
|
163
|
+
except Exception as e:
|
|
164
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
165
|
+
print(
|
|
166
|
+
f"[[Blacksheep.middleware]] Failed to initialize outbound header base: {e}",
|
|
167
|
+
log=False,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# 3. Capture request body if enabled
|
|
171
|
+
req_body = None
|
|
172
|
+
if SF_NETWORKHOP_CAPTURE_REQUEST_BODY:
|
|
173
|
+
try:
|
|
174
|
+
# BlackSheep provides async read method
|
|
175
|
+
# For GET requests, this will typically be empty
|
|
176
|
+
body = await request.read()
|
|
177
|
+
req_body = body[:_REQUEST_LIMIT_BYTES] if body else None
|
|
178
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
179
|
+
print(
|
|
180
|
+
f"[[Blacksheep]] Request body capture: {len(body) if body else 0} bytes (method={request.method})",
|
|
181
|
+
log=False,
|
|
182
|
+
)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
185
|
+
print(f"[[Blacksheep]] Failed to capture request body: {e}", log=False)
|
|
186
|
+
|
|
187
|
+
# 4. Capture endpoint metadata and register endpoint
|
|
188
|
+
endpoint_id = None
|
|
189
|
+
if not getattr(request, "_sf_hop_sent", False):
|
|
190
|
+
user_fn = _unwrap_user_func(handler)
|
|
191
|
+
if (
|
|
192
|
+
inspect.isfunction(user_fn)
|
|
193
|
+
and _is_user_code(user_fn.__code__.co_filename)
|
|
194
|
+
and not user_fn.__module__.startswith("strawberry")
|
|
195
|
+
):
|
|
196
|
+
fname = user_fn.__code__.co_filename
|
|
197
|
+
lno = user_fn.__code__.co_firstlineno
|
|
198
|
+
fname_str = user_fn.__name__
|
|
199
|
+
hop_key = (fname, lno)
|
|
200
|
+
|
|
201
|
+
# Get route pattern if available
|
|
202
|
+
route_pattern = getattr(request, "route_pattern", None)
|
|
203
|
+
route_str = str(route_pattern) if route_pattern else None
|
|
204
|
+
|
|
205
|
+
# Check if route should be skipped
|
|
206
|
+
if route_str and should_skip_route(route_str, _ROUTES_TO_SKIP):
|
|
207
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
208
|
+
print(
|
|
209
|
+
f"[[BlackSheep]] Skipping endpoint (route matches skip pattern): {route_str}",
|
|
210
|
+
log=False,
|
|
211
|
+
)
|
|
212
|
+
return await handler(request)
|
|
213
|
+
|
|
214
|
+
# Get or register endpoint
|
|
215
|
+
endpoint_id = _ENDPOINT_REGISTRY.get(hop_key)
|
|
216
|
+
if endpoint_id is None:
|
|
217
|
+
endpoint_id = register_endpoint(
|
|
218
|
+
line=str(lno),
|
|
219
|
+
column="0",
|
|
220
|
+
name=fname_str,
|
|
221
|
+
entrypoint=fname,
|
|
222
|
+
route=route_str,
|
|
223
|
+
)
|
|
224
|
+
if endpoint_id >= 0:
|
|
225
|
+
_ENDPOINT_REGISTRY[hop_key] = endpoint_id
|
|
226
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
227
|
+
print(
|
|
228
|
+
f"[[Blacksheep]] Registered endpoint: {fname_str} @ {fname}:{lno} (id={endpoint_id})",
|
|
229
|
+
log=False,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
request._sf_hop_sent = True
|
|
233
|
+
request._sf_endpoint_id = endpoint_id
|
|
234
|
+
|
|
235
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
236
|
+
print(
|
|
237
|
+
f"[[Blacksheep]] Captured endpoint: {fname_str} ({fname}:{lno}) endpoint_id={endpoint_id}",
|
|
238
|
+
log=False,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# 5. Call handler and capture exceptions (with cleanup in finally)
|
|
242
|
+
try:
|
|
243
|
+
try:
|
|
244
|
+
response = await handler(request)
|
|
245
|
+
except Exception as exc: # ← includes HTTPException & friends
|
|
246
|
+
custom_excepthook(type(exc), exc, exc.__traceback__)
|
|
247
|
+
raise # Let BlackSheep build the response
|
|
248
|
+
|
|
249
|
+
# 6. Capture response headers if enabled
|
|
250
|
+
resp_headers = None
|
|
251
|
+
if SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS and endpoint_id is not None:
|
|
252
|
+
try:
|
|
253
|
+
# BlackSheep response headers are in _headers (list of tuples)
|
|
254
|
+
if hasattr(response, "_headers") and response._headers:
|
|
255
|
+
resp_headers = {
|
|
256
|
+
k.decode() if isinstance(k, bytes) else k: (
|
|
257
|
+
v.decode() if isinstance(v, bytes) else v
|
|
258
|
+
)
|
|
259
|
+
for k, v in response._headers
|
|
260
|
+
}
|
|
261
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
262
|
+
print(
|
|
263
|
+
f"[[Blacksheep]] Captured response headers from _headers: {len(resp_headers)} headers",
|
|
264
|
+
log=False,
|
|
265
|
+
)
|
|
266
|
+
elif hasattr(response, "headers") and response.headers:
|
|
267
|
+
# Fallback to headers property
|
|
268
|
+
resp_headers = {
|
|
269
|
+
k.decode() if isinstance(k, bytes) else k: (
|
|
270
|
+
v.decode() if isinstance(v, bytes) else v
|
|
271
|
+
)
|
|
272
|
+
for k, v in response.headers
|
|
273
|
+
}
|
|
274
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
275
|
+
print(
|
|
276
|
+
f"[[Blacksheep]] Captured response headers from headers: {len(resp_headers)} headers",
|
|
277
|
+
log=False,
|
|
278
|
+
)
|
|
279
|
+
except Exception as e:
|
|
280
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
281
|
+
print(
|
|
282
|
+
f"[[Blacksheep]] Failed to capture response headers: {e}",
|
|
283
|
+
log=False,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# 7. Capture response body if enabled
|
|
287
|
+
resp_body = None
|
|
288
|
+
if SF_NETWORKHOP_CAPTURE_RESPONSE_BODY and endpoint_id is not None:
|
|
289
|
+
try:
|
|
290
|
+
# BlackSheep response.content is a Content object that needs special handling
|
|
291
|
+
if hasattr(response, "content") and response.content:
|
|
292
|
+
content_obj = response.content
|
|
293
|
+
|
|
294
|
+
# Check if it's a blacksheep.contents.Content object
|
|
295
|
+
if hasattr(content_obj, "body"):
|
|
296
|
+
# Content object has a body attribute
|
|
297
|
+
if isinstance(content_obj.body, bytes):
|
|
298
|
+
resp_body = content_obj.body[:_RESPONSE_LIMIT_BYTES]
|
|
299
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
300
|
+
print(
|
|
301
|
+
f"[[Blacksheep]] Captured from content.body (bytes): {len(resp_body)} bytes",
|
|
302
|
+
log=False,
|
|
303
|
+
)
|
|
304
|
+
elif isinstance(content_obj.body, str):
|
|
305
|
+
resp_body = content_obj.body.encode("utf-8")[
|
|
306
|
+
:_RESPONSE_LIMIT_BYTES
|
|
307
|
+
]
|
|
308
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
309
|
+
print(
|
|
310
|
+
f"[[Blacksheep]] Captured from content.body (str): {len(resp_body)} bytes",
|
|
311
|
+
log=False,
|
|
312
|
+
)
|
|
313
|
+
elif isinstance(content_obj, bytes):
|
|
314
|
+
resp_body = content_obj[:_RESPONSE_LIMIT_BYTES]
|
|
315
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
316
|
+
print(
|
|
317
|
+
f"[[Blacksheep]] Captured from content (bytes): {len(resp_body)} bytes",
|
|
318
|
+
log=False,
|
|
319
|
+
)
|
|
320
|
+
elif isinstance(content_obj, str):
|
|
321
|
+
resp_body = content_obj.encode("utf-8")[:_RESPONSE_LIMIT_BYTES]
|
|
322
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
323
|
+
print(
|
|
324
|
+
f"[[Blacksheep]] Captured from content (str): {len(resp_body)} bytes",
|
|
325
|
+
log=False,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# Fallback: try direct body attribute
|
|
329
|
+
if not resp_body and hasattr(response, "body") and response.body:
|
|
330
|
+
if isinstance(response.body, bytes):
|
|
331
|
+
resp_body = response.body[:_RESPONSE_LIMIT_BYTES]
|
|
332
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
333
|
+
print(
|
|
334
|
+
f"[[Blacksheep]] Captured from body (bytes): {len(resp_body)} bytes",
|
|
335
|
+
log=False,
|
|
336
|
+
)
|
|
337
|
+
elif isinstance(response.body, str):
|
|
338
|
+
resp_body = response.body.encode("utf-8")[
|
|
339
|
+
:_RESPONSE_LIMIT_BYTES
|
|
340
|
+
]
|
|
341
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
342
|
+
print(
|
|
343
|
+
f"[[Blacksheep]] Captured from body (str): {len(resp_body)} bytes",
|
|
344
|
+
log=False,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
if SF_DEBUG and not resp_body:
|
|
348
|
+
print(
|
|
349
|
+
f"[[Blacksheep]] No response body captured (content type: {type(response.content) if hasattr(response, 'content') else 'N/A'})",
|
|
350
|
+
log=False,
|
|
351
|
+
)
|
|
352
|
+
except Exception as e:
|
|
353
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
354
|
+
print(
|
|
355
|
+
f"[[Blacksheep]] Failed to capture response body: {e}",
|
|
356
|
+
log=False,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# 8. OTEL-STYLE: Emit network hop AFTER handler completes
|
|
360
|
+
if endpoint_id is not None and endpoint_id >= 0:
|
|
361
|
+
try:
|
|
362
|
+
_, session_id = get_or_set_sf_trace_id()
|
|
363
|
+
|
|
364
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
365
|
+
print(
|
|
366
|
+
f"[[Blacksheep]] About to emit network hop: endpoint_id={endpoint_id}, "
|
|
367
|
+
f"req_headers={'present' if req_headers else 'None'}, "
|
|
368
|
+
f"req_body={len(req_body) if req_body else 0} bytes, "
|
|
369
|
+
f"resp_headers={'present' if resp_headers else 'None'}, "
|
|
370
|
+
f"resp_body={len(resp_body) if resp_body else 0} bytes",
|
|
371
|
+
log=False,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# Direct C call - queues to background worker, returns instantly
|
|
375
|
+
# Extract route and query params from request
|
|
376
|
+
# BlackSheep's request.url.path returns bytes, need to decode to string
|
|
377
|
+
path_value = (
|
|
378
|
+
request.url.path
|
|
379
|
+
if hasattr(request.url, "path")
|
|
380
|
+
else str(request.url)
|
|
381
|
+
)
|
|
382
|
+
raw_path = (
|
|
383
|
+
path_value.decode("utf-8")
|
|
384
|
+
if isinstance(path_value, bytes)
|
|
385
|
+
else path_value
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# Handle None/empty from request.url.query (when no query string)
|
|
389
|
+
raw_query_value = (
|
|
390
|
+
request.url.query if hasattr(request.url, "query") else None
|
|
391
|
+
)
|
|
392
|
+
if raw_query_value is None or raw_query_value == "":
|
|
393
|
+
raw_query = b""
|
|
394
|
+
elif isinstance(raw_query_value, bytes):
|
|
395
|
+
raw_query = raw_query_value
|
|
396
|
+
else:
|
|
397
|
+
raw_query = raw_query_value.encode("utf-8")
|
|
398
|
+
|
|
399
|
+
fast_send_network_hop_fast(
|
|
400
|
+
session_id=session_id,
|
|
401
|
+
endpoint_id=endpoint_id,
|
|
402
|
+
raw_path=raw_path,
|
|
403
|
+
raw_query_string=raw_query,
|
|
404
|
+
request_headers=req_headers,
|
|
405
|
+
request_body=req_body,
|
|
406
|
+
response_headers=resp_headers,
|
|
407
|
+
response_body=resp_body,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
411
|
+
print(
|
|
412
|
+
f"[[Blacksheep]] Emitted network hop: endpoint_id={endpoint_id} "
|
|
413
|
+
f"session={session_id}",
|
|
414
|
+
log=False,
|
|
415
|
+
)
|
|
416
|
+
except Exception as e: # noqa: BLE001 S110
|
|
417
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
418
|
+
print(f"[[Blacksheep]] Failed to emit network hop: {e}", log=False)
|
|
419
|
+
|
|
420
|
+
return response
|
|
421
|
+
finally:
|
|
422
|
+
# CRITICAL: Clear C TLS to prevent stale data in thread pools
|
|
423
|
+
# This runs even if handler raises exception!
|
|
424
|
+
clear_c_tls_parent_trace_id()
|
|
425
|
+
|
|
426
|
+
# CRITICAL: Clear outbound header base to prevent stale cached headers
|
|
427
|
+
# ContextVar does NOT automatically clean up in thread pools - must clear explicitly
|
|
428
|
+
clear_outbound_header_base()
|
|
429
|
+
|
|
430
|
+
# CRITICAL: Clear trace_id to ensure fresh generation for next request
|
|
431
|
+
# Without this, get_or_set_sf_trace_id() reuses trace_id from previous request
|
|
432
|
+
# causing X-Sf4-Prid to stay constant when no incoming X-Sf3-Rid header
|
|
433
|
+
clear_trace_id()
|
|
434
|
+
|
|
435
|
+
# CRITICAL: Clear current request path to prevent stale data in thread pools
|
|
436
|
+
clear_current_request_path()
|
|
437
|
+
|
|
438
|
+
# Clear function span override for this request (ContextVar cleanup - also syncs C thread-local)
|
|
439
|
+
try:
|
|
440
|
+
clear_funcspan_override()
|
|
441
|
+
except Exception:
|
|
442
|
+
pass
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
# ------------------------------------------------------------------ #
|
|
446
|
+
# Monkey-patch Application.__init__
|
|
447
|
+
# ------------------------------------------------------------------ #
|
|
448
|
+
def patch_blacksheep(routes_to_skip: Optional[List[str]] = None):
|
|
449
|
+
"""
|
|
450
|
+
Injects the tracing middleware into every BlackSheep Application.
|
|
451
|
+
Safe no-op if BlackSheep isn't installed or already patched.
|
|
452
|
+
"""
|
|
453
|
+
global _ROUTES_TO_SKIP
|
|
454
|
+
_ROUTES_TO_SKIP = routes_to_skip or []
|
|
455
|
+
|
|
456
|
+
try:
|
|
457
|
+
from blacksheep import Application
|
|
458
|
+
except ImportError:
|
|
459
|
+
return
|
|
460
|
+
|
|
461
|
+
if getattr(Application, "__sf_tracing_patched__", False):
|
|
462
|
+
return # already patched
|
|
463
|
+
|
|
464
|
+
original_init = Application.__init__
|
|
465
|
+
|
|
466
|
+
def patched_init(self, *args, **kwargs):
|
|
467
|
+
original_init(self, *args, **kwargs)
|
|
468
|
+
|
|
469
|
+
# Note: Profiler is already installed by unified_interceptor.py
|
|
470
|
+
|
|
471
|
+
# Put our middleware first so we run before user middlewares
|
|
472
|
+
self.middlewares.insert(0, _sf_tracing_middleware)
|
|
473
|
+
|
|
474
|
+
Application.__init__ = patched_init
|
|
475
|
+
Application.__sf_tracing_patched__ = True
|
|
476
|
+
|
|
477
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
478
|
+
print("[[patch_blacksheep]] tracing middleware installed", log=False)
|
|
479
|
+
|
|
480
|
+
# ── CORS patching ──────────────────────────────────────────────────
|
|
481
|
+
patch_blacksheep_cors()
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def patch_blacksheep_cors():
|
|
485
|
+
"""
|
|
486
|
+
Patch BlackSheep's Application.use_cors to automatically inject Sailfish headers.
|
|
487
|
+
|
|
488
|
+
SAFE: Only modifies allow_headers if the application sets it.
|
|
489
|
+
Similar to Flask CORS patching - intercepts the configuration before it's applied.
|
|
490
|
+
"""
|
|
491
|
+
try:
|
|
492
|
+
from blacksheep import Application
|
|
493
|
+
except ImportError:
|
|
494
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
495
|
+
print(
|
|
496
|
+
"[[patch_blacksheep_cors]] BlackSheep Application not available, skipping",
|
|
497
|
+
log=False,
|
|
498
|
+
)
|
|
499
|
+
return
|
|
500
|
+
|
|
501
|
+
# Check if already patched
|
|
502
|
+
if hasattr(Application, "_sf_cors_patched"):
|
|
503
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
504
|
+
print("[[patch_blacksheep_cors]] Already patched, skipping", log=False)
|
|
505
|
+
return
|
|
506
|
+
|
|
507
|
+
# Patch Application.use_cors to intercept and modify allow_headers parameter
|
|
508
|
+
original_use_cors = Application.use_cors
|
|
509
|
+
|
|
510
|
+
def patched_use_cors(self, *args, **kwargs):
|
|
511
|
+
# Intercept allow_headers parameter
|
|
512
|
+
if "allow_headers" in kwargs:
|
|
513
|
+
original_headers = kwargs["allow_headers"]
|
|
514
|
+
if should_inject_headers(original_headers):
|
|
515
|
+
kwargs["allow_headers"] = inject_sailfish_headers(original_headers)
|
|
516
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
517
|
+
print(
|
|
518
|
+
f"[[patch_blacksheep_cors]] Injected Sailfish headers into use_cors: {original_headers} -> {kwargs['allow_headers']}",
|
|
519
|
+
log=False,
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
# Call original use_cors with potentially modified headers
|
|
523
|
+
return original_use_cors(self, *args, **kwargs)
|
|
524
|
+
|
|
525
|
+
Application.use_cors = patched_use_cors
|
|
526
|
+
Application._sf_cors_patched = True
|
|
527
|
+
|
|
528
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
529
|
+
print(
|
|
530
|
+
"[[patch_blacksheep_cors]] Successfully patched BlackSheep Application.use_cors",
|
|
531
|
+
log=False,
|
|
532
|
+
)
|