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,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
+ )