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,129 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from . import app_config
|
|
5
|
+
from .env_vars import PRINT_CONFIGURATION_STATUSES, SF_DEBUG
|
|
6
|
+
from .interceptors import LogInterceptor
|
|
7
|
+
from .thread_local import get_or_set_sf_trace_id, get_reentrancy_guard_logging_preactive
|
|
8
|
+
|
|
9
|
+
# Try native fast path (compiled C extension). If missing, we fall back.
|
|
10
|
+
try:
|
|
11
|
+
from . import _sffastlog as _FASTMOD
|
|
12
|
+
|
|
13
|
+
_FAST_OK = True
|
|
14
|
+
except Exception:
|
|
15
|
+
_FASTMOD = None
|
|
16
|
+
_FAST_OK = False
|
|
17
|
+
|
|
18
|
+
# Small constant table avoids attribute access for levelname on hot path
|
|
19
|
+
_LEVEL_NAME = {
|
|
20
|
+
50: "CRITICAL",
|
|
21
|
+
40: "ERROR",
|
|
22
|
+
30: "WARNING",
|
|
23
|
+
20: "INFO",
|
|
24
|
+
10: "DEBUG",
|
|
25
|
+
0: "NOTSET",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CustomLogHandler(logging.Handler, LogInterceptor):
|
|
30
|
+
"""
|
|
31
|
+
Ultra-light log handler:
|
|
32
|
+
- Avoid Formatter unless explicitly set.
|
|
33
|
+
- Avoid %-formatting if record.args is empty.
|
|
34
|
+
- Prefer levelno→name map (micro faster than attribute lookup).
|
|
35
|
+
- Fetch/generate trace_id once per emit (unchanged).
|
|
36
|
+
- DIRECT native send via _sffastlog when available; Python fallback otherwise.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self):
|
|
40
|
+
logging.Handler.__init__(self)
|
|
41
|
+
LogInterceptor.__init__(self, api_key=app_config._sailfish_api_key)
|
|
42
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
43
|
+
print("Intercepting log statements")
|
|
44
|
+
|
|
45
|
+
def emit(self, record: logging.LogRecord, trace_id: Optional[str] = None):
|
|
46
|
+
# Bind frequently used symbols to locals to reduce attribute/global lookups
|
|
47
|
+
_formatter = self.formatter
|
|
48
|
+
_get_tid = get_or_set_sf_trace_id
|
|
49
|
+
_get_preactive = get_reentrancy_guard_logging_preactive
|
|
50
|
+
_debug = SF_DEBUG
|
|
51
|
+
_level_table = _LEVEL_NAME
|
|
52
|
+
_fast = _FASTMOD
|
|
53
|
+
_fast_ok = _FAST_OK
|
|
54
|
+
_do_send = self.do_send # Python fallback
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
# FAST PATH message extraction:
|
|
58
|
+
# - If a formatter is attached, defer to it (caller explicitly wants decorations)
|
|
59
|
+
# - Otherwise:
|
|
60
|
+
# - If there are args, we must perform %-format (getMessage)
|
|
61
|
+
# - If no args and msg is already a str, reuse it directly (no str() allocation)
|
|
62
|
+
# - Else fall back to str(msg)
|
|
63
|
+
if _formatter is None:
|
|
64
|
+
if record.args:
|
|
65
|
+
log_entry = record.getMessage()
|
|
66
|
+
else:
|
|
67
|
+
msg = record.msg
|
|
68
|
+
log_entry = msg if (msg.__class__ is str) else str(msg)
|
|
69
|
+
else:
|
|
70
|
+
log_entry = self.format(record)
|
|
71
|
+
|
|
72
|
+
# Cheap ignore check (lives on LogInterceptor); skip everything if ignored
|
|
73
|
+
# Keeps semantics identical while saving work on hot path
|
|
74
|
+
try:
|
|
75
|
+
if self.check_if_contents_should_be_ignored(log_entry):
|
|
76
|
+
return
|
|
77
|
+
except AttributeError:
|
|
78
|
+
# If not defined for some reason, proceed (older builds)
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
# CHEAP LEVEL NAME:
|
|
82
|
+
lvlno = record.levelno
|
|
83
|
+
log_level = _level_table.get(lvlno)
|
|
84
|
+
if log_level is None: # uncommon/3rd-party custom levels
|
|
85
|
+
log_level = record.levelname
|
|
86
|
+
|
|
87
|
+
# TRACE ID ONCE (unchanged contract)
|
|
88
|
+
if trace_id is None:
|
|
89
|
+
_, trace_id = _get_tid(None)
|
|
90
|
+
|
|
91
|
+
# Debug printing (rarely enabled)
|
|
92
|
+
if _debug:
|
|
93
|
+
# Avoid f-strings to skip extra work when disabled
|
|
94
|
+
print(
|
|
95
|
+
"[[DEBUG custom_log_handler]]",
|
|
96
|
+
"trace_id=" + str(trace_id),
|
|
97
|
+
"[[" + log_level + "]]",
|
|
98
|
+
log_entry,
|
|
99
|
+
"[[/DEBUG]]",
|
|
100
|
+
log=False,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# ---------- Native fast path ----------
|
|
104
|
+
if _fast_ok and _fast is not None:
|
|
105
|
+
try:
|
|
106
|
+
preactive = bool(_get_preactive())
|
|
107
|
+
# Single C call → copy → enqueue → return; HTTP happens on native thread
|
|
108
|
+
_fast.log(
|
|
109
|
+
level=log_level or "UNKNOWN",
|
|
110
|
+
contents=log_entry,
|
|
111
|
+
session_id=str(trace_id),
|
|
112
|
+
preactive=preactive,
|
|
113
|
+
)
|
|
114
|
+
return
|
|
115
|
+
except Exception as _e:
|
|
116
|
+
print(
|
|
117
|
+
"[_sffastlog] fast-path failed; falling back:",
|
|
118
|
+
_e,
|
|
119
|
+
log=False,
|
|
120
|
+
)
|
|
121
|
+
# fall through to Python path
|
|
122
|
+
|
|
123
|
+
# ---------- Python fallback (deferred batching/async) ----------
|
|
124
|
+
_do_send((log_level, log_entry, trace_id), trace_id)
|
|
125
|
+
|
|
126
|
+
except Exception as e: # keep lean; avoid storms on storms
|
|
127
|
+
if _debug:
|
|
128
|
+
print("CustomLogHandler.emit error:", e)
|
|
129
|
+
self.handleError(record)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# sf_veritas/custom_output_wrapper.py
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from . import app_config
|
|
6
|
+
from .env_vars import PRINT_CONFIGURATION_STATUSES
|
|
7
|
+
from .interceptors import PrintInterceptor
|
|
8
|
+
from .thread_local import _thread_locals, get_or_set_sf_trace_id
|
|
9
|
+
|
|
10
|
+
# ---- optional native fast path (C extension) ----
|
|
11
|
+
try:
|
|
12
|
+
from . import _sffastlog # compiled extension with ring + libcurl sender
|
|
13
|
+
|
|
14
|
+
_FAST_OK = True
|
|
15
|
+
except Exception: # pragma: no cover
|
|
16
|
+
_sffastlog = None
|
|
17
|
+
_FAST_OK = False
|
|
18
|
+
|
|
19
|
+
_FAST_PRINT_READY = False # one-time init guard
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _ensure_fast_print_initialized() -> bool:
|
|
23
|
+
"""Idempotent, cheap check to init native print path once."""
|
|
24
|
+
global _FAST_PRINT_READY
|
|
25
|
+
if not _FAST_OK or _FAST_PRINT_READY:
|
|
26
|
+
return _FAST_PRINT_READY
|
|
27
|
+
|
|
28
|
+
endpoint = getattr(app_config, "_sailfish_graphql_endpoint", None)
|
|
29
|
+
api_key = getattr(app_config, "_sailfish_api_key", None)
|
|
30
|
+
service_uuid = getattr(app_config, "_service_uuid", None)
|
|
31
|
+
library = getattr(app_config, "library", "sailfish-python")
|
|
32
|
+
version = getattr(app_config, "version", "0.0.0")
|
|
33
|
+
http2 = 1 if os.getenv("SF_NBPOST_HTTP2", "0") == "1" else 0
|
|
34
|
+
|
|
35
|
+
if not (endpoint and api_key and service_uuid):
|
|
36
|
+
return False # not configured yet
|
|
37
|
+
|
|
38
|
+
# GraphQL mutation for print statements (must match server)
|
|
39
|
+
query = (
|
|
40
|
+
"mutation CollectPrintStatements("
|
|
41
|
+
"$apiKey: String!,"
|
|
42
|
+
"$serviceUuid: String!,"
|
|
43
|
+
"$sessionId: String!,"
|
|
44
|
+
"$contents: String!,"
|
|
45
|
+
"$reentrancyGuardPreactive: Boolean!,"
|
|
46
|
+
"$library: String!,"
|
|
47
|
+
"$timestampMs: String!,"
|
|
48
|
+
"$version: String!"
|
|
49
|
+
"){collectPrintStatements("
|
|
50
|
+
"apiKey:$apiKey,serviceUuid:$serviceUuid,sessionId:$sessionId,"
|
|
51
|
+
"contents:$contents,reentrancyGuardPreactive:$reentrancyGuardPreactive,"
|
|
52
|
+
"library:$library,timestampMs:$timestampMs,version:$version)}"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
ok = _sffastlog.init_print(
|
|
57
|
+
url=endpoint,
|
|
58
|
+
query=query,
|
|
59
|
+
api_key=str(api_key),
|
|
60
|
+
service_uuid=str(service_uuid),
|
|
61
|
+
library=str(library),
|
|
62
|
+
version=str(version),
|
|
63
|
+
http2=http2,
|
|
64
|
+
)
|
|
65
|
+
_FAST_PRINT_READY = bool(ok)
|
|
66
|
+
except Exception:
|
|
67
|
+
_FAST_PRINT_READY = False
|
|
68
|
+
|
|
69
|
+
return _FAST_PRINT_READY
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class CustomOutputWrapper:
|
|
73
|
+
"""
|
|
74
|
+
Ultra-thin sys.stdout wrapper:
|
|
75
|
+
- Writes straight to the real stream to preserve console output.
|
|
76
|
+
- If native fast path is ready, send to C ring immediately.
|
|
77
|
+
- Otherwise, fall back to PrintInterceptor (Python path).
|
|
78
|
+
- No locks, no regex, no unconditional flush.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
__slots__ = ("original", "print_interceptor")
|
|
82
|
+
|
|
83
|
+
def __init__(self, original):
|
|
84
|
+
self.original = original
|
|
85
|
+
self.print_interceptor = PrintInterceptor()
|
|
86
|
+
|
|
87
|
+
def write(self, msg):
|
|
88
|
+
# Respect your reentrancy guard quickly
|
|
89
|
+
if getattr(_thread_locals, "reentrancy_guard_logging_active", False):
|
|
90
|
+
self.original.write(msg)
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
# Always write to the real stream first (don’t flush unless caller asks)
|
|
94
|
+
self.original.write(msg)
|
|
95
|
+
|
|
96
|
+
# Cheap ignore cases (optional): skip totally empty or pure newline
|
|
97
|
+
if not msg or msg == "\n":
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
# Build once
|
|
101
|
+
message = msg
|
|
102
|
+
_, trace_id = get_or_set_sf_trace_id()
|
|
103
|
+
|
|
104
|
+
# Native fast path (C) — lowest overhead
|
|
105
|
+
if _ensure_fast_print_initialized():
|
|
106
|
+
try:
|
|
107
|
+
_sffastlog.print_(
|
|
108
|
+
contents=message, session_id=str(trace_id), preactive=0
|
|
109
|
+
)
|
|
110
|
+
return
|
|
111
|
+
except Exception:
|
|
112
|
+
pass # fall through to Python fallback
|
|
113
|
+
|
|
114
|
+
# Python fallback path
|
|
115
|
+
self.print_interceptor.do_send((message, trace_id), trace_id)
|
|
116
|
+
|
|
117
|
+
def flush(self):
|
|
118
|
+
# Only flush when caller flushes (keeps latency down)
|
|
119
|
+
self.original.flush()
|
|
120
|
+
|
|
121
|
+
def __getattr__(self, attr):
|
|
122
|
+
return getattr(self.original, attr)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def setup_custom_output_wrappers():
|
|
126
|
+
# Import here to avoid circular dependency
|
|
127
|
+
from .print_override import override_print
|
|
128
|
+
|
|
129
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
130
|
+
sys.__stdout__.write("setup_custom_output_wrappers\n")
|
|
131
|
+
|
|
132
|
+
# First override print to support log=True/False parameter
|
|
133
|
+
override_print()
|
|
134
|
+
|
|
135
|
+
# Then wrap stdout for interception
|
|
136
|
+
sys.stdout = CustomOutputWrapper(sys.stdout)
|
|
137
|
+
|
|
138
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
139
|
+
sys.__stdout__.write("setup_custom_output_wrappers...DONE\n")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def get_custom_output_wrapper_django():
|
|
143
|
+
"""Django-specific wrapper setup function called by Django patch."""
|
|
144
|
+
setup_custom_output_wrappers()
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# sf_veritas/custom_print.py
|
|
2
|
+
import builtins
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from . import app_config
|
|
7
|
+
from .env_vars import SF_DEBUG
|
|
8
|
+
from .thread_local import activate_reentrancy_guards_sys_stdout, get_or_set_sf_trace_id
|
|
9
|
+
|
|
10
|
+
# --- Optional native fast path (C extension) ---
|
|
11
|
+
try:
|
|
12
|
+
from . import _sffastlog # compiled extension
|
|
13
|
+
|
|
14
|
+
_FAST_OK = True
|
|
15
|
+
except Exception: # pragma: no cover
|
|
16
|
+
_sffastlog = None
|
|
17
|
+
_FAST_OK = False
|
|
18
|
+
|
|
19
|
+
# Keep an internal one-time init guard for the print fast path
|
|
20
|
+
_FAST_PRINT_READY = False
|
|
21
|
+
|
|
22
|
+
# GraphQL mutation for print statements (must match your server exactly)
|
|
23
|
+
# Mirrors interceptors.PrintInterceptor._QUERY variables/shape.
|
|
24
|
+
_COLLECT_PRINT_MUTATION = """
|
|
25
|
+
mutation CollectPrintStatements(
|
|
26
|
+
$apiKey: String!,
|
|
27
|
+
$serviceUuid: String!,
|
|
28
|
+
$sessionId: String!,
|
|
29
|
+
$contents: String!,
|
|
30
|
+
$reentrancyGuardPreactive: Boolean!,
|
|
31
|
+
$library: String!,
|
|
32
|
+
$timestampMs: String!,
|
|
33
|
+
$version: String!
|
|
34
|
+
) {
|
|
35
|
+
collectPrintStatements(
|
|
36
|
+
apiKey: $apiKey,
|
|
37
|
+
serviceUuid: $serviceUuid,
|
|
38
|
+
sessionId: $sessionId,
|
|
39
|
+
contents: $contents,
|
|
40
|
+
reentrancyGuardPreactive: $reentrancyGuardPreactive,
|
|
41
|
+
library: $library,
|
|
42
|
+
timestampMs: $timestampMs,
|
|
43
|
+
version: $version
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
""".strip()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _ensure_fast_print_initialized() -> bool:
|
|
50
|
+
"""
|
|
51
|
+
Lazily initialize the native print path. Safe to call every print; it becomes a no-op after the first success.
|
|
52
|
+
"""
|
|
53
|
+
global _FAST_PRINT_READY
|
|
54
|
+
|
|
55
|
+
if not _FAST_OK or _FAST_PRINT_READY:
|
|
56
|
+
return _FAST_PRINT_READY
|
|
57
|
+
|
|
58
|
+
# We require the same config used elsewhere in the package
|
|
59
|
+
endpoint = getattr(app_config, "_sailfish_graphql_endpoint", None)
|
|
60
|
+
api_key = getattr(app_config, "_sailfish_api_key", None)
|
|
61
|
+
service_uuid = getattr(app_config, "_service_uuid", None)
|
|
62
|
+
library = getattr(app_config, "library", "sailfish-python")
|
|
63
|
+
version = getattr(app_config, "version", "0.0.0")
|
|
64
|
+
http2 = 1 if os.getenv("SF_NBPOST_HTTP2", "0") == "1" else 0
|
|
65
|
+
|
|
66
|
+
if not (endpoint and api_key and service_uuid):
|
|
67
|
+
# Not configured yet; stay in Python fallback for now
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
ok = _sffastlog.init_print(
|
|
72
|
+
url=endpoint,
|
|
73
|
+
query=_COLLECT_PRINT_MUTATION,
|
|
74
|
+
api_key=str(api_key),
|
|
75
|
+
service_uuid=str(service_uuid),
|
|
76
|
+
library=str(library),
|
|
77
|
+
version=str(version),
|
|
78
|
+
http2=http2,
|
|
79
|
+
)
|
|
80
|
+
_FAST_PRINT_READY = bool(ok)
|
|
81
|
+
except Exception:
|
|
82
|
+
_FAST_PRINT_READY = False
|
|
83
|
+
|
|
84
|
+
return _FAST_PRINT_READY
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def custom_print(*args, log=True, **kwargs):
|
|
88
|
+
"""
|
|
89
|
+
Custom print function to intercept print statements.
|
|
90
|
+
- Writes to real stdout (sys.__stdout__) to avoid recursion.
|
|
91
|
+
- If log=True, ships the message via native _sffastlog.print_ when available,
|
|
92
|
+
otherwise falls back to the Python interceptor path: sys.stdout.print_interceptor.do_send(...)
|
|
93
|
+
"""
|
|
94
|
+
# Tag in debug without creating extra strings when not needed
|
|
95
|
+
activate_reentrancy_guards_sys_stdout()
|
|
96
|
+
|
|
97
|
+
# Build the message ONCE; use it for both console and network
|
|
98
|
+
if args:
|
|
99
|
+
# Fastest reasonable join; avoid f-strings on the hot path
|
|
100
|
+
msg = " ".join(map(str, args))
|
|
101
|
+
else:
|
|
102
|
+
msg = ""
|
|
103
|
+
|
|
104
|
+
# Write to the actual stdout (not our wrapper)
|
|
105
|
+
if SF_DEBUG:
|
|
106
|
+
# Keep your debug envelope; avoid doing any extra work when not in debug
|
|
107
|
+
builtins._original_print( # pylint: disable=protected-access
|
|
108
|
+
"[[CUSTOM-PRINT]]", msg, "[[/CUSTOM-PRINT]]", file=sys.__stdout__, **kwargs
|
|
109
|
+
)
|
|
110
|
+
else:
|
|
111
|
+
builtins._original_print( # pylint: disable=protected-access
|
|
112
|
+
msg, file=sys.__stdout__, **kwargs
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Optionally ship to backend
|
|
116
|
+
if not log:
|
|
117
|
+
return
|
|
118
|
+
if not msg.strip():
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
# Get (or set) trace id exactly the way you do elsewhere
|
|
122
|
+
_, trace_id = get_or_set_sf_trace_id()
|
|
123
|
+
|
|
124
|
+
# Try native fast path; otherwise Python fallback via the PrintInterceptor attached to sys.stdout
|
|
125
|
+
if _ensure_fast_print_initialized():
|
|
126
|
+
try:
|
|
127
|
+
# Your GraphQL schema includes "reentrancyGuardPreactive" for prints;
|
|
128
|
+
# keep it false here to mirror previous behavior.
|
|
129
|
+
_sffastlog.print_(contents=msg, session_id=str(trace_id), preactive=0)
|
|
130
|
+
return
|
|
131
|
+
except Exception:
|
|
132
|
+
# fall back below
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
# Python fallback (existing path)
|
|
136
|
+
# NOTE: sys.stdout is replaced by UnifiedInterceptor, which carries `print_interceptor`.
|
|
137
|
+
# If you’re not using UnifiedInterceptor in a given environment, ensure sys.stdout provides it.
|
|
138
|
+
try:
|
|
139
|
+
sys.stdout.print_interceptor.do_send((msg, trace_id), trace_id)
|
|
140
|
+
except Exception:
|
|
141
|
+
# As a last resort, swallow to keep print() non-fatal
|
|
142
|
+
if SF_DEBUG:
|
|
143
|
+
builtins._original_print(
|
|
144
|
+
"[[CUSTOM-PRINT-FALLBACK-ERROR]] failed to send print payload [[/CUSTOM-PRINT-FALLBACK-ERROR]]",
|
|
145
|
+
file=sys.__stdout__,
|
|
146
|
+
)
|
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)
|