sf-veritas 0.10.3__cp311-cp311-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.
- sf_veritas/__init__.py +20 -0
- sf_veritas/_sffastlog.c +889 -0
- sf_veritas/_sffastlog.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +924 -0
- sf_veritas/_sffastnet.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +730 -0
- sf_veritas/_sffastnetworkrequest.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2155 -0
- sf_veritas/_sffuncspan.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +617 -0
- sf_veritas/_sffuncspan_config.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfheadercheck.c +341 -0
- sf_veritas/_sfheadercheck.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfnetworkhop.c +1451 -0
- sf_veritas/_sfnetworkhop.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1175 -0
- sf_veritas/_sfservice.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfteepreload.c +5167 -0
- sf_veritas/app_config.py +49 -0
- sf_veritas/cli.py +336 -0
- sf_veritas/constants.py +10 -0
- sf_veritas/custom_excepthook.py +304 -0
- sf_veritas/custom_log_handler.py +129 -0
- sf_veritas/custom_output_wrapper.py +144 -0
- sf_veritas/custom_print.py +146 -0
- sf_veritas/django_app.py +5 -0
- sf_veritas/env_vars.py +186 -0
- sf_veritas/exception_handling_middleware.py +18 -0
- sf_veritas/exception_metaclass.py +69 -0
- sf_veritas/fast_frame_info.py +116 -0
- sf_veritas/fast_network_hop.py +293 -0
- sf_veritas/frame_tools.py +112 -0
- sf_veritas/funcspan_config_loader.py +556 -0
- sf_veritas/function_span_profiler.py +1174 -0
- sf_veritas/import_hook.py +62 -0
- sf_veritas/infra_details/__init__.py +3 -0
- sf_veritas/infra_details/get_infra_details.py +24 -0
- sf_veritas/infra_details/kubernetes/__init__.py +3 -0
- sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
- sf_veritas/infra_details/kubernetes/get_details.py +7 -0
- sf_veritas/infra_details/running_on/__init__.py +17 -0
- sf_veritas/infra_details/running_on/kubernetes.py +11 -0
- sf_veritas/interceptors.py +497 -0
- sf_veritas/libsfnettee.so +0 -0
- sf_veritas/local_env_detect.py +118 -0
- sf_veritas/package_metadata.py +6 -0
- sf_veritas/patches/__init__.py +0 -0
- sf_veritas/patches/concurrent_futures.py +19 -0
- sf_veritas/patches/constants.py +1 -0
- sf_veritas/patches/exceptions.py +82 -0
- sf_veritas/patches/multiprocessing.py +32 -0
- sf_veritas/patches/network_libraries/__init__.py +76 -0
- sf_veritas/patches/network_libraries/aiohttp.py +281 -0
- sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
- sf_veritas/patches/network_libraries/http_client.py +419 -0
- sf_veritas/patches/network_libraries/httpcore.py +515 -0
- sf_veritas/patches/network_libraries/httplib2.py +204 -0
- sf_veritas/patches/network_libraries/httpx.py +515 -0
- sf_veritas/patches/network_libraries/niquests.py +211 -0
- sf_veritas/patches/network_libraries/pycurl.py +385 -0
- sf_veritas/patches/network_libraries/requests.py +633 -0
- sf_veritas/patches/network_libraries/tornado.py +341 -0
- sf_veritas/patches/network_libraries/treq.py +270 -0
- sf_veritas/patches/network_libraries/urllib_request.py +468 -0
- sf_veritas/patches/network_libraries/utils.py +398 -0
- sf_veritas/patches/os.py +17 -0
- sf_veritas/patches/threading.py +218 -0
- sf_veritas/patches/web_frameworks/__init__.py +54 -0
- sf_veritas/patches/web_frameworks/aiohttp.py +793 -0
- sf_veritas/patches/web_frameworks/async_websocket_consumer.py +317 -0
- sf_veritas/patches/web_frameworks/blacksheep.py +527 -0
- sf_veritas/patches/web_frameworks/bottle.py +502 -0
- sf_veritas/patches/web_frameworks/cherrypy.py +678 -0
- sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
- sf_veritas/patches/web_frameworks/django.py +944 -0
- sf_veritas/patches/web_frameworks/eve.py +395 -0
- sf_veritas/patches/web_frameworks/falcon.py +926 -0
- sf_veritas/patches/web_frameworks/fastapi.py +724 -0
- sf_veritas/patches/web_frameworks/flask.py +520 -0
- sf_veritas/patches/web_frameworks/klein.py +501 -0
- sf_veritas/patches/web_frameworks/litestar.py +551 -0
- sf_veritas/patches/web_frameworks/pyramid.py +428 -0
- sf_veritas/patches/web_frameworks/quart.py +824 -0
- sf_veritas/patches/web_frameworks/robyn.py +697 -0
- sf_veritas/patches/web_frameworks/sanic.py +857 -0
- sf_veritas/patches/web_frameworks/starlette.py +723 -0
- sf_veritas/patches/web_frameworks/strawberry.py +813 -0
- sf_veritas/patches/web_frameworks/tornado.py +481 -0
- sf_veritas/patches/web_frameworks/utils.py +91 -0
- sf_veritas/print_override.py +13 -0
- sf_veritas/regular_data_transmitter.py +409 -0
- sf_veritas/request_interceptor.py +401 -0
- sf_veritas/request_utils.py +550 -0
- sf_veritas/server_status.py +1 -0
- sf_veritas/shutdown_flag.py +11 -0
- sf_veritas/subprocess_startup.py +3 -0
- sf_veritas/test_cli.py +145 -0
- sf_veritas/thread_local.py +970 -0
- sf_veritas/timeutil.py +114 -0
- sf_veritas/transmit_exception_to_sailfish.py +28 -0
- sf_veritas/transmitter.py +132 -0
- sf_veritas/types.py +47 -0
- sf_veritas/unified_interceptor.py +1580 -0
- sf_veritas/utils.py +39 -0
- sf_veritas-0.10.3.dist-info/METADATA +97 -0
- sf_veritas-0.10.3.dist-info/RECORD +132 -0
- sf_veritas-0.10.3.dist-info/WHEEL +5 -0
- sf_veritas-0.10.3.dist-info/entry_points.txt +2 -0
- sf_veritas-0.10.3.dist-info/top_level.txt +1 -0
- sf_veritas.libs/libbrotlicommon-6ce2a53c.so.1.0.6 +0 -0
- sf_veritas.libs/libbrotlidec-811d1be3.so.1.0.6 +0 -0
- sf_veritas.libs/libcom_err-730ca923.so.2.1 +0 -0
- sf_veritas.libs/libcrypt-52aca757.so.1.1.0 +0 -0
- sf_veritas.libs/libcrypto-bdaed0ea.so.1.1.1k +0 -0
- sf_veritas.libs/libcurl-eaa3cf66.so.4.5.0 +0 -0
- sf_veritas.libs/libgssapi_krb5-323bbd21.so.2.2 +0 -0
- sf_veritas.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
- sf_veritas.libs/libk5crypto-9a74ff38.so.3.1 +0 -0
- sf_veritas.libs/libkeyutils-2777d33d.so.1.6 +0 -0
- sf_veritas.libs/libkrb5-a55300e8.so.3.3 +0 -0
- sf_veritas.libs/libkrb5support-e6594cfc.so.0.1 +0 -0
- sf_veritas.libs/liblber-2-d20824ef.4.so.2.10.9 +0 -0
- sf_veritas.libs/libldap-2-cea2a960.4.so.2.10.9 +0 -0
- sf_veritas.libs/libnghttp2-39367a22.so.14.17.0 +0 -0
- sf_veritas.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
- sf_veritas.libs/libpsl-99becdd3.so.5.3.1 +0 -0
- sf_veritas.libs/libsasl2-7de4d792.so.3.0.0 +0 -0
- sf_veritas.libs/libselinux-d0805dcb.so.1 +0 -0
- sf_veritas.libs/libssh-c11d285b.so.4.8.7 +0 -0
- sf_veritas.libs/libssl-60250281.so.1.1.1k +0 -0
- sf_veritas.libs/libunistring-05abdd40.so.2.1.0 +0 -0
- sf_veritas.libs/libuuid-95b83d40.so.1.3.0 +0 -0
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
import ctypes
|
|
3
|
+
import fnmatch
|
|
4
|
+
import functools
|
|
5
|
+
import os
|
|
6
|
+
import threading
|
|
7
|
+
import uuid
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from contextvars import ContextVar
|
|
10
|
+
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
|
11
|
+
from uuid import UUID
|
|
12
|
+
|
|
13
|
+
from . import app_config
|
|
14
|
+
from .constants import (
|
|
15
|
+
FUNCSPAN_OVERRIDE_HEADER,
|
|
16
|
+
NONSESSION_APPLOGS,
|
|
17
|
+
PARENT_SESSION_ID_HEADER,
|
|
18
|
+
SAILFISH_TRACING_HEADER,
|
|
19
|
+
)
|
|
20
|
+
from .env_vars import SF_DEBUG
|
|
21
|
+
|
|
22
|
+
# Check if LD_PRELOAD is active (cached for performance)
|
|
23
|
+
_ld_preload_active: Optional[bool] = None
|
|
24
|
+
|
|
25
|
+
# Cache SF_DEBUG flag at module load to avoid repeated checks in hot paths
|
|
26
|
+
_SF_DEBUG_ENABLED = False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def is_ld_preload_active() -> bool:
|
|
30
|
+
"""
|
|
31
|
+
Check if LD_PRELOAD with _sfteepreload is active.
|
|
32
|
+
|
|
33
|
+
When LD_PRELOAD is active, the C extension handles UUID generation
|
|
34
|
+
and appending to X-Sf3-Rid headers (much faster than Python).
|
|
35
|
+
|
|
36
|
+
Returns True if:
|
|
37
|
+
- LD_PRELOAD env var contains 'libsfnettee.so'
|
|
38
|
+
- OR SF_TEEPRELOAD_ACTIVE env var is set to '1'
|
|
39
|
+
|
|
40
|
+
This is cached on first call for performance.
|
|
41
|
+
"""
|
|
42
|
+
global _ld_preload_active, _SF_DEBUG_ENABLED
|
|
43
|
+
|
|
44
|
+
if _ld_preload_active is not None:
|
|
45
|
+
return _ld_preload_active
|
|
46
|
+
|
|
47
|
+
# Check if LD_PRELOAD contains our library
|
|
48
|
+
ld_preload = os.getenv("LD_PRELOAD", "")
|
|
49
|
+
if "libsfnettee.so" in ld_preload:
|
|
50
|
+
_ld_preload_active = True
|
|
51
|
+
_SF_DEBUG_ENABLED = SF_DEBUG and app_config._interceptors_initialized
|
|
52
|
+
if _SF_DEBUG_ENABLED:
|
|
53
|
+
print(f"[thread_local] LD_PRELOAD active: {ld_preload}", log=False)
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
# Check explicit activation flag (set by LD_PRELOAD library itself)
|
|
57
|
+
if os.getenv("SF_TEEPRELOAD_ACTIVE") == "1":
|
|
58
|
+
_ld_preload_active = True
|
|
59
|
+
_SF_DEBUG_ENABLED = SF_DEBUG and app_config._interceptors_initialized
|
|
60
|
+
if _SF_DEBUG_ENABLED:
|
|
61
|
+
print("[thread_local] SF_TEEPRELOAD_ACTIVE=1", log=False)
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
_ld_preload_active = False
|
|
65
|
+
_SF_DEBUG_ENABLED = SF_DEBUG and app_config._interceptors_initialized
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# Eager initialization at module load for C TLS function pointer
|
|
70
|
+
_sf_tls_setter = None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _init_c_tls_setter():
|
|
74
|
+
"""Initialize C TLS setter at module load time to avoid ctypes.CDLL overhead in hot path."""
|
|
75
|
+
global _sf_tls_setter
|
|
76
|
+
try:
|
|
77
|
+
# Use the main process (LD_PRELOAD library is in the global namespace)
|
|
78
|
+
_lib = ctypes.CDLL(None)
|
|
79
|
+
_fn = _lib.sf_set_parent_trace_id_tls
|
|
80
|
+
_fn.argtypes = [ctypes.c_char_p]
|
|
81
|
+
_fn.restype = None
|
|
82
|
+
_sf_tls_setter = _fn
|
|
83
|
+
except Exception:
|
|
84
|
+
_sf_tls_setter = False # don't retry every call
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# Initialize at module load time (moves expensive CDLL call out of hot path)
|
|
88
|
+
_init_c_tls_setter()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _set_c_tls_parent_trace_id(parent: str) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Set parent trace ID in C TLS for ultra-fast access by LD_PRELOAD hooks.
|
|
94
|
+
|
|
95
|
+
This avoids Python lookups in the C extension - the C hooks can read
|
|
96
|
+
the parent PRID directly from TLS with a single memory access.
|
|
97
|
+
|
|
98
|
+
CRITICAL: Must call clear_c_tls_parent_trace_id() at end of request to prevent stale data!
|
|
99
|
+
|
|
100
|
+
OPTIMIZED: Disabled when LD_PRELOAD active - C code reads from ContextVar directly (faster).
|
|
101
|
+
C function pointer initialized at module load time (not on first call).
|
|
102
|
+
"""
|
|
103
|
+
# PERFORMANCE: Skip TLS call when LD_PRELOAD active - C reads from ContextVar/shared registry
|
|
104
|
+
# This eliminates: string encoding (expensive!), thread-local attribute setting, ctypes overhead
|
|
105
|
+
if not _ld_preload_active and _sf_tls_setter:
|
|
106
|
+
# Keep bytes alive for the request lifetime to keep C pointer valid
|
|
107
|
+
b = parent.encode("ascii", "ignore")
|
|
108
|
+
_cached_outbound_headers_tls._tls_parent_prid_bytes = b # anchor
|
|
109
|
+
_sf_tls_setter(b)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def clear_c_tls_parent_trace_id() -> None:
|
|
113
|
+
"""
|
|
114
|
+
Clear parent trace ID from C TLS at end of request.
|
|
115
|
+
|
|
116
|
+
CRITICAL: Prevents stale data when threads are reused (e.g., thread pools).
|
|
117
|
+
Must be called at the end of EVERY request that set the C TLS.
|
|
118
|
+
|
|
119
|
+
OPTIMIZED: Disabled when LD_PRELOAD active - no TLS to clear.
|
|
120
|
+
"""
|
|
121
|
+
# PERFORMANCE: Skip when LD_PRELOAD active - nothing was set in TLS
|
|
122
|
+
if not _ld_preload_active and _sf_tls_setter and _sf_tls_setter is not False:
|
|
123
|
+
try:
|
|
124
|
+
# Set to NULL to clear
|
|
125
|
+
_sf_tls_setter(None)
|
|
126
|
+
# Clear the anchored bytes
|
|
127
|
+
if hasattr(_cached_outbound_headers_tls, "_tls_parent_prid_bytes"):
|
|
128
|
+
delattr(_cached_outbound_headers_tls, "_tls_parent_prid_bytes")
|
|
129
|
+
except Exception:
|
|
130
|
+
pass # Ignore errors during cleanup
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def clear_outbound_header_base() -> None:
|
|
134
|
+
"""
|
|
135
|
+
Clear outbound header base from ContextVar at end of request.
|
|
136
|
+
|
|
137
|
+
CRITICAL: Prevents stale X-Sf4-Prid data from persisting across requests.
|
|
138
|
+
Must be called at the end of EVERY request that set the outbound header base.
|
|
139
|
+
|
|
140
|
+
This ensures fresh header generation for each request with proper isolation.
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
outbound_header_base_ctx.set(None)
|
|
144
|
+
if _SF_DEBUG_ENABLED:
|
|
145
|
+
print(
|
|
146
|
+
"[clear_outbound_header_base] Cleared outbound_header_base_ctx ContextVar",
|
|
147
|
+
log=False,
|
|
148
|
+
)
|
|
149
|
+
except Exception as e:
|
|
150
|
+
# Don't let cleanup errors break the app
|
|
151
|
+
if _SF_DEBUG_ENABLED:
|
|
152
|
+
print(
|
|
153
|
+
f"[clear_outbound_header_base] ⚠️ Error during cleanup: {e}", log=False
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def clear_trace_id() -> None:
|
|
158
|
+
"""
|
|
159
|
+
Clear trace_id from ContextVar at end of request.
|
|
160
|
+
|
|
161
|
+
CRITICAL: Ensures fresh trace_id generation for requests without incoming X-Sf3-Rid header.
|
|
162
|
+
Must be called at the end of EVERY request that didn't have an incoming trace header.
|
|
163
|
+
|
|
164
|
+
Without this, get_or_set_sf_trace_id() reuses the trace_id from the previous request,
|
|
165
|
+
causing X-Sf4-Prid to remain constant across multiple requests (same parent_trace_id).
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
trace_id_ctx.set(None)
|
|
169
|
+
if _SF_DEBUG_ENABLED:
|
|
170
|
+
print("[clear_trace_id] Cleared trace_id_ctx ContextVar", log=False)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
# Don't let cleanup errors break the app
|
|
173
|
+
if _SF_DEBUG_ENABLED:
|
|
174
|
+
print(f"[clear_trace_id] ⚠️ Error during cleanup: {e}", log=False)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# Define context variables
|
|
178
|
+
trace_id_ctx = ContextVar("trace_id", default=None)
|
|
179
|
+
handled_exceptions_ctx = ContextVar("handled_exceptions", default=set())
|
|
180
|
+
reentrancy_guard_logging_active_ctx = ContextVar(
|
|
181
|
+
"reentrancy_guard_logging_active", default=False
|
|
182
|
+
)
|
|
183
|
+
reentrancy_guard_logging_preactive_ctx = ContextVar(
|
|
184
|
+
"reentrancy_guard_logging_preactive", default=False
|
|
185
|
+
)
|
|
186
|
+
reentrancy_guard_print_active_ctx = ContextVar(
|
|
187
|
+
"reentrancy_guard_print_active", default=False
|
|
188
|
+
)
|
|
189
|
+
reentrancy_guard_print_preactive_ctx = ContextVar(
|
|
190
|
+
"reentrancy_guard_print_preactive", default=False
|
|
191
|
+
)
|
|
192
|
+
reentrancy_guard_exception_active_ctx = ContextVar(
|
|
193
|
+
"reentrancy_guard_exception_active", default=False
|
|
194
|
+
)
|
|
195
|
+
reentrancy_guard_exception_preactive_ctx = ContextVar(
|
|
196
|
+
"reentrancy_guard_exception_preactive", default=False
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Suppressors
|
|
200
|
+
suppress_network_recording_ctx = ContextVar("suppress_network_recording", default=False)
|
|
201
|
+
suppress_log_output_ctx = ContextVar("suppress_log_output", default=False)
|
|
202
|
+
|
|
203
|
+
# Current request path for route-based suppression
|
|
204
|
+
current_request_path_ctx = ContextVar("current_request_path", default=None)
|
|
205
|
+
|
|
206
|
+
# Function span capture override (for header propagation)
|
|
207
|
+
funcspan_override_ctx = ContextVar("funcspan_override", default=None)
|
|
208
|
+
|
|
209
|
+
# Outbound header base (for ultra-fast header injection with cross-thread support)
|
|
210
|
+
outbound_header_base_ctx = ContextVar("outbound_header_base", default=None)
|
|
211
|
+
|
|
212
|
+
reentrancy_guard_sys_stdout_active_ctx = ContextVar(
|
|
213
|
+
"reentrancy_guard_sys_stdout_active", default=False
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# Thread-local storage as a fallback
|
|
217
|
+
_thread_locals = threading.local()
|
|
218
|
+
|
|
219
|
+
_shared_trace_registry = {}
|
|
220
|
+
_shared_trace_registry_lock = threading.RLock()
|
|
221
|
+
|
|
222
|
+
# Shared registry for outbound header base (cross-thread support, same pattern as trace_id)
|
|
223
|
+
_shared_outbound_header_base_registry = {}
|
|
224
|
+
_shared_outbound_header_base_lock = threading.RLock()
|
|
225
|
+
|
|
226
|
+
# ULTRA-FAST: Cached headers dict in thread-local storage (NO LOCK, ~10-20ns access)
|
|
227
|
+
# This is the fully-built headers dict, ready to inject (no dict building overhead)
|
|
228
|
+
_cached_outbound_headers_tls = threading.local()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _set_shared_trace_id(trace_id: Optional[str]) -> None:
|
|
232
|
+
# PERFORMANCE: In LD_PRELOAD mode, skip lock (ContextVar is primary source)
|
|
233
|
+
if _ld_preload_active:
|
|
234
|
+
_shared_trace_registry["trace_id"] = trace_id
|
|
235
|
+
return
|
|
236
|
+
with _shared_trace_registry_lock:
|
|
237
|
+
_shared_trace_registry["trace_id"] = trace_id
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _set_shared_outbound_header_base(base_dict: Optional[dict]) -> None:
|
|
241
|
+
"""Store outbound header base in shared registry (works across threads)."""
|
|
242
|
+
# PERFORMANCE: In LD_PRELOAD mode, skip lock (ContextVar is primary source)
|
|
243
|
+
if _ld_preload_active:
|
|
244
|
+
_shared_outbound_header_base_registry["base_dict"] = base_dict
|
|
245
|
+
return
|
|
246
|
+
with _shared_outbound_header_base_lock:
|
|
247
|
+
_shared_outbound_header_base_registry["base_dict"] = base_dict
|
|
248
|
+
_clear_cached_outbound_headers()
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _get_shared_outbound_header_base() -> Optional[dict]:
|
|
252
|
+
"""Get outbound header base from shared registry (works across threads)."""
|
|
253
|
+
# PERFORMANCE: In LD_PRELOAD mode, skip lock (ContextVar is primary source)
|
|
254
|
+
if _ld_preload_active:
|
|
255
|
+
return _shared_outbound_header_base_registry.get("base_dict")
|
|
256
|
+
with _shared_outbound_header_base_lock:
|
|
257
|
+
return _shared_outbound_header_base_registry.get("base_dict")
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _clear_cached_outbound_headers() -> None:
|
|
261
|
+
"""Clear thread-local cached headers (called when base changes)."""
|
|
262
|
+
try:
|
|
263
|
+
if hasattr(_cached_outbound_headers_tls, "headers"):
|
|
264
|
+
delattr(_cached_outbound_headers_tls, "headers")
|
|
265
|
+
except AttributeError:
|
|
266
|
+
pass
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _get_shared_trace_id() -> Optional[str]:
|
|
270
|
+
# PERFORMANCE: In LD_PRELOAD mode, skip lock (ContextVar is primary source)
|
|
271
|
+
if _ld_preload_active:
|
|
272
|
+
return _shared_trace_registry.get("trace_id")
|
|
273
|
+
with _shared_trace_registry_lock:
|
|
274
|
+
return _shared_trace_registry.get("trace_id")
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _get_context_or_thread_local(
|
|
278
|
+
ctx_var: ContextVar, attr_name: str, default: Any
|
|
279
|
+
) -> Any:
|
|
280
|
+
return ctx_var.get() # or getattr(_thread_locals, attr_name, default)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _set_context_and_thread_local(
|
|
284
|
+
ctx_var: ContextVar, attr_name: str, value: Any
|
|
285
|
+
) -> Any:
|
|
286
|
+
ctx_var.set(value)
|
|
287
|
+
# setattr(_thread_locals, attr_name, value)
|
|
288
|
+
return value
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def unset_sf_trace_id() -> None:
|
|
292
|
+
_set_shared_trace_id(None)
|
|
293
|
+
_set_context_and_thread_local(trace_id_ctx, "trace_id", None)
|
|
294
|
+
if _SF_DEBUG_ENABLED:
|
|
295
|
+
print("[[DEBUG]] unset_sf_trace_id: trace_id cleared", log=False)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _get_or_set_context_and_thread_local(
|
|
299
|
+
ctx_var: ContextVar, attr_name: str, value_if_not_set
|
|
300
|
+
) -> Tuple[bool, Any]:
|
|
301
|
+
value = ctx_var.get() # or getattr(_thread_locals, attr_name, None)
|
|
302
|
+
if value is None:
|
|
303
|
+
_set_context_and_thread_local(ctx_var, attr_name, value_if_not_set)
|
|
304
|
+
return True, value_if_not_set
|
|
305
|
+
return False, value
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# Trace ID functions
|
|
309
|
+
def get_sf_trace_id() -> Optional[Union[str, UUID]]:
|
|
310
|
+
# Use ContextVar for both LD_PRELOAD and Python-only modes
|
|
311
|
+
# ContextVar is async-safe and thread-safe, no shared registry needed
|
|
312
|
+
return _get_context_or_thread_local(trace_id_ctx, "trace_id", None)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def set_sf_trace_id(trace_id: Union[str, UUID]) -> Union[str, UUID]:
|
|
316
|
+
# Set in ContextVar for both LD_PRELOAD and Python-only modes
|
|
317
|
+
# ContextVar is async-safe and thread-safe, no shared registry needed
|
|
318
|
+
return _set_context_and_thread_local(trace_id_ctx, "trace_id", trace_id)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def generate_new_trace_id() -> str:
|
|
322
|
+
"""
|
|
323
|
+
Generate and set a fresh trace_id for requests without incoming X-Sf3-Rid header.
|
|
324
|
+
|
|
325
|
+
This is called explicitly when there's no incoming tracing header, ensuring
|
|
326
|
+
a fresh trace_id is generated for each request (avoiding stale ContextVar reuse).
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
The newly generated trace_id string.
|
|
330
|
+
"""
|
|
331
|
+
unique_id = uuid.uuid4()
|
|
332
|
+
trace_id = f"{NONSESSION_APPLOGS}-v3/{app_config._sailfish_api_key}/{unique_id}"
|
|
333
|
+
|
|
334
|
+
# Set in ContextVar
|
|
335
|
+
_set_context_and_thread_local(trace_id_ctx, "trace_id", trace_id)
|
|
336
|
+
|
|
337
|
+
if _SF_DEBUG_ENABLED:
|
|
338
|
+
print(f"[generate_new_trace_id] Generated fresh trace_id: {trace_id}", log=False)
|
|
339
|
+
|
|
340
|
+
return trace_id
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def get_or_set_sf_trace_id(
|
|
344
|
+
new_trace_id_if_not_set: Optional[str] = None,
|
|
345
|
+
is_associated_with_inbound_request: bool = False,
|
|
346
|
+
) -> Tuple[bool, Union[str, UUID]]:
|
|
347
|
+
###
|
|
348
|
+
###
|
|
349
|
+
###
|
|
350
|
+
### IMPLEMENT skip if not ready yet?
|
|
351
|
+
###
|
|
352
|
+
###
|
|
353
|
+
###
|
|
354
|
+
# Check if trace_id already exists
|
|
355
|
+
if not new_trace_id_if_not_set:
|
|
356
|
+
# Use ContextVar for both LD_PRELOAD and Python-only modes
|
|
357
|
+
trace_id = _get_context_or_thread_local(trace_id_ctx, "trace_id", None)
|
|
358
|
+
if trace_id:
|
|
359
|
+
if _SF_DEBUG_ENABLED:
|
|
360
|
+
print(f"[trace_id] Returning existing trace_id: {trace_id}", log=False)
|
|
361
|
+
return False, trace_id
|
|
362
|
+
|
|
363
|
+
# No trace_id found - generate new one
|
|
364
|
+
if _SF_DEBUG_ENABLED:
|
|
365
|
+
print("[trace_id] No trace_id found. Generating new trace_id.", log=False)
|
|
366
|
+
unique_id = uuid.uuid4()
|
|
367
|
+
trace_id = f"{NONSESSION_APPLOGS}-v3/{app_config._sailfish_api_key}/{unique_id}"
|
|
368
|
+
|
|
369
|
+
# Set using ContextVar only (no shared registry)
|
|
370
|
+
_set_context_and_thread_local(trace_id_ctx, "trace_id", trace_id)
|
|
371
|
+
|
|
372
|
+
if _SF_DEBUG_ENABLED:
|
|
373
|
+
print(f"[trace_id] Generated and set new trace_id: {trace_id}", log=False)
|
|
374
|
+
return True, trace_id
|
|
375
|
+
|
|
376
|
+
# new_trace_id_if_not_set provided - set it directly
|
|
377
|
+
if _SF_DEBUG_ENABLED:
|
|
378
|
+
print(
|
|
379
|
+
f"[trace_id] Setting new trace_id from argument: {new_trace_id_if_not_set}",
|
|
380
|
+
log=False,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# Set using ContextVar only (no shared registry)
|
|
384
|
+
_set_context_and_thread_local(trace_id_ctx, "trace_id", new_trace_id_if_not_set)
|
|
385
|
+
|
|
386
|
+
return True, new_trace_id_if_not_set
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
# Handled exceptions functions
|
|
390
|
+
def get_handled_exceptions() -> Set[Any]:
|
|
391
|
+
return _get_context_or_thread_local(
|
|
392
|
+
handled_exceptions_ctx, "handled_exceptions", set()
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def set_handled_exceptions(exceptions_set: Set[Any]) -> Set[Any]:
|
|
397
|
+
return _set_context_and_thread_local(
|
|
398
|
+
handled_exceptions_ctx, "handled_exceptions", exceptions_set
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def get_or_set_handled_exceptions(default: set = None) -> Tuple[bool, Set[Any]]:
|
|
403
|
+
if default is None:
|
|
404
|
+
default = set()
|
|
405
|
+
return _get_or_set_context_and_thread_local(
|
|
406
|
+
handled_exceptions_ctx, "handled_exceptions", default
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def mark_exception_handled(exception) -> None:
|
|
411
|
+
handled = get_handled_exceptions()
|
|
412
|
+
handled.add(id(exception))
|
|
413
|
+
set_handled_exceptions(handled)
|
|
414
|
+
if hasattr(exception, "_handled"):
|
|
415
|
+
setattr(exception, "_handled", True)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def has_handled_exception(exception) -> bool:
|
|
419
|
+
return id(exception) in get_handled_exceptions() or getattr(
|
|
420
|
+
exception, "_handled", False
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def reset_handled_exceptions() -> Set[Any]:
|
|
425
|
+
return set_handled_exceptions(set())
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
# Reentrancy guards (logging)
|
|
429
|
+
def get_reentrancy_guard_logging_active() -> bool:
|
|
430
|
+
return _get_context_or_thread_local(
|
|
431
|
+
reentrancy_guard_logging_active_ctx, "reentrancy_guard_logging_active", False
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def set_reentrancy_guard_logging_active(value: bool) -> bool:
|
|
436
|
+
return _set_context_and_thread_local(
|
|
437
|
+
reentrancy_guard_logging_active_ctx, "reentrancy_guard_logging_active", value
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def get_or_set_reentrancy_guard_logging_active(
|
|
442
|
+
value_if_not_set: bool,
|
|
443
|
+
) -> Tuple[bool, bool]:
|
|
444
|
+
return _get_or_set_context_and_thread_local(
|
|
445
|
+
reentrancy_guard_logging_active_ctx,
|
|
446
|
+
"reentrancy_guard_logging_active",
|
|
447
|
+
value_if_not_set,
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def activate_reentrancy_guards_logging() -> bool:
|
|
452
|
+
set_reentrancy_guard_logging_active(True)
|
|
453
|
+
set_reentrancy_guard_logging_preactive(True)
|
|
454
|
+
return True
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def get_reentrancy_guard_logging_preactive() -> bool:
|
|
458
|
+
return _get_context_or_thread_local(
|
|
459
|
+
reentrancy_guard_logging_preactive_ctx,
|
|
460
|
+
"reentrancy_guard_logging_preactive",
|
|
461
|
+
False,
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def set_reentrancy_guard_logging_preactive(value: bool) -> bool:
|
|
466
|
+
return _set_context_and_thread_local(
|
|
467
|
+
reentrancy_guard_logging_preactive_ctx,
|
|
468
|
+
"reentrancy_guard_logging_preactive",
|
|
469
|
+
value,
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def get_or_set_reentrancy_guard_logging_preactive(
|
|
474
|
+
value_if_not_set: bool,
|
|
475
|
+
) -> Tuple[bool, bool]:
|
|
476
|
+
return _get_or_set_context_and_thread_local(
|
|
477
|
+
reentrancy_guard_logging_preactive_ctx,
|
|
478
|
+
"reentrancy_guard_logging_preactive",
|
|
479
|
+
value_if_not_set,
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def activate_reentrancy_guards_logging_preactive() -> bool:
|
|
484
|
+
return set_reentrancy_guard_logging_preactive(True)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
# Reentrancy guards (stdout)
|
|
488
|
+
def get_reentrancy_guard_sys_stdout_active() -> bool:
|
|
489
|
+
return _get_context_or_thread_local(
|
|
490
|
+
reentrancy_guard_sys_stdout_active_ctx,
|
|
491
|
+
"reentrancy_guard_sys_stdout_active",
|
|
492
|
+
False,
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def set_reentrancy_guard_sys_stdout_active(value: bool) -> bool:
|
|
497
|
+
return _set_context_and_thread_local(
|
|
498
|
+
reentrancy_guard_sys_stdout_active_ctx,
|
|
499
|
+
"reentrancy_guard_sys_stdout_active",
|
|
500
|
+
value,
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def activate_reentrancy_guards_sys_stdout() -> bool:
|
|
505
|
+
set_reentrancy_guard_sys_stdout_active(True)
|
|
506
|
+
return True
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
# Reentrancy guards (print)
|
|
510
|
+
def get_reentrancy_guard_print_active() -> bool:
|
|
511
|
+
return _get_context_or_thread_local(
|
|
512
|
+
reentrancy_guard_print_active_ctx, "reentrancy_guard_print_active", False
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def set_reentrancy_guard_print_active(value: bool) -> bool:
|
|
517
|
+
return _set_context_and_thread_local(
|
|
518
|
+
reentrancy_guard_print_active_ctx, "reentrancy_guard_print_active", value
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def get_or_set_reentrancy_guard_print_active(
|
|
523
|
+
value_if_not_set: bool,
|
|
524
|
+
) -> Tuple[bool, bool]:
|
|
525
|
+
return _get_or_set_context_and_thread_local(
|
|
526
|
+
reentrancy_guard_print_active_ctx,
|
|
527
|
+
"reentrancy_guard_print_active",
|
|
528
|
+
value_if_not_set,
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def activate_reentrancy_guards_print() -> bool:
|
|
533
|
+
set_reentrancy_guard_print_active(True)
|
|
534
|
+
set_reentrancy_guard_print_preactive(True)
|
|
535
|
+
return True
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def get_reentrancy_guard_print_preactive() -> bool:
|
|
539
|
+
return _get_context_or_thread_local(
|
|
540
|
+
reentrancy_guard_print_preactive_ctx, "reentrancy_guard_print_preactive", False
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def set_reentrancy_guard_print_preactive(value: bool) -> bool:
|
|
545
|
+
return _set_context_and_thread_local(
|
|
546
|
+
reentrancy_guard_print_preactive_ctx, "reentrancy_guard_print_preactive", value
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def get_or_set_reentrancy_guard_print_preactive(
|
|
551
|
+
value_if_not_set: bool,
|
|
552
|
+
) -> Tuple[bool, bool]:
|
|
553
|
+
return _get_or_set_context_and_thread_local(
|
|
554
|
+
reentrancy_guard_print_preactive_ctx,
|
|
555
|
+
"reentrancy_guard_print_preactive",
|
|
556
|
+
value_if_not_set,
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def activate_reentrancy_guards_print_preactive() -> bool:
|
|
561
|
+
return set_reentrancy_guard_print_preactive(True)
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
# Reentrancy guards (exception)
|
|
565
|
+
def get_reentrancy_guard_exception_active() -> bool:
|
|
566
|
+
return _get_context_or_thread_local(
|
|
567
|
+
reentrancy_guard_exception_active_ctx,
|
|
568
|
+
"reentrancy_guard_exception_active",
|
|
569
|
+
False,
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def set_reentrancy_guard_exception_active(value: bool) -> bool:
|
|
574
|
+
return _set_context_and_thread_local(
|
|
575
|
+
reentrancy_guard_exception_active_ctx,
|
|
576
|
+
"reentrancy_guard_exception_active",
|
|
577
|
+
value,
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def get_or_set_reentrancy_guard_exception_active(
|
|
582
|
+
value_if_not_set: bool,
|
|
583
|
+
) -> Tuple[bool, bool]:
|
|
584
|
+
return _get_or_set_context_and_thread_local(
|
|
585
|
+
reentrancy_guard_exception_active_ctx,
|
|
586
|
+
"reentrancy_guard_exception_active",
|
|
587
|
+
value_if_not_set,
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def activate_reentrancy_guards_exception() -> bool:
|
|
592
|
+
set_reentrancy_guard_exception_active(True)
|
|
593
|
+
set_reentrancy_guard_exception_preactive(True)
|
|
594
|
+
return True
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def get_reentrancy_guard_exception_preactive() -> bool:
|
|
598
|
+
return _get_context_or_thread_local(
|
|
599
|
+
reentrancy_guard_exception_preactive_ctx,
|
|
600
|
+
"reentrancy_guard_exception_preactive",
|
|
601
|
+
False,
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def set_reentrancy_guard_exception_preactive(value: bool) -> bool:
|
|
606
|
+
return _set_context_and_thread_local(
|
|
607
|
+
reentrancy_guard_exception_preactive_ctx,
|
|
608
|
+
"reentrancy_guard_exception_preactive",
|
|
609
|
+
value,
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def get_or_set_reentrancy_guard_exception_preactive(
|
|
614
|
+
value_if_not_set: bool,
|
|
615
|
+
) -> Tuple[bool, bool]:
|
|
616
|
+
return _get_or_set_context_and_thread_local(
|
|
617
|
+
reentrancy_guard_exception_preactive_ctx,
|
|
618
|
+
"reentrancy_guard_exception_preactive",
|
|
619
|
+
value_if_not_set,
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def activate_reentrancy_guards_exception_preactive() -> bool:
|
|
624
|
+
return set_reentrancy_guard_exception_preactive(True)
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
# Get and set context
|
|
628
|
+
def get_context() -> Dict[str, Any]:
|
|
629
|
+
return {
|
|
630
|
+
"trace_id": get_sf_trace_id(),
|
|
631
|
+
"handled_exceptions": get_handled_exceptions(),
|
|
632
|
+
"reentrancy_guard_logging_active": get_reentrancy_guard_logging_active(),
|
|
633
|
+
"reentrancy_guard_logging_preactive": get_reentrancy_guard_logging_preactive(),
|
|
634
|
+
"reentrancy_guard_print_active": get_reentrancy_guard_print_active(),
|
|
635
|
+
"reentrancy_guard_print_preactive": get_reentrancy_guard_print_preactive(),
|
|
636
|
+
"reentrancy_guard_exception_active": get_reentrancy_guard_exception_active(),
|
|
637
|
+
"reentrancy_guard_exception_preactive": get_reentrancy_guard_exception_preactive(),
|
|
638
|
+
"reentrancy_guard_sys_stdout_active": get_reentrancy_guard_sys_stdout_active(),
|
|
639
|
+
"suppress_network_recording": is_network_recording_suppressed(),
|
|
640
|
+
"suppress_log_output": is_log_output_suppressed(),
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def set_context(context) -> None:
|
|
645
|
+
set_sf_trace_id(context.get("trace_id"))
|
|
646
|
+
set_handled_exceptions(context.get("handled_exceptions", set()))
|
|
647
|
+
set_reentrancy_guard_logging_active(
|
|
648
|
+
context.get("reentrancy_guard_logging_active", False)
|
|
649
|
+
)
|
|
650
|
+
set_reentrancy_guard_logging_preactive(
|
|
651
|
+
context.get("reentrancy_guard_logging_preactive", False)
|
|
652
|
+
)
|
|
653
|
+
set_reentrancy_guard_print_active(
|
|
654
|
+
context.get("reentrancy_guard_print_active", False)
|
|
655
|
+
)
|
|
656
|
+
set_reentrancy_guard_print_preactive(
|
|
657
|
+
context.get("reentrancy_guard_print_preactive", False)
|
|
658
|
+
)
|
|
659
|
+
set_reentrancy_guard_exception_active(
|
|
660
|
+
context.get("reentrancy_guard_exception_active", False)
|
|
661
|
+
)
|
|
662
|
+
set_reentrancy_guard_exception_preactive(
|
|
663
|
+
context.get("reentrancy_guard_exception_preactive", False)
|
|
664
|
+
)
|
|
665
|
+
set_reentrancy_guard_sys_stdout_active(
|
|
666
|
+
context.get("reentrancy_guard_sys_stdout_active", False)
|
|
667
|
+
)
|
|
668
|
+
# suppressors are transient; don't set them from incoming context
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
@contextmanager
|
|
672
|
+
def suppress_network_recording():
|
|
673
|
+
token = suppress_network_recording_ctx.set(True)
|
|
674
|
+
try:
|
|
675
|
+
yield
|
|
676
|
+
finally:
|
|
677
|
+
suppress_network_recording_ctx.reset(token)
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
@functools.lru_cache(maxsize=1)
|
|
681
|
+
def _get_disabled_route_patterns() -> List[str]:
|
|
682
|
+
"""
|
|
683
|
+
Get route patterns to skip network hop capture.
|
|
684
|
+
|
|
685
|
+
Routes are configured via setup_interceptors(routes_to_skip_network_hops=[...])
|
|
686
|
+
which defaults to the SF_DISABLE_INBOUND_NETWORK_TRACING_ON_ROUTES env var.
|
|
687
|
+
|
|
688
|
+
Returns a list of route patterns with wildcard support (* and ? characters).
|
|
689
|
+
Cached for performance (called on every request).
|
|
690
|
+
|
|
691
|
+
Examples:
|
|
692
|
+
"/healthz, /metrics" -> ["/healthz", "/metrics"]
|
|
693
|
+
"/admin/*, /api/v1/status*" -> ["/admin/*", "/api/v1/status*"]
|
|
694
|
+
"""
|
|
695
|
+
# Get patterns from app_config (already contains parameter or env var default)
|
|
696
|
+
patterns = getattr(app_config, "_routes_to_skip_network_hops", [])
|
|
697
|
+
|
|
698
|
+
if _SF_DEBUG_ENABLED and patterns:
|
|
699
|
+
print(f"[_get_disabled_route_patterns] Route patterns to skip: {patterns}", log=False)
|
|
700
|
+
|
|
701
|
+
return patterns
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def _route_matches_pattern(path: str) -> bool:
|
|
705
|
+
"""
|
|
706
|
+
Check if the given path matches any disabled route pattern.
|
|
707
|
+
|
|
708
|
+
Uses fnmatch for glob-style pattern matching:
|
|
709
|
+
- * matches any sequence of characters
|
|
710
|
+
- ? matches any single character
|
|
711
|
+
|
|
712
|
+
Args:
|
|
713
|
+
path: Request path to check (e.g., "/api/v1/users")
|
|
714
|
+
|
|
715
|
+
Returns:
|
|
716
|
+
True if path matches any disabled pattern, False otherwise.
|
|
717
|
+
|
|
718
|
+
Examples:
|
|
719
|
+
_route_matches_pattern("/healthz") -> True if "/healthz" in patterns
|
|
720
|
+
_route_matches_pattern("/admin/users") -> True if "/admin/*" in patterns
|
|
721
|
+
_route_matches_pattern("/api/v1/status") -> True if "/api/v1/status*" in patterns
|
|
722
|
+
"""
|
|
723
|
+
patterns = _get_disabled_route_patterns()
|
|
724
|
+
if not patterns:
|
|
725
|
+
return False
|
|
726
|
+
|
|
727
|
+
# Use fnmatch for glob pattern matching (* and ? wildcards)
|
|
728
|
+
for pattern in patterns:
|
|
729
|
+
if fnmatch.fnmatch(path, pattern):
|
|
730
|
+
if _SF_DEBUG_ENABLED:
|
|
731
|
+
print(f"[_route_matches_pattern] Path '{path}' matches pattern '{pattern}' - suppressing", log=False)
|
|
732
|
+
return True
|
|
733
|
+
|
|
734
|
+
return False
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
def is_network_recording_suppressed() -> bool:
|
|
738
|
+
"""
|
|
739
|
+
Check if network recording is suppressed.
|
|
740
|
+
|
|
741
|
+
Checks three suppression mechanisms (any one triggers suppression):
|
|
742
|
+
1. Explicit suppression via context manager or decorator (suppress_network_recording_ctx)
|
|
743
|
+
2. Route-based suppression via SF_DISABLE_INBOUND_NETWORK_TRACING_ON_ROUTES
|
|
744
|
+
3. Thread-local C telemetry guard (g_in_telemetry_send) - used by C extension sender threads
|
|
745
|
+
|
|
746
|
+
Returns:
|
|
747
|
+
True if network recording is suppressed by any mechanism, False otherwise.
|
|
748
|
+
"""
|
|
749
|
+
# Check explicit suppression (context manager / decorator)
|
|
750
|
+
if suppress_network_recording_ctx.get():
|
|
751
|
+
return True
|
|
752
|
+
|
|
753
|
+
# Check route-based suppression
|
|
754
|
+
current_path = get_current_request_path()
|
|
755
|
+
if current_path and _route_matches_pattern(current_path):
|
|
756
|
+
return True
|
|
757
|
+
|
|
758
|
+
return False
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
@contextmanager
|
|
762
|
+
def suppress_log_output():
|
|
763
|
+
token = suppress_log_output_ctx.set(True)
|
|
764
|
+
try:
|
|
765
|
+
yield
|
|
766
|
+
finally:
|
|
767
|
+
suppress_log_output_ctx.reset(token)
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
def is_log_output_suppressed() -> bool:
|
|
771
|
+
return suppress_log_output_ctx.get()
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
# Current request path functions (for route-based suppression)
|
|
775
|
+
def set_current_request_path(path: str) -> None:
|
|
776
|
+
"""Set current request path for route-based network suppression."""
|
|
777
|
+
current_request_path_ctx.set(path)
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
def get_current_request_path() -> Optional[str]:
|
|
781
|
+
"""Get current request path."""
|
|
782
|
+
return current_request_path_ctx.get()
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def clear_current_request_path() -> None:
|
|
786
|
+
"""Clear current request path at end of request."""
|
|
787
|
+
current_request_path_ctx.set(None)
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
# Function span capture override functions (ultra-fast, <10ns)
|
|
791
|
+
def get_funcspan_override() -> Optional[str]:
|
|
792
|
+
"""Get function span capture override header value (fast ContextVar lookup ~8ns)."""
|
|
793
|
+
return funcspan_override_ctx.get()
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
def set_funcspan_override(value: Optional[str]) -> Optional[str]:
|
|
797
|
+
"""Set function span capture override header value."""
|
|
798
|
+
funcspan_override_ctx.set(value)
|
|
799
|
+
return value
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
def clear_funcspan_override() -> None:
|
|
803
|
+
"""Clear function span capture override."""
|
|
804
|
+
funcspan_override_ctx.set(None)
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
# ================================
|
|
808
|
+
# Outbound header generation (ultra-fast header injection with shared registry + ContextVar)
|
|
809
|
+
# ================================
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
def set_outbound_header_base(
|
|
813
|
+
base_trace: str, parent_trace_id: str, funcspan: Optional[str]
|
|
814
|
+
) -> None:
|
|
815
|
+
"""
|
|
816
|
+
Store base header info in BOTH shared registry AND ContextVar.
|
|
817
|
+
|
|
818
|
+
**OPTIMIZATION:** When LD_PRELOAD is active, pre-builds the headers dict HERE
|
|
819
|
+
so all outbound calls can reuse it (~10ns vs 1-10μs per call).
|
|
820
|
+
|
|
821
|
+
Uses same pattern as trace_id for cross-thread support.
|
|
822
|
+
|
|
823
|
+
Args:
|
|
824
|
+
base_trace: Base trace path (e.g., "session_id/page_visit_id") - used for generating new X-Sf3-Rid
|
|
825
|
+
parent_trace_id: FULL incoming trace_id (e.g., "session_id/page_visit_id/request_uuid") - used for X-Sf4-Prid
|
|
826
|
+
funcspan: Optional function span capture override header value
|
|
827
|
+
|
|
828
|
+
Performance: <1μs (dict creation + set operations)
|
|
829
|
+
|
|
830
|
+
ULTRA-OPTIMIZED: Lockless path when LD_PRELOAD active, minimal dict allocations, no C calls.
|
|
831
|
+
"""
|
|
832
|
+
# Pre-build cached headers for both LD_PRELOAD and Python-only modes
|
|
833
|
+
# This optimization applies to both modes now
|
|
834
|
+
if funcspan:
|
|
835
|
+
# Construct nested dict in one go for funcspan case
|
|
836
|
+
cached_headers = {
|
|
837
|
+
SAILFISH_TRACING_HEADER: base_trace,
|
|
838
|
+
PARENT_SESSION_ID_HEADER: parent_trace_id,
|
|
839
|
+
FUNCSPAN_OVERRIDE_HEADER: funcspan,
|
|
840
|
+
}
|
|
841
|
+
base_dict = {
|
|
842
|
+
"base_trace": base_trace,
|
|
843
|
+
"parent_trace_id": parent_trace_id,
|
|
844
|
+
"funcspan": funcspan,
|
|
845
|
+
"_cached_headers": cached_headers,
|
|
846
|
+
}
|
|
847
|
+
else:
|
|
848
|
+
# Fast path: No funcspan (most common case) - construct in one go
|
|
849
|
+
cached_headers = {
|
|
850
|
+
SAILFISH_TRACING_HEADER: base_trace,
|
|
851
|
+
PARENT_SESSION_ID_HEADER: parent_trace_id,
|
|
852
|
+
}
|
|
853
|
+
base_dict = {
|
|
854
|
+
"base_trace": base_trace,
|
|
855
|
+
"parent_trace_id": parent_trace_id,
|
|
856
|
+
"funcspan": None,
|
|
857
|
+
"_cached_headers": cached_headers,
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
# Store in ContextVar only (no shared registry)
|
|
861
|
+
outbound_header_base_ctx.set(base_dict)
|
|
862
|
+
|
|
863
|
+
# DEBUG: Log when outbound header base is set (helps troubleshoot X-Sf4-Prid issues)
|
|
864
|
+
if _SF_DEBUG_ENABLED:
|
|
865
|
+
print(
|
|
866
|
+
f"[set_outbound_header_base] Set parent_trace_id={parent_trace_id}, base_trace={base_trace}, cached_headers={cached_headers}",
|
|
867
|
+
log=False,
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
# Set C TLS for non-LD_PRELOAD mode
|
|
871
|
+
if not _ld_preload_active:
|
|
872
|
+
_set_c_tls_parent_trace_id(parent_trace_id)
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
def get_outbound_headers_with_new_uuid() -> dict:
|
|
876
|
+
"""
|
|
877
|
+
Generate fresh outbound headers with new UUID appended to base trace.
|
|
878
|
+
|
|
879
|
+
**ULTRA-FAST when LD_PRELOAD is active:**
|
|
880
|
+
- Headers dict cached in ContextVar (no lock, ~10-20ns)
|
|
881
|
+
- C appends UUID at socket intercept time (no Python UUID generation)
|
|
882
|
+
- Total overhead: ~10-20ns (just ContextVar read + dict return)
|
|
883
|
+
|
|
884
|
+
**When LD_PRELOAD inactive:**
|
|
885
|
+
- Each call generates new UUID in Python (~100ns)
|
|
886
|
+
- Headers dict built fresh each time
|
|
887
|
+
|
|
888
|
+
Returns:
|
|
889
|
+
Dictionary with X-Sf3-Rid, X-Sf4-Prid, and optionally X-Sf3-FunctionSpanCaptureOverride.
|
|
890
|
+
Empty dict if no base initialized.
|
|
891
|
+
|
|
892
|
+
Performance:
|
|
893
|
+
- LD_PRELOAD active: ~10-20ns (cached headers from ContextVar, NO LOCK)
|
|
894
|
+
- LD_PRELOAD inactive: ~100ns (uuid4 generation + string concat + dict creation)
|
|
895
|
+
"""
|
|
896
|
+
# Get base_dict from ContextVar (no shared registry fallback)
|
|
897
|
+
base_dict = outbound_header_base_ctx.get()
|
|
898
|
+
|
|
899
|
+
if not base_dict:
|
|
900
|
+
if _SF_DEBUG_ENABLED:
|
|
901
|
+
print(
|
|
902
|
+
f"[get_outbound_headers_with_new_uuid] ⚠️ No outbound header base found in ContextVar",
|
|
903
|
+
log=False,
|
|
904
|
+
)
|
|
905
|
+
return {}
|
|
906
|
+
|
|
907
|
+
# ULTRA-FAST PATH: Return pre-built headers if available (LD_PRELOAD mode)
|
|
908
|
+
cached_headers = base_dict.get("_cached_headers")
|
|
909
|
+
if cached_headers:
|
|
910
|
+
# DEBUG: ENABLED for troubleshooting X-Sf4-Prid issue
|
|
911
|
+
if _SF_DEBUG_ENABLED:
|
|
912
|
+
print(
|
|
913
|
+
f"[get_outbound_headers_with_new_uuid] ⚡ Returning pre-built headers: {cached_headers}",
|
|
914
|
+
log=False,
|
|
915
|
+
)
|
|
916
|
+
# Return a shallow copy to prevent mutations from affecting cached dict
|
|
917
|
+
return dict(cached_headers)
|
|
918
|
+
|
|
919
|
+
# SLOW PATH: Generate UUID in Python (Python-only mode)
|
|
920
|
+
if _SF_DEBUG_ENABLED:
|
|
921
|
+
print(
|
|
922
|
+
f"[get_outbound_headers_with_new_uuid] 🐌 LD_PRELOAD inactive - generating UUID in Python",
|
|
923
|
+
log=False,
|
|
924
|
+
)
|
|
925
|
+
|
|
926
|
+
base_trace = base_dict.get("base_trace")
|
|
927
|
+
parent_trace_id = base_dict.get("parent_trace_id")
|
|
928
|
+
funcspan = base_dict.get("funcspan")
|
|
929
|
+
|
|
930
|
+
if not base_trace or not parent_trace_id:
|
|
931
|
+
if _SF_DEBUG_ENABLED:
|
|
932
|
+
print(
|
|
933
|
+
f"[get_outbound_headers_with_new_uuid] ⚠️ Missing base_trace or parent_trace_id!",
|
|
934
|
+
log=False,
|
|
935
|
+
)
|
|
936
|
+
return {}
|
|
937
|
+
|
|
938
|
+
# Generate new UUID for each call
|
|
939
|
+
new_uuid = str(uuid.uuid4())
|
|
940
|
+
outbound_trace_id = f"{base_trace}/{new_uuid}"
|
|
941
|
+
|
|
942
|
+
headers = {
|
|
943
|
+
SAILFISH_TRACING_HEADER: outbound_trace_id, # X-Sf3-Rid: session/page/uuid (Python)
|
|
944
|
+
PARENT_SESSION_ID_HEADER: parent_trace_id,
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
if funcspan:
|
|
948
|
+
headers[FUNCSPAN_OVERRIDE_HEADER] = funcspan
|
|
949
|
+
|
|
950
|
+
return headers
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
import logging
|
|
954
|
+
|
|
955
|
+
# include httpcore/h11/h2 because we now call httpcore directly
|
|
956
|
+
_HTTPX_LOGGERS = ("httpx", "httpcore", "h11", "h2")
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
@contextmanager
|
|
960
|
+
def suppress_logs():
|
|
961
|
+
"""Temporarily silence client libraries without touching global logging config."""
|
|
962
|
+
loggers = [logging.getLogger(n) for n in _HTTPX_LOGGERS]
|
|
963
|
+
prev_disabled = [lg.disabled for lg in loggers]
|
|
964
|
+
try:
|
|
965
|
+
for lg in loggers:
|
|
966
|
+
lg.disabled = True
|
|
967
|
+
yield
|
|
968
|
+
finally:
|
|
969
|
+
for lg, was_disabled in zip(loggers, prev_disabled):
|
|
970
|
+
lg.disabled = was_disabled
|