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