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.
Files changed (141) hide show
  1. sf_veritas/__init__.py +46 -0
  2. sf_veritas/_auto_preload.py +73 -0
  3. sf_veritas/_sfconfig.c +162 -0
  4. sf_veritas/_sfconfig.cpython-314-x86_64-linux-gnu.so +0 -0
  5. sf_veritas/_sfcrashhandler.c +267 -0
  6. sf_veritas/_sfcrashhandler.cpython-314-x86_64-linux-gnu.so +0 -0
  7. sf_veritas/_sffastlog.c +953 -0
  8. sf_veritas/_sffastlog.cpython-314-x86_64-linux-gnu.so +0 -0
  9. sf_veritas/_sffastnet.c +994 -0
  10. sf_veritas/_sffastnet.cpython-314-x86_64-linux-gnu.so +0 -0
  11. sf_veritas/_sffastnetworkrequest.c +727 -0
  12. sf_veritas/_sffastnetworkrequest.cpython-314-x86_64-linux-gnu.so +0 -0
  13. sf_veritas/_sffuncspan.c +2791 -0
  14. sf_veritas/_sffuncspan.cpython-314-x86_64-linux-gnu.so +0 -0
  15. sf_veritas/_sffuncspan_config.c +730 -0
  16. sf_veritas/_sffuncspan_config.cpython-314-x86_64-linux-gnu.so +0 -0
  17. sf_veritas/_sfheadercheck.c +341 -0
  18. sf_veritas/_sfheadercheck.cpython-314-x86_64-linux-gnu.so +0 -0
  19. sf_veritas/_sfnetworkhop.c +1454 -0
  20. sf_veritas/_sfnetworkhop.cpython-314-x86_64-linux-gnu.so +0 -0
  21. sf_veritas/_sfservice.c +1223 -0
  22. sf_veritas/_sfservice.cpython-314-x86_64-linux-gnu.so +0 -0
  23. sf_veritas/_sfteepreload.c +6227 -0
  24. sf_veritas/app_config.py +57 -0
  25. sf_veritas/cli.py +336 -0
  26. sf_veritas/constants.py +10 -0
  27. sf_veritas/custom_excepthook.py +304 -0
  28. sf_veritas/custom_log_handler.py +146 -0
  29. sf_veritas/custom_output_wrapper.py +153 -0
  30. sf_veritas/custom_print.py +153 -0
  31. sf_veritas/django_app.py +5 -0
  32. sf_veritas/env_vars.py +186 -0
  33. sf_veritas/exception_handling_middleware.py +18 -0
  34. sf_veritas/exception_metaclass.py +69 -0
  35. sf_veritas/fast_frame_info.py +116 -0
  36. sf_veritas/fast_network_hop.py +293 -0
  37. sf_veritas/frame_tools.py +112 -0
  38. sf_veritas/funcspan_config_loader.py +693 -0
  39. sf_veritas/function_span_profiler.py +1313 -0
  40. sf_veritas/get_preload_path.py +34 -0
  41. sf_veritas/import_hook.py +62 -0
  42. sf_veritas/infra_details/__init__.py +3 -0
  43. sf_veritas/infra_details/get_infra_details.py +24 -0
  44. sf_veritas/infra_details/kubernetes/__init__.py +3 -0
  45. sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
  46. sf_veritas/infra_details/kubernetes/get_details.py +7 -0
  47. sf_veritas/infra_details/running_on/__init__.py +17 -0
  48. sf_veritas/infra_details/running_on/kubernetes.py +11 -0
  49. sf_veritas/interceptors.py +543 -0
  50. sf_veritas/libsfnettee.so +0 -0
  51. sf_veritas/local_env_detect.py +118 -0
  52. sf_veritas/package_metadata.py +6 -0
  53. sf_veritas/patches/__init__.py +0 -0
  54. sf_veritas/patches/_patch_tracker.py +74 -0
  55. sf_veritas/patches/concurrent_futures.py +19 -0
  56. sf_veritas/patches/constants.py +1 -0
  57. sf_veritas/patches/exceptions.py +82 -0
  58. sf_veritas/patches/multiprocessing.py +32 -0
  59. sf_veritas/patches/network_libraries/__init__.py +99 -0
  60. sf_veritas/patches/network_libraries/aiohttp.py +294 -0
  61. sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
  62. sf_veritas/patches/network_libraries/http_client.py +670 -0
  63. sf_veritas/patches/network_libraries/httpcore.py +580 -0
  64. sf_veritas/patches/network_libraries/httplib2.py +315 -0
  65. sf_veritas/patches/network_libraries/httpx.py +557 -0
  66. sf_veritas/patches/network_libraries/niquests.py +218 -0
  67. sf_veritas/patches/network_libraries/pycurl.py +399 -0
  68. sf_veritas/patches/network_libraries/requests.py +595 -0
  69. sf_veritas/patches/network_libraries/ssl_socket.py +822 -0
  70. sf_veritas/patches/network_libraries/tornado.py +360 -0
  71. sf_veritas/patches/network_libraries/treq.py +270 -0
  72. sf_veritas/patches/network_libraries/urllib_request.py +483 -0
  73. sf_veritas/patches/network_libraries/utils.py +598 -0
  74. sf_veritas/patches/os.py +17 -0
  75. sf_veritas/patches/threading.py +231 -0
  76. sf_veritas/patches/web_frameworks/__init__.py +54 -0
  77. sf_veritas/patches/web_frameworks/aiohttp.py +798 -0
  78. sf_veritas/patches/web_frameworks/async_websocket_consumer.py +337 -0
  79. sf_veritas/patches/web_frameworks/blacksheep.py +532 -0
  80. sf_veritas/patches/web_frameworks/bottle.py +513 -0
  81. sf_veritas/patches/web_frameworks/cherrypy.py +683 -0
  82. sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
  83. sf_veritas/patches/web_frameworks/django.py +963 -0
  84. sf_veritas/patches/web_frameworks/eve.py +401 -0
  85. sf_veritas/patches/web_frameworks/falcon.py +931 -0
  86. sf_veritas/patches/web_frameworks/fastapi.py +738 -0
  87. sf_veritas/patches/web_frameworks/flask.py +526 -0
  88. sf_veritas/patches/web_frameworks/klein.py +501 -0
  89. sf_veritas/patches/web_frameworks/litestar.py +616 -0
  90. sf_veritas/patches/web_frameworks/pyramid.py +440 -0
  91. sf_veritas/patches/web_frameworks/quart.py +841 -0
  92. sf_veritas/patches/web_frameworks/robyn.py +708 -0
  93. sf_veritas/patches/web_frameworks/sanic.py +874 -0
  94. sf_veritas/patches/web_frameworks/starlette.py +742 -0
  95. sf_veritas/patches/web_frameworks/strawberry.py +1446 -0
  96. sf_veritas/patches/web_frameworks/tornado.py +485 -0
  97. sf_veritas/patches/web_frameworks/utils.py +170 -0
  98. sf_veritas/print_override.py +13 -0
  99. sf_veritas/regular_data_transmitter.py +444 -0
  100. sf_veritas/request_interceptor.py +401 -0
  101. sf_veritas/request_utils.py +550 -0
  102. sf_veritas/segfault_handler.py +116 -0
  103. sf_veritas/server_status.py +1 -0
  104. sf_veritas/shutdown_flag.py +11 -0
  105. sf_veritas/subprocess_startup.py +3 -0
  106. sf_veritas/test_cli.py +145 -0
  107. sf_veritas/thread_local.py +1319 -0
  108. sf_veritas/timeutil.py +114 -0
  109. sf_veritas/transmit_exception_to_sailfish.py +28 -0
  110. sf_veritas/transmitter.py +132 -0
  111. sf_veritas/types.py +47 -0
  112. sf_veritas/unified_interceptor.py +1678 -0
  113. sf_veritas/utils.py +39 -0
  114. sf_veritas-0.11.10.dist-info/METADATA +97 -0
  115. sf_veritas-0.11.10.dist-info/RECORD +141 -0
  116. sf_veritas-0.11.10.dist-info/WHEEL +5 -0
  117. sf_veritas-0.11.10.dist-info/entry_points.txt +2 -0
  118. sf_veritas-0.11.10.dist-info/top_level.txt +1 -0
  119. sf_veritas.libs/libbrotlicommon-6ce2a53c.so.1.0.6 +0 -0
  120. sf_veritas.libs/libbrotlidec-811d1be3.so.1.0.6 +0 -0
  121. sf_veritas.libs/libcom_err-730ca923.so.2.1 +0 -0
  122. sf_veritas.libs/libcrypt-52aca757.so.1.1.0 +0 -0
  123. sf_veritas.libs/libcrypto-bdaed0ea.so.1.1.1k +0 -0
  124. sf_veritas.libs/libcurl-eaa3cf66.so.4.5.0 +0 -0
  125. sf_veritas.libs/libgssapi_krb5-323bbd21.so.2.2 +0 -0
  126. sf_veritas.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
  127. sf_veritas.libs/libk5crypto-9a74ff38.so.3.1 +0 -0
  128. sf_veritas.libs/libkeyutils-2777d33d.so.1.6 +0 -0
  129. sf_veritas.libs/libkrb5-a55300e8.so.3.3 +0 -0
  130. sf_veritas.libs/libkrb5support-e6594cfc.so.0.1 +0 -0
  131. sf_veritas.libs/liblber-2-d20824ef.4.so.2.10.9 +0 -0
  132. sf_veritas.libs/libldap-2-cea2a960.4.so.2.10.9 +0 -0
  133. sf_veritas.libs/libnghttp2-39367a22.so.14.17.0 +0 -0
  134. sf_veritas.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
  135. sf_veritas.libs/libpsl-99becdd3.so.5.3.1 +0 -0
  136. sf_veritas.libs/libsasl2-7de4d792.so.3.0.0 +0 -0
  137. sf_veritas.libs/libselinux-d0805dcb.so.1 +0 -0
  138. sf_veritas.libs/libssh-c11d285b.so.4.8.7 +0 -0
  139. sf_veritas.libs/libssl-60250281.so.1.1.1k +0 -0
  140. sf_veritas.libs/libunistring-05abdd40.so.2.1.0 +0 -0
  141. sf_veritas.libs/libuuid-95b83d40.so.1.3.0 +0 -0
