@reproapp/node-sdk 0.0.5 → 0.0.7
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.
- package/dist/index.d.ts +16 -10
- package/dist/index.js +630 -580
- package/package.json +2 -2
- package/src/index.ts +739 -647
- package/test/circular-capture.test.js +1 -1
- package/test/disable-subtree.test.js +1 -1
- package/test/kafka-runtime-privacy-policy.test.js +451 -18
- package/test/request-flush-timing.test.js +123 -0
- package/test/runtime-privacy-materialization.test.js +76 -0
package/src/index.ts
CHANGED
|
@@ -355,9 +355,6 @@ type TraceEventRecord = {
|
|
|
355
355
|
threw?: boolean;
|
|
356
356
|
error?: any;
|
|
357
357
|
unawaited?: boolean;
|
|
358
|
-
argsValueCapture?: TraceValueCaptureRef;
|
|
359
|
-
returnValueCapture?: TraceValueCaptureRef;
|
|
360
|
-
errorValueCapture?: TraceValueCaptureRef;
|
|
361
358
|
};
|
|
362
359
|
|
|
363
360
|
type PendingTraceEventRecord = TraceEventRecord & {
|
|
@@ -390,10 +387,7 @@ type TraceValueBatchEntry = {
|
|
|
390
387
|
| 'request.body'
|
|
391
388
|
| 'request.params'
|
|
392
389
|
| 'request.query'
|
|
393
|
-
| 'response.body'
|
|
394
|
-
| 'trace.args'
|
|
395
|
-
| 'trace.returnValue'
|
|
396
|
-
| 'trace.error';
|
|
390
|
+
| 'response.body';
|
|
397
391
|
value: any;
|
|
398
392
|
truncatedForStorage?: boolean;
|
|
399
393
|
projectedKind?: string;
|
|
@@ -1094,6 +1088,10 @@ function traceEventCandidateFromRecord(event: PendingTraceEventRecord): TraceEve
|
|
|
1094
1088
|
};
|
|
1095
1089
|
}
|
|
1096
1090
|
|
|
1091
|
+
function shouldOmitJsonBuiltinTraceValues(event: TraceEventForFilter | { fn?: any }): boolean {
|
|
1092
|
+
return event.fn === 'JSON.parse' || event.fn === 'JSON.stringify';
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1097
1095
|
function preparePendingTraceEventsForFlush(events: PendingTraceEventRecord[]): PendingTraceEventRecord[] {
|
|
1098
1096
|
const prepared: PendingTraceEventRecord[] = [];
|
|
1099
1097
|
const excludedSpanIds = new Set<string>();
|
|
@@ -1116,6 +1114,13 @@ function preparePendingTraceEventsForFlush(events: PendingTraceEventRecord[]): P
|
|
|
1116
1114
|
|
|
1117
1115
|
if (event.__reproPending) {
|
|
1118
1116
|
event.__reproPending.candidate = candidate;
|
|
1117
|
+
if (shouldOmitJsonBuiltinTraceValues(candidate)) {
|
|
1118
|
+
delete event.__reproPending.argsRaw;
|
|
1119
|
+
delete event.__reproPending.returnValueRaw;
|
|
1120
|
+
if (event.__reproPending.errorRaw === undefined) {
|
|
1121
|
+
delete event.__reproPending;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1119
1124
|
}
|
|
1120
1125
|
delete event.__reproSourceFile;
|
|
1121
1126
|
prepared.push(event);
|
|
@@ -1180,6 +1185,12 @@ const SDK_BACKGROUND_BUSY_IDLE_DELAY_MS = (() => {
|
|
|
1180
1185
|
return 1000;
|
|
1181
1186
|
})();
|
|
1182
1187
|
|
|
1188
|
+
const SDK_BACKGROUND_MAX_DEFER_MS = (() => {
|
|
1189
|
+
const env = Number(process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS);
|
|
1190
|
+
if (Number.isFinite(env) && env >= 0) return Math.trunc(env);
|
|
1191
|
+
return 2000;
|
|
1192
|
+
})();
|
|
1193
|
+
|
|
1183
1194
|
const SDK_BACKGROUND_WORK_TIMEOUT_MS = (() => {
|
|
1184
1195
|
const env = Number(process.env.REPRO_SDK_BACKGROUND_WORK_TIMEOUT_MS);
|
|
1185
1196
|
if (Number.isFinite(env) && env >= 0) return Math.trunc(env);
|
|
@@ -1217,6 +1228,7 @@ const TRACE_MATERIALIZE_WORKER_IDLE_SHUTDOWN_MS = (() => {
|
|
|
1217
1228
|
})();
|
|
1218
1229
|
|
|
1219
1230
|
let activeClientRequestCount = 0;
|
|
1231
|
+
let oldestActiveClientRequestAt: number | null = null;
|
|
1220
1232
|
let lastClientActivityAt = 0;
|
|
1221
1233
|
let sdkBackgroundQuietUntil = 0;
|
|
1222
1234
|
const sdkBackgroundQueue: Array<() => Promise<void> | void> = [];
|
|
@@ -1243,6 +1255,11 @@ function noteClientActivity(now = Date.now()): void {
|
|
|
1243
1255
|
|
|
1244
1256
|
function getSdkBackgroundDelayMs(now = Date.now()): number {
|
|
1245
1257
|
if (hasActiveClientRequests()) {
|
|
1258
|
+
if (oldestActiveClientRequestAt !== null && SDK_BACKGROUND_MAX_DEFER_MS >= 0) {
|
|
1259
|
+
const activeForMs = now - oldestActiveClientRequestAt;
|
|
1260
|
+
if (activeForMs >= SDK_BACKGROUND_MAX_DEFER_MS) return 0;
|
|
1261
|
+
return Math.min(SDK_BACKGROUND_IDLE_DELAY_MS, Math.max(0, SDK_BACKGROUND_MAX_DEFER_MS - activeForMs));
|
|
1262
|
+
}
|
|
1246
1263
|
return SDK_BACKGROUND_IDLE_DELAY_MS;
|
|
1247
1264
|
}
|
|
1248
1265
|
const sinceLastActivity = now - lastClientActivityAt;
|
|
@@ -1433,9 +1450,18 @@ function shouldOffloadPendingTraceEvents(events: PendingTraceEventRecord[]): boo
|
|
|
1433
1450
|
for (const event of events) {
|
|
1434
1451
|
const pending = event.__reproPending;
|
|
1435
1452
|
if (!pending) continue;
|
|
1436
|
-
if (pending.argsRaw !== undefined)
|
|
1437
|
-
|
|
1438
|
-
|
|
1453
|
+
if (pending.argsRaw !== undefined) {
|
|
1454
|
+
if (traceInlineValueExceedsLimit(pending.argsRaw)) return false;
|
|
1455
|
+
pendingValueCount += 1;
|
|
1456
|
+
}
|
|
1457
|
+
if (pending.returnValueRaw !== undefined) {
|
|
1458
|
+
if (traceInlineValueExceedsLimit(pending.returnValueRaw)) return false;
|
|
1459
|
+
pendingValueCount += 1;
|
|
1460
|
+
}
|
|
1461
|
+
if (pending.errorRaw !== undefined) {
|
|
1462
|
+
if (traceInlineValueExceedsLimit(pending.errorRaw)) return false;
|
|
1463
|
+
pendingValueCount += 1;
|
|
1464
|
+
}
|
|
1439
1465
|
if (pendingValueCount >= TRACE_MATERIALIZE_OFFTHREAD_MIN_VALUES) {
|
|
1440
1466
|
return true;
|
|
1441
1467
|
}
|
|
@@ -1449,6 +1475,9 @@ function beginClientRequest(): void {
|
|
|
1449
1475
|
sdkBackgroundQuietUntil = Math.max(sdkBackgroundQuietUntil, now + SDK_BACKGROUND_BUSY_IDLE_DELAY_MS);
|
|
1450
1476
|
}
|
|
1451
1477
|
noteClientActivity(now);
|
|
1478
|
+
if (activeClientRequestCount === 0) {
|
|
1479
|
+
oldestActiveClientRequestAt = now;
|
|
1480
|
+
}
|
|
1452
1481
|
activeClientRequestCount += 1;
|
|
1453
1482
|
if (sdkBackgroundTimer) {
|
|
1454
1483
|
try { clearTimeout(sdkBackgroundTimer); } catch {}
|
|
@@ -1460,6 +1489,7 @@ function endClientRequest(): void {
|
|
|
1460
1489
|
activeClientRequestCount = Math.max(0, activeClientRequestCount - 1);
|
|
1461
1490
|
noteClientActivity();
|
|
1462
1491
|
if (!hasActiveClientRequests()) {
|
|
1492
|
+
oldestActiveClientRequestAt = null;
|
|
1463
1493
|
kickIngestQueueDrain();
|
|
1464
1494
|
if (sdkBackgroundTimer) {
|
|
1465
1495
|
try { clearTimeout(sdkBackgroundTimer); } catch {}
|
|
@@ -1987,25 +2017,16 @@ const TRACE_VALUE_MAX_DEPTH = 3;
|
|
|
1987
2017
|
const TRACE_VALUE_MAX_KEYS = 20;
|
|
1988
2018
|
const TRACE_VALUE_MAX_ITEMS = 20;
|
|
1989
2019
|
const TRACE_VALUE_MAX_STRING = 2000;
|
|
1990
|
-
const TRACE_FULL_VALUE_MAX_DEPTH =
|
|
1991
|
-
const TRACE_FULL_VALUE_MAX_KEYS =
|
|
1992
|
-
const TRACE_FULL_VALUE_MAX_ITEMS =
|
|
1993
|
-
const TRACE_FULL_VALUE_MAX_STRING =
|
|
1994
|
-
const
|
|
1995
|
-
const
|
|
1996
|
-
const TRACE_FULL_VALUE_FALLBACK_ITEMS = 100;
|
|
1997
|
-
const TRACE_FULL_VALUE_FALLBACK_STRING = 8000;
|
|
1998
|
-
const TRACE_FULL_VALUE_MAX_SERIALIZED_CHARS = 128 * 1024;
|
|
1999
|
-
const INLINE_PRIVACY_MAX_SERIALIZED_CHARS = (() => {
|
|
2000
|
-
const env = Number(process.env.REPRO_SDK_INLINE_PRIVACY_MAX_SERIALIZED_CHARS);
|
|
2020
|
+
const TRACE_FULL_VALUE_MAX_DEPTH = Number.MAX_SAFE_INTEGER;
|
|
2021
|
+
const TRACE_FULL_VALUE_MAX_KEYS = Number.MAX_SAFE_INTEGER;
|
|
2022
|
+
const TRACE_FULL_VALUE_MAX_ITEMS = Number.MAX_SAFE_INTEGER;
|
|
2023
|
+
const TRACE_FULL_VALUE_MAX_STRING = Number.MAX_SAFE_INTEGER;
|
|
2024
|
+
const TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS = (() => {
|
|
2025
|
+
const env = Number(process.env.REPRO_SDK_TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS);
|
|
2001
2026
|
if (Number.isFinite(env) && env > 0) return Math.trunc(env);
|
|
2002
2027
|
return 8 * 1024;
|
|
2003
2028
|
})();
|
|
2004
|
-
const
|
|
2005
|
-
const INLINE_PRIVACY_PREVIEW_MAX_KEYS = 12;
|
|
2006
|
-
const INLINE_PRIVACY_PREVIEW_MAX_ITEMS = 12;
|
|
2007
|
-
const INLINE_PRIVACY_PREVIEW_MAX_STRING = 512;
|
|
2008
|
-
const INLINE_PRIVACY_PREVIEW_JSON_PARSE_MAX_CHARS = 128 * 1024;
|
|
2029
|
+
const TRACE_VALUE_SIZE_EXCEEDED = Symbol('trace-value-size-exceeded');
|
|
2009
2030
|
const TRACE_BATCH_SIZE = 100;
|
|
2010
2031
|
const TRACE_FLUSH_DELAY_MS = 20;
|
|
2011
2032
|
// Choose how to order trace events in payloads.
|
|
@@ -2208,6 +2229,7 @@ function isMongooseQueryLike(value: any): boolean {
|
|
|
2208
2229
|
|
|
2209
2230
|
type TraceSanitizeOptions = {
|
|
2210
2231
|
preserveLongStrings?: boolean;
|
|
2232
|
+
disableTruncation?: boolean;
|
|
2211
2233
|
};
|
|
2212
2234
|
|
|
2213
2235
|
function circularReference(path?: string): Record<string, string> {
|
|
@@ -2352,7 +2374,7 @@ function sanitizeTraceValue(
|
|
|
2352
2374
|
if (existingPath) return circularReference(existingPath);
|
|
2353
2375
|
seen.set(value, valuePath);
|
|
2354
2376
|
|
|
2355
|
-
if (depth >= TRACE_VALUE_MAX_DEPTH) {
|
|
2377
|
+
if (!options.disableTruncation && depth >= TRACE_VALUE_MAX_DEPTH) {
|
|
2356
2378
|
const shallow = safeJson(value);
|
|
2357
2379
|
if (shallow !== undefined) {
|
|
2358
2380
|
return shallow;
|
|
@@ -2364,9 +2386,10 @@ function sanitizeTraceValue(
|
|
|
2364
2386
|
}
|
|
2365
2387
|
|
|
2366
2388
|
if (Array.isArray(value)) {
|
|
2367
|
-
const
|
|
2389
|
+
const sourceItems = options.disableTruncation ? value : value.slice(0, TRACE_VALUE_MAX_ITEMS);
|
|
2390
|
+
const out = sourceItems
|
|
2368
2391
|
.map((item, index) => sanitizeTraceValue(item, depth + 1, seen, options, childCapturePath(valuePath, index)));
|
|
2369
|
-
if (value.length > TRACE_VALUE_MAX_ITEMS) {
|
|
2392
|
+
if (!options.disableTruncation && value.length > TRACE_VALUE_MAX_ITEMS) {
|
|
2370
2393
|
out.push(`…(${value.length - TRACE_VALUE_MAX_ITEMS} more items)`);
|
|
2371
2394
|
}
|
|
2372
2395
|
return out;
|
|
@@ -2376,12 +2399,12 @@ function sanitizeTraceValue(
|
|
|
2376
2399
|
const out: Record<string, any> = {};
|
|
2377
2400
|
let index = 0;
|
|
2378
2401
|
for (const [k, v] of value) {
|
|
2379
|
-
if (index >= TRACE_VALUE_MAX_ITEMS) break;
|
|
2402
|
+
if (!options.disableTruncation && index >= TRACE_VALUE_MAX_ITEMS) break;
|
|
2380
2403
|
const key = normalizeCollectionKeyForCapture(k, index);
|
|
2381
2404
|
out[key] = sanitizeTraceValue(v, depth + 1, seen, options, childCapturePath(valuePath, key));
|
|
2382
2405
|
index += 1;
|
|
2383
2406
|
}
|
|
2384
|
-
if (value.size > TRACE_VALUE_MAX_ITEMS) {
|
|
2407
|
+
if (!options.disableTruncation && value.size > TRACE_VALUE_MAX_ITEMS) {
|
|
2385
2408
|
out.__truncatedEntries = value.size - TRACE_VALUE_MAX_ITEMS;
|
|
2386
2409
|
}
|
|
2387
2410
|
return out;
|
|
@@ -2390,10 +2413,10 @@ function sanitizeTraceValue(
|
|
|
2390
2413
|
if (value instanceof Set) {
|
|
2391
2414
|
const arr: any[] = [];
|
|
2392
2415
|
for (const item of value) {
|
|
2393
|
-
if (arr.length >= TRACE_VALUE_MAX_ITEMS) break;
|
|
2416
|
+
if (!options.disableTruncation && arr.length >= TRACE_VALUE_MAX_ITEMS) break;
|
|
2394
2417
|
arr.push(sanitizeTraceValue(item, depth + 1, seen, options, childCapturePath(valuePath, arr.length)));
|
|
2395
2418
|
}
|
|
2396
|
-
if (value.size > TRACE_VALUE_MAX_ITEMS) {
|
|
2419
|
+
if (!options.disableTruncation && value.size > TRACE_VALUE_MAX_ITEMS) {
|
|
2397
2420
|
arr.push(`…(${value.size - TRACE_VALUE_MAX_ITEMS} more items)`);
|
|
2398
2421
|
}
|
|
2399
2422
|
return arr;
|
|
@@ -2402,14 +2425,15 @@ function sanitizeTraceValue(
|
|
|
2402
2425
|
const ctor = value?.constructor?.name;
|
|
2403
2426
|
const result: Record<string, any> = {};
|
|
2404
2427
|
const keys = Object.keys(value);
|
|
2405
|
-
|
|
2428
|
+
const visibleKeys = options.disableTruncation ? keys : keys.slice(0, TRACE_VALUE_MAX_KEYS);
|
|
2429
|
+
for (const key of visibleKeys) {
|
|
2406
2430
|
try {
|
|
2407
2431
|
result[key] = sanitizeTraceValue((value as any)[key], depth + 1, seen, options, childCapturePath(valuePath, key));
|
|
2408
2432
|
} catch (err) {
|
|
2409
2433
|
result[key] = `[Cannot serialize: ${(err as Error)?.message || 'unknown error'}]`;
|
|
2410
2434
|
}
|
|
2411
2435
|
}
|
|
2412
|
-
if (keys.length > TRACE_VALUE_MAX_KEYS) {
|
|
2436
|
+
if (!options.disableTruncation && keys.length > TRACE_VALUE_MAX_KEYS) {
|
|
2413
2437
|
result.__truncatedKeys = keys.length - TRACE_VALUE_MAX_KEYS;
|
|
2414
2438
|
}
|
|
2415
2439
|
if (ctor && ctor !== 'Object') {
|
|
@@ -2424,7 +2448,11 @@ function sanitizeTraceArgs(values: any): any {
|
|
|
2424
2448
|
}
|
|
2425
2449
|
|
|
2426
2450
|
function sanitizeTraceValueForPrivacy(value: any): any {
|
|
2427
|
-
return sanitizeTraceValue(value, 0, new WeakMap(), { preserveLongStrings: true });
|
|
2451
|
+
return sanitizeTraceValue(value, 0, new WeakMap(), { preserveLongStrings: true, disableTruncation: true });
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
function sanitizeMaterializedTraceValue(value: any): any {
|
|
2455
|
+
return sanitizeTraceValue(value, 0, new WeakMap(), { preserveLongStrings: true, disableTruncation: true });
|
|
2428
2456
|
}
|
|
2429
2457
|
|
|
2430
2458
|
function sanitizeTraceArgsForPrivacy(values: any): any {
|
|
@@ -2437,6 +2465,10 @@ function normalizeRawTraceArgs(values: any): any[] {
|
|
|
2437
2465
|
return [values];
|
|
2438
2466
|
}
|
|
2439
2467
|
|
|
2468
|
+
function hasMeaningfulRawTraceError(error: any): boolean {
|
|
2469
|
+
return error !== undefined && error !== null;
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2440
2472
|
async function materializePendingTraceEvents(
|
|
2441
2473
|
events: PendingTraceEventRecord[],
|
|
2442
2474
|
params: {
|
|
@@ -2452,96 +2484,41 @@ async function materializePendingTraceEvents(
|
|
|
2452
2484
|
delete evt.__reproPending;
|
|
2453
2485
|
|
|
2454
2486
|
const candidate = pending.candidate ?? traceEventCandidateFromRecord(evt);
|
|
2487
|
+
const omitJsonBuiltinValues = shouldOmitJsonBuiltinTraceValues(candidate);
|
|
2455
2488
|
const activePrivacy = params.resolvePrivacy();
|
|
2456
|
-
const capture: FullValueCaptureContext = {
|
|
2457
|
-
runtimeConfig: params.cfg,
|
|
2458
|
-
captureHeaders: params.cfg.captureHeaders,
|
|
2459
|
-
maskReq: params.maskReq,
|
|
2460
|
-
trace: candidate,
|
|
2461
|
-
masking: params.masking,
|
|
2462
|
-
privacy: activePrivacy,
|
|
2463
|
-
};
|
|
2464
2489
|
|
|
2465
|
-
if (pending.argsRaw !== undefined) {
|
|
2466
|
-
|
|
2467
|
-
const argsMaterialization = await materializeInlinePrivacyValueAsync(
|
|
2490
|
+
if (!omitJsonBuiltinValues && pending.argsRaw !== undefined) {
|
|
2491
|
+
evt.args = sanitizeMaterializedTraceValue(await materializeTracePrivacyValueAsync(
|
|
2468
2492
|
'trace.args',
|
|
2469
|
-
|
|
2493
|
+
sanitizeTraceArgsForPrivacy(pending.argsRaw),
|
|
2470
2494
|
params.cfg,
|
|
2471
2495
|
params.maskReq,
|
|
2472
2496
|
candidate,
|
|
2473
2497
|
params.masking,
|
|
2474
2498
|
activePrivacy,
|
|
2475
|
-
);
|
|
2476
|
-
if (argsMaterialization.skipped) {
|
|
2477
|
-
(evt as any).argsMaterialization = argsMaterialization.skipped;
|
|
2478
|
-
if (argsMaterialization.value !== undefined) {
|
|
2479
|
-
evt.args = sanitizeTraceValue(argsMaterialization.value);
|
|
2480
|
-
}
|
|
2481
|
-
} else {
|
|
2482
|
-
evt.args = sanitizeTraceValue(argsMaterialization.value);
|
|
2483
|
-
evt.argsValueCapture = await maybeCaptureFullTraceValueAsync({
|
|
2484
|
-
target: 'trace.args',
|
|
2485
|
-
rawValue: pending.argsRaw,
|
|
2486
|
-
previewValue: evt.args,
|
|
2487
|
-
event: evt,
|
|
2488
|
-
capture,
|
|
2489
|
-
});
|
|
2490
|
-
}
|
|
2499
|
+
));
|
|
2491
2500
|
}
|
|
2492
|
-
if (pending.returnValueRaw !== undefined) {
|
|
2493
|
-
|
|
2494
|
-
const returnValueMaterialization = await materializeInlinePrivacyValueAsync(
|
|
2501
|
+
if (!omitJsonBuiltinValues && pending.returnValueRaw !== undefined) {
|
|
2502
|
+
evt.returnValue = sanitizeMaterializedTraceValue(await materializeTracePrivacyValueAsync(
|
|
2495
2503
|
'trace.returnValue',
|
|
2496
|
-
|
|
2504
|
+
sanitizeTraceValueForPrivacy(pending.returnValueRaw),
|
|
2497
2505
|
params.cfg,
|
|
2498
2506
|
params.maskReq,
|
|
2499
2507
|
candidate,
|
|
2500
2508
|
params.masking,
|
|
2501
2509
|
activePrivacy,
|
|
2502
|
-
);
|
|
2503
|
-
if (returnValueMaterialization.skipped) {
|
|
2504
|
-
(evt as any).returnValueMaterialization = returnValueMaterialization.skipped;
|
|
2505
|
-
if (returnValueMaterialization.value !== undefined) {
|
|
2506
|
-
evt.returnValue = sanitizeTraceValue(returnValueMaterialization.value);
|
|
2507
|
-
}
|
|
2508
|
-
} else {
|
|
2509
|
-
evt.returnValue = sanitizeTraceValue(returnValueMaterialization.value);
|
|
2510
|
-
evt.returnValueCapture = await maybeCaptureFullTraceValueAsync({
|
|
2511
|
-
target: 'trace.returnValue',
|
|
2512
|
-
rawValue: pending.returnValueRaw,
|
|
2513
|
-
previewValue: evt.returnValue,
|
|
2514
|
-
event: evt,
|
|
2515
|
-
capture,
|
|
2516
|
-
});
|
|
2517
|
-
}
|
|
2510
|
+
));
|
|
2518
2511
|
}
|
|
2519
2512
|
if (pending.errorRaw !== undefined) {
|
|
2520
|
-
|
|
2521
|
-
const errorMaterialization = await materializeInlinePrivacyValueAsync(
|
|
2513
|
+
evt.error = sanitizeMaterializedTraceValue(await materializeTracePrivacyValueAsync(
|
|
2522
2514
|
'trace.error',
|
|
2523
|
-
|
|
2515
|
+
sanitizeTraceValueForPrivacy(pending.errorRaw),
|
|
2524
2516
|
params.cfg,
|
|
2525
2517
|
params.maskReq,
|
|
2526
2518
|
candidate,
|
|
2527
2519
|
params.masking,
|
|
2528
2520
|
activePrivacy,
|
|
2529
|
-
);
|
|
2530
|
-
if (errorMaterialization.skipped) {
|
|
2531
|
-
(evt as any).errorMaterialization = errorMaterialization.skipped;
|
|
2532
|
-
if (errorMaterialization.value !== undefined) {
|
|
2533
|
-
evt.error = sanitizeTraceValue(errorMaterialization.value);
|
|
2534
|
-
}
|
|
2535
|
-
} else {
|
|
2536
|
-
evt.error = sanitizeTraceValue(errorMaterialization.value);
|
|
2537
|
-
evt.errorValueCapture = await maybeCaptureFullTraceValueAsync({
|
|
2538
|
-
target: 'trace.error',
|
|
2539
|
-
rawValue: pending.errorRaw,
|
|
2540
|
-
previewValue: evt.error,
|
|
2541
|
-
event: evt,
|
|
2542
|
-
capture,
|
|
2543
|
-
});
|
|
2544
|
-
}
|
|
2521
|
+
));
|
|
2545
2522
|
}
|
|
2546
2523
|
}
|
|
2547
2524
|
return events;
|
|
@@ -2554,10 +2531,6 @@ function stripPendingTraceValues(
|
|
|
2554
2531
|
for (const evt of events) {
|
|
2555
2532
|
if (!evt.__reproPending) continue;
|
|
2556
2533
|
delete evt.__reproPending;
|
|
2557
|
-
(evt as any).valueMaterialization = {
|
|
2558
|
-
status: 'skipped',
|
|
2559
|
-
reason,
|
|
2560
|
-
};
|
|
2561
2534
|
}
|
|
2562
2535
|
return events;
|
|
2563
2536
|
}
|
|
@@ -2646,7 +2619,7 @@ export async function __materializePendingTraceEventsForWorker(
|
|
|
2646
2619
|
function sanitizeRequestSnapshot(value: any) {
|
|
2647
2620
|
if (value === undefined) return undefined;
|
|
2648
2621
|
try {
|
|
2649
|
-
return sanitizeTraceValue(value);
|
|
2622
|
+
return sanitizeTraceValue(value, 0, new WeakMap(), { preserveLongStrings: true, disableTruncation: true });
|
|
2650
2623
|
} catch {
|
|
2651
2624
|
if (value === null) return null;
|
|
2652
2625
|
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
@@ -2860,6 +2833,22 @@ function shouldCapturePrivacyRaw(cfg: ReproMiddlewareConfig): boolean {
|
|
|
2860
2833
|
return cfg.privacy?.captureRaw === true;
|
|
2861
2834
|
}
|
|
2862
2835
|
|
|
2836
|
+
function normalizeValueForRuntimePrivacy(value: any): any {
|
|
2837
|
+
if (value === undefined || value === null) {
|
|
2838
|
+
return value;
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
try {
|
|
2842
|
+
return sanitizeTraceValue(value, 0, new WeakMap(), {
|
|
2843
|
+
preserveLongStrings: true,
|
|
2844
|
+
disableTruncation: true,
|
|
2845
|
+
});
|
|
2846
|
+
} catch {
|
|
2847
|
+
const fallback = safeJson(value);
|
|
2848
|
+
return fallback === undefined ? value : fallback;
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2863
2852
|
function resolveRuntimePrivacyStartupMaxWaitMs(cfg?: RuntimePrivacyConfig | null): number {
|
|
2864
2853
|
const value = Number(cfg?.startupMaxWaitMs);
|
|
2865
2854
|
if (!Number.isFinite(value)) {
|
|
@@ -3092,7 +3081,8 @@ function applyPrivacyThenMask(
|
|
|
3092
3081
|
if (shouldCapturePrivacyRaw(cfg)) {
|
|
3093
3082
|
return value;
|
|
3094
3083
|
}
|
|
3095
|
-
const
|
|
3084
|
+
const privacyInput = normalizeValueForRuntimePrivacy(value);
|
|
3085
|
+
const transformed = applyRuntimePrivacyPolicy(privacy, target, privacyInput, {
|
|
3096
3086
|
routeKey: req.key,
|
|
3097
3087
|
trace: trace
|
|
3098
3088
|
? {
|
|
@@ -3121,7 +3111,8 @@ async function applyPrivacyThenMaskAsync(
|
|
|
3121
3111
|
if (shouldCapturePrivacyRaw(cfg)) {
|
|
3122
3112
|
return value;
|
|
3123
3113
|
}
|
|
3124
|
-
const
|
|
3114
|
+
const privacyInput = normalizeValueForRuntimePrivacy(value);
|
|
3115
|
+
const transformed = await applyRuntimePrivacyPolicyAsync(privacy, target, privacyInput, {
|
|
3125
3116
|
routeKey: req.key,
|
|
3126
3117
|
trace: trace
|
|
3127
3118
|
? {
|
|
@@ -3145,150 +3136,56 @@ type InlinePrivacyMaterializationResult = {
|
|
|
3145
3136
|
target: RuntimePrivacySurface;
|
|
3146
3137
|
serializedLength?: number;
|
|
3147
3138
|
limit?: number;
|
|
3148
|
-
preview?: 'bounded';
|
|
3149
3139
|
};
|
|
3150
3140
|
};
|
|
3151
3141
|
|
|
3152
|
-
function
|
|
3153
|
-
return (
|
|
3154
|
-
target === 'request.body' ||
|
|
3155
|
-
target === 'response.body' ||
|
|
3156
|
-
target === 'trace.args' ||
|
|
3157
|
-
target === 'trace.returnValue' ||
|
|
3158
|
-
target === 'trace.error' ||
|
|
3159
|
-
target === 'db.before' ||
|
|
3160
|
-
target === 'db.after' ||
|
|
3161
|
-
target === 'db.query' ||
|
|
3162
|
-
target === 'db.resultMeta' ||
|
|
3163
|
-
target === 'db.error'
|
|
3164
|
-
);
|
|
3142
|
+
function traceInlineValueExceedsLimit(value: any): boolean {
|
|
3143
|
+
return boundedSerializedTraceValueLength(value, TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS).exceeded;
|
|
3165
3144
|
}
|
|
3166
3145
|
|
|
3167
|
-
function
|
|
3146
|
+
function limitInlinePrivacyValue(
|
|
3147
|
+
target: RuntimePrivacySurface,
|
|
3168
3148
|
value: any,
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
if (value === null || value === undefined) return value;
|
|
3174
|
-
|
|
3175
|
-
const type = typeof value;
|
|
3176
|
-
if (type === 'string') {
|
|
3177
|
-
if (value.length <= INLINE_PRIVACY_PREVIEW_MAX_STRING) {
|
|
3178
|
-
return preserveScalarValue ? value : { __type: 'string', length: value.length };
|
|
3179
|
-
}
|
|
3180
|
-
const trimmed = value.trim();
|
|
3181
|
-
const mayBeJson = trimmed.startsWith('{') || trimmed.startsWith('[');
|
|
3182
|
-
if (mayBeJson && value.length <= INLINE_PRIVACY_PREVIEW_JSON_PARSE_MAX_CHARS) {
|
|
3183
|
-
try {
|
|
3184
|
-
return {
|
|
3185
|
-
__type: 'json-string',
|
|
3186
|
-
length: value.length,
|
|
3187
|
-
parsed: buildOversizedInlinePreview(JSON.parse(value), depth + 1, seen, false),
|
|
3188
|
-
};
|
|
3189
|
-
} catch {}
|
|
3190
|
-
}
|
|
3191
|
-
return {
|
|
3192
|
-
__type: 'string',
|
|
3193
|
-
length: value.length,
|
|
3194
|
-
truncated: true,
|
|
3195
|
-
};
|
|
3196
|
-
}
|
|
3197
|
-
if (type === 'number' || type === 'boolean') return preserveScalarValue ? value : { __type: type };
|
|
3198
|
-
if (type === 'bigint') return preserveScalarValue ? `${value.toString()}n` : { __type: 'bigint' };
|
|
3199
|
-
if (type === 'symbol') return preserveScalarValue ? value.toString() : { __type: 'symbol' };
|
|
3200
|
-
if (type === 'function') return `[Function${value.name ? ` ${value.name}` : ''}]`;
|
|
3201
|
-
|
|
3202
|
-
if (Buffer.isBuffer(value) || value instanceof Uint8Array) {
|
|
3203
|
-
return {
|
|
3204
|
-
__type: Buffer.isBuffer(value) ? 'Buffer' : 'Uint8Array',
|
|
3205
|
-
length: value.length,
|
|
3206
|
-
preview: value.length ? Buffer.from(value).slice(0, 32).toString('hex') : '',
|
|
3207
|
-
};
|
|
3208
|
-
}
|
|
3209
|
-
if (value instanceof Date) return preserveScalarValue ? value.toISOString() : { __type: 'Date' };
|
|
3210
|
-
if (value instanceof RegExp) return preserveScalarValue ? value.toString() : { __type: 'RegExp' };
|
|
3211
|
-
if (value instanceof Error) {
|
|
3212
|
-
return {
|
|
3213
|
-
__type: 'Error',
|
|
3214
|
-
name: value.name,
|
|
3215
|
-
message: preserveScalarValue ? value.message : { __type: 'string', length: value.message.length },
|
|
3216
|
-
};
|
|
3217
|
-
}
|
|
3218
|
-
if (value instanceof WeakMap || value instanceof WeakSet) {
|
|
3219
|
-
return { __type: getCtorName(value) || 'WeakCollection', uninspectable: true };
|
|
3220
|
-
}
|
|
3221
|
-
|
|
3222
|
-
if (type !== 'object') return preserveScalarValue ? String(value) : { __type: type };
|
|
3223
|
-
if (seen.has(value)) return { __type: 'circular-reference' };
|
|
3224
|
-
seen.add(value);
|
|
3225
|
-
|
|
3226
|
-
if (Array.isArray(value)) {
|
|
3227
|
-
const items = value
|
|
3228
|
-
.slice(0, INLINE_PRIVACY_PREVIEW_MAX_ITEMS)
|
|
3229
|
-
.map((item) => buildOversizedInlinePreview(item, depth + 1, seen, true));
|
|
3230
|
-
if (value.length > INLINE_PRIVACY_PREVIEW_MAX_ITEMS) {
|
|
3231
|
-
items.push({ __truncatedItems: value.length - INLINE_PRIVACY_PREVIEW_MAX_ITEMS });
|
|
3232
|
-
}
|
|
3233
|
-
return items;
|
|
3234
|
-
}
|
|
3149
|
+
): InlinePrivacyMaterializationResult {
|
|
3150
|
+
void target;
|
|
3151
|
+
return { value };
|
|
3152
|
+
}
|
|
3235
3153
|
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
}
|
|
3244
|
-
if (value.size > INLINE_PRIVACY_PREVIEW_MAX_ITEMS) {
|
|
3245
|
-
out.__truncatedEntries = value.size - INLINE_PRIVACY_PREVIEW_MAX_ITEMS;
|
|
3246
|
-
}
|
|
3247
|
-
return out;
|
|
3248
|
-
}
|
|
3154
|
+
function limitRawInlinePrivacyValue(
|
|
3155
|
+
target: RuntimePrivacySurface,
|
|
3156
|
+
value: any,
|
|
3157
|
+
): InlinePrivacyMaterializationResult | null {
|
|
3158
|
+
const limited = limitInlinePrivacyValue(target, value);
|
|
3159
|
+
return limited.skipped ? limited : null;
|
|
3160
|
+
}
|
|
3249
3161
|
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
return {
|
|
3262
|
-
__type: 'Set',
|
|
3263
|
-
size: value.size,
|
|
3264
|
-
items,
|
|
3265
|
-
};
|
|
3162
|
+
function materializeInlinePrivacyValue(
|
|
3163
|
+
target: RuntimePrivacySurface,
|
|
3164
|
+
value: any,
|
|
3165
|
+
cfg: ReproMiddlewareConfig,
|
|
3166
|
+
req: MaskRequestContext,
|
|
3167
|
+
trace: TraceEventForFilter | null,
|
|
3168
|
+
masking: NormalizedMaskingConfig | null,
|
|
3169
|
+
privacy: NormalizedRuntimePrivacyPolicy | null,
|
|
3170
|
+
db?: RuntimePrivacyDbContext | null,
|
|
3171
|
+
): InlinePrivacyMaterializationResult {
|
|
3172
|
+
if (value === undefined || shouldCapturePrivacyRaw(cfg)) {
|
|
3173
|
+
return { value };
|
|
3266
3174
|
}
|
|
3267
3175
|
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3176
|
+
try {
|
|
3177
|
+
const valueAfterPrivacy = applyPrivacyThenMask(target, value, cfg, req, trace, masking, privacy, db);
|
|
3178
|
+
return { value: valueAfterPrivacy };
|
|
3179
|
+
} catch {
|
|
3271
3180
|
return {
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3181
|
+
value: undefined,
|
|
3182
|
+
skipped: {
|
|
3183
|
+
status: 'skipped',
|
|
3184
|
+
reason: 'inline_privacy_failed',
|
|
3185
|
+
target,
|
|
3186
|
+
},
|
|
3277
3187
|
};
|
|
3278
3188
|
}
|
|
3279
|
-
|
|
3280
|
-
const out: Record<string, any> = ctor && ctor !== 'Object' ? { __type: ctor } : {};
|
|
3281
|
-
for (const key of keys.slice(0, INLINE_PRIVACY_PREVIEW_MAX_KEYS)) {
|
|
3282
|
-
try {
|
|
3283
|
-
out[key] = buildOversizedInlinePreview(value[key], depth + 1, seen, false);
|
|
3284
|
-
} catch (err) {
|
|
3285
|
-
out[key] = { __type: 'uninspectable', message: (err as Error)?.message || 'unknown error' };
|
|
3286
|
-
}
|
|
3287
|
-
}
|
|
3288
|
-
if (keys.length > INLINE_PRIVACY_PREVIEW_MAX_KEYS) {
|
|
3289
|
-
out.__truncatedKeys = keys.length - INLINE_PRIVACY_PREVIEW_MAX_KEYS;
|
|
3290
|
-
}
|
|
3291
|
-
return out;
|
|
3292
3189
|
}
|
|
3293
3190
|
|
|
3294
3191
|
async function materializeInlinePrivacyValueAsync(
|
|
@@ -3305,32 +3202,9 @@ async function materializeInlinePrivacyValueAsync(
|
|
|
3305
3202
|
return { value };
|
|
3306
3203
|
}
|
|
3307
3204
|
|
|
3308
|
-
if (shouldBoundInlinePrivacyTarget(target)) {
|
|
3309
|
-
const serialized = stableSerializeTraceValue(value);
|
|
3310
|
-
const serializedLength = serialized?.length ?? 0;
|
|
3311
|
-
if (serializedLength > INLINE_PRIVACY_MAX_SERIALIZED_CHARS) {
|
|
3312
|
-
let preview = buildOversizedInlinePreview(value);
|
|
3313
|
-
try {
|
|
3314
|
-
preview = await applyPrivacyThenMaskAsync(target, preview, cfg, req, trace, masking, privacy, db);
|
|
3315
|
-
} catch {}
|
|
3316
|
-
return {
|
|
3317
|
-
value: preview,
|
|
3318
|
-
skipped: {
|
|
3319
|
-
status: 'skipped',
|
|
3320
|
-
reason: 'inline_value_too_large',
|
|
3321
|
-
target,
|
|
3322
|
-
serializedLength,
|
|
3323
|
-
limit: INLINE_PRIVACY_MAX_SERIALIZED_CHARS,
|
|
3324
|
-
preview: 'bounded',
|
|
3325
|
-
},
|
|
3326
|
-
};
|
|
3327
|
-
}
|
|
3328
|
-
}
|
|
3329
|
-
|
|
3330
3205
|
try {
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
};
|
|
3206
|
+
const valueAfterPrivacy = await applyPrivacyThenMaskAsync(target, value, cfg, req, trace, masking, privacy, db);
|
|
3207
|
+
return { value: valueAfterPrivacy };
|
|
3334
3208
|
} catch {
|
|
3335
3209
|
return {
|
|
3336
3210
|
value: undefined,
|
|
@@ -3343,6 +3217,36 @@ async function materializeInlinePrivacyValueAsync(
|
|
|
3343
3217
|
}
|
|
3344
3218
|
}
|
|
3345
3219
|
|
|
3220
|
+
function materializeTracePrivacyValue(
|
|
3221
|
+
target: 'trace.args' | 'trace.returnValue' | 'trace.error',
|
|
3222
|
+
value: any,
|
|
3223
|
+
cfg: ReproMiddlewareConfig,
|
|
3224
|
+
req: MaskRequestContext,
|
|
3225
|
+
trace: TraceEventForFilter | null,
|
|
3226
|
+
masking: NormalizedMaskingConfig | null,
|
|
3227
|
+
privacy: NormalizedRuntimePrivacyPolicy | null,
|
|
3228
|
+
): any {
|
|
3229
|
+
if (value === undefined || shouldCapturePrivacyRaw(cfg)) {
|
|
3230
|
+
return value;
|
|
3231
|
+
}
|
|
3232
|
+
return applyPrivacyThenMask(target, value, cfg, req, trace, masking, privacy);
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3235
|
+
async function materializeTracePrivacyValueAsync(
|
|
3236
|
+
target: 'trace.args' | 'trace.returnValue' | 'trace.error',
|
|
3237
|
+
value: any,
|
|
3238
|
+
cfg: ReproMiddlewareConfig,
|
|
3239
|
+
req: MaskRequestContext,
|
|
3240
|
+
trace: TraceEventForFilter | null,
|
|
3241
|
+
masking: NormalizedMaskingConfig | null,
|
|
3242
|
+
privacy: NormalizedRuntimePrivacyPolicy | null,
|
|
3243
|
+
): Promise<any> {
|
|
3244
|
+
if (value === undefined || shouldCapturePrivacyRaw(cfg)) {
|
|
3245
|
+
return value;
|
|
3246
|
+
}
|
|
3247
|
+
return applyPrivacyThenMaskAsync(target, value, cfg, req, trace, masking, privacy);
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3346
3250
|
function maskWhenRequiresTrace(when: ReproMaskWhen): boolean {
|
|
3347
3251
|
return Boolean(
|
|
3348
3252
|
when.fn ||
|
|
@@ -3603,6 +3507,134 @@ function stableSerializeTraceValue(value: any): string | null {
|
|
|
3603
3507
|
}
|
|
3604
3508
|
}
|
|
3605
3509
|
|
|
3510
|
+
function boundedSerializedTraceValueLength(value: any, limit: number): { exceeded: boolean; length: number } {
|
|
3511
|
+
let length = 0;
|
|
3512
|
+
const seen = new WeakSet<object>();
|
|
3513
|
+
|
|
3514
|
+
const add = (amount: number) => {
|
|
3515
|
+
length += amount;
|
|
3516
|
+
if (length > limit) {
|
|
3517
|
+
throw TRACE_VALUE_SIZE_EXCEEDED;
|
|
3518
|
+
}
|
|
3519
|
+
};
|
|
3520
|
+
|
|
3521
|
+
const addString = (input: string) => {
|
|
3522
|
+
if (length + input.length + 2 > limit) {
|
|
3523
|
+
throw TRACE_VALUE_SIZE_EXCEEDED;
|
|
3524
|
+
}
|
|
3525
|
+
add(JSON.stringify(input).length);
|
|
3526
|
+
};
|
|
3527
|
+
|
|
3528
|
+
const walk = (input: any): void => {
|
|
3529
|
+
if (input === undefined) {
|
|
3530
|
+
addString('__undefined__');
|
|
3531
|
+
return;
|
|
3532
|
+
}
|
|
3533
|
+
if (input === null) {
|
|
3534
|
+
add(4);
|
|
3535
|
+
return;
|
|
3536
|
+
}
|
|
3537
|
+
const inputType = typeof input;
|
|
3538
|
+
if (inputType === 'string') {
|
|
3539
|
+
addString(input);
|
|
3540
|
+
return;
|
|
3541
|
+
}
|
|
3542
|
+
if (inputType === 'number' || inputType === 'boolean') {
|
|
3543
|
+
add(JSON.stringify(input)?.length ?? String(input).length);
|
|
3544
|
+
return;
|
|
3545
|
+
}
|
|
3546
|
+
if (inputType === 'bigint') {
|
|
3547
|
+
addString(`${input.toString()}n`);
|
|
3548
|
+
return;
|
|
3549
|
+
}
|
|
3550
|
+
if (inputType === 'symbol') {
|
|
3551
|
+
addString(input.toString());
|
|
3552
|
+
return;
|
|
3553
|
+
}
|
|
3554
|
+
if (inputType === 'function') {
|
|
3555
|
+
addString('[Function]');
|
|
3556
|
+
return;
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
if (Array.isArray(input)) {
|
|
3560
|
+
add(1);
|
|
3561
|
+
input.forEach((item, index) => {
|
|
3562
|
+
if (index > 0) add(1);
|
|
3563
|
+
walk(item);
|
|
3564
|
+
});
|
|
3565
|
+
add(1);
|
|
3566
|
+
return;
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
if (input instanceof Map) {
|
|
3570
|
+
add(1);
|
|
3571
|
+
let index = 0;
|
|
3572
|
+
for (const [key, item] of input.entries()) {
|
|
3573
|
+
if (index > 0) add(1);
|
|
3574
|
+
addString(normalizeCollectionKeyForCapture(key, index));
|
|
3575
|
+
add(1);
|
|
3576
|
+
walk(item);
|
|
3577
|
+
index += 1;
|
|
3578
|
+
}
|
|
3579
|
+
add(1);
|
|
3580
|
+
return;
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3583
|
+
if (input instanceof Set) {
|
|
3584
|
+
add(1);
|
|
3585
|
+
let index = 0;
|
|
3586
|
+
for (const item of input.values()) {
|
|
3587
|
+
if (index > 0) add(1);
|
|
3588
|
+
walk(item);
|
|
3589
|
+
index += 1;
|
|
3590
|
+
}
|
|
3591
|
+
add(1);
|
|
3592
|
+
return;
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
if (input instanceof WeakMap || input instanceof WeakSet) {
|
|
3596
|
+
addString(`[${getCtorName(input) || 'WeakCollection'}: uninspectable]`);
|
|
3597
|
+
return;
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
if (input && typeof input === 'object') {
|
|
3601
|
+
if (seen.has(input)) {
|
|
3602
|
+
addString('[Circular]');
|
|
3603
|
+
return;
|
|
3604
|
+
}
|
|
3605
|
+
seen.add(input);
|
|
3606
|
+
add(1);
|
|
3607
|
+
let index = 0;
|
|
3608
|
+
for (const key of Object.keys(input).sort()) {
|
|
3609
|
+
if (index > 0) add(1);
|
|
3610
|
+
addString(key);
|
|
3611
|
+
add(1);
|
|
3612
|
+
try {
|
|
3613
|
+
walk((input as Record<string, any>)[key]);
|
|
3614
|
+
} catch (err) {
|
|
3615
|
+
if (err === TRACE_VALUE_SIZE_EXCEEDED) throw err;
|
|
3616
|
+
addString(`[Cannot serialize: ${(err as Error)?.message || 'unknown error'}]`);
|
|
3617
|
+
}
|
|
3618
|
+
index += 1;
|
|
3619
|
+
}
|
|
3620
|
+
add(1);
|
|
3621
|
+
return;
|
|
3622
|
+
}
|
|
3623
|
+
|
|
3624
|
+
addString(String(input));
|
|
3625
|
+
};
|
|
3626
|
+
|
|
3627
|
+
try {
|
|
3628
|
+
walk(value);
|
|
3629
|
+
return { exceeded: false, length };
|
|
3630
|
+
} catch (err) {
|
|
3631
|
+
if (err === TRACE_VALUE_SIZE_EXCEEDED) {
|
|
3632
|
+
return { exceeded: true, length };
|
|
3633
|
+
}
|
|
3634
|
+
return { exceeded: true, length: limit + 1 };
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3606
3638
|
function normalizeCollectionKeyForCapture(value: any, index: number): string {
|
|
3607
3639
|
const mongoId = coerceMongoId(value);
|
|
3608
3640
|
if (mongoId !== null) return mongoId;
|
|
@@ -4147,10 +4179,7 @@ function createCapturedValueEntry(
|
|
|
4147
4179
|
| 'request.body'
|
|
4148
4180
|
| 'request.params'
|
|
4149
4181
|
| 'request.query'
|
|
4150
|
-
| 'response.body'
|
|
4151
|
-
| 'trace.args'
|
|
4152
|
-
| 'trace.returnValue'
|
|
4153
|
-
| 'trace.error';
|
|
4182
|
+
| 'response.body';
|
|
4154
4183
|
rawValue: any;
|
|
4155
4184
|
previewValue: any;
|
|
4156
4185
|
capture: FullValueCaptureContext;
|
|
@@ -4165,13 +4194,6 @@ function createCapturedValueEntry(
|
|
|
4165
4194
|
maxItems: TRACE_FULL_VALUE_MAX_ITEMS,
|
|
4166
4195
|
maxString: TRACE_FULL_VALUE_MAX_STRING,
|
|
4167
4196
|
};
|
|
4168
|
-
const fallbackLimits: FullValueCloneLimits = {
|
|
4169
|
-
maxDepth: TRACE_FULL_VALUE_FALLBACK_DEPTH,
|
|
4170
|
-
maxKeys: TRACE_FULL_VALUE_FALLBACK_KEYS,
|
|
4171
|
-
maxItems: TRACE_FULL_VALUE_FALLBACK_ITEMS,
|
|
4172
|
-
maxString: TRACE_FULL_VALUE_FALLBACK_STRING,
|
|
4173
|
-
};
|
|
4174
|
-
|
|
4175
4197
|
const buildMaskedCandidate = (limits: FullValueCloneLimits) => {
|
|
4176
4198
|
const projected = params.target === 'request.headers'
|
|
4177
4199
|
? sanitizeHeaders(params.rawValue, params.capture.captureHeaders)
|
|
@@ -4200,15 +4222,6 @@ function createCapturedValueEntry(
|
|
|
4200
4222
|
return undefined;
|
|
4201
4223
|
}
|
|
4202
4224
|
|
|
4203
|
-
let truncatedForStorage = false;
|
|
4204
|
-
if (candidateSerialized.length > TRACE_FULL_VALUE_MAX_SERIALIZED_CHARS) {
|
|
4205
|
-
const fallback = buildMaskedCandidate(fallbackLimits);
|
|
4206
|
-
if (fallback !== undefined && hasMeaningfulFullValue(fallback)) {
|
|
4207
|
-
candidate = fallback;
|
|
4208
|
-
truncatedForStorage = true;
|
|
4209
|
-
}
|
|
4210
|
-
}
|
|
4211
|
-
|
|
4212
4225
|
const valueId = randomUUID();
|
|
4213
4226
|
const projectedKind =
|
|
4214
4227
|
candidate && typeof candidate === 'object'
|
|
@@ -4218,13 +4231,13 @@ function createCapturedValueEntry(
|
|
|
4218
4231
|
id: valueId,
|
|
4219
4232
|
target: params.target,
|
|
4220
4233
|
value: candidate,
|
|
4221
|
-
truncatedForStorage,
|
|
4234
|
+
truncatedForStorage: false,
|
|
4222
4235
|
projectedKind: typeof projectedKind === 'string' ? projectedKind : undefined,
|
|
4223
4236
|
...(params.metadata ?? {}),
|
|
4224
4237
|
};
|
|
4225
4238
|
return {
|
|
4226
4239
|
captureRef: {
|
|
4227
|
-
status:
|
|
4240
|
+
status: 'stored',
|
|
4228
4241
|
valueId,
|
|
4229
4242
|
projectedKind: entry.projectedKind,
|
|
4230
4243
|
},
|
|
@@ -4245,10 +4258,7 @@ async function createCapturedValueEntryAsync(
|
|
|
4245
4258
|
| 'request.body'
|
|
4246
4259
|
| 'request.params'
|
|
4247
4260
|
| 'request.query'
|
|
4248
|
-
| 'response.body'
|
|
4249
|
-
| 'trace.args'
|
|
4250
|
-
| 'trace.returnValue'
|
|
4251
|
-
| 'trace.error';
|
|
4261
|
+
| 'response.body';
|
|
4252
4262
|
rawValue: any;
|
|
4253
4263
|
previewValue: any;
|
|
4254
4264
|
capture: FullValueCaptureContext;
|
|
@@ -4263,13 +4273,6 @@ async function createCapturedValueEntryAsync(
|
|
|
4263
4273
|
maxItems: TRACE_FULL_VALUE_MAX_ITEMS,
|
|
4264
4274
|
maxString: TRACE_FULL_VALUE_MAX_STRING,
|
|
4265
4275
|
};
|
|
4266
|
-
const fallbackLimits: FullValueCloneLimits = {
|
|
4267
|
-
maxDepth: TRACE_FULL_VALUE_FALLBACK_DEPTH,
|
|
4268
|
-
maxKeys: TRACE_FULL_VALUE_FALLBACK_KEYS,
|
|
4269
|
-
maxItems: TRACE_FULL_VALUE_FALLBACK_ITEMS,
|
|
4270
|
-
maxString: TRACE_FULL_VALUE_FALLBACK_STRING,
|
|
4271
|
-
};
|
|
4272
|
-
|
|
4273
4276
|
const buildMaskedCandidate = async (limits: FullValueCloneLimits) => {
|
|
4274
4277
|
const projected = params.target === 'request.headers'
|
|
4275
4278
|
? sanitizeHeaders(params.rawValue, params.capture.captureHeaders)
|
|
@@ -4298,15 +4301,6 @@ async function createCapturedValueEntryAsync(
|
|
|
4298
4301
|
return undefined;
|
|
4299
4302
|
}
|
|
4300
4303
|
|
|
4301
|
-
let truncatedForStorage = false;
|
|
4302
|
-
if (candidateSerialized.length > TRACE_FULL_VALUE_MAX_SERIALIZED_CHARS) {
|
|
4303
|
-
const fallback = await buildMaskedCandidate(fallbackLimits);
|
|
4304
|
-
if (fallback !== undefined && hasMeaningfulFullValue(fallback)) {
|
|
4305
|
-
candidate = fallback;
|
|
4306
|
-
truncatedForStorage = true;
|
|
4307
|
-
}
|
|
4308
|
-
}
|
|
4309
|
-
|
|
4310
4304
|
const valueId = randomUUID();
|
|
4311
4305
|
const projectedKind =
|
|
4312
4306
|
candidate && typeof candidate === 'object'
|
|
@@ -4316,13 +4310,13 @@ async function createCapturedValueEntryAsync(
|
|
|
4316
4310
|
id: valueId,
|
|
4317
4311
|
target: params.target,
|
|
4318
4312
|
value: candidate,
|
|
4319
|
-
truncatedForStorage,
|
|
4313
|
+
truncatedForStorage: false,
|
|
4320
4314
|
projectedKind: typeof projectedKind === 'string' ? projectedKind : undefined,
|
|
4321
4315
|
...(params.metadata ?? {}),
|
|
4322
4316
|
};
|
|
4323
4317
|
return {
|
|
4324
4318
|
captureRef: {
|
|
4325
|
-
status:
|
|
4319
|
+
status: 'stored',
|
|
4326
4320
|
valueId,
|
|
4327
4321
|
projectedKind: entry.projectedKind,
|
|
4328
4322
|
},
|
|
@@ -4330,84 +4324,6 @@ async function createCapturedValueEntryAsync(
|
|
|
4330
4324
|
};
|
|
4331
4325
|
}
|
|
4332
4326
|
|
|
4333
|
-
function maybeCaptureFullTraceValue(
|
|
4334
|
-
params: {
|
|
4335
|
-
target: 'trace.args' | 'trace.returnValue' | 'trace.error';
|
|
4336
|
-
rawValue: any;
|
|
4337
|
-
previewValue: any;
|
|
4338
|
-
event: TraceEventRecord;
|
|
4339
|
-
capture: FullValueCaptureContext;
|
|
4340
|
-
},
|
|
4341
|
-
): TraceValueCaptureRef | undefined {
|
|
4342
|
-
const result = createCapturedValueEntry({
|
|
4343
|
-
target: params.target,
|
|
4344
|
-
rawValue: params.rawValue,
|
|
4345
|
-
previewValue: params.previewValue,
|
|
4346
|
-
capture: params.capture,
|
|
4347
|
-
metadata: {
|
|
4348
|
-
fn: params.event.fn,
|
|
4349
|
-
file: params.event.file,
|
|
4350
|
-
line: params.event.line ?? null,
|
|
4351
|
-
spanId: params.event.spanId ?? null,
|
|
4352
|
-
parentSpanId: params.event.parentSpanId ?? null,
|
|
4353
|
-
},
|
|
4354
|
-
});
|
|
4355
|
-
if (!result) return undefined;
|
|
4356
|
-
if (result.entry) {
|
|
4357
|
-
const existing = (params.event as any)[TRACE_FULL_VALUE_ENTRY_SYMBOL] as TraceValueBatchEntry[] | undefined;
|
|
4358
|
-
if (Array.isArray(existing)) {
|
|
4359
|
-
existing.push(result.entry);
|
|
4360
|
-
} else {
|
|
4361
|
-
Object.defineProperty(params.event, TRACE_FULL_VALUE_ENTRY_SYMBOL, {
|
|
4362
|
-
value: [result.entry],
|
|
4363
|
-
enumerable: false,
|
|
4364
|
-
configurable: true,
|
|
4365
|
-
writable: true,
|
|
4366
|
-
});
|
|
4367
|
-
}
|
|
4368
|
-
}
|
|
4369
|
-
return result.captureRef;
|
|
4370
|
-
}
|
|
4371
|
-
|
|
4372
|
-
async function maybeCaptureFullTraceValueAsync(
|
|
4373
|
-
params: {
|
|
4374
|
-
target: 'trace.args' | 'trace.returnValue' | 'trace.error';
|
|
4375
|
-
rawValue: any;
|
|
4376
|
-
previewValue: any;
|
|
4377
|
-
event: TraceEventRecord;
|
|
4378
|
-
capture: FullValueCaptureContext;
|
|
4379
|
-
},
|
|
4380
|
-
): Promise<TraceValueCaptureRef | undefined> {
|
|
4381
|
-
const result = await createCapturedValueEntryAsync({
|
|
4382
|
-
target: params.target,
|
|
4383
|
-
rawValue: params.rawValue,
|
|
4384
|
-
previewValue: params.previewValue,
|
|
4385
|
-
capture: params.capture,
|
|
4386
|
-
metadata: {
|
|
4387
|
-
fn: params.event.fn,
|
|
4388
|
-
file: params.event.file,
|
|
4389
|
-
line: params.event.line ?? null,
|
|
4390
|
-
spanId: params.event.spanId ?? null,
|
|
4391
|
-
parentSpanId: params.event.parentSpanId ?? null,
|
|
4392
|
-
},
|
|
4393
|
-
});
|
|
4394
|
-
if (!result) return undefined;
|
|
4395
|
-
if (result.entry) {
|
|
4396
|
-
const existing = (params.event as any)[TRACE_FULL_VALUE_ENTRY_SYMBOL] as TraceValueBatchEntry[] | undefined;
|
|
4397
|
-
if (Array.isArray(existing)) {
|
|
4398
|
-
existing.push(result.entry);
|
|
4399
|
-
} else {
|
|
4400
|
-
Object.defineProperty(params.event, TRACE_FULL_VALUE_ENTRY_SYMBOL, {
|
|
4401
|
-
value: [result.entry],
|
|
4402
|
-
enumerable: false,
|
|
4403
|
-
configurable: true,
|
|
4404
|
-
writable: true,
|
|
4405
|
-
});
|
|
4406
|
-
}
|
|
4407
|
-
}
|
|
4408
|
-
return result.captureRef;
|
|
4409
|
-
}
|
|
4410
|
-
|
|
4411
4327
|
function maybeCaptureRequestValue(
|
|
4412
4328
|
params: {
|
|
4413
4329
|
target: 'request.headers' | 'request.body' | 'request.params' | 'request.query' | 'response.body';
|
|
@@ -4583,6 +4499,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4583
4499
|
let idleTimer: NodeJS.Timeout | null = null;
|
|
4584
4500
|
let hardStopTimer: NodeJS.Timeout | null = null;
|
|
4585
4501
|
let flushPayload: null | (() => Promise<void>) = null;
|
|
4502
|
+
let requestCaptureScheduled = false;
|
|
4586
4503
|
let sessionDrainWait: Promise<void> | null = null;
|
|
4587
4504
|
const activeSpans = new Set<string>();
|
|
4588
4505
|
let anonymousSpanDepth = 0;
|
|
@@ -4656,6 +4573,226 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4656
4573
|
scheduleIdleFlush();
|
|
4657
4574
|
};
|
|
4658
4575
|
|
|
4576
|
+
const chooseRequestEndpoint = (): {
|
|
4577
|
+
chosenEndpoint: EndpointTraceInfo;
|
|
4578
|
+
hasTraceEvents: boolean;
|
|
4579
|
+
} => {
|
|
4580
|
+
const pendingEvents = preparePendingTraceEventsForFlush(events.slice());
|
|
4581
|
+
const baseEvents = balanceTraceEvents(pendingEvents.slice() as TraceEventRecord[]);
|
|
4582
|
+
const orderedEvents = TRACE_ORDER_MODE === 'tree'
|
|
4583
|
+
? reorderTraceEvents(baseEvents)
|
|
4584
|
+
: sortTraceEventsChronologically(baseEvents);
|
|
4585
|
+
const summary = summarizeEndpointFromEvents(orderedEvents);
|
|
4586
|
+
return {
|
|
4587
|
+
chosenEndpoint: summary.endpointTrace
|
|
4588
|
+
?? summary.preferredAppTrace
|
|
4589
|
+
?? summary.firstAppTrace
|
|
4590
|
+
?? endpointTrace
|
|
4591
|
+
?? preferredAppTrace
|
|
4592
|
+
?? firstAppTrace
|
|
4593
|
+
?? { fn: null, file: null, line: null, functionType: null },
|
|
4594
|
+
hasTraceEvents: orderedEvents.length > 0,
|
|
4595
|
+
};
|
|
4596
|
+
};
|
|
4597
|
+
|
|
4598
|
+
const buildRequestCapturePayloadAsync = async (
|
|
4599
|
+
chosenEndpoint: EndpointTraceInfo,
|
|
4600
|
+
hasTraceEvents: boolean,
|
|
4601
|
+
): Promise<{
|
|
4602
|
+
requestPayload: Record<string, any>;
|
|
4603
|
+
requestValueEntries: TraceValueBatchEntry[];
|
|
4604
|
+
}> => {
|
|
4605
|
+
const endpointTraceCtx: TraceEventForFilter | null = (() => {
|
|
4606
|
+
if (!chosenEndpoint?.fn && !chosenEndpoint?.file) return null;
|
|
4607
|
+
return {
|
|
4608
|
+
type: 'enter',
|
|
4609
|
+
eventType: 'enter',
|
|
4610
|
+
fn: chosenEndpoint.fn ?? undefined,
|
|
4611
|
+
wrapperClass: inferWrapperClassFromFn(chosenEndpoint.fn),
|
|
4612
|
+
file: chosenEndpoint.file ?? null,
|
|
4613
|
+
line: chosenEndpoint.line ?? null,
|
|
4614
|
+
functionType: chosenEndpoint.functionType ?? null,
|
|
4615
|
+
library: inferLibraryNameFromFile(chosenEndpoint.file),
|
|
4616
|
+
};
|
|
4617
|
+
})();
|
|
4618
|
+
const activePrivacy = resolvePrivacy();
|
|
4619
|
+
|
|
4620
|
+
const requestBodyRaw = (req as any).body;
|
|
4621
|
+
const requestBodyMaterialization = limitRawInlinePrivacyValue('request.body', requestBodyRaw)
|
|
4622
|
+
?? await materializeInlinePrivacyValueAsync(
|
|
4623
|
+
'request.body',
|
|
4624
|
+
sanitizeRequestSnapshot(requestBodyRaw),
|
|
4625
|
+
cfg,
|
|
4626
|
+
maskReq,
|
|
4627
|
+
endpointTraceCtx,
|
|
4628
|
+
masking,
|
|
4629
|
+
activePrivacy,
|
|
4630
|
+
);
|
|
4631
|
+
const requestBody = requestBodyMaterialization.value;
|
|
4632
|
+
const requestParams = await applyPrivacyThenMaskAsync(
|
|
4633
|
+
'request.params',
|
|
4634
|
+
sanitizeRequestSnapshot((req as any).params),
|
|
4635
|
+
cfg,
|
|
4636
|
+
maskReq,
|
|
4637
|
+
endpointTraceCtx,
|
|
4638
|
+
masking,
|
|
4639
|
+
activePrivacy,
|
|
4640
|
+
);
|
|
4641
|
+
const requestQuery = await applyPrivacyThenMaskAsync(
|
|
4642
|
+
'request.query',
|
|
4643
|
+
sanitizeRequestSnapshot((req as any).query),
|
|
4644
|
+
cfg,
|
|
4645
|
+
maskReq,
|
|
4646
|
+
endpointTraceCtx,
|
|
4647
|
+
masking,
|
|
4648
|
+
activePrivacy,
|
|
4649
|
+
);
|
|
4650
|
+
const maskedHeaders = await applyPrivacyThenMaskAsync(
|
|
4651
|
+
'request.headers',
|
|
4652
|
+
requestHeaders,
|
|
4653
|
+
cfg,
|
|
4654
|
+
maskReq,
|
|
4655
|
+
endpointTraceCtx,
|
|
4656
|
+
masking,
|
|
4657
|
+
activePrivacy,
|
|
4658
|
+
);
|
|
4659
|
+
const responseBodyMaterialization = capturedBody === undefined
|
|
4660
|
+
? { value: undefined }
|
|
4661
|
+
: limitRawInlinePrivacyValue('response.body', capturedBody)
|
|
4662
|
+
?? await materializeInlinePrivacyValueAsync(
|
|
4663
|
+
'response.body',
|
|
4664
|
+
sanitizeRequestSnapshot(capturedBody),
|
|
4665
|
+
cfg,
|
|
4666
|
+
maskReq,
|
|
4667
|
+
endpointTraceCtx,
|
|
4668
|
+
masking,
|
|
4669
|
+
activePrivacy,
|
|
4670
|
+
);
|
|
4671
|
+
const responseBody = responseBodyMaterialization.value;
|
|
4672
|
+
const requestValueEntries: TraceValueBatchEntry[] = [];
|
|
4673
|
+
const bodyValueCapture = requestBodyMaterialization.skipped
|
|
4674
|
+
? undefined
|
|
4675
|
+
: await maybeCaptureRequestValueAsync({
|
|
4676
|
+
target: 'request.body',
|
|
4677
|
+
rawValue: (req as any).body,
|
|
4678
|
+
previewValue: requestBody,
|
|
4679
|
+
capture: {
|
|
4680
|
+
runtimeConfig: cfg,
|
|
4681
|
+
captureHeaders: cfg.captureHeaders,
|
|
4682
|
+
maskReq,
|
|
4683
|
+
trace: endpointTraceCtx,
|
|
4684
|
+
masking,
|
|
4685
|
+
privacy: activePrivacy,
|
|
4686
|
+
},
|
|
4687
|
+
}, requestValueEntries);
|
|
4688
|
+
const paramsValueCapture = await maybeCaptureRequestValueAsync({
|
|
4689
|
+
target: 'request.params',
|
|
4690
|
+
rawValue: (req as any).params,
|
|
4691
|
+
previewValue: requestParams,
|
|
4692
|
+
capture: {
|
|
4693
|
+
runtimeConfig: cfg,
|
|
4694
|
+
captureHeaders: cfg.captureHeaders,
|
|
4695
|
+
maskReq,
|
|
4696
|
+
trace: endpointTraceCtx,
|
|
4697
|
+
masking,
|
|
4698
|
+
privacy: activePrivacy,
|
|
4699
|
+
},
|
|
4700
|
+
}, requestValueEntries);
|
|
4701
|
+
const queryValueCapture = await maybeCaptureRequestValueAsync({
|
|
4702
|
+
target: 'request.query',
|
|
4703
|
+
rawValue: (req as any).query,
|
|
4704
|
+
previewValue: requestQuery,
|
|
4705
|
+
capture: {
|
|
4706
|
+
runtimeConfig: cfg,
|
|
4707
|
+
captureHeaders: cfg.captureHeaders,
|
|
4708
|
+
maskReq,
|
|
4709
|
+
trace: endpointTraceCtx,
|
|
4710
|
+
masking,
|
|
4711
|
+
privacy: activePrivacy,
|
|
4712
|
+
},
|
|
4713
|
+
}, requestValueEntries);
|
|
4714
|
+
const headersValueCapture = await maybeCaptureRequestValueAsync({
|
|
4715
|
+
target: 'request.headers',
|
|
4716
|
+
rawValue: req.headers,
|
|
4717
|
+
previewValue: maskedHeaders,
|
|
4718
|
+
capture: {
|
|
4719
|
+
runtimeConfig: cfg,
|
|
4720
|
+
captureHeaders: cfg.captureHeaders,
|
|
4721
|
+
maskReq,
|
|
4722
|
+
trace: endpointTraceCtx,
|
|
4723
|
+
masking,
|
|
4724
|
+
privacy: activePrivacy,
|
|
4725
|
+
},
|
|
4726
|
+
}, requestValueEntries);
|
|
4727
|
+
const respBodyValueCapture = responseBodyMaterialization.skipped
|
|
4728
|
+
? undefined
|
|
4729
|
+
: await maybeCaptureRequestValueAsync({
|
|
4730
|
+
target: 'response.body',
|
|
4731
|
+
rawValue: capturedBody,
|
|
4732
|
+
previewValue: responseBody,
|
|
4733
|
+
capture: {
|
|
4734
|
+
runtimeConfig: cfg,
|
|
4735
|
+
captureHeaders: cfg.captureHeaders,
|
|
4736
|
+
maskReq,
|
|
4737
|
+
trace: endpointTraceCtx,
|
|
4738
|
+
masking,
|
|
4739
|
+
privacy: activePrivacy,
|
|
4740
|
+
},
|
|
4741
|
+
}, requestValueEntries);
|
|
4742
|
+
|
|
4743
|
+
const requestPayload: Record<string, any> = {
|
|
4744
|
+
rid,
|
|
4745
|
+
method: req.method,
|
|
4746
|
+
url,
|
|
4747
|
+
path,
|
|
4748
|
+
status: res.statusCode,
|
|
4749
|
+
durMs: Date.now() - t0,
|
|
4750
|
+
headers: maskedHeaders,
|
|
4751
|
+
key,
|
|
4752
|
+
respBody: responseBody,
|
|
4753
|
+
trace: hasTraceEvents ? undefined : [],
|
|
4754
|
+
};
|
|
4755
|
+
if (requestBody !== undefined) requestPayload.body = requestBody;
|
|
4756
|
+
if (bodyValueCapture) requestPayload.bodyValueCapture = bodyValueCapture;
|
|
4757
|
+
if (requestParams !== undefined) requestPayload.params = requestParams;
|
|
4758
|
+
if (paramsValueCapture) requestPayload.paramsValueCapture = paramsValueCapture;
|
|
4759
|
+
if (requestQuery !== undefined) requestPayload.query = requestQuery;
|
|
4760
|
+
if (queryValueCapture) requestPayload.queryValueCapture = queryValueCapture;
|
|
4761
|
+
if (headersValueCapture) requestPayload.headersValueCapture = headersValueCapture;
|
|
4762
|
+
if (respBodyValueCapture) requestPayload.respBodyValueCapture = respBodyValueCapture;
|
|
4763
|
+
if (requestBodyMaterialization.skipped) {
|
|
4764
|
+
requestPayload.bodyMaterialization = requestBodyMaterialization.skipped;
|
|
4765
|
+
}
|
|
4766
|
+
if (responseBodyMaterialization.skipped) {
|
|
4767
|
+
requestPayload.respBodyMaterialization = responseBodyMaterialization.skipped;
|
|
4768
|
+
}
|
|
4769
|
+
requestPayload.entryPoint = chosenEndpoint;
|
|
4770
|
+
|
|
4771
|
+
return { requestPayload, requestValueEntries };
|
|
4772
|
+
};
|
|
4773
|
+
|
|
4774
|
+
const emitRequestCaptureAsync = async (): Promise<void> => {
|
|
4775
|
+
if (requestCaptureScheduled) return;
|
|
4776
|
+
requestCaptureScheduled = true;
|
|
4777
|
+
try {
|
|
4778
|
+
const { chosenEndpoint, hasTraceEvents } = chooseRequestEndpoint();
|
|
4779
|
+
const { requestPayload, requestValueEntries } = await buildRequestCapturePayloadAsync(
|
|
4780
|
+
chosenEndpoint,
|
|
4781
|
+
hasTraceEvents,
|
|
4782
|
+
);
|
|
4783
|
+
post(cfg, sid, {
|
|
4784
|
+
entries: [{
|
|
4785
|
+
actionId: aid,
|
|
4786
|
+
request: requestPayload,
|
|
4787
|
+
requestValues: requestValueEntries.length ? requestValueEntries : undefined,
|
|
4788
|
+
t: requestEpochMs,
|
|
4789
|
+
}]
|
|
4790
|
+
});
|
|
4791
|
+
} catch {
|
|
4792
|
+
// never break user code
|
|
4793
|
+
}
|
|
4794
|
+
};
|
|
4795
|
+
|
|
4659
4796
|
try {
|
|
4660
4797
|
if (__TRACER__?.tracer?.on) {
|
|
4661
4798
|
const getTid = __TRACER__?.getCurrentTraceId;
|
|
@@ -4710,7 +4847,8 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4710
4847
|
scheduleIdleFlush();
|
|
4711
4848
|
}
|
|
4712
4849
|
|
|
4713
|
-
|
|
4850
|
+
const hasErrorPayload = hasMeaningfulRawTraceError(ev.error);
|
|
4851
|
+
if (ev.args !== undefined || ev.returnValue !== undefined || hasErrorPayload) {
|
|
4714
4852
|
evt.__reproPending = {
|
|
4715
4853
|
...(ev.args !== undefined
|
|
4716
4854
|
? {
|
|
@@ -4722,7 +4860,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4722
4860
|
returnValueRaw: ev.returnValue,
|
|
4723
4861
|
}
|
|
4724
4862
|
: {}),
|
|
4725
|
-
...(
|
|
4863
|
+
...(hasErrorPayload
|
|
4726
4864
|
? {
|
|
4727
4865
|
errorRaw: ev.error,
|
|
4728
4866
|
}
|
|
@@ -4750,6 +4888,8 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4750
4888
|
capturedBody = coerceBodyToStorable(buf, res.getHeader?.('content-type'));
|
|
4751
4889
|
}
|
|
4752
4890
|
|
|
4891
|
+
void emitRequestCaptureAsync();
|
|
4892
|
+
|
|
4753
4893
|
if (!flushPayload) {
|
|
4754
4894
|
flushPayload = async () => {
|
|
4755
4895
|
try {
|
|
@@ -4764,184 +4904,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4764
4904
|
const orderedEvents = TRACE_ORDER_MODE === 'tree'
|
|
4765
4905
|
? reorderTraceEvents(baseEvents)
|
|
4766
4906
|
: sortTraceEventsChronologically(baseEvents);
|
|
4767
|
-
const summary = summarizeEndpointFromEvents(orderedEvents);
|
|
4768
|
-
const chosenEndpoint = summary.endpointTrace
|
|
4769
|
-
?? summary.preferredAppTrace
|
|
4770
|
-
?? summary.firstAppTrace
|
|
4771
|
-
?? endpointTrace
|
|
4772
|
-
?? preferredAppTrace
|
|
4773
|
-
?? firstAppTrace
|
|
4774
|
-
?? { fn: null, file: null, line: null, functionType: null };
|
|
4775
4907
|
const traceBatches = chunkArray(orderedEvents, TRACE_BATCH_SIZE);
|
|
4776
|
-
const endpointTraceCtx: TraceEventForFilter | null = (() => {
|
|
4777
|
-
if (!chosenEndpoint?.fn && !chosenEndpoint?.file) return null;
|
|
4778
|
-
return {
|
|
4779
|
-
type: 'enter',
|
|
4780
|
-
eventType: 'enter',
|
|
4781
|
-
fn: chosenEndpoint.fn ?? undefined,
|
|
4782
|
-
wrapperClass: inferWrapperClassFromFn(chosenEndpoint.fn),
|
|
4783
|
-
file: chosenEndpoint.file ?? null,
|
|
4784
|
-
line: chosenEndpoint.line ?? null,
|
|
4785
|
-
functionType: chosenEndpoint.functionType ?? null,
|
|
4786
|
-
library: inferLibraryNameFromFile(chosenEndpoint.file),
|
|
4787
|
-
};
|
|
4788
|
-
})();
|
|
4789
|
-
const activePrivacy = resolvePrivacy();
|
|
4790
|
-
|
|
4791
|
-
const requestBodyMaterialization = await materializeInlinePrivacyValueAsync(
|
|
4792
|
-
'request.body',
|
|
4793
|
-
sanitizeRequestSnapshot((req as any).body),
|
|
4794
|
-
cfg,
|
|
4795
|
-
maskReq,
|
|
4796
|
-
endpointTraceCtx,
|
|
4797
|
-
masking,
|
|
4798
|
-
activePrivacy,
|
|
4799
|
-
);
|
|
4800
|
-
const requestBody = requestBodyMaterialization.value;
|
|
4801
|
-
const requestParams = await applyPrivacyThenMaskAsync(
|
|
4802
|
-
'request.params',
|
|
4803
|
-
sanitizeRequestSnapshot((req as any).params),
|
|
4804
|
-
cfg,
|
|
4805
|
-
maskReq,
|
|
4806
|
-
endpointTraceCtx,
|
|
4807
|
-
masking,
|
|
4808
|
-
activePrivacy,
|
|
4809
|
-
);
|
|
4810
|
-
const requestQuery = await applyPrivacyThenMaskAsync(
|
|
4811
|
-
'request.query',
|
|
4812
|
-
sanitizeRequestSnapshot((req as any).query),
|
|
4813
|
-
cfg,
|
|
4814
|
-
maskReq,
|
|
4815
|
-
endpointTraceCtx,
|
|
4816
|
-
masking,
|
|
4817
|
-
activePrivacy,
|
|
4818
|
-
);
|
|
4819
|
-
const maskedHeaders = await applyPrivacyThenMaskAsync(
|
|
4820
|
-
'request.headers',
|
|
4821
|
-
requestHeaders,
|
|
4822
|
-
cfg,
|
|
4823
|
-
maskReq,
|
|
4824
|
-
endpointTraceCtx,
|
|
4825
|
-
masking,
|
|
4826
|
-
activePrivacy,
|
|
4827
|
-
);
|
|
4828
|
-
const responseBodyMaterialization = await materializeInlinePrivacyValueAsync(
|
|
4829
|
-
'response.body',
|
|
4830
|
-
capturedBody === undefined ? undefined : sanitizeRequestSnapshot(capturedBody),
|
|
4831
|
-
cfg,
|
|
4832
|
-
maskReq,
|
|
4833
|
-
endpointTraceCtx,
|
|
4834
|
-
masking,
|
|
4835
|
-
activePrivacy,
|
|
4836
|
-
);
|
|
4837
|
-
const responseBody = responseBodyMaterialization.value;
|
|
4838
|
-
const requestValueEntries: TraceValueBatchEntry[] = [];
|
|
4839
|
-
const bodyValueCapture = requestBodyMaterialization.skipped
|
|
4840
|
-
? undefined
|
|
4841
|
-
: await maybeCaptureRequestValueAsync({
|
|
4842
|
-
target: 'request.body',
|
|
4843
|
-
rawValue: (req as any).body,
|
|
4844
|
-
previewValue: requestBody,
|
|
4845
|
-
capture: {
|
|
4846
|
-
runtimeConfig: cfg,
|
|
4847
|
-
captureHeaders: cfg.captureHeaders,
|
|
4848
|
-
maskReq,
|
|
4849
|
-
trace: endpointTraceCtx,
|
|
4850
|
-
masking,
|
|
4851
|
-
privacy: activePrivacy,
|
|
4852
|
-
},
|
|
4853
|
-
}, requestValueEntries);
|
|
4854
|
-
const paramsValueCapture = await maybeCaptureRequestValueAsync({
|
|
4855
|
-
target: 'request.params',
|
|
4856
|
-
rawValue: (req as any).params,
|
|
4857
|
-
previewValue: requestParams,
|
|
4858
|
-
capture: {
|
|
4859
|
-
runtimeConfig: cfg,
|
|
4860
|
-
captureHeaders: cfg.captureHeaders,
|
|
4861
|
-
maskReq,
|
|
4862
|
-
trace: endpointTraceCtx,
|
|
4863
|
-
masking,
|
|
4864
|
-
privacy: activePrivacy,
|
|
4865
|
-
},
|
|
4866
|
-
}, requestValueEntries);
|
|
4867
|
-
const queryValueCapture = await maybeCaptureRequestValueAsync({
|
|
4868
|
-
target: 'request.query',
|
|
4869
|
-
rawValue: (req as any).query,
|
|
4870
|
-
previewValue: requestQuery,
|
|
4871
|
-
capture: {
|
|
4872
|
-
runtimeConfig: cfg,
|
|
4873
|
-
captureHeaders: cfg.captureHeaders,
|
|
4874
|
-
maskReq,
|
|
4875
|
-
trace: endpointTraceCtx,
|
|
4876
|
-
masking,
|
|
4877
|
-
privacy: activePrivacy,
|
|
4878
|
-
},
|
|
4879
|
-
}, requestValueEntries);
|
|
4880
|
-
const headersValueCapture = await maybeCaptureRequestValueAsync({
|
|
4881
|
-
target: 'request.headers',
|
|
4882
|
-
rawValue: req.headers,
|
|
4883
|
-
previewValue: maskedHeaders,
|
|
4884
|
-
capture: {
|
|
4885
|
-
runtimeConfig: cfg,
|
|
4886
|
-
captureHeaders: cfg.captureHeaders,
|
|
4887
|
-
maskReq,
|
|
4888
|
-
trace: endpointTraceCtx,
|
|
4889
|
-
masking,
|
|
4890
|
-
privacy: activePrivacy,
|
|
4891
|
-
},
|
|
4892
|
-
}, requestValueEntries);
|
|
4893
|
-
const respBodyValueCapture = responseBodyMaterialization.skipped
|
|
4894
|
-
? undefined
|
|
4895
|
-
: await maybeCaptureRequestValueAsync({
|
|
4896
|
-
target: 'response.body',
|
|
4897
|
-
rawValue: capturedBody,
|
|
4898
|
-
previewValue: responseBody,
|
|
4899
|
-
capture: {
|
|
4900
|
-
runtimeConfig: cfg,
|
|
4901
|
-
captureHeaders: cfg.captureHeaders,
|
|
4902
|
-
maskReq,
|
|
4903
|
-
trace: endpointTraceCtx,
|
|
4904
|
-
masking,
|
|
4905
|
-
privacy: activePrivacy,
|
|
4906
|
-
},
|
|
4907
|
-
}, requestValueEntries);
|
|
4908
|
-
|
|
4909
|
-
const requestPayload: Record<string, any> = {
|
|
4910
|
-
rid,
|
|
4911
|
-
method: req.method,
|
|
4912
|
-
url,
|
|
4913
|
-
path,
|
|
4914
|
-
status: res.statusCode,
|
|
4915
|
-
durMs: Date.now() - t0,
|
|
4916
|
-
headers: maskedHeaders,
|
|
4917
|
-
key,
|
|
4918
|
-
respBody: responseBody,
|
|
4919
|
-
trace: traceBatches.length ? undefined : [],
|
|
4920
|
-
};
|
|
4921
|
-
if (requestBody !== undefined) requestPayload.body = requestBody;
|
|
4922
|
-
if (bodyValueCapture) requestPayload.bodyValueCapture = bodyValueCapture;
|
|
4923
|
-
if (requestParams !== undefined) requestPayload.params = requestParams;
|
|
4924
|
-
if (paramsValueCapture) requestPayload.paramsValueCapture = paramsValueCapture;
|
|
4925
|
-
if (requestQuery !== undefined) requestPayload.query = requestQuery;
|
|
4926
|
-
if (queryValueCapture) requestPayload.queryValueCapture = queryValueCapture;
|
|
4927
|
-
if (headersValueCapture) requestPayload.headersValueCapture = headersValueCapture;
|
|
4928
|
-
if (respBodyValueCapture) requestPayload.respBodyValueCapture = respBodyValueCapture;
|
|
4929
|
-
if (requestBodyMaterialization.skipped) {
|
|
4930
|
-
requestPayload.bodyMaterialization = requestBodyMaterialization.skipped;
|
|
4931
|
-
}
|
|
4932
|
-
if (responseBodyMaterialization.skipped) {
|
|
4933
|
-
requestPayload.respBodyMaterialization = responseBodyMaterialization.skipped;
|
|
4934
|
-
}
|
|
4935
|
-
requestPayload.entryPoint = chosenEndpoint;
|
|
4936
|
-
|
|
4937
|
-
post(cfg, sid, {
|
|
4938
|
-
entries: [{
|
|
4939
|
-
actionId: aid,
|
|
4940
|
-
request: requestPayload,
|
|
4941
|
-
requestValues: requestValueEntries.length ? requestValueEntries : undefined,
|
|
4942
|
-
t: requestEpochMs,
|
|
4943
|
-
}]
|
|
4944
|
-
});
|
|
4945
4908
|
|
|
4946
4909
|
if (traceBatches.length) {
|
|
4947
4910
|
for (let i = 0; i < traceBatches.length; i++) {
|
|
@@ -5390,7 +5353,11 @@ function sanitizeResultForMeta(value: any, options: TraceSanitizeOptions = {}) {
|
|
|
5390
5353
|
if (value === undefined) return undefined;
|
|
5391
5354
|
if (typeof value === 'function') return undefined;
|
|
5392
5355
|
try {
|
|
5393
|
-
return sanitizeTraceValue(value, 0, new WeakMap(),
|
|
5356
|
+
return sanitizeTraceValue(value, 0, new WeakMap(), {
|
|
5357
|
+
preserveLongStrings: true,
|
|
5358
|
+
disableTruncation: true,
|
|
5359
|
+
...options,
|
|
5360
|
+
});
|
|
5394
5361
|
} catch {
|
|
5395
5362
|
const fallback = safeJson(value);
|
|
5396
5363
|
return fallback === undefined ? undefined : fallback;
|
|
@@ -5471,7 +5438,7 @@ async function emitDbQueryAsync(cfg: any, sid?: string, aid?: string, payload?:
|
|
|
5471
5438
|
const afterPreview = afterMaterialization.value;
|
|
5472
5439
|
const resultMetaMaterialization = await materializeInlinePrivacyValueAsync('db.resultMeta', resultMetaSource ?? undefined, cfg, maskReq, null, null, privacy, dbContext);
|
|
5473
5440
|
const resultMetaPreviewRaw = resultMetaMaterialization.value;
|
|
5474
|
-
const resultMetaPreview =
|
|
5441
|
+
const resultMetaPreview = sanitizeMaterializedTraceValue(resultMetaPreviewRaw);
|
|
5475
5442
|
const errorMaterialization = await materializeInlinePrivacyValueAsync('db.error', payload.error ?? undefined, cfg, maskReq, null, null, privacy, dbContext);
|
|
5476
5443
|
const errorPreview = errorMaterialization.value;
|
|
5477
5444
|
const capture: FullValueCaptureContext = {
|
|
@@ -5894,27 +5861,111 @@ function normalizeKafkaHeadersForPayload(headers: any): Record<string, string> {
|
|
|
5894
5861
|
return out;
|
|
5895
5862
|
}
|
|
5896
5863
|
|
|
5897
|
-
function previewKafkaValue(value: any, maxLength: number = 120): string | null {
|
|
5898
|
-
if (value === null || value === undefined) return null;
|
|
5899
|
-
let text: string;
|
|
5900
|
-
if (typeof value === 'string') {
|
|
5901
|
-
text = value;
|
|
5902
|
-
} else if (Buffer.isBuffer(value)) {
|
|
5903
|
-
text = value.toString('utf8');
|
|
5904
|
-
} else {
|
|
5905
|
-
return null;
|
|
5906
|
-
}
|
|
5907
|
-
if (text.length <= maxLength) return text;
|
|
5908
|
-
return `${text.slice(0, Math.max(0, maxLength - 3))}...`;
|
|
5909
|
-
}
|
|
5910
|
-
|
|
5911
5864
|
function kafkaValueSize(value: any): number | null {
|
|
5912
5865
|
if (value === null || value === undefined) return null;
|
|
5913
5866
|
if (typeof value === 'string') return Buffer.byteLength(value, 'utf8');
|
|
5914
5867
|
if (Buffer.isBuffer(value)) return value.length;
|
|
5868
|
+
if (value instanceof Uint8Array) return value.byteLength;
|
|
5915
5869
|
return null;
|
|
5916
5870
|
}
|
|
5917
5871
|
|
|
5872
|
+
function decodeKafkaValue(value: any): any {
|
|
5873
|
+
if (value === null || value === undefined) return null;
|
|
5874
|
+
if (typeof value === 'string') return value;
|
|
5875
|
+
if (Buffer.isBuffer(value)) return value.toString('utf8');
|
|
5876
|
+
if (value instanceof Uint8Array) return Buffer.from(value).toString('utf8');
|
|
5877
|
+
if (typeof value === 'number' || typeof value === 'boolean') return value;
|
|
5878
|
+
return sanitizeResultForMeta(value);
|
|
5879
|
+
}
|
|
5880
|
+
|
|
5881
|
+
function parseKafkaJsonString(value: string): { parsed: true; value: any } | { parsed: false; value: string } {
|
|
5882
|
+
const trimmed = value.trim();
|
|
5883
|
+
if (!trimmed || !'{['.includes(trimmed[0]!)) {
|
|
5884
|
+
return { parsed: false, value };
|
|
5885
|
+
}
|
|
5886
|
+
try {
|
|
5887
|
+
return { parsed: true, value: JSON.parse(value) };
|
|
5888
|
+
} catch {
|
|
5889
|
+
return { parsed: false, value };
|
|
5890
|
+
}
|
|
5891
|
+
}
|
|
5892
|
+
|
|
5893
|
+
function decodeKafkaMessageValue(value: any): {
|
|
5894
|
+
value: any;
|
|
5895
|
+
rawValue?: string;
|
|
5896
|
+
valueEncoding: string | null;
|
|
5897
|
+
} {
|
|
5898
|
+
const decoded = decodeKafkaValue(value);
|
|
5899
|
+
if (typeof decoded !== 'string') {
|
|
5900
|
+
return {
|
|
5901
|
+
value: decoded,
|
|
5902
|
+
valueEncoding: decoded === null ? null : typeof decoded,
|
|
5903
|
+
};
|
|
5904
|
+
}
|
|
5905
|
+
const parsed = parseKafkaJsonString(decoded);
|
|
5906
|
+
if (parsed.parsed) {
|
|
5907
|
+
return {
|
|
5908
|
+
value: parsed.value,
|
|
5909
|
+
rawValue: decoded,
|
|
5910
|
+
valueEncoding: 'json',
|
|
5911
|
+
};
|
|
5912
|
+
}
|
|
5913
|
+
return {
|
|
5914
|
+
value: decoded,
|
|
5915
|
+
valueEncoding: 'utf8',
|
|
5916
|
+
};
|
|
5917
|
+
}
|
|
5918
|
+
|
|
5919
|
+
function buildKafkaMessagePayload(message: any): Record<string, any> {
|
|
5920
|
+
const msgObj = message && typeof message === 'object' ? message : { value: message };
|
|
5921
|
+
const decoded = decodeKafkaMessageValue(msgObj.value);
|
|
5922
|
+
const payload: Record<string, any> = {
|
|
5923
|
+
key: decodeKafkaValue(msgObj.key),
|
|
5924
|
+
value: decoded.value,
|
|
5925
|
+
valueEncoding: decoded.valueEncoding,
|
|
5926
|
+
valueBytes: kafkaValueSize(msgObj.value),
|
|
5927
|
+
headers: normalizeKafkaHeadersForPayload(msgObj.headers),
|
|
5928
|
+
};
|
|
5929
|
+
if (decoded.rawValue !== undefined) {
|
|
5930
|
+
payload.rawValue = decoded.rawValue;
|
|
5931
|
+
}
|
|
5932
|
+
if (msgObj.partition !== undefined) {
|
|
5933
|
+
payload.partition = msgObj.partition;
|
|
5934
|
+
}
|
|
5935
|
+
if (msgObj.timestamp !== undefined) {
|
|
5936
|
+
payload.timestamp = msgObj.timestamp;
|
|
5937
|
+
}
|
|
5938
|
+
if (msgObj.offset !== undefined) {
|
|
5939
|
+
payload.offset = msgObj.offset;
|
|
5940
|
+
}
|
|
5941
|
+
return payload;
|
|
5942
|
+
}
|
|
5943
|
+
|
|
5944
|
+
function cloneKafkaTelemetryValue(value: any): any {
|
|
5945
|
+
if (value === undefined || value === null) return value;
|
|
5946
|
+
try {
|
|
5947
|
+
return JSON.parse(JSON.stringify(value));
|
|
5948
|
+
} catch {
|
|
5949
|
+
return value;
|
|
5950
|
+
}
|
|
5951
|
+
}
|
|
5952
|
+
|
|
5953
|
+
async function materializeKafkaRequestBody(
|
|
5954
|
+
cfg: KafkaJsPatchConfig,
|
|
5955
|
+
maskReq: MaskRequestContext,
|
|
5956
|
+
body: Record<string, any>,
|
|
5957
|
+
): Promise<InlinePrivacyMaterializationResult> {
|
|
5958
|
+
return materializeInlinePrivacyValueAsync(
|
|
5959
|
+
'request.body',
|
|
5960
|
+
sanitizeTraceValueForPrivacy(body),
|
|
5961
|
+
cfg,
|
|
5962
|
+
maskReq,
|
|
5963
|
+
null,
|
|
5964
|
+
normalizeMaskingConfig(cfg.masking),
|
|
5965
|
+
getRuntimePrivacyState(cfg).policy ?? null,
|
|
5966
|
+
);
|
|
5967
|
+
}
|
|
5968
|
+
|
|
5918
5969
|
function summarizeKafkaError(error: any): Record<string, unknown> {
|
|
5919
5970
|
const message =
|
|
5920
5971
|
typeof error?.message === 'string'
|
|
@@ -6005,6 +6056,7 @@ function recordKafkaTraceEvent(
|
|
|
6005
6056
|
library: inferLibraryNameFromFile(sourceFileForLibrary ?? raw.file),
|
|
6006
6057
|
};
|
|
6007
6058
|
if (shouldDropTraceEvent(candidate)) return;
|
|
6059
|
+
const omitJsonBuiltinValues = shouldOmitJsonBuiltinTraceValues(candidate);
|
|
6008
6060
|
|
|
6009
6061
|
const evt: TraceEventRecord = {
|
|
6010
6062
|
t: alignTimestamp(typeof raw.t === 'number' ? raw.t : Date.now()),
|
|
@@ -6020,8 +6072,8 @@ function recordKafkaTraceEvent(
|
|
|
6020
6072
|
|
|
6021
6073
|
const privacy = getRuntimePrivacyState(cfg).policy ?? null;
|
|
6022
6074
|
const masking = normalizeMaskingConfig(cfg.masking);
|
|
6023
|
-
if (raw.args !== undefined) {
|
|
6024
|
-
evt.args =
|
|
6075
|
+
if (!omitJsonBuiltinValues && raw.args !== undefined) {
|
|
6076
|
+
evt.args = sanitizeMaterializedTraceValue(materializeTracePrivacyValue(
|
|
6025
6077
|
'trace.args',
|
|
6026
6078
|
sanitizeTraceArgsForPrivacy(raw.args),
|
|
6027
6079
|
cfg,
|
|
@@ -6031,8 +6083,8 @@ function recordKafkaTraceEvent(
|
|
|
6031
6083
|
privacy,
|
|
6032
6084
|
));
|
|
6033
6085
|
}
|
|
6034
|
-
if (raw.returnValue !== undefined) {
|
|
6035
|
-
evt.returnValue =
|
|
6086
|
+
if (!omitJsonBuiltinValues && raw.returnValue !== undefined) {
|
|
6087
|
+
evt.returnValue = sanitizeMaterializedTraceValue(materializeTracePrivacyValue(
|
|
6036
6088
|
'trace.returnValue',
|
|
6037
6089
|
sanitizeTraceValueForPrivacy(raw.returnValue),
|
|
6038
6090
|
cfg,
|
|
@@ -6042,8 +6094,8 @@ function recordKafkaTraceEvent(
|
|
|
6042
6094
|
privacy,
|
|
6043
6095
|
));
|
|
6044
6096
|
}
|
|
6045
|
-
if (raw.error
|
|
6046
|
-
evt.error =
|
|
6097
|
+
if (hasMeaningfulRawTraceError(raw.error)) {
|
|
6098
|
+
evt.error = sanitizeMaterializedTraceValue(materializeTracePrivacyValue(
|
|
6047
6099
|
'trace.error',
|
|
6048
6100
|
sanitizeTraceValueForPrivacy(raw.error),
|
|
6049
6101
|
cfg,
|
|
@@ -6079,6 +6131,7 @@ function buildPendingKafkaTraceEvent(raw: any): PendingTraceEventRecord | null {
|
|
|
6079
6131
|
library: inferLibraryNameFromFile(sourceFileForLibrary ?? raw.file),
|
|
6080
6132
|
};
|
|
6081
6133
|
if (shouldDropTraceEvent(candidate)) return null;
|
|
6134
|
+
const omitJsonBuiltinValues = shouldOmitJsonBuiltinTraceValues(candidate);
|
|
6082
6135
|
|
|
6083
6136
|
const evt: PendingTraceEventRecord = {
|
|
6084
6137
|
t: alignTimestamp(typeof raw.t === 'number' ? raw.t : Date.now()),
|
|
@@ -6094,19 +6147,20 @@ function buildPendingKafkaTraceEvent(raw: any): PendingTraceEventRecord | null {
|
|
|
6094
6147
|
if (typeof raw.sourceFile === 'string' && raw.sourceFile) {
|
|
6095
6148
|
evt.__reproSourceFile = String(raw.sourceFile);
|
|
6096
6149
|
}
|
|
6097
|
-
|
|
6150
|
+
const hasErrorPayload = hasMeaningfulRawTraceError(raw.error);
|
|
6151
|
+
if ((!omitJsonBuiltinValues && (raw.args !== undefined || raw.returnValue !== undefined)) || hasErrorPayload) {
|
|
6098
6152
|
evt.__reproPending = {
|
|
6099
|
-
...(raw.args !== undefined
|
|
6153
|
+
...(!omitJsonBuiltinValues && raw.args !== undefined
|
|
6100
6154
|
? {
|
|
6101
6155
|
argsRaw: normalizeRawTraceArgs(raw.args),
|
|
6102
6156
|
}
|
|
6103
6157
|
: {}),
|
|
6104
|
-
...(raw.returnValue !== undefined
|
|
6158
|
+
...(!omitJsonBuiltinValues && raw.returnValue !== undefined
|
|
6105
6159
|
? {
|
|
6106
6160
|
returnValueRaw: raw.returnValue,
|
|
6107
6161
|
}
|
|
6108
6162
|
: {}),
|
|
6109
|
-
...(
|
|
6163
|
+
...(hasErrorPayload
|
|
6110
6164
|
? {
|
|
6111
6165
|
errorRaw: raw.error,
|
|
6112
6166
|
}
|
|
@@ -6251,14 +6305,26 @@ function patchKafkaProducerInstance(producer: any, cfg: KafkaJsPatchConfig) {
|
|
|
6251
6305
|
throw err;
|
|
6252
6306
|
} finally {
|
|
6253
6307
|
if (!sid) return;
|
|
6254
|
-
const previewKeys = propagatedMessages
|
|
6255
|
-
.slice(0, 10)
|
|
6256
|
-
.map((message: any) => previewKafkaValue(message?.key))
|
|
6257
|
-
.filter((value: string | null): value is string => value !== null);
|
|
6258
6308
|
const totalValueBytes = propagatedMessages.reduce((sum: number, message: any) => {
|
|
6259
6309
|
const size = kafkaValueSize(message?.value);
|
|
6260
6310
|
return sum + (size ?? 0);
|
|
6261
6311
|
}, 0);
|
|
6312
|
+
const requestKey = `KAFKA_PUBLISH ${topic}`;
|
|
6313
|
+
const maskReq: MaskRequestContext = {
|
|
6314
|
+
method: 'KAFKA_PUBLISH',
|
|
6315
|
+
path: `kafka://${topic}`,
|
|
6316
|
+
key: requestKey,
|
|
6317
|
+
};
|
|
6318
|
+
const bodyMaterialization = await materializeKafkaRequestBody(cfg, maskReq, {
|
|
6319
|
+
transport: 'kafka',
|
|
6320
|
+
topic,
|
|
6321
|
+
messageCount: propagatedMessages.length,
|
|
6322
|
+
totalValueBytes,
|
|
6323
|
+
messages: propagatedMessages.map((message: any) => buildKafkaMessagePayload(message)),
|
|
6324
|
+
traceId: span?.traceId ?? null,
|
|
6325
|
+
spanId: span?.spanId ?? null,
|
|
6326
|
+
parentSpanId: span?.parentSpanId ?? null,
|
|
6327
|
+
});
|
|
6262
6328
|
|
|
6263
6329
|
const requestPayload: Record<string, any> = {
|
|
6264
6330
|
rid: publishRid,
|
|
@@ -6267,22 +6333,14 @@ function patchKafkaProducerInstance(producer: any, cfg: KafkaJsPatchConfig) {
|
|
|
6267
6333
|
path: `kafka://${topic}`,
|
|
6268
6334
|
status: threw ? 500 : 200,
|
|
6269
6335
|
durMs: Date.now() - t0,
|
|
6270
|
-
key:
|
|
6336
|
+
key: requestKey,
|
|
6271
6337
|
headers: {
|
|
6272
6338
|
transport: 'kafka',
|
|
6273
6339
|
topic,
|
|
6274
6340
|
messageCount: propagatedMessages.length,
|
|
6275
6341
|
},
|
|
6276
|
-
body:
|
|
6277
|
-
|
|
6278
|
-
topic,
|
|
6279
|
-
messageCount: propagatedMessages.length,
|
|
6280
|
-
totalValueBytes,
|
|
6281
|
-
keyPreview: previewKeys,
|
|
6282
|
-
traceId: span?.traceId ?? null,
|
|
6283
|
-
spanId: span?.spanId ?? null,
|
|
6284
|
-
parentSpanId: span?.parentSpanId ?? null,
|
|
6285
|
-
},
|
|
6342
|
+
body: bodyMaterialization.value,
|
|
6343
|
+
bodyMaterialization: bodyMaterialization.skipped,
|
|
6286
6344
|
respBody: threw
|
|
6287
6345
|
? { error: summarizeKafkaError(error) }
|
|
6288
6346
|
: summarizeKafkaProducerResult(result),
|
|
@@ -6373,30 +6431,51 @@ function patchKafkaProducerInstance(producer: any, cfg: KafkaJsPatchConfig) {
|
|
|
6373
6431
|
const count = Array.isArray(entry.messages) ? entry.messages.length : 0;
|
|
6374
6432
|
return sum + count;
|
|
6375
6433
|
}, 0);
|
|
6434
|
+
const totalValueBytes = patchedTopicMessages.reduce((sum: number, entry: any) => {
|
|
6435
|
+
const messages = Array.isArray(entry.messages) ? entry.messages : [];
|
|
6436
|
+
return sum + messages.reduce((messageSum: number, message: any) => {
|
|
6437
|
+
const size = kafkaValueSize(message?.value);
|
|
6438
|
+
return messageSum + (size ?? 0);
|
|
6439
|
+
}, 0);
|
|
6440
|
+
}, 0);
|
|
6441
|
+
const requestKey = uniqueTopics.length === 1
|
|
6442
|
+
? `KAFKA_PUBLISH_BATCH ${uniqueTopics[0]}`
|
|
6443
|
+
: 'KAFKA_PUBLISH_BATCH';
|
|
6444
|
+
const requestPath = uniqueTopics.length === 1 ? `kafka://${uniqueTopics[0]}` : 'kafka://batch';
|
|
6445
|
+
const maskReq: MaskRequestContext = {
|
|
6446
|
+
method: 'KAFKA_PUBLISH_BATCH',
|
|
6447
|
+
path: requestPath,
|
|
6448
|
+
key: requestKey,
|
|
6449
|
+
};
|
|
6450
|
+
const bodyMaterialization = await materializeKafkaRequestBody(cfg, maskReq, {
|
|
6451
|
+
transport: 'kafka',
|
|
6452
|
+
topics: uniqueTopics,
|
|
6453
|
+
topicMessages: patchedTopicMessages.map((entry: any) => ({
|
|
6454
|
+
topic: sanitizeKafkaTopic(entry.topic),
|
|
6455
|
+
messages: (Array.isArray(entry.messages) ? entry.messages : [])
|
|
6456
|
+
.map((message: any) => buildKafkaMessagePayload(message)),
|
|
6457
|
+
})),
|
|
6458
|
+
messageCount,
|
|
6459
|
+
totalValueBytes,
|
|
6460
|
+
traceId: span?.traceId ?? null,
|
|
6461
|
+
spanId: span?.spanId ?? null,
|
|
6462
|
+
parentSpanId: span?.parentSpanId ?? null,
|
|
6463
|
+
});
|
|
6376
6464
|
const requestPayload: Record<string, any> = {
|
|
6377
6465
|
rid: publishRid,
|
|
6378
6466
|
method: 'KAFKA_PUBLISH_BATCH',
|
|
6379
|
-
url:
|
|
6380
|
-
path:
|
|
6467
|
+
url: requestPath,
|
|
6468
|
+
path: requestPath,
|
|
6381
6469
|
status: threw ? 500 : 200,
|
|
6382
6470
|
durMs: Date.now() - t0,
|
|
6383
|
-
key:
|
|
6384
|
-
uniqueTopics.length === 1
|
|
6385
|
-
? `KAFKA_PUBLISH_BATCH ${uniqueTopics[0]}`
|
|
6386
|
-
: 'KAFKA_PUBLISH_BATCH',
|
|
6471
|
+
key: requestKey,
|
|
6387
6472
|
headers: {
|
|
6388
6473
|
transport: 'kafka',
|
|
6389
6474
|
topicCount: uniqueTopics.length,
|
|
6390
6475
|
messageCount,
|
|
6391
6476
|
},
|
|
6392
|
-
body:
|
|
6393
|
-
|
|
6394
|
-
topics: uniqueTopics,
|
|
6395
|
-
messageCount,
|
|
6396
|
-
traceId: span?.traceId ?? null,
|
|
6397
|
-
spanId: span?.spanId ?? null,
|
|
6398
|
-
parentSpanId: span?.parentSpanId ?? null,
|
|
6399
|
-
},
|
|
6477
|
+
body: bodyMaterialization.value,
|
|
6478
|
+
bodyMaterialization: bodyMaterialization.skipped,
|
|
6400
6479
|
respBody: threw
|
|
6401
6480
|
? { error: summarizeKafkaError(error) }
|
|
6402
6481
|
: summarizeKafkaProducerResult(result),
|
|
@@ -6478,6 +6557,24 @@ function wrapKafkaEachMessageHandler(eachMessage: any, cfg: KafkaJsPatchConfig,
|
|
|
6478
6557
|
throw err;
|
|
6479
6558
|
} finally {
|
|
6480
6559
|
if (!sid) return;
|
|
6560
|
+
const messagePayload = buildKafkaMessagePayload(message);
|
|
6561
|
+
const bodyMaterialization = await materializeKafkaRequestBody(cfg, maskReq, {
|
|
6562
|
+
transport: 'kafka',
|
|
6563
|
+
topic,
|
|
6564
|
+
partition,
|
|
6565
|
+
offset: message?.offset ?? null,
|
|
6566
|
+
timestamp: message?.timestamp ?? null,
|
|
6567
|
+
groupId: consumer?.__repro_group_id ?? null,
|
|
6568
|
+
parentRequestRid: runtime.requestRid,
|
|
6569
|
+
parentTraceId: runtime.traceId,
|
|
6570
|
+
parentSpanId: runtime.parentSpanId,
|
|
6571
|
+
messageKey: messagePayload.key,
|
|
6572
|
+
value: cloneKafkaTelemetryValue(messagePayload.value),
|
|
6573
|
+
rawValue: messagePayload.rawValue,
|
|
6574
|
+
valueEncoding: messagePayload.valueEncoding,
|
|
6575
|
+
valueBytes: messagePayload.valueBytes,
|
|
6576
|
+
message: messagePayload,
|
|
6577
|
+
});
|
|
6481
6578
|
const requestPayload: Record<string, any> = {
|
|
6482
6579
|
rid: consumeRid,
|
|
6483
6580
|
method: 'KAFKA_CONSUME',
|
|
@@ -6487,19 +6584,8 @@ function wrapKafkaEachMessageHandler(eachMessage: any, cfg: KafkaJsPatchConfig,
|
|
|
6487
6584
|
durMs: Date.now() - t0,
|
|
6488
6585
|
key: `KAFKA_CONSUME ${topic}`,
|
|
6489
6586
|
headers: normalizeKafkaHeadersForPayload(message?.headers),
|
|
6490
|
-
body:
|
|
6491
|
-
|
|
6492
|
-
topic,
|
|
6493
|
-
partition,
|
|
6494
|
-
offset: message?.offset ?? null,
|
|
6495
|
-
timestamp: message?.timestamp ?? null,
|
|
6496
|
-
groupId: consumer?.__repro_group_id ?? null,
|
|
6497
|
-
parentRequestRid: runtime.requestRid,
|
|
6498
|
-
parentTraceId: runtime.traceId,
|
|
6499
|
-
parentSpanId: runtime.parentSpanId,
|
|
6500
|
-
messageKey: previewKafkaValue(message?.key),
|
|
6501
|
-
valueBytes: kafkaValueSize(message?.value),
|
|
6502
|
-
},
|
|
6587
|
+
body: bodyMaterialization.value,
|
|
6588
|
+
bodyMaterialization: bodyMaterialization.skipped,
|
|
6503
6589
|
respBody: threw ? { error: summarizeKafkaError(error) } : undefined,
|
|
6504
6590
|
};
|
|
6505
6591
|
|
|
@@ -6580,6 +6666,20 @@ function wrapKafkaEachBatchHandler(eachBatch: any, cfg: KafkaJsPatchConfig, cons
|
|
|
6580
6666
|
if (!sid) return;
|
|
6581
6667
|
const firstOffset = messages.length ? messages[0]?.offset ?? null : null;
|
|
6582
6668
|
const lastOffset = messages.length ? messages[messages.length - 1]?.offset ?? null : null;
|
|
6669
|
+
const firstMessagePayload = firstMessage ? buildKafkaMessagePayload(firstMessage) : null;
|
|
6670
|
+
const bodyMaterialization = await materializeKafkaRequestBody(cfg, maskReq, {
|
|
6671
|
+
transport: 'kafka',
|
|
6672
|
+
topic,
|
|
6673
|
+
groupId: consumer?.__repro_group_id ?? null,
|
|
6674
|
+
messageCount: messages.length,
|
|
6675
|
+
firstOffset,
|
|
6676
|
+
lastOffset,
|
|
6677
|
+
parentRequestRid: runtime.requestRid,
|
|
6678
|
+
parentTraceId: runtime.traceId,
|
|
6679
|
+
parentSpanId: runtime.parentSpanId,
|
|
6680
|
+
firstMessageKey: firstMessagePayload?.key ?? null,
|
|
6681
|
+
messages: messages.map((message: any) => buildKafkaMessagePayload(message)),
|
|
6682
|
+
});
|
|
6583
6683
|
const requestPayload: Record<string, any> = {
|
|
6584
6684
|
rid: consumeRid,
|
|
6585
6685
|
method: 'KAFKA_CONSUME_BATCH',
|
|
@@ -6589,18 +6689,8 @@ function wrapKafkaEachBatchHandler(eachBatch: any, cfg: KafkaJsPatchConfig, cons
|
|
|
6589
6689
|
durMs: Date.now() - t0,
|
|
6590
6690
|
key: `KAFKA_CONSUME_BATCH ${topic}`,
|
|
6591
6691
|
headers: firstMessage ? normalizeKafkaHeadersForPayload(firstMessage.headers) : {},
|
|
6592
|
-
body:
|
|
6593
|
-
|
|
6594
|
-
topic,
|
|
6595
|
-
groupId: consumer?.__repro_group_id ?? null,
|
|
6596
|
-
messageCount: messages.length,
|
|
6597
|
-
firstOffset,
|
|
6598
|
-
lastOffset,
|
|
6599
|
-
parentRequestRid: runtime.requestRid,
|
|
6600
|
-
parentTraceId: runtime.traceId,
|
|
6601
|
-
parentSpanId: runtime.parentSpanId,
|
|
6602
|
-
firstMessageKey: firstMessage ? previewKafkaValue(firstMessage.key) : null,
|
|
6603
|
-
},
|
|
6692
|
+
body: bodyMaterialization.value,
|
|
6693
|
+
bodyMaterialization: bodyMaterialization.skipped,
|
|
6604
6694
|
respBody: threw ? { error: summarizeKafkaError(error) } : undefined,
|
|
6605
6695
|
};
|
|
6606
6696
|
|
|
@@ -7476,5 +7566,7 @@ export const __reproTestHooks = {
|
|
|
7476
7566
|
getRuntimePrivacyState(cfg).policy = policy;
|
|
7477
7567
|
},
|
|
7478
7568
|
recordKafkaTraceEventAsyncForTest: recordKafkaTraceEventAsync,
|
|
7569
|
+
materializeInlinePrivacyValueAsyncForTest: materializeInlinePrivacyValueAsync,
|
|
7570
|
+
patchKafkaProducerInstanceForTest: patchKafkaProducerInstance,
|
|
7479
7571
|
wrapKafkaEachMessageHandlerForTest: wrapKafkaEachMessageHandler,
|
|
7480
7572
|
};
|