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.
- sf_veritas/__init__.py +46 -0
- sf_veritas/_auto_preload.py +73 -0
- sf_veritas/_sfconfig.c +162 -0
- sf_veritas/_sfconfig.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfcrashhandler.c +267 -0
- sf_veritas/_sfcrashhandler.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastlog.c +953 -0
- sf_veritas/_sffastlog.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +994 -0
- sf_veritas/_sffastnet.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +727 -0
- sf_veritas/_sffastnetworkrequest.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2791 -0
- sf_veritas/_sffuncspan.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +730 -0
- sf_veritas/_sffuncspan_config.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfheadercheck.c +341 -0
- sf_veritas/_sfheadercheck.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfnetworkhop.c +1454 -0
- sf_veritas/_sfnetworkhop.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1223 -0
- sf_veritas/_sfservice.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfteepreload.c +6227 -0
- sf_veritas/app_config.py +57 -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 +146 -0
- sf_veritas/custom_output_wrapper.py +153 -0
- sf_veritas/custom_print.py +153 -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 +693 -0
- sf_veritas/function_span_profiler.py +1313 -0
- sf_veritas/get_preload_path.py +34 -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 +543 -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/_patch_tracker.py +74 -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 +99 -0
- sf_veritas/patches/network_libraries/aiohttp.py +294 -0
- sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
- sf_veritas/patches/network_libraries/http_client.py +670 -0
- sf_veritas/patches/network_libraries/httpcore.py +580 -0
- sf_veritas/patches/network_libraries/httplib2.py +315 -0
- sf_veritas/patches/network_libraries/httpx.py +557 -0
- sf_veritas/patches/network_libraries/niquests.py +218 -0
- sf_veritas/patches/network_libraries/pycurl.py +399 -0
- sf_veritas/patches/network_libraries/requests.py +595 -0
- sf_veritas/patches/network_libraries/ssl_socket.py +822 -0
- sf_veritas/patches/network_libraries/tornado.py +360 -0
- sf_veritas/patches/network_libraries/treq.py +270 -0
- sf_veritas/patches/network_libraries/urllib_request.py +483 -0
- sf_veritas/patches/network_libraries/utils.py +598 -0
- sf_veritas/patches/os.py +17 -0
- sf_veritas/patches/threading.py +231 -0
- sf_veritas/patches/web_frameworks/__init__.py +54 -0
- sf_veritas/patches/web_frameworks/aiohttp.py +798 -0
- sf_veritas/patches/web_frameworks/async_websocket_consumer.py +337 -0
- sf_veritas/patches/web_frameworks/blacksheep.py +532 -0
- sf_veritas/patches/web_frameworks/bottle.py +513 -0
- sf_veritas/patches/web_frameworks/cherrypy.py +683 -0
- sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
- sf_veritas/patches/web_frameworks/django.py +963 -0
- sf_veritas/patches/web_frameworks/eve.py +401 -0
- sf_veritas/patches/web_frameworks/falcon.py +931 -0
- sf_veritas/patches/web_frameworks/fastapi.py +738 -0
- sf_veritas/patches/web_frameworks/flask.py +526 -0
- sf_veritas/patches/web_frameworks/klein.py +501 -0
- sf_veritas/patches/web_frameworks/litestar.py +616 -0
- sf_veritas/patches/web_frameworks/pyramid.py +440 -0
- sf_veritas/patches/web_frameworks/quart.py +841 -0
- sf_veritas/patches/web_frameworks/robyn.py +708 -0
- sf_veritas/patches/web_frameworks/sanic.py +874 -0
- sf_veritas/patches/web_frameworks/starlette.py +742 -0
- sf_veritas/patches/web_frameworks/strawberry.py +1446 -0
- sf_veritas/patches/web_frameworks/tornado.py +485 -0
- sf_veritas/patches/web_frameworks/utils.py +170 -0
- sf_veritas/print_override.py +13 -0
- sf_veritas/regular_data_transmitter.py +444 -0
- sf_veritas/request_interceptor.py +401 -0
- sf_veritas/request_utils.py +550 -0
- sf_veritas/segfault_handler.py +116 -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 +1319 -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 +1678 -0
- sf_veritas/utils.py +39 -0
- sf_veritas-0.11.10.dist-info/METADATA +97 -0
- sf_veritas-0.11.10.dist-info/RECORD +141 -0
- sf_veritas-0.11.10.dist-info/WHEEL +5 -0
- sf_veritas-0.11.10.dist-info/entry_points.txt +2 -0
- sf_veritas-0.11.10.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,153 @@
|
|
|
1
|
+
# sf_veritas/custom_print.py
|
|
2
|
+
import builtins
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from . import app_config, transmit_exception_to_sailfish
|
|
8
|
+
from .env_vars import SF_DEBUG
|
|
9
|
+
from .thread_local import activate_reentrancy_guards_sys_stdout, get_or_set_sf_trace_id
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
# --- Optional native fast path (C extension) ---
|
|
14
|
+
try:
|
|
15
|
+
from . import _sffastlog # compiled extension
|
|
16
|
+
|
|
17
|
+
_FAST_OK = True
|
|
18
|
+
except Exception: # pragma: no cover
|
|
19
|
+
_sffastlog = None
|
|
20
|
+
_FAST_OK = False
|
|
21
|
+
|
|
22
|
+
# Keep an internal one-time init guard for the print fast path
|
|
23
|
+
_FAST_PRINT_READY = False
|
|
24
|
+
|
|
25
|
+
# GraphQL mutation for print statements (must match your server exactly)
|
|
26
|
+
# Mirrors interceptors.PrintInterceptor._QUERY variables/shape.
|
|
27
|
+
_COLLECT_PRINT_MUTATION = """
|
|
28
|
+
mutation CollectPrintStatements(
|
|
29
|
+
$apiKey: String!,
|
|
30
|
+
$serviceUuid: String!,
|
|
31
|
+
$sessionId: String!,
|
|
32
|
+
$contents: String!,
|
|
33
|
+
$reentrancyGuardPreactive: Boolean!,
|
|
34
|
+
$library: String!,
|
|
35
|
+
$timestampMs: String!,
|
|
36
|
+
$version: String!
|
|
37
|
+
) {
|
|
38
|
+
collectPrintStatements(
|
|
39
|
+
apiKey: $apiKey,
|
|
40
|
+
serviceUuid: $serviceUuid,
|
|
41
|
+
sessionId: $sessionId,
|
|
42
|
+
contents: $contents,
|
|
43
|
+
reentrancyGuardPreactive: $reentrancyGuardPreactive,
|
|
44
|
+
library: $library,
|
|
45
|
+
timestampMs: $timestampMs,
|
|
46
|
+
version: $version
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
""".strip()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _ensure_fast_print_initialized() -> bool:
|
|
53
|
+
"""
|
|
54
|
+
Lazily initialize the native print path. Safe to call every print; it becomes a no-op after the first success.
|
|
55
|
+
"""
|
|
56
|
+
global _FAST_PRINT_READY
|
|
57
|
+
|
|
58
|
+
if not _FAST_OK or _FAST_PRINT_READY:
|
|
59
|
+
return _FAST_PRINT_READY
|
|
60
|
+
|
|
61
|
+
# We require the same config used elsewhere in the package
|
|
62
|
+
endpoint = getattr(app_config, "_sailfish_graphql_endpoint", None)
|
|
63
|
+
api_key = getattr(app_config, "_sailfish_api_key", None)
|
|
64
|
+
service_uuid = getattr(app_config, "_service_uuid", None)
|
|
65
|
+
library = getattr(app_config, "library", "sailfish-python")
|
|
66
|
+
version = getattr(app_config, "version", "0.0.0")
|
|
67
|
+
http2 = 1 if os.getenv("SF_NBPOST_HTTP2", "0") == "1" else 0
|
|
68
|
+
|
|
69
|
+
if not (endpoint and api_key and service_uuid):
|
|
70
|
+
# Not configured yet; stay in Python fallback for now
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
ok = _sffastlog.init_print(
|
|
75
|
+
url=endpoint,
|
|
76
|
+
query=_COLLECT_PRINT_MUTATION,
|
|
77
|
+
api_key=str(api_key),
|
|
78
|
+
service_uuid=str(service_uuid),
|
|
79
|
+
library=str(library),
|
|
80
|
+
version=str(version),
|
|
81
|
+
http2=http2,
|
|
82
|
+
)
|
|
83
|
+
_FAST_PRINT_READY = bool(ok)
|
|
84
|
+
except Exception:
|
|
85
|
+
_FAST_PRINT_READY = False
|
|
86
|
+
|
|
87
|
+
return _FAST_PRINT_READY
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def custom_print(*args, log=True, **kwargs):
|
|
91
|
+
"""
|
|
92
|
+
Custom print function to intercept print statements.
|
|
93
|
+
- Writes to real stdout (sys.__stdout__) to avoid recursion.
|
|
94
|
+
- If log=True, ships the message via native _sffastlog.print_ when available,
|
|
95
|
+
otherwise falls back to the Python interceptor path: sys.stdout.print_interceptor.do_send(...)
|
|
96
|
+
"""
|
|
97
|
+
# Tag in debug without creating extra strings when not needed
|
|
98
|
+
activate_reentrancy_guards_sys_stdout()
|
|
99
|
+
|
|
100
|
+
# Build the message ONCE; use it for both console and network
|
|
101
|
+
if args:
|
|
102
|
+
# Fastest reasonable join; avoid f-strings on the hot path
|
|
103
|
+
msg = " ".join(map(str, args))
|
|
104
|
+
else:
|
|
105
|
+
msg = ""
|
|
106
|
+
|
|
107
|
+
# Write to the actual stdout (not our wrapper)
|
|
108
|
+
if SF_DEBUG:
|
|
109
|
+
# Keep your debug envelope; avoid doing any extra work when not in debug
|
|
110
|
+
builtins._original_print( # pylint: disable=protected-access
|
|
111
|
+
"[[CUSTOM-PRINT]]", msg, "[[/CUSTOM-PRINT]]", file=sys.__stdout__, **kwargs
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
builtins._original_print( # pylint: disable=protected-access
|
|
115
|
+
msg, file=sys.__stdout__, **kwargs
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Optionally ship to backend
|
|
119
|
+
if not log:
|
|
120
|
+
return
|
|
121
|
+
if not msg.strip():
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
# Get (or set) trace id exactly the way you do elsewhere
|
|
125
|
+
_, trace_id = get_or_set_sf_trace_id()
|
|
126
|
+
|
|
127
|
+
# Try native fast path; otherwise Python fallback via the PrintInterceptor attached to sys.stdout
|
|
128
|
+
if _ensure_fast_print_initialized():
|
|
129
|
+
try:
|
|
130
|
+
# Your GraphQL schema includes "reentrancyGuardPreactive" for prints;
|
|
131
|
+
# keep it false here to mirror previous behavior.
|
|
132
|
+
_sffastlog.print_(contents=msg, session_id=str(trace_id), preactive=0)
|
|
133
|
+
return
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.exception(e)
|
|
136
|
+
transmit_exception_to_sailfish(e)
|
|
137
|
+
# fall back below
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
# Python fallback (existing path)
|
|
141
|
+
# NOTE: sys.stdout is replaced by UnifiedInterceptor, which carries `print_interceptor`.
|
|
142
|
+
# If you’re not using UnifiedInterceptor in a given environment, ensure sys.stdout provides it.
|
|
143
|
+
try:
|
|
144
|
+
sys.stdout.print_interceptor.do_send((msg, trace_id), trace_id)
|
|
145
|
+
except Exception:
|
|
146
|
+
logger.exception(e)
|
|
147
|
+
transmit_exception_to_sailfish(e)
|
|
148
|
+
# As a last resort, swallow to keep print() non-fatal
|
|
149
|
+
if SF_DEBUG:
|
|
150
|
+
builtins._original_print(
|
|
151
|
+
"[[CUSTOM-PRINT-FALLBACK-ERROR]] failed to send print payload [[/CUSTOM-PRINT-FALLBACK-ERROR]]",
|
|
152
|
+
file=sys.__stdout__,
|
|
153
|
+
)
|
sf_veritas/django_app.py
ADDED
sf_veritas/env_vars.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from .utils import strtobool
|
|
5
|
+
|
|
6
|
+
LITE_DEBUGGING = strtobool(os.getenv("LITE_DEBUGGING", "false"))
|
|
7
|
+
SF_DEBUG = strtobool(os.getenv("SF_DEBUG", "false"))
|
|
8
|
+
SF_DEBUG_TRACES = strtobool(os.getenv("SF_DEBUG_TRACES", "false"))
|
|
9
|
+
SF_INTERNAL = strtobool(os.getenv("SF_INTERNAL", "false"))
|
|
10
|
+
IS_SAILFISH_COLLECTOR = strtobool(os.getenv("IS_SAILFISH_COLLECTOR", "false"))
|
|
11
|
+
STRAWBERRY_DEBUG = strtobool(os.getenv("STRAWBERRY_DEBUG", "false"))
|
|
12
|
+
CAPTURE_STRAWBERRY_ERRORS_WITH_DATA = strtobool(
|
|
13
|
+
os.getenv("CAPTURE_STRAWBERRY_ERRORS_WITH_DATA", "false")
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Log filtering - regex pattern to suppress logs from being sent to Sailfish
|
|
17
|
+
# Default: suppress successful (2xx) requests to /healthz and /graphql/ endpoints
|
|
18
|
+
SF_LOG_IGNORE_REGEX = os.getenv(
|
|
19
|
+
"SF_LOG_IGNORE_REGEX", r"HTTP\s(POST|GET)\s(\/healthz|\/graphql\/)\s2\d{2}\s.*"
|
|
20
|
+
)
|
|
21
|
+
PRINT_CONFIGURATION_STATUSES = strtobool(
|
|
22
|
+
os.getenv("PRINT_CONFIGURATION_STATUSES", "false")
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Parent death signal control (Linux only)
|
|
26
|
+
# Disables automatic registration for SIGTERM on parent process death
|
|
27
|
+
# Useful if customers have custom signal handling that conflicts
|
|
28
|
+
SF_DISABLE_PARENT_DEATH_SIGNAL = strtobool(
|
|
29
|
+
os.getenv("SF_DISABLE_PARENT_DEATH_SIGNAL", "false")
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Parent process monitoring (cross-platform)
|
|
33
|
+
# Interval in milliseconds to check if parent process has died (orphaned)
|
|
34
|
+
# Lower = faster detection, higher = less CPU overhead
|
|
35
|
+
# Set to 0 to disable orphan detection
|
|
36
|
+
SF_PARENT_MONITOR_INTERVAL_MS = int(
|
|
37
|
+
os.getenv("SF_PARENT_MONITOR_INTERVAL_MS", "100") # Default: check every 100ms
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Exception Tools
|
|
41
|
+
SAILFISH_EXCEPTION_LOCALS_HIDE_SELF = strtobool(
|
|
42
|
+
os.getenv("SAILFISH_EXCEPTION_LOCALS_HIDE_SELF", "true")
|
|
43
|
+
)
|
|
44
|
+
SAILFISH_EXCEPTION_LOCALS_HIDE_DUNDER = strtobool(
|
|
45
|
+
os.getenv("SAILFISH_EXCEPTION_LOCALS_HIDE_DUNDER", "true")
|
|
46
|
+
)
|
|
47
|
+
SAILFISH_EXCEPTION_LOCALS_HIDE_SUNDER = strtobool(
|
|
48
|
+
os.getenv("SAILFISH_EXCEPTION_LOCALS_HIDE_SUNDER", "true")
|
|
49
|
+
)
|
|
50
|
+
SAILFISH_EXCEPTION_STACK_DEPTH_LOCALS = int(
|
|
51
|
+
os.getenv("SAILFISH_EXCEPTION_STACK_DEPTH_LOCALS", "5")
|
|
52
|
+
)
|
|
53
|
+
SAILFISH_EXCEPTION_STACK_DEPTH_CODE_TRACE_DEPTH = os.getenv(
|
|
54
|
+
"SAILFISH_EXCEPTION_STACK_DEPTH_CODE_TRACE_DEPTH", "full"
|
|
55
|
+
)
|
|
56
|
+
SAILFISH_EXCEPTION_STACK_DEPTH_CODE_TRACE_TYPE = os.getenv(
|
|
57
|
+
"SAILFISH_EXCEPTION_STACK_DEPTH_CODE_TRACE_TYPE", "line"
|
|
58
|
+
)
|
|
59
|
+
SAILFISH_EXCEPTION_STACK_DEPTH_CODE_TRACE_TYPE_AT_OFFENSIVE_CALL = os.getenv(
|
|
60
|
+
"SAILFISH_EXCEPTION_STACK_DEPTH_CODE_TRACE_TYPE_AT_OFFENSIVE_CALL", "line"
|
|
61
|
+
)
|
|
62
|
+
SAILFISH_EXCEPTION_STACK_DEPTH_CODE_VALUES_AT_FULL_DEPTH_EXCEPTION = strtobool(
|
|
63
|
+
os.getenv(
|
|
64
|
+
"SAILFISH_EXCEPTION_STACK_DEPTH_CODE_VALUES_AT_FULL_DEPTH_EXCEPTION", "false"
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
SAILFISH_EXCEPTION_LOCALS_TYPES_TO_IGNORE = os.getenv(
|
|
68
|
+
"SAILFISH_EXCEPTION_LOCALS_TYPES_TO_IGNORE",
|
|
69
|
+
"strawberry.types.info.Info,graphql.type.definition.GraphQLResolveInfo,strawberry.field.StrawberryField",
|
|
70
|
+
)
|
|
71
|
+
SAILFISH_EXCEPTION_FETCH_BEYOND_OFFENDER_DEPTH = int(
|
|
72
|
+
os.getenv("SAILFISH_EXCEPTION_FETCH_BEYOND_OFFENDER_DEPTH", "3")
|
|
73
|
+
)
|
|
74
|
+
SAILFISH_EXCEPTION_FETCH_LOCALS_BEYOND_OFFENDER_DEPTH = int(
|
|
75
|
+
os.getenv(
|
|
76
|
+
"SAILFISH_EXCEPTION_FETCH_LOCALS_BEYOND_OFFENDER_DEPTH", "5"
|
|
77
|
+
) # Sibyl launch - lower this
|
|
78
|
+
)
|
|
79
|
+
SAILFISH_EXCEPTION_FETCH_ABOVE_OFFENDER_DEPTH = int(
|
|
80
|
+
os.getenv(
|
|
81
|
+
"SAILFISH_EXCEPTION_FETCH_ABOVE_OFFENDER_DEPTH", "3"
|
|
82
|
+
) # Sibyl launch - lower this
|
|
83
|
+
)
|
|
84
|
+
SAILFISH_EXCEPTION_FETCH_LOCALS_ABOVE_OFFENDER_DEPTH = int(
|
|
85
|
+
os.getenv(
|
|
86
|
+
"SAILFISH_EXCEPTION_FETCH_LOCALS_ABOVE_OFFENDER_DEPTH", "-1"
|
|
87
|
+
) # Sibyl launch - lower this
|
|
88
|
+
)
|
|
89
|
+
SAILFISH_EXCEPTION_FETCH_ABOVE_OFFENDER_INCLUDE_INSTALLED_PACKAGES = strtobool(
|
|
90
|
+
os.getenv(
|
|
91
|
+
"SAILFISH_EXCEPTION_FETCH_ABOVE_OFFENDER_INCLUDE_INSTALLED_PACKAGES", "false"
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_log_level():
|
|
97
|
+
if LOG_LEVEL_ENV_VAR == "DEBUG":
|
|
98
|
+
return logging.DEBUG
|
|
99
|
+
if LOG_LEVEL_ENV_VAR == "INFO":
|
|
100
|
+
return logging.INFO
|
|
101
|
+
if LOG_LEVEL_ENV_VAR == "WARN":
|
|
102
|
+
return logging.WARN
|
|
103
|
+
if LOG_LEVEL_ENV_VAR == "ERROR":
|
|
104
|
+
return logging.ERROR
|
|
105
|
+
return logging.CRITICAL
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
LOG_LEVEL_ENV_VAR = os.getenv("LOG_LEVEL", "INFO")
|
|
109
|
+
LOG_LEVEL = get_log_level()
|
|
110
|
+
|
|
111
|
+
# Function Span Serialization
|
|
112
|
+
SF_FUNCSPAN_PARSE_JSON_STRINGS = strtobool(
|
|
113
|
+
os.getenv("SF_FUNCSPAN_PARSE_JSON_STRINGS", "true")
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Function Span Capture Control - Granular configuration
|
|
117
|
+
SF_FUNCSPAN_CAPTURE_ARGUMENTS = strtobool(
|
|
118
|
+
os.getenv("SF_FUNCSPAN_CAPTURE_ARGUMENTS", "true")
|
|
119
|
+
)
|
|
120
|
+
SF_FUNCSPAN_CAPTURE_RETURN_VALUE = strtobool(
|
|
121
|
+
os.getenv("SF_FUNCSPAN_CAPTURE_RETURN_VALUE", "true")
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Separate size limits for arguments vs return values
|
|
125
|
+
SF_FUNCSPAN_ARG_LIMIT_MB = int(os.getenv("SF_FUNCSPAN_ARG_LIMIT_MB", "1"))
|
|
126
|
+
SF_FUNCSPAN_RETURN_LIMIT_MB = int(os.getenv("SF_FUNCSPAN_RETURN_LIMIT_MB", "1"))
|
|
127
|
+
|
|
128
|
+
# Auto-capture child functions (default: true)
|
|
129
|
+
# When false, only capture top-level functions, not their children
|
|
130
|
+
SF_FUNCSPAN_AUTOCAPTURE_ALL_CHILD_FUNCTIONS = strtobool(
|
|
131
|
+
os.getenv("SF_FUNCSPAN_AUTOCAPTURE_ALL_CHILD_FUNCTIONS", "true")
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Two-tier profiler control:
|
|
135
|
+
# SF_ENABLE_PROFILER: Gates installation of profiler hooks (default: false)
|
|
136
|
+
# SF_ENABLE_FUNCTION_SPANS: Master kill switch for capture/transmission (default: true)
|
|
137
|
+
SF_ENABLE_PROFILER = strtobool(os.getenv("SF_ENABLE_PROFILER", "false"))
|
|
138
|
+
|
|
139
|
+
# Include Django view functions in tracing (default: false)
|
|
140
|
+
# Django view functions are typically framework entry points and already traced by web framework instrumentation
|
|
141
|
+
# Set to true if you want detailed function-level tracing inside Django views
|
|
142
|
+
SF_FUNCSPAN_INCLUDE_DJANGO_VIEW_FUNCTIONS = strtobool(
|
|
143
|
+
os.getenv("SF_FUNCSPAN_INCLUDE_DJANGO_VIEW_FUNCTIONS", "false")
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Capture spans from installed packages (site-packages, stdlib, etc.) (default: false)
|
|
147
|
+
# When false, only captures user code; when true, also captures installed libraries
|
|
148
|
+
SF_FUNCSPAN_CAPTURE_INSTALLED_PACKAGES = strtobool(
|
|
149
|
+
os.getenv("SF_FUNCSPAN_CAPTURE_INSTALLED_PACKAGES", "false")
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Capture spans from sf_veritas telemetry code itself (default: false)
|
|
153
|
+
# When false, skips sf_veritas code; when true, captures our own telemetry code
|
|
154
|
+
SF_FUNCSPAN_CAPTURE_SF_VERITAS = strtobool(
|
|
155
|
+
os.getenv("SF_FUNCSPAN_CAPTURE_SF_VERITAS", "false")
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Network Hop I/O Capture - OTEL-style defaults (bodies OFF for performance)
|
|
159
|
+
SF_NETWORKHOP_CAPTURE_ENABLED = strtobool(
|
|
160
|
+
os.getenv("SF_NETWORKHOP_CAPTURE_ENABLED", "true") # OTEL doesn't capture bodies
|
|
161
|
+
)
|
|
162
|
+
SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS = strtobool(
|
|
163
|
+
os.getenv("SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS", "false")
|
|
164
|
+
)
|
|
165
|
+
SF_NETWORKHOP_CAPTURE_REQUEST_BODY = strtobool(
|
|
166
|
+
os.getenv(
|
|
167
|
+
"SF_NETWORKHOP_CAPTURE_REQUEST_BODY", "false"
|
|
168
|
+
) # OTEL doesn't capture bodies
|
|
169
|
+
)
|
|
170
|
+
SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS = strtobool(
|
|
171
|
+
os.getenv("SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS", "false")
|
|
172
|
+
)
|
|
173
|
+
SF_NETWORKHOP_CAPTURE_RESPONSE_BODY = strtobool(
|
|
174
|
+
os.getenv(
|
|
175
|
+
"SF_NETWORKHOP_CAPTURE_RESPONSE_BODY", "false"
|
|
176
|
+
) # OTEL doesn't capture bodies
|
|
177
|
+
)
|
|
178
|
+
SF_NETWORKHOP_REQUEST_LIMIT_MB = int(os.getenv("SF_NETWORKHOP_REQUEST_LIMIT_MB", "1"))
|
|
179
|
+
SF_NETWORKHOP_RESPONSE_LIMIT_MB = int(os.getenv("SF_NETWORKHOP_RESPONSE_LIMIT_MB", "1"))
|
|
180
|
+
|
|
181
|
+
# Route-based suppression for inbound network tracing (comma-separated route patterns with wildcard support)
|
|
182
|
+
# Example: "/healthz, /metrics, /admin/*, /api/v1/status*"
|
|
183
|
+
# Supports wildcards: * (matches any sequence) and ? (matches single character)
|
|
184
|
+
SF_DISABLE_INBOUND_NETWORK_TRACING_ON_ROUTES = os.getenv(
|
|
185
|
+
"SF_DISABLE_INBOUND_NETWORK_TRACING_ON_ROUTES", ""
|
|
186
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from .custom_excepthook import custom_excepthook
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CustomExceptionMiddleware:
|
|
7
|
+
def __init__(self, app):
|
|
8
|
+
self.app = app
|
|
9
|
+
|
|
10
|
+
async def __call__(self, scope, receive, send):
|
|
11
|
+
try:
|
|
12
|
+
await self.app(scope, receive, send)
|
|
13
|
+
except Exception as exc:
|
|
14
|
+
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
15
|
+
custom_excepthook(exc_type, exc_value, exc_traceback)
|
|
16
|
+
|
|
17
|
+
def __getattr__(self, attr):
|
|
18
|
+
return getattr(self.original, attr)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from abc import ABCMeta
|
|
2
|
+
|
|
3
|
+
from .custom_excepthook import transmit_exception
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ExceptionMeta(ABCMeta):
|
|
7
|
+
"""
|
|
8
|
+
Metaclass to add `capture_even_if_caught` functionality to exceptions
|
|
9
|
+
and provide a `transmit_to_sailfish` method for all exceptions.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __new__(cls, name, bases, dct):
|
|
13
|
+
# Wrap or define the `__init__` method
|
|
14
|
+
if "__init__" in dct:
|
|
15
|
+
original_init = dct["__init__"]
|
|
16
|
+
|
|
17
|
+
def wrapped_init(self, *args, **kwargs):
|
|
18
|
+
# Add the `capture_even_if_caught` attribute
|
|
19
|
+
self.capture_even_if_caught = kwargs.pop(
|
|
20
|
+
"capture_even_if_caught", False
|
|
21
|
+
)
|
|
22
|
+
self._handled = False # Ensure `_handled` is initialized
|
|
23
|
+
original_init(self, *args, **kwargs)
|
|
24
|
+
|
|
25
|
+
dct["__init__"] = wrapped_init
|
|
26
|
+
else:
|
|
27
|
+
|
|
28
|
+
def default_init(self, *args, **kwargs):
|
|
29
|
+
# Add the `capture_even_if_caught` attribute
|
|
30
|
+
self.capture_even_if_caught = kwargs.pop(
|
|
31
|
+
"capture_even_if_caught", False
|
|
32
|
+
)
|
|
33
|
+
self._handled = False # Ensure `_handled` is initialized
|
|
34
|
+
super(Exception, self).__init__(*args, **kwargs)
|
|
35
|
+
|
|
36
|
+
dct["__init__"] = default_init
|
|
37
|
+
|
|
38
|
+
# Add `transmit_to_sailfish` method to all exceptions
|
|
39
|
+
def transmit_to_sailfish(self, was_caught: bool = False):
|
|
40
|
+
"""
|
|
41
|
+
Transmit this exception to Sailfish.
|
|
42
|
+
"""
|
|
43
|
+
if not getattr(self, "_handled", False):
|
|
44
|
+
transmit_exception(type(self), self, self.__traceback__, was_caught)
|
|
45
|
+
setattr(
|
|
46
|
+
self, "_handled", True
|
|
47
|
+
) # Mark as handled to prevent duplication
|
|
48
|
+
|
|
49
|
+
dct["transmit_to_sailfish"] = transmit_to_sailfish
|
|
50
|
+
|
|
51
|
+
return super().__new__(cls, name, bases, dct)
|
|
52
|
+
|
|
53
|
+
def __call__(cls, *args, **kwargs):
|
|
54
|
+
"""
|
|
55
|
+
Intercept exception instantiation to handle `capture_even_if_caught`.
|
|
56
|
+
"""
|
|
57
|
+
instance = super().__call__(*args, **kwargs)
|
|
58
|
+
# Automatically handle `capture_even_if_caught` exceptions
|
|
59
|
+
if getattr(instance, "capture_even_if_caught", False):
|
|
60
|
+
instance.transmit_to_sailfish()
|
|
61
|
+
return instance
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class PatchedException(Exception, metaclass=ExceptionMeta):
|
|
65
|
+
"""
|
|
66
|
+
A patched version of the built-in Exception class with universal interception capabilities.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
pass
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ultra-fast frame introspection using C extension.
|
|
3
|
+
Falls back to Python if C extension is not available.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sysconfig
|
|
7
|
+
from functools import lru_cache
|
|
8
|
+
from typing import Optional, Tuple
|
|
9
|
+
|
|
10
|
+
# Try to import C extension
|
|
11
|
+
try:
|
|
12
|
+
from . import _sfframeinfo
|
|
13
|
+
|
|
14
|
+
_FAST_FRAME_OK = True
|
|
15
|
+
except ImportError:
|
|
16
|
+
_sfframeinfo = None
|
|
17
|
+
_FAST_FRAME_OK = False
|
|
18
|
+
|
|
19
|
+
# Fallback Python implementation
|
|
20
|
+
_STDLIB = sysconfig.get_paths()["stdlib"]
|
|
21
|
+
_SITE_TAGS = ("site-packages", "dist-packages")
|
|
22
|
+
_SKIP_PREFIXES = (_STDLIB, "/usr/local/lib/python", "/usr/lib/python")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@lru_cache(maxsize=512)
|
|
26
|
+
def _is_user_code_py(path: Optional[str]) -> bool:
|
|
27
|
+
"""Python fallback for checking if path is user code."""
|
|
28
|
+
if not path or path.startswith("<"):
|
|
29
|
+
return False
|
|
30
|
+
for p in _SKIP_PREFIXES:
|
|
31
|
+
if path.startswith(p):
|
|
32
|
+
return False
|
|
33
|
+
return not any(tag in path for tag in _SITE_TAGS)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_code_info(func) -> Optional[Tuple[str, int, str]]:
|
|
37
|
+
"""
|
|
38
|
+
Extract (filename, lineno, name) from a function object.
|
|
39
|
+
Returns None if not user code or extraction fails.
|
|
40
|
+
|
|
41
|
+
Uses C extension if available for ~10x speedup.
|
|
42
|
+
"""
|
|
43
|
+
if _FAST_FRAME_OK:
|
|
44
|
+
try:
|
|
45
|
+
result = _sfframeinfo.get_code_info(func)
|
|
46
|
+
if result:
|
|
47
|
+
# Result is (filename, lineno, name, is_user)
|
|
48
|
+
return (result[0], result[1], result[2])
|
|
49
|
+
return None
|
|
50
|
+
except Exception:
|
|
51
|
+
pass # Fall back to Python
|
|
52
|
+
|
|
53
|
+
# Python fallback
|
|
54
|
+
try:
|
|
55
|
+
code = getattr(func, "__code__", None)
|
|
56
|
+
if not code:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
filename = code.co_filename
|
|
60
|
+
if not _is_user_code_py(filename):
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
return (filename, code.co_firstlineno, func.__name__)
|
|
64
|
+
except Exception:
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_frame_info(frame) -> Optional[Tuple[str, int, str]]:
|
|
69
|
+
"""
|
|
70
|
+
Extract (filename, lineno, name) from a frame object.
|
|
71
|
+
Returns None if not user code or extraction fails.
|
|
72
|
+
|
|
73
|
+
Uses C extension if available for ~10x speedup.
|
|
74
|
+
"""
|
|
75
|
+
if _FAST_FRAME_OK:
|
|
76
|
+
try:
|
|
77
|
+
result = _sfframeinfo.get_frame_info(frame)
|
|
78
|
+
if result:
|
|
79
|
+
# Result is (filename, lineno, name, is_user)
|
|
80
|
+
return (result[0], result[1], result[2])
|
|
81
|
+
return None
|
|
82
|
+
except Exception:
|
|
83
|
+
pass # Fall back to Python
|
|
84
|
+
|
|
85
|
+
# Python fallback
|
|
86
|
+
try:
|
|
87
|
+
code = frame.f_code
|
|
88
|
+
filename = code.co_filename
|
|
89
|
+
if not _is_user_code_py(filename):
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
return (filename, frame.f_lineno, code.co_name)
|
|
93
|
+
except Exception:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def is_user_code_func(func) -> bool:
|
|
98
|
+
"""
|
|
99
|
+
Quick check if function is user code.
|
|
100
|
+
|
|
101
|
+
Uses C extension if available for ~10x speedup.
|
|
102
|
+
"""
|
|
103
|
+
if _FAST_FRAME_OK:
|
|
104
|
+
try:
|
|
105
|
+
return bool(_sfframeinfo.is_user_code_func(func))
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
# Python fallback
|
|
110
|
+
try:
|
|
111
|
+
code = getattr(func, "__code__", None)
|
|
112
|
+
if not code:
|
|
113
|
+
return False
|
|
114
|
+
return _is_user_code_py(code.co_filename)
|
|
115
|
+
except Exception:
|
|
116
|
+
return False
|