sf-veritas 0.10.3__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.
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-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +924 -0
- sf_veritas/_sffastnet.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +730 -0
- sf_veritas/_sffastnetworkrequest.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2155 -0
- sf_veritas/_sffuncspan.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +617 -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 +1451 -0
- sf_veritas/_sfnetworkhop.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1175 -0
- sf_veritas/_sfservice.cpython-314-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
sf_veritas/_sffastlog.c
ADDED
|
@@ -0,0 +1,889 @@
|
|
|
1
|
+
// sf_veritas/_sffastlog.c
|
|
2
|
+
// NOTE: Previously used g_in_telemetry_send from _sfteepreload.c
|
|
3
|
+
// Now uses X-Sf3-IsTelemetryMessage header for detection (no external symbol needed)
|
|
4
|
+
|
|
5
|
+
#define PY_SSIZE_T_CLEAN
|
|
6
|
+
#include <Python.h>
|
|
7
|
+
#include <pthread.h>
|
|
8
|
+
#include <curl/curl.h>
|
|
9
|
+
#include <stdatomic.h>
|
|
10
|
+
#include <stdint.h>
|
|
11
|
+
#include <stdlib.h>
|
|
12
|
+
#include <string.h>
|
|
13
|
+
#include <time.h>
|
|
14
|
+
#include <sys/time.h>
|
|
15
|
+
|
|
16
|
+
// ---------- Thread-local guard flag ----------
|
|
17
|
+
// CRITICAL: Prevents _sfteepreload.c from capturing our telemetry traffic
|
|
18
|
+
// Set to 1 in sender threads, provides ~5ns overhead vs 50-100ns for header parsing
|
|
19
|
+
__attribute__((visibility("default")))
|
|
20
|
+
__thread int g_in_telemetry_send = 0;
|
|
21
|
+
|
|
22
|
+
// ---------- Ring buffer ----------
|
|
23
|
+
#ifndef SFF_RING_CAP
|
|
24
|
+
#define SFF_RING_CAP 65536 // power-of-two recommended
|
|
25
|
+
#endif
|
|
26
|
+
|
|
27
|
+
typedef struct {
|
|
28
|
+
char *body; // malloc'd HTTP JSON body
|
|
29
|
+
size_t len;
|
|
30
|
+
} sff_msg_t;
|
|
31
|
+
|
|
32
|
+
static sff_msg_t *g_ring = NULL;
|
|
33
|
+
static size_t g_cap = 0;
|
|
34
|
+
static _Atomic size_t g_head = 0; // consumer
|
|
35
|
+
static _Atomic size_t g_tail = 0; // producer
|
|
36
|
+
|
|
37
|
+
// tiny spinlock to make push MPMC-safe enough for Python producers
|
|
38
|
+
static atomic_flag g_push_lock = ATOMIC_FLAG_INIT;
|
|
39
|
+
|
|
40
|
+
// wake/sleep
|
|
41
|
+
static pthread_mutex_t g_cv_mtx = PTHREAD_MUTEX_INITIALIZER;
|
|
42
|
+
static pthread_cond_t g_cv = PTHREAD_COND_INITIALIZER;
|
|
43
|
+
static _Atomic int g_running = 0;
|
|
44
|
+
|
|
45
|
+
// Thread pool for parallel sending (configurable via SF_LOG_SENDER_THREADS)
|
|
46
|
+
#define MAX_SENDER_THREADS 16
|
|
47
|
+
static pthread_t g_sender_threads[MAX_SENDER_THREADS];
|
|
48
|
+
static int g_num_sender_threads = 0;
|
|
49
|
+
static int g_configured_sender_threads = 1; // Default: 1 thread
|
|
50
|
+
|
|
51
|
+
// curl state (per-thread)
|
|
52
|
+
__thread CURL *g_telem_curl = NULL;
|
|
53
|
+
static struct curl_slist *g_hdrs = NULL;
|
|
54
|
+
|
|
55
|
+
// config (owned strings)
|
|
56
|
+
static char *g_url = NULL;
|
|
57
|
+
|
|
58
|
+
static char *g_query_escaped = NULL; // logs mutation (escaped)
|
|
59
|
+
static char *g_api_key = NULL;
|
|
60
|
+
static char *g_service_uuid = NULL;
|
|
61
|
+
static char *g_library = NULL;
|
|
62
|
+
static char *g_version = NULL;
|
|
63
|
+
static int g_http2 = 0;
|
|
64
|
+
|
|
65
|
+
// prebuilt JSON prefix for LOGS:
|
|
66
|
+
// {"query":"<escaped_query>","variables":{"apiKey":"...","serviceUuid":"...","library":"...","version":"..."
|
|
67
|
+
static char *g_json_prefix = NULL;
|
|
68
|
+
|
|
69
|
+
// --- PRINT channel state ---
|
|
70
|
+
static char *g_print_query_escaped = NULL; // prints mutation (escaped)
|
|
71
|
+
static char *g_json_prefix_print = NULL; // same prefix style for print
|
|
72
|
+
|
|
73
|
+
// --- EXCEPTION channel state ---
|
|
74
|
+
static char *g_exception_query_escaped = NULL; // exception mutation (escaped)
|
|
75
|
+
static char *g_json_prefix_exception = NULL; // same prefix style for exception
|
|
76
|
+
|
|
77
|
+
static const char *JSON_SUFFIX = "}}";
|
|
78
|
+
|
|
79
|
+
// ---------- helpers ----------
|
|
80
|
+
static inline uint64_t now_ms(void) {
|
|
81
|
+
#if defined(CLOCK_REALTIME_COARSE)
|
|
82
|
+
struct timespec ts;
|
|
83
|
+
clock_gettime(CLOCK_REALTIME_COARSE, &ts);
|
|
84
|
+
return ((uint64_t)ts.tv_sec) * 1000ULL + (uint64_t)(ts.tv_nsec / 1000000ULL);
|
|
85
|
+
#else
|
|
86
|
+
struct timeval tv;
|
|
87
|
+
gettimeofday(&tv, NULL);
|
|
88
|
+
return ((uint64_t)tv.tv_sec) * 1000ULL + (uint64_t)(tv.tv_usec / 1000ULL);
|
|
89
|
+
#endif
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
static char *str_dup(const char *s) {
|
|
93
|
+
size_t n = strlen(s);
|
|
94
|
+
char *p = (char*)malloc(n + 1);
|
|
95
|
+
if (!p) return NULL;
|
|
96
|
+
memcpy(p, s, n);
|
|
97
|
+
p[n] = 0;
|
|
98
|
+
return p;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// escape for generic JSON string fields
|
|
102
|
+
static char *json_escape(const char *s) {
|
|
103
|
+
const unsigned char *in = (const unsigned char*)s;
|
|
104
|
+
size_t extra = 0;
|
|
105
|
+
for (const unsigned char *p = in; *p; ++p) {
|
|
106
|
+
switch (*p) {
|
|
107
|
+
case '\\': case '"': extra++; break;
|
|
108
|
+
default:
|
|
109
|
+
if (*p < 0x20) extra += 5; // \u00XX
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
size_t inlen = strlen(s);
|
|
113
|
+
char *out = (char*)malloc(inlen + extra + 1);
|
|
114
|
+
if (!out) return NULL;
|
|
115
|
+
|
|
116
|
+
char *o = out;
|
|
117
|
+
for (const unsigned char *p = in; *p; ++p) {
|
|
118
|
+
switch (*p) {
|
|
119
|
+
case '\\': *o++='\\'; *o++='\\'; break;
|
|
120
|
+
case '"': *o++='\\'; *o++='"'; break;
|
|
121
|
+
default:
|
|
122
|
+
if (*p < 0x20) {
|
|
123
|
+
static const char hex[] = "0123456789abcdef";
|
|
124
|
+
*o++='\\'; *o++='u'; *o++='0'; *o++='0';
|
|
125
|
+
*o++=hex[(*p)>>4]; *o++=hex[(*p)&0xF];
|
|
126
|
+
} else {
|
|
127
|
+
*o++ = (char)*p;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
*o = 0;
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// escape for the GraphQL "query" string (handle \n, \r, \t too)
|
|
136
|
+
static char *json_escape_query(const char *s) {
|
|
137
|
+
const unsigned char *in = (const unsigned char*)s;
|
|
138
|
+
size_t extra = 0;
|
|
139
|
+
for (const unsigned char *p = in; *p; ++p) {
|
|
140
|
+
switch (*p) {
|
|
141
|
+
case '\\': case '"': case '\n': case '\r': case '\t': extra++; break;
|
|
142
|
+
default: break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
size_t inlen = strlen(s);
|
|
146
|
+
char *out = (char*)malloc(inlen + extra + 1);
|
|
147
|
+
if (!out) return NULL;
|
|
148
|
+
char *o = out;
|
|
149
|
+
for (const unsigned char *p = in; *p; ++p) {
|
|
150
|
+
switch (*p) {
|
|
151
|
+
case '\\': *o++='\\'; *o++='\\'; break;
|
|
152
|
+
case '"': *o++='\\'; *o++='"'; break;
|
|
153
|
+
case '\n': *o++='\\'; *o++='n'; break;
|
|
154
|
+
case '\r': *o++='\\'; *o++='r'; break;
|
|
155
|
+
case '\t': *o++='\\'; *o++='t'; break;
|
|
156
|
+
default: *o++=(char)*p;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
*o=0;
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// generic prefix builder for a given escaped query
|
|
164
|
+
static int build_prefix_for_query(const char *query_escaped, char **out_prefix) {
|
|
165
|
+
const char *p1 = "{\"query\":\"";
|
|
166
|
+
const char *p2 = "\",\"variables\":{";
|
|
167
|
+
const char *k1 = "\"apiKey\":\"";
|
|
168
|
+
const char *k2 = "\",\"serviceUuid\":\"";
|
|
169
|
+
const char *k3 = "\",\"library\":\"";
|
|
170
|
+
const char *k4 = "\",\"version\":\"";
|
|
171
|
+
|
|
172
|
+
size_t n = strlen(p1) + strlen(query_escaped) + strlen(p2)
|
|
173
|
+
+ strlen(k1) + strlen(g_api_key)
|
|
174
|
+
+ strlen(k2) + strlen(g_service_uuid)
|
|
175
|
+
+ strlen(k3) + strlen(g_library)
|
|
176
|
+
+ strlen(k4) + strlen(g_version) + 5;
|
|
177
|
+
|
|
178
|
+
char *prefix = (char*)malloc(n);
|
|
179
|
+
if (!prefix) return 0;
|
|
180
|
+
|
|
181
|
+
char *o = prefix;
|
|
182
|
+
o += sprintf(o, "%s%s%s", p1, query_escaped, p2);
|
|
183
|
+
o += sprintf(o, "%s%s", k1, g_api_key);
|
|
184
|
+
o += sprintf(o, "%s%s", k2, g_service_uuid);
|
|
185
|
+
o += sprintf(o, "%s%s", k3, g_library);
|
|
186
|
+
o += sprintf(o, "%s%s\"", k4, g_version);
|
|
187
|
+
*o = '\0';
|
|
188
|
+
|
|
189
|
+
*out_prefix = prefix;
|
|
190
|
+
return 1;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Build LOG body with level
|
|
194
|
+
static int build_body_log(
|
|
195
|
+
const char *session_id,
|
|
196
|
+
const char *level,
|
|
197
|
+
const char *contents,
|
|
198
|
+
int preactive,
|
|
199
|
+
char **out_body,
|
|
200
|
+
size_t *out_len
|
|
201
|
+
) {
|
|
202
|
+
char *sid_esc = json_escape(session_id ? session_id : "");
|
|
203
|
+
char *lvl_esc = json_escape(level ? level : "UNKNOWN");
|
|
204
|
+
char *msg_esc = json_escape(contents ? contents : "");
|
|
205
|
+
if (!sid_esc || !lvl_esc || !msg_esc) {
|
|
206
|
+
free(sid_esc); free(lvl_esc); free(msg_esc);
|
|
207
|
+
return 0;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
uint64_t tms = now_ms();
|
|
211
|
+
const char *k_sid = ",\"sessionId\":\"";
|
|
212
|
+
const char *k_lvl = "\",\"level\":\"";
|
|
213
|
+
const char *k_cts = "\",\"contents\":\"";
|
|
214
|
+
const char *k_ts = "\",\"timestampMs\":\"";
|
|
215
|
+
const char *k_pre = ",\"reentrancyGuardPreactive\":";
|
|
216
|
+
char ts_buf[32];
|
|
217
|
+
int ts_len = snprintf(ts_buf, sizeof(ts_buf), "%llu", (unsigned long long)tms);
|
|
218
|
+
const char *pre_str = preactive ? "true" : "false";
|
|
219
|
+
|
|
220
|
+
size_t len = strlen(g_json_prefix)
|
|
221
|
+
+ strlen(k_sid) + strlen(sid_esc)
|
|
222
|
+
+ strlen(k_lvl) + strlen(lvl_esc)
|
|
223
|
+
+ strlen(k_cts) + strlen(msg_esc)
|
|
224
|
+
+ strlen(k_ts) + (size_t)ts_len + 1 // +1 for closing quote
|
|
225
|
+
+ strlen(k_pre) + strlen(pre_str)
|
|
226
|
+
+ strlen(JSON_SUFFIX);
|
|
227
|
+
|
|
228
|
+
char *body = (char*)malloc(len + 1);
|
|
229
|
+
if (!body) { free(sid_esc); free(lvl_esc); free(msg_esc); return 0; }
|
|
230
|
+
|
|
231
|
+
char *o = body;
|
|
232
|
+
o += sprintf(o, "%s", g_json_prefix);
|
|
233
|
+
o += sprintf(o, "%s%s", k_sid, sid_esc);
|
|
234
|
+
o += sprintf(o, "%s%s", k_lvl, lvl_esc);
|
|
235
|
+
o += sprintf(o, "%s%s", k_cts, msg_esc);
|
|
236
|
+
o += sprintf(o, "%s%s\"", k_ts, ts_buf);
|
|
237
|
+
o += sprintf(o, "%s%s", k_pre, pre_str);
|
|
238
|
+
o += sprintf(o, "%s", JSON_SUFFIX);
|
|
239
|
+
*o = '\0';
|
|
240
|
+
|
|
241
|
+
*out_body = body;
|
|
242
|
+
*out_len = (size_t)(o - body);
|
|
243
|
+
|
|
244
|
+
free(sid_esc); free(lvl_esc); free(msg_esc);
|
|
245
|
+
return 1;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Build PRINT body (no level), using g_json_prefix_print
|
|
249
|
+
static int build_body_print(
|
|
250
|
+
const char *session_id, size_t session_len,
|
|
251
|
+
const char *contents, size_t contents_len,
|
|
252
|
+
int preactive,
|
|
253
|
+
char **out_body, size_t *out_len
|
|
254
|
+
){
|
|
255
|
+
// Escape session_id & contents
|
|
256
|
+
// (We have lengths available but json_escape takes NUL-terminated; copy into temp with NUL)
|
|
257
|
+
char *sid_tmp = (char*)malloc(session_len + 1);
|
|
258
|
+
if (!sid_tmp) return 0;
|
|
259
|
+
memcpy(sid_tmp, session_id ? session_id : "", session_len);
|
|
260
|
+
sid_tmp[session_len] = 0;
|
|
261
|
+
|
|
262
|
+
char *cts_tmp = (char*)malloc(contents_len + 1);
|
|
263
|
+
if (!cts_tmp) { free(sid_tmp); return 0; }
|
|
264
|
+
memcpy(cts_tmp, contents ? contents : "", contents_len);
|
|
265
|
+
cts_tmp[contents_len] = 0;
|
|
266
|
+
|
|
267
|
+
char *sid_esc = json_escape(sid_tmp);
|
|
268
|
+
char *msg_esc = json_escape(cts_tmp);
|
|
269
|
+
free(sid_tmp); free(cts_tmp);
|
|
270
|
+
if (!sid_esc || !msg_esc) {
|
|
271
|
+
free(sid_esc); free(msg_esc);
|
|
272
|
+
return 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
uint64_t tms = now_ms();
|
|
276
|
+
const char *k_sid = ",\"sessionId\":\"";
|
|
277
|
+
const char *k_cts = "\",\"contents\":\"";
|
|
278
|
+
const char *k_ts = "\",\"timestampMs\":\"";
|
|
279
|
+
const char *k_pre = ",\"reentrancyGuardPreactive\":";
|
|
280
|
+
char ts_buf[32];
|
|
281
|
+
int ts_len = snprintf(ts_buf, sizeof(ts_buf), "%llu", (unsigned long long)tms);
|
|
282
|
+
const char *pre_str = preactive ? "true" : "false";
|
|
283
|
+
|
|
284
|
+
if (!g_json_prefix_print) { free(sid_esc); free(msg_esc); return 0; }
|
|
285
|
+
|
|
286
|
+
size_t len = strlen(g_json_prefix_print)
|
|
287
|
+
+ strlen(k_sid) + strlen(sid_esc)
|
|
288
|
+
+ strlen(k_cts) + strlen(msg_esc)
|
|
289
|
+
+ strlen(k_ts) + (size_t)ts_len + 1 // +1 for closing quote
|
|
290
|
+
+ strlen(k_pre) + strlen(pre_str)
|
|
291
|
+
+ strlen(JSON_SUFFIX);
|
|
292
|
+
|
|
293
|
+
char *body = (char*)malloc(len + 1);
|
|
294
|
+
if (!body) { free(sid_esc); free(msg_esc); return 0; }
|
|
295
|
+
|
|
296
|
+
char *o = body;
|
|
297
|
+
o += sprintf(o, "%s", g_json_prefix_print);
|
|
298
|
+
o += sprintf(o, "%s%s", k_sid, sid_esc);
|
|
299
|
+
o += sprintf(o, "%s%s", k_cts, msg_esc);
|
|
300
|
+
o += sprintf(o, "%s%s\"", k_ts, ts_buf);
|
|
301
|
+
o += sprintf(o, "%s%s", k_pre, pre_str);
|
|
302
|
+
o += sprintf(o, "%s", JSON_SUFFIX);
|
|
303
|
+
*o = '\0';
|
|
304
|
+
|
|
305
|
+
*out_body = body;
|
|
306
|
+
*out_len = (size_t)(o - body);
|
|
307
|
+
|
|
308
|
+
free(sid_esc); free(msg_esc);
|
|
309
|
+
return 1;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Build EXCEPTION body
|
|
313
|
+
static int build_body_exception(
|
|
314
|
+
const char *session_id, size_t session_len,
|
|
315
|
+
const char *exception_message, size_t exception_len,
|
|
316
|
+
const char *trace_json, size_t trace_len,
|
|
317
|
+
int was_caught,
|
|
318
|
+
int is_from_local_service,
|
|
319
|
+
char **out_body, size_t *out_len
|
|
320
|
+
){
|
|
321
|
+
// Escape session_id, exception_message & trace_json
|
|
322
|
+
char *sid_tmp = (char*)malloc(session_len + 1);
|
|
323
|
+
if (!sid_tmp) return 0;
|
|
324
|
+
memcpy(sid_tmp, session_id ? session_id : "", session_len);
|
|
325
|
+
sid_tmp[session_len] = 0;
|
|
326
|
+
|
|
327
|
+
char *exc_tmp = (char*)malloc(exception_len + 1);
|
|
328
|
+
if (!exc_tmp) { free(sid_tmp); return 0; }
|
|
329
|
+
memcpy(exc_tmp, exception_message ? exception_message : "", exception_len);
|
|
330
|
+
exc_tmp[exception_len] = 0;
|
|
331
|
+
|
|
332
|
+
char *trace_tmp = (char*)malloc(trace_len + 1);
|
|
333
|
+
if (!trace_tmp) { free(sid_tmp); free(exc_tmp); return 0; }
|
|
334
|
+
memcpy(trace_tmp, trace_json ? trace_json : "", trace_len);
|
|
335
|
+
trace_tmp[trace_len] = 0;
|
|
336
|
+
|
|
337
|
+
char *sid_esc = json_escape(sid_tmp);
|
|
338
|
+
char *exc_esc = json_escape(exc_tmp);
|
|
339
|
+
char *trace_esc = json_escape(trace_tmp);
|
|
340
|
+
free(sid_tmp); free(exc_tmp); free(trace_tmp);
|
|
341
|
+
|
|
342
|
+
if (!sid_esc || !exc_esc || !trace_esc) {
|
|
343
|
+
free(sid_esc); free(exc_esc); free(trace_esc);
|
|
344
|
+
return 0;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
uint64_t tms = now_ms();
|
|
348
|
+
const char *k_sid = ",\"sessionId\":\"";
|
|
349
|
+
const char *k_exc = "\",\"exceptionMessage\":\"";
|
|
350
|
+
const char *k_trace = "\",\"traceJson\":\"";
|
|
351
|
+
const char *k_caught = "\",\"wasCaught\":";
|
|
352
|
+
const char *k_local = ",\"isFromLocalService\":";
|
|
353
|
+
const char *k_ts = ",\"timestampMs\":\"";
|
|
354
|
+
const char *k_pre = ",\"reentrancyGuardPreactive\":";
|
|
355
|
+
|
|
356
|
+
char ts_buf[32];
|
|
357
|
+
int ts_len = snprintf(ts_buf, sizeof(ts_buf), "%llu", (unsigned long long)tms);
|
|
358
|
+
const char *caught_str = was_caught ? "true" : "false";
|
|
359
|
+
const char *local_str = is_from_local_service ? "true" : "false";
|
|
360
|
+
|
|
361
|
+
if (!g_json_prefix_exception) { free(sid_esc); free(exc_esc); free(trace_esc); return 0; }
|
|
362
|
+
|
|
363
|
+
size_t len = strlen(g_json_prefix_exception)
|
|
364
|
+
+ strlen(k_sid) + strlen(sid_esc)
|
|
365
|
+
+ strlen(k_exc) + strlen(exc_esc)
|
|
366
|
+
+ strlen(k_trace) + strlen(trace_esc)
|
|
367
|
+
+ strlen(k_caught) + strlen(caught_str)
|
|
368
|
+
+ strlen(k_local) + strlen(local_str)
|
|
369
|
+
+ strlen(k_ts) + (size_t)ts_len + 1 // +1 for closing quote
|
|
370
|
+
+ strlen(k_pre) + 5 // "false"
|
|
371
|
+
+ strlen(JSON_SUFFIX);
|
|
372
|
+
|
|
373
|
+
char *body = (char*)malloc(len + 1);
|
|
374
|
+
if (!body) { free(sid_esc); free(exc_esc); free(trace_esc); return 0; }
|
|
375
|
+
|
|
376
|
+
char *o = body;
|
|
377
|
+
o += sprintf(o, "%s", g_json_prefix_exception);
|
|
378
|
+
o += sprintf(o, "%s%s", k_sid, sid_esc);
|
|
379
|
+
o += sprintf(o, "%s%s", k_exc, exc_esc);
|
|
380
|
+
o += sprintf(o, "%s%s", k_trace, trace_esc);
|
|
381
|
+
o += sprintf(o, "%s%s", k_caught, caught_str);
|
|
382
|
+
o += sprintf(o, "%s%s", k_local, local_str);
|
|
383
|
+
o += sprintf(o, "%s%s\"", k_ts, ts_buf);
|
|
384
|
+
o += sprintf(o, "%sfalse", k_pre); // reentrancyGuardPreactive always false for exceptions
|
|
385
|
+
o += sprintf(o, "%s", JSON_SUFFIX);
|
|
386
|
+
*o = '\0';
|
|
387
|
+
|
|
388
|
+
*out_body = body;
|
|
389
|
+
*out_len = (size_t)(o - body);
|
|
390
|
+
|
|
391
|
+
free(sid_esc); free(exc_esc); free(trace_esc);
|
|
392
|
+
return 1;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ---------- ring ops ----------
|
|
396
|
+
static inline size_t ring_count(void) {
|
|
397
|
+
size_t h = atomic_load_explicit(&g_head, memory_order_acquire);
|
|
398
|
+
size_t t = atomic_load_explicit(&g_tail, memory_order_acquire);
|
|
399
|
+
return t - h;
|
|
400
|
+
}
|
|
401
|
+
static inline int ring_empty(void) { return ring_count() == 0; }
|
|
402
|
+
|
|
403
|
+
static int ring_push(char *body, size_t len) {
|
|
404
|
+
while (atomic_flag_test_and_set_explicit(&g_push_lock, memory_order_acquire)) {
|
|
405
|
+
// brief spin
|
|
406
|
+
}
|
|
407
|
+
size_t t = atomic_load_explicit(&g_tail, memory_order_relaxed);
|
|
408
|
+
size_t h = atomic_load_explicit(&g_head, memory_order_acquire);
|
|
409
|
+
if ((t - h) >= g_cap) {
|
|
410
|
+
atomic_flag_clear_explicit(&g_push_lock, memory_order_release);
|
|
411
|
+
return 0; // full (drop)
|
|
412
|
+
}
|
|
413
|
+
size_t idx = t % g_cap;
|
|
414
|
+
g_ring[idx].body = body;
|
|
415
|
+
g_ring[idx].len = len;
|
|
416
|
+
atomic_store_explicit(&g_tail, t + 1, memory_order_release);
|
|
417
|
+
atomic_flag_clear_explicit(&g_push_lock, memory_order_release);
|
|
418
|
+
|
|
419
|
+
pthread_mutex_lock(&g_cv_mtx);
|
|
420
|
+
pthread_cond_signal(&g_cv);
|
|
421
|
+
pthread_mutex_unlock(&g_cv_mtx);
|
|
422
|
+
return 1;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
static int ring_pop(char **body, size_t *len) {
|
|
426
|
+
size_t h = atomic_load_explicit(&g_head, memory_order_relaxed);
|
|
427
|
+
size_t t = atomic_load_explicit(&g_tail, memory_order_acquire);
|
|
428
|
+
if (h == t) return 0;
|
|
429
|
+
size_t idx = h % g_cap;
|
|
430
|
+
*body = g_ring[idx].body;
|
|
431
|
+
*len = g_ring[idx].len;
|
|
432
|
+
g_ring[idx].body = NULL;
|
|
433
|
+
g_ring[idx].len = 0;
|
|
434
|
+
atomic_store_explicit(&g_head, h + 1, memory_order_release);
|
|
435
|
+
return 1;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ---------- curl sink callbacks ----------
|
|
439
|
+
static size_t _sink_write(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
440
|
+
(void)ptr; (void)userdata;
|
|
441
|
+
return size * nmemb;
|
|
442
|
+
}
|
|
443
|
+
static size_t _sink_header(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
444
|
+
(void)ptr; (void)userdata;
|
|
445
|
+
return size * nmemb;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ---------- pthread cleanup handler ----------
|
|
449
|
+
static void sender_cleanup(void *arg) {
|
|
450
|
+
(void)arg;
|
|
451
|
+
|
|
452
|
+
// Close thread-local curl handle
|
|
453
|
+
if (g_telem_curl) {
|
|
454
|
+
curl_easy_cleanup(g_telem_curl);
|
|
455
|
+
g_telem_curl = NULL;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ---------- sender thread ----------
|
|
460
|
+
static void *sender_main(void *arg) {
|
|
461
|
+
(void)arg;
|
|
462
|
+
|
|
463
|
+
// CRITICAL: Set telemetry guard for this thread (prevents _sfteepreload.c capture)
|
|
464
|
+
g_in_telemetry_send = 1;
|
|
465
|
+
|
|
466
|
+
// Initialize thread-local curl handle
|
|
467
|
+
g_telem_curl = curl_easy_init();
|
|
468
|
+
if (!g_telem_curl) {
|
|
469
|
+
return NULL;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Configure curl handle (copy from global settings)
|
|
473
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_URL, g_url);
|
|
474
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_TCP_KEEPALIVE, 1L);
|
|
475
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_TCP_NODELAY, 1L); // NEW: Eliminate Nagle delay
|
|
476
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_HTTPHEADER, g_hdrs);
|
|
477
|
+
#ifdef CURL_HTTP_VERSION_2TLS
|
|
478
|
+
if (g_http2) {
|
|
479
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
|
|
480
|
+
}
|
|
481
|
+
#endif
|
|
482
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_WRITEFUNCTION, _sink_write);
|
|
483
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_HEADERFUNCTION, _sink_header);
|
|
484
|
+
|
|
485
|
+
// Register cleanup handler (executes on thread exit)
|
|
486
|
+
pthread_cleanup_push(sender_cleanup, NULL);
|
|
487
|
+
|
|
488
|
+
while (1) {
|
|
489
|
+
// Keep running until shutdown requested AND queue is fully drained
|
|
490
|
+
if (ring_empty()) {
|
|
491
|
+
// Queue is empty - check if we should exit
|
|
492
|
+
if (!atomic_load(&g_running)) {
|
|
493
|
+
// Shutting down and queue is empty - safe to exit
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
// Still running - wait for new items
|
|
497
|
+
pthread_mutex_lock(&g_cv_mtx);
|
|
498
|
+
if (ring_empty() && atomic_load(&g_running))
|
|
499
|
+
pthread_cond_wait(&g_cv, &g_cv_mtx);
|
|
500
|
+
pthread_mutex_unlock(&g_cv_mtx);
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Drain ALL items from queue (don't check g_running mid-drain)
|
|
505
|
+
char *body = NULL; size_t len = 0;
|
|
506
|
+
while (ring_pop(&body, &len)) {
|
|
507
|
+
if (!body) continue;
|
|
508
|
+
|
|
509
|
+
// Use thread-local curl handle (each thread has its own persistent connection)
|
|
510
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_POSTFIELDS, body);
|
|
511
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_POSTFIELDSIZE, (long)len);
|
|
512
|
+
(void)curl_easy_perform(g_telem_curl); // fire-and-forget
|
|
513
|
+
|
|
514
|
+
free(body);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
pthread_cleanup_pop(1); // Execute cleanup handler
|
|
519
|
+
return NULL;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// ---------- Python API ----------
|
|
523
|
+
static PyObject *py_init(PyObject *self, PyObject *args, PyObject *kw) {
|
|
524
|
+
const char *url, *query, *api_key, *service_uuid, *library, *version;
|
|
525
|
+
int http2 = 0;
|
|
526
|
+
static char *kwlist[] = {"url","query","api_key","service_uuid","library","version","http2", NULL};
|
|
527
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "ssssssi",
|
|
528
|
+
kwlist, &url, &query, &api_key, &service_uuid, &library, &version, &http2)) {
|
|
529
|
+
return NULL; // Exception already set by PyArg_ParseTupleAndKeywords
|
|
530
|
+
}
|
|
531
|
+
if (g_running) Py_RETURN_TRUE;
|
|
532
|
+
|
|
533
|
+
g_url = str_dup(url);
|
|
534
|
+
g_query_escaped = json_escape_query(query);
|
|
535
|
+
g_api_key = str_dup(api_key);
|
|
536
|
+
g_service_uuid = str_dup(service_uuid);
|
|
537
|
+
g_library = str_dup(library);
|
|
538
|
+
g_version = str_dup(version);
|
|
539
|
+
g_http2 = http2 ? 1 : 0;
|
|
540
|
+
if (!g_url || !g_query_escaped || !g_api_key || !g_service_uuid || !g_library || !g_version) {
|
|
541
|
+
Py_RETURN_FALSE;
|
|
542
|
+
}
|
|
543
|
+
if (!build_prefix_for_query(g_query_escaped, &g_json_prefix)) { Py_RETURN_FALSE; }
|
|
544
|
+
|
|
545
|
+
g_cap = SFF_RING_CAP;
|
|
546
|
+
g_ring = (sff_msg_t*)calloc(g_cap, sizeof(sff_msg_t));
|
|
547
|
+
if (!g_ring) { Py_RETURN_FALSE; }
|
|
548
|
+
|
|
549
|
+
// Parse SF_LOG_SENDER_THREADS environment variable
|
|
550
|
+
const char *env_threads = getenv("SF_LOG_SENDER_THREADS");
|
|
551
|
+
if (env_threads) {
|
|
552
|
+
int t = atoi(env_threads);
|
|
553
|
+
if (t > 0 && t <= MAX_SENDER_THREADS) {
|
|
554
|
+
g_configured_sender_threads = t;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
curl_global_init(CURL_GLOBAL_DEFAULT);
|
|
559
|
+
|
|
560
|
+
// Initialize shared curl headers (Content-Type only)
|
|
561
|
+
// NOTE: Removed X-Sf3-IsTelemetryMessage header - now use g_in_telemetry_send flag
|
|
562
|
+
g_hdrs = NULL;
|
|
563
|
+
g_hdrs = curl_slist_append(g_hdrs, "Content-Type: application/json");
|
|
564
|
+
|
|
565
|
+
// Start sender thread pool
|
|
566
|
+
atomic_store(&g_running, 1);
|
|
567
|
+
g_num_sender_threads = g_configured_sender_threads;
|
|
568
|
+
for (int i = 0; i < g_num_sender_threads; i++) {
|
|
569
|
+
if (pthread_create(&g_sender_threads[i], NULL, sender_main, NULL) != 0) {
|
|
570
|
+
atomic_store(&g_running, 0);
|
|
571
|
+
// Clean up already-started threads
|
|
572
|
+
for (int j = 0; j < i; j++) {
|
|
573
|
+
pthread_join(g_sender_threads[j], NULL);
|
|
574
|
+
}
|
|
575
|
+
Py_RETURN_FALSE;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
Py_RETURN_TRUE;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
static PyObject *py_init_print(PyObject *self, PyObject *args, PyObject *kw) {
|
|
583
|
+
const char *url, *query, *api_key, *service_uuid, *library, *version;
|
|
584
|
+
int http2 = 1;
|
|
585
|
+
static char *kwlist[] = {"url","query","api_key","service_uuid","library","version","http2",NULL};
|
|
586
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "ssssssi", kwlist,
|
|
587
|
+
&url, &query, &api_key, &service_uuid, &library, &version, &http2)) {
|
|
588
|
+
return NULL; // Exception already set by PyArg_ParseTupleAndKeywords
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// If not initialized yet, call init() first with the log query (empty for now)
|
|
592
|
+
if (!g_running) {
|
|
593
|
+
// Build a dummy log query just to initialize the transport
|
|
594
|
+
const char *dummy_log_query =
|
|
595
|
+
"mutation CollectLogs("
|
|
596
|
+
"$apiKey: String!, "
|
|
597
|
+
"$serviceUuid: String!, "
|
|
598
|
+
"$sessionId: String!, "
|
|
599
|
+
"$level: String!, "
|
|
600
|
+
"$contents: String!, "
|
|
601
|
+
"$reentrancyGuardPreactive: Boolean!, "
|
|
602
|
+
"$library: String!, "
|
|
603
|
+
"$timestampMs: String!, "
|
|
604
|
+
"$version: String!"
|
|
605
|
+
") { "
|
|
606
|
+
"collectLogs("
|
|
607
|
+
"apiKey: $apiKey, "
|
|
608
|
+
"serviceUuid: $serviceUuid, "
|
|
609
|
+
"sessionId: $sessionId, "
|
|
610
|
+
"level: $level, "
|
|
611
|
+
"contents: $contents, "
|
|
612
|
+
"reentrancyGuardPreactive: $reentrancyGuardPreactive, "
|
|
613
|
+
"library: $library, "
|
|
614
|
+
"timestampMs: $timestampMs, "
|
|
615
|
+
"version: $version"
|
|
616
|
+
") }";
|
|
617
|
+
|
|
618
|
+
// Call py_init to set up the transport
|
|
619
|
+
PyObject *init_args = Py_BuildValue("(ssssssi)", url, dummy_log_query, api_key, service_uuid, library, version, http2);
|
|
620
|
+
if (!init_args) return NULL;
|
|
621
|
+
|
|
622
|
+
PyObject *init_result = py_init(self, init_args, NULL);
|
|
623
|
+
Py_DECREF(init_args);
|
|
624
|
+
|
|
625
|
+
if (!init_result || init_result == Py_False) {
|
|
626
|
+
Py_XDECREF(init_result);
|
|
627
|
+
Py_RETURN_FALSE;
|
|
628
|
+
}
|
|
629
|
+
Py_DECREF(init_result);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Now set up the print query + prefix
|
|
633
|
+
if (g_print_query_escaped) { free(g_print_query_escaped); g_print_query_escaped = NULL; }
|
|
634
|
+
if (g_json_prefix_print) { free(g_json_prefix_print); g_json_prefix_print = NULL; }
|
|
635
|
+
|
|
636
|
+
g_print_query_escaped = json_escape_query(query);
|
|
637
|
+
if (!g_print_query_escaped) Py_RETURN_FALSE;
|
|
638
|
+
if (!build_prefix_for_query(g_print_query_escaped, &g_json_prefix_print)) {
|
|
639
|
+
Py_RETURN_FALSE;
|
|
640
|
+
}
|
|
641
|
+
Py_RETURN_TRUE;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
static PyObject *py_init_exception(PyObject *self, PyObject *args, PyObject *kw) {
|
|
645
|
+
const char *url, *query, *api_key, *service_uuid, *library, *version;
|
|
646
|
+
int http2 = 1;
|
|
647
|
+
static char *kwlist[] = {"url","query","api_key","service_uuid","library","version","http2",NULL};
|
|
648
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "ssssssi", kwlist,
|
|
649
|
+
&url, &query, &api_key, &service_uuid, &library, &version, &http2)) {
|
|
650
|
+
return NULL; // Exception already set by PyArg_ParseTupleAndKeywords
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// If not initialized yet, call init() first with the log query
|
|
654
|
+
if (!g_running) {
|
|
655
|
+
// Build a dummy log query just to initialize the transport
|
|
656
|
+
const char *dummy_log_query =
|
|
657
|
+
"mutation CollectLogs("
|
|
658
|
+
"$apiKey: String!, "
|
|
659
|
+
"$serviceUuid: String!, "
|
|
660
|
+
"$sessionId: String!, "
|
|
661
|
+
"$level: String!, "
|
|
662
|
+
"$contents: String!, "
|
|
663
|
+
"$reentrancyGuardPreactive: Boolean!, "
|
|
664
|
+
"$library: String!, "
|
|
665
|
+
"$timestampMs: String!, "
|
|
666
|
+
"$version: String!"
|
|
667
|
+
") { "
|
|
668
|
+
"collectLogs("
|
|
669
|
+
"apiKey: $apiKey, "
|
|
670
|
+
"serviceUuid: $serviceUuid, "
|
|
671
|
+
"sessionId: $sessionId, "
|
|
672
|
+
"level: $level, "
|
|
673
|
+
"contents: $contents, "
|
|
674
|
+
"reentrancyGuardPreactive: $reentrancyGuardPreactive, "
|
|
675
|
+
"library: $library, "
|
|
676
|
+
"timestampMs: $timestampMs, "
|
|
677
|
+
"version: $version"
|
|
678
|
+
") }";
|
|
679
|
+
|
|
680
|
+
// Call py_init to set up the transport
|
|
681
|
+
PyObject *init_args = Py_BuildValue("(ssssssi)", url, dummy_log_query, api_key, service_uuid, library, version, http2);
|
|
682
|
+
if (!init_args) return NULL;
|
|
683
|
+
|
|
684
|
+
PyObject *init_result = py_init(self, init_args, NULL);
|
|
685
|
+
Py_DECREF(init_args);
|
|
686
|
+
|
|
687
|
+
if (!init_result || init_result == Py_False) {
|
|
688
|
+
Py_XDECREF(init_result);
|
|
689
|
+
Py_RETURN_FALSE;
|
|
690
|
+
}
|
|
691
|
+
Py_DECREF(init_result);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Now set up the exception query + prefix
|
|
695
|
+
if (g_exception_query_escaped) { free(g_exception_query_escaped); g_exception_query_escaped = NULL; }
|
|
696
|
+
if (g_json_prefix_exception) { free(g_json_prefix_exception); g_json_prefix_exception = NULL; }
|
|
697
|
+
|
|
698
|
+
g_exception_query_escaped = json_escape_query(query);
|
|
699
|
+
if (!g_exception_query_escaped) Py_RETURN_FALSE;
|
|
700
|
+
if (!build_prefix_for_query(g_exception_query_escaped, &g_json_prefix_exception)) {
|
|
701
|
+
Py_RETURN_FALSE;
|
|
702
|
+
}
|
|
703
|
+
Py_RETURN_TRUE;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
static PyObject *py_log(PyObject *self, PyObject *args, PyObject *kw) {
|
|
707
|
+
const char *level, *contents, *session_id;
|
|
708
|
+
int preactive = 0;
|
|
709
|
+
static char *kwlist[] = {"level","contents","session_id","preactive", NULL};
|
|
710
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "sss|p", kwlist,
|
|
711
|
+
&level, &contents, &session_id, &preactive)) {
|
|
712
|
+
Py_RETURN_NONE;
|
|
713
|
+
}
|
|
714
|
+
if (!g_running) Py_RETURN_NONE;
|
|
715
|
+
|
|
716
|
+
// OPTIMIZATION: Release GIL during JSON building + ring push
|
|
717
|
+
char *body = NULL;
|
|
718
|
+
size_t len = 0;
|
|
719
|
+
int ok = 0;
|
|
720
|
+
|
|
721
|
+
Py_BEGIN_ALLOW_THREADS
|
|
722
|
+
// Build JSON body (WITHOUT GIL - pure C string operations)
|
|
723
|
+
if (build_body_log(session_id, level, contents, preactive, &body, &len)) {
|
|
724
|
+
// Push to ring buffer (WITHOUT GIL)
|
|
725
|
+
ok = ring_push(body, len);
|
|
726
|
+
}
|
|
727
|
+
Py_END_ALLOW_THREADS
|
|
728
|
+
|
|
729
|
+
if (!ok) { free(body); }
|
|
730
|
+
Py_RETURN_NONE;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
static PyObject *py_print(PyObject *self, PyObject *args, PyObject *kw) {
|
|
734
|
+
const char *contents, *session_id;
|
|
735
|
+
Py_ssize_t contents_len = 0, session_len = 0;
|
|
736
|
+
int preactive = 0;
|
|
737
|
+
static char *kwlist[] = {"contents","session_id","preactive", NULL};
|
|
738
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "s#s#|p", kwlist,
|
|
739
|
+
&contents, &contents_len,
|
|
740
|
+
&session_id, &session_len,
|
|
741
|
+
&preactive)) {
|
|
742
|
+
Py_RETURN_NONE;
|
|
743
|
+
}
|
|
744
|
+
if (!g_running || g_json_prefix_print == NULL) Py_RETURN_NONE;
|
|
745
|
+
|
|
746
|
+
// OPTIMIZATION: Release GIL during JSON building + ring push
|
|
747
|
+
char *body = NULL;
|
|
748
|
+
size_t len = 0;
|
|
749
|
+
int ok = 0;
|
|
750
|
+
|
|
751
|
+
Py_BEGIN_ALLOW_THREADS
|
|
752
|
+
// Build JSON body (WITHOUT GIL - pure C string operations)
|
|
753
|
+
if (build_body_print(session_id, (size_t)session_len,
|
|
754
|
+
contents, (size_t)contents_len,
|
|
755
|
+
preactive, &body, &len)) {
|
|
756
|
+
// Push to ring buffer (WITHOUT GIL)
|
|
757
|
+
ok = ring_push(body, len);
|
|
758
|
+
}
|
|
759
|
+
Py_END_ALLOW_THREADS
|
|
760
|
+
|
|
761
|
+
if (!ok) { free(body); } // ring owns on success
|
|
762
|
+
Py_RETURN_NONE;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
static PyObject *py_exception(PyObject *self, PyObject *args, PyObject *kw) {
|
|
766
|
+
const char *exception_message, *trace_json, *session_id;
|
|
767
|
+
Py_ssize_t exception_len = 0, trace_len = 0, session_len = 0;
|
|
768
|
+
int was_caught = 1;
|
|
769
|
+
int is_from_local_service = 0;
|
|
770
|
+
static char *kwlist[] = {"exception_message","trace_json","session_id","was_caught","is_from_local_service", NULL};
|
|
771
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "s#s#s#|pp", kwlist,
|
|
772
|
+
&exception_message, &exception_len,
|
|
773
|
+
&trace_json, &trace_len,
|
|
774
|
+
&session_id, &session_len,
|
|
775
|
+
&was_caught,
|
|
776
|
+
&is_from_local_service)) {
|
|
777
|
+
Py_RETURN_NONE;
|
|
778
|
+
}
|
|
779
|
+
if (!g_running || g_json_prefix_exception == NULL) Py_RETURN_NONE;
|
|
780
|
+
|
|
781
|
+
// OPTIMIZATION: Release GIL during JSON building + ring push
|
|
782
|
+
char *body = NULL;
|
|
783
|
+
size_t len = 0;
|
|
784
|
+
int ok = 0;
|
|
785
|
+
|
|
786
|
+
Py_BEGIN_ALLOW_THREADS
|
|
787
|
+
// Build JSON body (WITHOUT GIL - pure C string operations)
|
|
788
|
+
if (build_body_exception(session_id, (size_t)session_len,
|
|
789
|
+
exception_message, (size_t)exception_len,
|
|
790
|
+
trace_json, (size_t)trace_len,
|
|
791
|
+
was_caught, is_from_local_service,
|
|
792
|
+
&body, &len)) {
|
|
793
|
+
// Push to ring buffer (WITHOUT GIL)
|
|
794
|
+
ok = ring_push(body, len);
|
|
795
|
+
}
|
|
796
|
+
Py_END_ALLOW_THREADS
|
|
797
|
+
|
|
798
|
+
if (!ok) { free(body); } // ring owns on success
|
|
799
|
+
Py_RETURN_NONE;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
static PyObject *py_shutdown(PyObject *self, PyObject *args) {
|
|
803
|
+
if (!g_running) Py_RETURN_NONE;
|
|
804
|
+
|
|
805
|
+
atomic_store(&g_running, 0);
|
|
806
|
+
|
|
807
|
+
// Wake all sender threads (use broadcast for multiple threads)
|
|
808
|
+
pthread_mutex_lock(&g_cv_mtx);
|
|
809
|
+
pthread_cond_broadcast(&g_cv);
|
|
810
|
+
pthread_mutex_unlock(&g_cv_mtx);
|
|
811
|
+
|
|
812
|
+
// Join all sender threads (cleanup handlers execute automatically)
|
|
813
|
+
for (int i = 0; i < g_num_sender_threads; i++) {
|
|
814
|
+
if (g_sender_threads[i]) {
|
|
815
|
+
pthread_join(g_sender_threads[i], NULL);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Clean up shared curl resources
|
|
820
|
+
// NOTE: g_curl is now per-thread, cleaned by pthread_cleanup_push
|
|
821
|
+
if (g_hdrs) {
|
|
822
|
+
curl_slist_free_all(g_hdrs);
|
|
823
|
+
g_hdrs = NULL;
|
|
824
|
+
}
|
|
825
|
+
curl_global_cleanup();
|
|
826
|
+
|
|
827
|
+
// Free all allocated strings (NULL after free to prevent use-after-free)
|
|
828
|
+
free(g_url);
|
|
829
|
+
g_url = NULL;
|
|
830
|
+
|
|
831
|
+
free(g_query_escaped);
|
|
832
|
+
g_query_escaped = NULL;
|
|
833
|
+
free(g_json_prefix);
|
|
834
|
+
g_json_prefix = NULL;
|
|
835
|
+
|
|
836
|
+
free(g_print_query_escaped);
|
|
837
|
+
g_print_query_escaped = NULL;
|
|
838
|
+
free(g_json_prefix_print);
|
|
839
|
+
g_json_prefix_print = NULL;
|
|
840
|
+
|
|
841
|
+
free(g_exception_query_escaped);
|
|
842
|
+
g_exception_query_escaped = NULL;
|
|
843
|
+
free(g_json_prefix_exception);
|
|
844
|
+
g_json_prefix_exception = NULL;
|
|
845
|
+
|
|
846
|
+
free(g_api_key);
|
|
847
|
+
g_api_key = NULL;
|
|
848
|
+
free(g_service_uuid);
|
|
849
|
+
g_service_uuid = NULL;
|
|
850
|
+
free(g_library);
|
|
851
|
+
g_library = NULL;
|
|
852
|
+
free(g_version);
|
|
853
|
+
g_version = NULL;
|
|
854
|
+
|
|
855
|
+
// Free ring buffer (drain remaining messages first)
|
|
856
|
+
if (g_ring) {
|
|
857
|
+
char *b;
|
|
858
|
+
size_t l;
|
|
859
|
+
while (ring_pop(&b, &l)) free(b);
|
|
860
|
+
free(g_ring);
|
|
861
|
+
g_ring = NULL;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
Py_RETURN_NONE;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// ---------- Module table (SINGLE definition) ----------
|
|
868
|
+
static PyMethodDef SFFastLogMethods[] = {
|
|
869
|
+
{"init", (PyCFunction)py_init, METH_VARARGS | METH_KEYWORDS, "Init (logs) and start sender"},
|
|
870
|
+
{"init_print", (PyCFunction)py_init_print, METH_VARARGS | METH_KEYWORDS, "Init (prints) query/prefix"},
|
|
871
|
+
{"init_exception", (PyCFunction)py_init_exception, METH_VARARGS | METH_KEYWORDS, "Init (exception) query/prefix"},
|
|
872
|
+
{"log", (PyCFunction)py_log, METH_VARARGS | METH_KEYWORDS, "Send log"},
|
|
873
|
+
{"print_", (PyCFunction)py_print, METH_VARARGS | METH_KEYWORDS, "Send print"},
|
|
874
|
+
{"exception", (PyCFunction)py_exception, METH_VARARGS | METH_KEYWORDS, "Send exception"},
|
|
875
|
+
{"shutdown", (PyCFunction)py_shutdown, METH_NOARGS, "Shutdown sender and free state"},
|
|
876
|
+
{NULL, NULL, 0, NULL}
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
static struct PyModuleDef sffastlogmodule = {
|
|
880
|
+
PyModuleDef_HEAD_INIT,
|
|
881
|
+
"_sffastlog",
|
|
882
|
+
"sf_veritas ultra-fast logging/printing",
|
|
883
|
+
-1,
|
|
884
|
+
SFFastLogMethods
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
PyMODINIT_FUNC PyInit__sffastlog(void) {
|
|
888
|
+
return PyModule_Create(&sffastlogmodule);
|
|
889
|
+
}
|