sf-veritas 0.10.3__cp314-cp314-manylinux_2_28_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

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