@@ -0,0 +1,742 @@
1
+ """
2
+ OTEL-STYLE PURE ASYNC PATTERN:
3
+ • Call C extension directly AFTER response sent
4
+ • C queues to lock-free ring buffer and returns in ~1µs
5
+ • ASGI event loop returns instantly (doesn't wait)
6
+ • C background thread does ALL work with GIL released
7
+ • This should MATCH OTEL performance (identical pattern)
8
+
9
+ KEY INSIGHT: No Python threads! C extension handles everything.
10
+ """
11
+
12
+ import gc
13
+ import os
14
+ import threading
15
+ from typing import List, Optional
16
+
17
+ from ... import _sffuncspan, app_config
18
+ from ...constants import (
19
+ FUNCSPAN_OVERRIDE_HEADER_BYTES,
20
+ SAILFISH_TRACING_HEADER,
21
+ SAILFISH_TRACING_HEADER_BYTES,
22
+ )
23
+ from ...custom_excepthook import custom_excepthook
24
+ from ...env_vars import (
25
+ SF_DEBUG,
26
+ SF_NETWORKHOP_CAPTURE_ENABLED,
27
+ SF_NETWORKHOP_CAPTURE_REQUEST_BODY,
28
+ SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS,
29
+ SF_NETWORKHOP_CAPTURE_RESPONSE_BODY,
30
+ SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS,
31
+ SF_NETWORKHOP_REQUEST_LIMIT_MB,
32
+ SF_NETWORKHOP_RESPONSE_LIMIT_MB,
33
+ )
34
+ from ...fast_network_hop import fast_send_network_hop_fast, register_endpoint
35
+ from ...thread_local import (
36
+ clear_c_tls_parent_trace_id,
37
+ clear_current_request_path,
38
+ clear_funcspan_override,
39
+ clear_outbound_header_base,
40
+ clear_trace_id,
41
+ generate_new_trace_id,
42
+ get_or_set_sf_trace_id,
43
+ get_sf_trace_id,
44
+ set_current_request_path,
45
+ set_funcspan_override,
46
+ set_outbound_header_base,
47
+ )
48
+ from .cors_utils import inject_sailfish_headers, should_inject_headers
49
+ from .utils import _is_user_code, _unwrap_user_func, should_skip_route, reinitialize_log_print_capture_for_worker
50
+
51
+ _SKIP_TRACING_ATTR = "_sf_skip_tracing"
52
+
53
+ # Size limits in bytes
54
+ _REQUEST_LIMIT_BYTES = SF_NETWORKHOP_REQUEST_LIMIT_MB * 1024 * 1024
55
+ _RESPONSE_LIMIT_BYTES = SF_NETWORKHOP_RESPONSE_LIMIT_MB * 1024 * 1024
56
+
57
+ # Pre-registered endpoint IDs (maps endpoint function id -> endpoint_id from C extension)
58
+ _ENDPOINT_REGISTRY: dict[int, int] = {}
59
+
60
+ # Track which Starlette app instances have been registered (to support multiple apps)
61
+ _REGISTERED_APPS: set[int] = set()
62
+
63
+ # Routes to skip (set by patch_starlette)
64
+ _ROUTES_TO_SKIP = []
65
+
66
+ # Guard so we only patch once
67
+ _starlette_patched = False
68
+
69
+ try:
70
+ from starlette.applications import Starlette
71
+ from starlette.types import ASGIApp, Receive, Scope, Send
72
+ except ImportError:
73
+
74
+ def patch_starlette(routes_to_skip: Optional[List[str]] = None):
75
+ return
76
+
77
+ else:
78
+
79
+ def _should_trace_endpoint(endpoint_fn) -> bool:
80
+ """Check if endpoint should be traced."""
81
+ if getattr(endpoint_fn, _SKIP_TRACING_ATTR, False):
82
+ return False
83
+
84
+ code = getattr(endpoint_fn, "__code__", None)
85
+ if not code:
86
+ return False
87
+
88
+ filename = code.co_filename
89
+ if not _is_user_code(filename):
90
+ return False
91
+
92
+ if getattr(endpoint_fn, "__module__", "").startswith("strawberry"):
93
+ return False
94
+
95
+ return True
96
+
97
+ def _pre_register_endpoints(
98
+ app: Starlette, routes_to_skip: Optional[List[str]] = None
99
+ ):
100
+ """Pre-register all endpoints at startup."""
101
+ routes_to_skip = routes_to_skip or []
102
+ count = 0
103
+ skipped = 0
104
+
105
+ app_id = id(app)
106
+
107
+ # Check if this app has already been registered
108
+ if app_id in _REGISTERED_APPS:
109
+ if SF_DEBUG and app_config._interceptors_initialized:
110
+ print(
111
+ f"[[_pre_register_endpoints]] App {app_id} already registered, skipping",
112
+ log=False,
113
+ )
114
+ return
115
+
116
+ if SF_DEBUG and app_config._interceptors_initialized:
117
+ print(
118
+ f"[[_pre_register_endpoints]] Starting registration for app {app_id}, app has {len(app.routes)} routes",
119
+ log=False,
120
+ )
121
+
122
+ for route in app.routes:
123
+ if not hasattr(route, "endpoint"):
124
+ skipped += 1
125
+ if SF_DEBUG and app_config._interceptors_initialized:
126
+ print(
127
+ f"[[_pre_register_endpoints]] Skipping route (no endpoint): {route}",
128
+ log=False,
129
+ )
130
+ continue
131
+
132
+ original_endpoint = route.endpoint
133
+ endpoint_fn_id = id(original_endpoint)
134
+
135
+ # Check if this specific endpoint function is already registered
136
+ if endpoint_fn_id in _ENDPOINT_REGISTRY:
137
+ if SF_DEBUG and app_config._interceptors_initialized:
138
+ print(
139
+ f"[[_pre_register_endpoints]] Endpoint function {original_endpoint.__name__ if hasattr(original_endpoint, '__name__') else original_endpoint} already registered (id={_ENDPOINT_REGISTRY[endpoint_fn_id]}), skipping",
140
+ log=False,
141
+ )
142
+ continue
143
+
144
+ # Check for @skip_network_tracing on the wrapped function BEFORE unwrapping
145
+ if getattr(original_endpoint, _SKIP_TRACING_ATTR, False):
146
+ skipped += 1
147
+ if SF_DEBUG and app_config._interceptors_initialized:
148
+ print(
149
+ f"[[_pre_register_endpoints]] Skipping endpoint (marked with @skip_network_tracing): {original_endpoint.__name__ if hasattr(original_endpoint, '__name__') else original_endpoint}",
150
+ log=False,
151
+ )
152
+ continue
153
+
154
+ unwrapped = _unwrap_user_func(original_endpoint)
155
+
156
+ if not _should_trace_endpoint(unwrapped):
157
+ skipped += 1
158
+ if SF_DEBUG and app_config._interceptors_initialized:
159
+ print(
160
+ f"[[_pre_register_endpoints]] Skipping endpoint (not user code): {unwrapped.__name__ if hasattr(unwrapped, '__name__') else unwrapped}",
161
+ log=False,
162
+ )
163
+ continue
164
+
165
+ code = unwrapped.__code__
166
+ line_no_str = str(code.co_firstlineno)
167
+ name = unwrapped.__name__
168
+ filename = code.co_filename
169
+
170
+ # Extract route pattern (e.g., "/log/{n}")
171
+ route_pattern = getattr(route, "path", None)
172
+
173
+ # Check if route should be skipped based on wildcard patterns
174
+ if should_skip_route(route_pattern, routes_to_skip):
175
+ skipped += 1
176
+ if SF_DEBUG and app_config._interceptors_initialized:
177
+ print(
178
+ f"[[_pre_register_endpoints]] Skipping endpoint (route matches skip pattern): {route_pattern}",
179
+ log=False,
180
+ )
181
+ continue
182
+
183
+ endpoint_id = register_endpoint(
184
+ line=line_no_str,
185
+ column="0",
186
+ name=name,
187
+ entrypoint=filename,
188
+ route=route_pattern,
189
+ )
190
+
191
+ if endpoint_id < 0:
192
+ if SF_DEBUG and app_config._interceptors_initialized:
193
+ print(
194
+ f"[[_pre_register_endpoints]] Failed to register {name} (endpoint_id={endpoint_id})",
195
+ log=False,
196
+ )
197
+ continue
198
+
199
+ _ENDPOINT_REGISTRY[endpoint_fn_id] = endpoint_id
200
+ count += 1
201
+
202
+ if SF_DEBUG and app_config._interceptors_initialized:
203
+ print(
204
+ f"[[patch_starlette]] Registered: {name} @ {filename}:{line_no_str} route={route_pattern} (endpoint_fn_id={endpoint_fn_id}, endpoint_id={endpoint_id})",
205
+ log=False,
206
+ )
207
+
208
+ if SF_DEBUG and app_config._interceptors_initialized:
209
+ print(f"[[patch_starlette]] Total endpoints registered: {count}", log=False)
210
+
211
+ # Only mark this app as registered if we actually registered user endpoints
212
+ if count > 0:
213
+ _REGISTERED_APPS.add(app_id)
214
+ if SF_DEBUG and app_config._interceptors_initialized:
215
+ print(
216
+ f"[[patch_starlette]] App {app_id} marked as registered", log=False
217
+ )
218
+ else:
219
+ if SF_DEBUG and app_config._interceptors_initialized:
220
+ print(
221
+ f"[[patch_starlette]] No user endpoints registered yet for app {app_id}, will retry on startup",
222
+ log=False,
223
+ )
224
+
225
+ def patch_starlette(routes_to_skip: Optional[List[str]] = None):
226
+ """
227
+ OTEL-STYLE PURE ASYNC:
228
+ • Captures endpoint metadata via one-shot profiler
229
+ • Emits NetworkHop AFTER response sent (no blocking)
230
+ • Direct C call returns instantly (~1µs)
231
+ • C background thread handles all network I/O
232
+ • Zero Python threads = OTEL-level performance
233
+ """
234
+ global _starlette_patched, _ROUTES_TO_SKIP
235
+ if _starlette_patched:
236
+ return
237
+ _starlette_patched = True
238
+ _ROUTES_TO_SKIP = routes_to_skip or []
239
+
240
+ # ----------------- ASGI middleware -----------------
241
+ class NetworkHopMiddleware:
242
+ """ZERO-OVERHEAD network hop capture middleware.
243
+
244
+ OTEL-STYLE PATTERN:
245
+ - Call C extension directly AFTER send completes
246
+ - C queues message and returns in ~1µs (lock-free ring buffer)
247
+ - C background thread does ALL network work with GIL released
248
+ - ASGI event loop never blocks!
249
+
250
+ This matches OTEL's performance because we use the same pattern!
251
+ """
252
+
253
+ def __init__(self, app: ASGIApp):
254
+ self.app = app
255
+
256
+ async def __call__(self, scope: Scope, receive: Receive, send: Send):
257
+ if scope.get("type") != "http":
258
+ await self.app(scope, receive, send)
259
+ return
260
+
261
+ # Set current request path for route-based suppression (SF_DISABLE_INBOUND_NETWORK_TRACING_ON_ROUTES)
262
+ request_path = scope.get("path", "")
263
+ set_current_request_path(request_path)
264
+
265
+ # Always print to verify middleware is being called
266
+ if SF_DEBUG and app_config._interceptors_initialized:
267
+ print(
268
+ f"[[NetworkHopMiddleware.__call__]] HTTP request to {request_path}, type={scope.get('type')}",
269
+ log=False,
270
+ )
271
+
272
+ # PERFORMANCE: Single-pass bytes-level header scan (no dict allocation until needed)
273
+ # Scan headers once on bytes, only decode what we need, use latin-1 (fast 1:1 byte map)
274
+ hdr_tuples = scope.get("headers") or ()
275
+ incoming_trace_raw = None # bytes
276
+ funcspan_raw = None # bytes
277
+ req_headers = None # dict[str,str] only if capture enabled
278
+
279
+ capture_req_headers = (
280
+ SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS # local cache
281
+ )
282
+
283
+ if capture_req_headers:
284
+ # decode once using latin-1 (1:1 bytes, faster than utf-8 and never throws)
285
+ tmp = {}
286
+ for k, v in hdr_tuples:
287
+ kl = k.lower()
288
+ if kl == SAILFISH_TRACING_HEADER_BYTES:
289
+ incoming_trace_raw = v
290
+ elif kl == FUNCSPAN_OVERRIDE_HEADER_BYTES:
291
+ funcspan_raw = v
292
+ # build the dict while we're here
293
+ tmp[k.decode("latin-1")] = v.decode("latin-1")
294
+ req_headers = tmp
295
+ else:
296
+ for k, v in hdr_tuples:
297
+ kl = k.lower()
298
+ if kl == SAILFISH_TRACING_HEADER_BYTES:
299
+ incoming_trace_raw = v
300
+ elif kl == FUNCSPAN_OVERRIDE_HEADER_BYTES:
301
+ funcspan_raw = v
302
+ # no dict build
303
+
304
+ # CRITICAL: Seed/ensure trace_id immediately (BEFORE any outbound work)
305
+ if incoming_trace_raw:
306
+ # Incoming X-Sf3-Rid header provided - use it
307
+ incoming_trace = incoming_trace_raw.decode("latin-1")
308
+ get_or_set_sf_trace_id(
309
+ incoming_trace, is_associated_with_inbound_request=True
310
+ )
311
+ else:
312
+ # No incoming X-Sf3-Rid header - generate fresh trace_id for this request
313
+ generate_new_trace_id()
314
+
315
+ # Optional funcspan override (decode only if present)
316
+ funcspan_override_header = (
317
+ funcspan_raw.decode("latin-1") if funcspan_raw else None
318
+ )
319
+ if funcspan_override_header:
320
+ try:
321
+ set_funcspan_override(funcspan_override_header)
322
+ if SF_DEBUG and app_config._interceptors_initialized:
323
+ print(
324
+ f"[[NetworkHopMiddleware]] Set function span override from header: {funcspan_override_header}",
325
+ log=False,
326
+ )
327
+ except Exception as e:
328
+ if SF_DEBUG and app_config._interceptors_initialized:
329
+ print(
330
+ f"[[NetworkHopMiddleware]] Failed to set function span override: {e}",
331
+ log=False,
332
+ )
333
+
334
+ # Initialize outbound base without list/allocs from split()
335
+ try:
336
+ trace_id = get_sf_trace_id()
337
+ if trace_id:
338
+ s = str(trace_id)
339
+ i = s.find("/") # session
340
+ j = s.find("/", i + 1) if i != -1 else -1 # page
341
+ if j != -1:
342
+ base_trace = s[:j] # "session/page"
343
+ set_outbound_header_base(
344
+ base_trace=base_trace,
345
+ parent_trace_id=s, # "session/page/uuid"
346
+ funcspan=funcspan_override_header,
347
+ )
348
+ if SF_DEBUG and app_config._interceptors_initialized:
349
+ print(
350
+ f"[[NetworkHopMiddleware]] Initialized outbound header base (base={base_trace[:16]}...)",
351
+ log=False,
352
+ )
353
+ except Exception as e:
354
+ if SF_DEBUG and app_config._interceptors_initialized:
355
+ print(
356
+ f"[[NetworkHopMiddleware]] Failed to initialize outbound header base: {e}",
357
+ log=False,
358
+ )
359
+
360
+ # OPTIMIZATION: Skip ALL capture infrastructure if not capturing network hops
361
+ # We still needed to set up trace_id and outbound header base above (for outbound call tracing)
362
+ # but we can skip all request/response capture overhead
363
+ if not SF_NETWORKHOP_CAPTURE_ENABLED:
364
+ try:
365
+ await self.app(scope, receive, send)
366
+ except Exception as exc: # noqa: BLE001
367
+ custom_excepthook(type(exc), exc, exc.__traceback__)
368
+ raise
369
+ finally:
370
+ # CRITICAL: Clear C TLS to prevent stale data in thread pools
371
+ clear_c_tls_parent_trace_id()
372
+
373
+ # CRITICAL: Clear outbound header base to prevent stale cached headers
374
+ clear_outbound_header_base()
375
+
376
+ # CRITICAL: Clear trace_id to ensure fresh generation for next request
377
+ clear_trace_id()
378
+
379
+ # CRITICAL: Clear current request path to prevent stale data in thread pools
380
+ clear_current_request_path()
381
+
382
+ # Clear function span override for this request (ContextVar cleanup - also syncs C thread-local)
383
+ try:
384
+ clear_funcspan_override()
385
+ except Exception:
386
+ pass
387
+ return
388
+
389
+ # NOTE: req_headers already captured in single-pass scan above (if enabled)
390
+
391
+ # Capture request body if enabled (must intercept receive)
392
+ req_body_chunks = []
393
+
394
+ # OPTIMIZATION: Only wrap receive if we need to capture request body
395
+ if SF_NETWORKHOP_CAPTURE_REQUEST_BODY:
396
+
397
+ async def wrapped_receive():
398
+ message = await receive()
399
+ if message["type"] == "http.request":
400
+ body = message.get("body", b"")
401
+ if body:
402
+ req_body_chunks.append(body)
403
+ return message
404
+
405
+ else:
406
+ wrapped_receive = receive
407
+
408
+ # Capture response headers and body if enabled
409
+ resp_headers = None
410
+ resp_body_chunks = []
411
+
412
+ # OPTIMIZATION: Cache debug flag check (avoid repeated lookups)
413
+ _debug_enabled = SF_DEBUG and app_config._interceptors_initialized
414
+
415
+ # OPTIMIZATION: Cache capture flags (avoid repeated global lookups in hot path)
416
+ _capture_resp_headers = SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS
417
+ _capture_resp_body = SF_NETWORKHOP_CAPTURE_RESPONSE_BODY
418
+
419
+ async def wrapped_send(message):
420
+ nonlocal resp_headers
421
+
422
+ # ULTRA-FAST PATH: Most messages just pass through without any processing
423
+ # Only http.response.body (final) triggers network hop collection
424
+ msg_type = message.get("type")
425
+
426
+ # FAST PATH: Early exit for non-body messages (http.response.start, etc.)
427
+ if msg_type != "http.response.body":
428
+ # Capture response headers if needed (only on http.response.start)
429
+ if _capture_resp_headers and msg_type == "http.response.start":
430
+ try:
431
+ resp_headers = {
432
+ k.decode(): v.decode()
433
+ for k, v in message.get("headers", [])
434
+ }
435
+ except Exception:
436
+ pass
437
+ await send(message)
438
+ return
439
+
440
+ # BODY PATH: Capture body chunks if needed
441
+ if _capture_resp_body:
442
+ body = message.get("body", b"")
443
+ if body:
444
+ resp_body_chunks.append(body)
445
+
446
+ # Send the actual message first
447
+ await send(message)
448
+
449
+ # OPTIMIZATION: Early exit if there's more body chunks coming
450
+ if message.get("more_body", False):
451
+ return
452
+
453
+ # NOW we can get the endpoint (it's been populated by the router)
454
+ endpoint_fn = scope.get("endpoint")
455
+ if not endpoint_fn:
456
+ return # Early exit if no endpoint
457
+
458
+ endpoint_id = _ENDPOINT_REGISTRY.get(id(endpoint_fn))
459
+ if endpoint_id is None or endpoint_id < 0:
460
+ if _debug_enabled:
461
+ print(
462
+ f"[[NetworkHopMiddleware]] Skipping NetworkHop (endpoint_id={endpoint_id}), registry has {len(_ENDPOINT_REGISTRY)} endpoints",
463
+ log=False,
464
+ )
465
+ return # Early exit if invalid endpoint
466
+
467
+ if _debug_enabled:
468
+ print(
469
+ f"[[NetworkHopMiddleware]] Response complete for {scope.get('path')}, endpoint_fn={endpoint_fn.__name__ if hasattr(endpoint_fn, '__name__') else endpoint_fn}, endpoint_id={endpoint_id}",
470
+ log=False,
471
+ )
472
+
473
+ # Only proceed if we have valid endpoint
474
+ try:
475
+ # OPTIMIZATION: Use get_sf_trace_id() directly instead of get_or_set_sf_trace_id()
476
+ # Trace ID is GUARANTEED to be set at request start
477
+ # This saves ~11-12μs by avoiding tuple unpacking and conditional logic
478
+ session_id = get_sf_trace_id()
479
+
480
+ # OPTIMIZATION: Consolidate body chunks efficiently
481
+ req_body = None
482
+ if req_body_chunks:
483
+ joined = b"".join(req_body_chunks)
484
+ req_body = (
485
+ joined
486
+ if len(joined) <= _REQUEST_LIMIT_BYTES
487
+ else joined[:_REQUEST_LIMIT_BYTES]
488
+ )
489
+
490
+ resp_body = None
491
+ if resp_body_chunks:
492
+ joined = b"".join(resp_body_chunks)
493
+ resp_body = (
494
+ joined
495
+ if len(joined) <= _RESPONSE_LIMIT_BYTES
496
+ else joined[:_RESPONSE_LIMIT_BYTES]
497
+ )
498
+
499
+ # Direct C call - it queues to background worker, returns instantly
500
+ fast_send_network_hop_fast(
501
+ session_id=session_id,
502
+ endpoint_id=endpoint_id,
503
+ raw_path=scope["path"],
504
+ raw_query_string=scope["query_string"],
505
+ request_headers=req_headers,
506
+ request_body=req_body,
507
+ response_headers=resp_headers,
508
+ response_body=resp_body,
509
+ )
510
+ if _debug_enabled:
511
+ print(
512
+ f"[[NetworkHopMiddleware]] Emitted NetworkHop for endpoint_id={endpoint_id}",
513
+ log=False,
514
+ )
515
+ except Exception as e: # noqa: BLE001 S110
516
+ if _debug_enabled:
517
+ print(
518
+ f"[[NetworkHopMiddleware]] Failed to emit NetworkHop: {e}",
519
+ log=False,
520
+ )
521
+
522
+ try:
523
+ await self.app(scope, wrapped_receive, wrapped_send)
524
+ except Exception as exc: # noqa: BLE001
525
+ custom_excepthook(type(exc), exc, exc.__traceback__)
526
+ raise
527
+ finally:
528
+ # CRITICAL: Clear C TLS to prevent stale data in thread pools
529
+ clear_c_tls_parent_trace_id()
530
+
531
+ # CRITICAL: Clear outbound header base to prevent stale cached headers
532
+ # ContextVar does NOT automatically clean up in thread pools - must clear explicitly
533
+ clear_outbound_header_base()
534
+
535
+ # CRITICAL: Clear trace_id to ensure fresh generation for next request
536
+ # Without this, get_or_set_sf_trace_id() reuses trace_id from previous request
537
+ # causing X-Sf4-Prid to stay constant when no incoming X-Sf3-Rid header
538
+ clear_trace_id()
539
+
540
+ # CRITICAL: Clear current request path to prevent stale data in thread pools
541
+ clear_current_request_path()
542
+
543
+ # Clear function span override for this request (ContextVar cleanup - also syncs C thread-local)
544
+ try:
545
+ clear_funcspan_override()
546
+ except Exception:
547
+ pass
548
+
549
+ # ----------------- patch Starlette init -----------------
550
+ original_init = Starlette.__init__
551
+
552
+ def patched_init(self, *args, **kwargs):
553
+ # 1) Run the original constructor
554
+ original_init(self, *args, **kwargs)
555
+
556
+ # 2) Skip if this is a FastAPI app (FastAPI has its own patching)
557
+ # FastAPI inherits from Starlette, so we check the class name
558
+ if self.__class__.__name__ == "FastAPI":
559
+ if SF_DEBUG and app_config._interceptors_initialized:
560
+ print(
561
+ "[[patch_starlette]] Skipping FastAPI app (has dedicated patching)",
562
+ log=False,
563
+ )
564
+ return
565
+
566
+ if SF_DEBUG and app_config._interceptors_initialized:
567
+ print(
568
+ f"[[patch_starlette]] Starlette app created: {self.__class__.__name__}",
569
+ log=False,
570
+ )
571
+
572
+ # 3) Insert our ASGI middleware at the top
573
+ self.add_middleware(NetworkHopMiddleware)
574
+
575
+ # Try to register endpoints immediately if routes are already defined
576
+ if hasattr(self, "routes") and self.routes:
577
+ try:
578
+ if SF_DEBUG and app_config._interceptors_initialized:
579
+ print(
580
+ f"[[patch_starlette]] Routes already defined ({len(self.routes)} routes), registering immediately",
581
+ log=False,
582
+ )
583
+ _pre_register_endpoints(self, _ROUTES_TO_SKIP)
584
+ except Exception as e:
585
+ if SF_DEBUG and app_config._interceptors_initialized:
586
+ print(
587
+ f"[[patch_starlette]] Immediate registration failed: {e}",
588
+ log=False,
589
+ )
590
+
591
+ # Also register on startup event as a fallback (for routes added after __init__)
592
+ @self.on_event("startup")
593
+ async def _sf_startup():
594
+ # Note: Profiler is already installed by unified_interceptor.py
595
+
596
+ if SF_DEBUG and app_config._interceptors_initialized:
597
+ print(
598
+ "[[patch_starlette]] Startup event fired, registering endpoints",
599
+ log=False,
600
+ )
601
+ # _pre_register_endpoints now checks if this app was already registered
602
+ _pre_register_endpoints(self, _ROUTES_TO_SKIP)
603
+ if SF_DEBUG and app_config._interceptors_initialized:
604
+ print(
605
+ "[[patch_starlette]] ZERO-OVERHEAD pattern activated (truly async, no blocking)",
606
+ log=False,
607
+ )
608
+
609
+ if SF_DEBUG and app_config._interceptors_initialized:
610
+ print("[[patch_starlette]] Installed NetworkHopMiddleware", log=False)
611
+
612
+ Starlette.__init__ = patched_init
613
+
614
+ # Also patch any existing Starlette instances that were created before patching
615
+ # This handles the case where app = Starlette() happens before setup_interceptors()
616
+ for obj in gc.get_objects():
617
+ try:
618
+ # Wrap in try-except to safely handle lazy objects (e.g., Django settings)
619
+ # that trigger initialization on attribute access
620
+ if isinstance(obj, Starlette) and obj.__class__.__name__ != "FastAPI":
621
+ # Check if this app already has our middleware
622
+ has_our_middleware = any(
623
+ m.__class__.__name__ == "NetworkHopMiddleware"
624
+ for m in getattr(obj, "user_middleware", [])
625
+ )
626
+ if not has_our_middleware:
627
+ if SF_DEBUG and app_config._interceptors_initialized:
628
+ print(
629
+ f"[[patch_starlette]] Retroactively patching existing Starlette app",
630
+ log=False,
631
+ )
632
+ obj.add_middleware(NetworkHopMiddleware)
633
+
634
+ # Try immediate registration if routes exist
635
+ if obj.routes:
636
+ try:
637
+ if SF_DEBUG and app_config._interceptors_initialized:
638
+ print(
639
+ f"[[patch_starlette]] Retroactive immediate registration ({len(obj.routes)} routes)",
640
+ log=False,
641
+ )
642
+ _pre_register_endpoints(obj, _ROUTES_TO_SKIP)
643
+ except Exception as e:
644
+ if SF_DEBUG and app_config._interceptors_initialized:
645
+ print(
646
+ f"[[patch_starlette]] Retroactive immediate registration failed: {e}",
647
+ log=False,
648
+ )
649
+
650
+ @obj.on_event("startup")
651
+ async def _sf_startup_retro():
652
+ # Note: Profiler is already installed by unified_interceptor.py
653
+
654
+ if SF_DEBUG and app_config._interceptors_initialized:
655
+ print(
656
+ "[[patch_starlette]] Retroactive startup event fired",
657
+ log=False,
658
+ )
659
+ # _pre_register_endpoints now checks if this app was already registered
660
+ _pre_register_endpoints(obj, _ROUTES_TO_SKIP)
661
+ if SF_DEBUG and app_config._interceptors_initialized:
662
+ print(
663
+ "[[patch_starlette]] Retroactive registration complete",
664
+ log=False,
665
+ )
666
+
667
+ except Exception:
668
+ # Silently skip objects that fail isinstance checks (e.g., Django lazy settings)
669
+ pass
670
+
671
+ # ----------------- CORS patching -----------------
672
+ def patch_starlette_cors():
673
+ """
674
+ Patch Starlette's CORSMiddleware to automatically inject Sailfish headers.
675
+
676
+ SAFE: Only modifies CORS if CORSMiddleware is used by the application.
677
+ """
678
+ try:
679
+ from starlette.middleware.cors import CORSMiddleware
680
+ except ImportError:
681
+ # CORSMiddleware not available, skip patching
682
+ if SF_DEBUG and app_config._interceptors_initialized:
683
+ print(
684
+ "[[patch_starlette_cors]] Starlette CORSMiddleware not found, skipping",
685
+ log=False,
686
+ )
687
+ return
688
+
689
+ # Check if already patched (might be patched by FastAPI)
690
+ if hasattr(CORSMiddleware, "_sf_cors_patched"):
691
+ if SF_DEBUG and app_config._interceptors_initialized:
692
+ print(
693
+ "[[patch_starlette_cors]] Already patched, skipping", log=False
694
+ )
695
+ return
696
+
697
+ original_cors_init = CORSMiddleware.__init__
698
+
699
+ def patched_cors_init(
700
+ self,
701
+ app,
702
+ allow_origins=(),
703
+ allow_methods=(),
704
+ allow_headers=(),
705
+ allow_credentials=False,
706
+ allow_origin_regex=None,
707
+ expose_headers=(),
708
+ max_age=600,
709
+ ):
710
+ # Intercept allow_headers parameter
711
+ if should_inject_headers(allow_headers):
712
+ allow_headers = inject_sailfish_headers(allow_headers)
713
+ if SF_DEBUG and app_config._interceptors_initialized:
714
+ print(
715
+ "[[patch_starlette_cors]] Injected Sailfish headers into CORSMiddleware",
716
+ log=False,
717
+ )
718
+
719
+ # Call original init with potentially modified headers
720
+ original_cors_init(
721
+ self,
722
+ app,
723
+ allow_origins=allow_origins,
724
+ allow_methods=allow_methods,
725
+ allow_headers=allow_headers,
726
+ allow_credentials=allow_credentials,
727
+ allow_origin_regex=allow_origin_regex,
728
+ expose_headers=expose_headers,
729
+ max_age=max_age,
730
+ )
731
+
732
+ CORSMiddleware.__init__ = patched_cors_init
733
+ CORSMiddleware._sf_cors_patched = True
734
+
735
+ if SF_DEBUG and app_config._interceptors_initialized:
736
+ print(
737
+ "[[patch_starlette_cors]] Successfully patched Starlette CORSMiddleware",
738
+ log=False,
739
+ )
740
+
741
+ # Call CORS patching
742
+ patch_starlette_cors()