@reproapp/node-sdk 0.0.4 → 0.0.6
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 +462 -439
- package/package.json +2 -2
- package/src/index.ts +539 -488
- 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/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';
|
|
@@ -4710,7 +4626,8 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4710
4626
|
scheduleIdleFlush();
|
|
4711
4627
|
}
|
|
4712
4628
|
|
|
4713
|
-
|
|
4629
|
+
const hasErrorPayload = hasMeaningfulRawTraceError(ev.error);
|
|
4630
|
+
if (ev.args !== undefined || ev.returnValue !== undefined || hasErrorPayload) {
|
|
4714
4631
|
evt.__reproPending = {
|
|
4715
4632
|
...(ev.args !== undefined
|
|
4716
4633
|
? {
|
|
@@ -4722,7 +4639,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4722
4639
|
returnValueRaw: ev.returnValue,
|
|
4723
4640
|
}
|
|
4724
4641
|
: {}),
|
|
4725
|
-
...(
|
|
4642
|
+
...(hasErrorPayload
|
|
4726
4643
|
? {
|
|
4727
4644
|
errorRaw: ev.error,
|
|
4728
4645
|
}
|
|
@@ -4788,15 +4705,17 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4788
4705
|
})();
|
|
4789
4706
|
const activePrivacy = resolvePrivacy();
|
|
4790
4707
|
|
|
4791
|
-
const
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4708
|
+
const requestBodyRaw = (req as any).body;
|
|
4709
|
+
const requestBodyMaterialization = limitRawInlinePrivacyValue('request.body', requestBodyRaw)
|
|
4710
|
+
?? await materializeInlinePrivacyValueAsync(
|
|
4711
|
+
'request.body',
|
|
4712
|
+
sanitizeRequestSnapshot(requestBodyRaw),
|
|
4713
|
+
cfg,
|
|
4714
|
+
maskReq,
|
|
4715
|
+
endpointTraceCtx,
|
|
4716
|
+
masking,
|
|
4717
|
+
activePrivacy,
|
|
4718
|
+
);
|
|
4800
4719
|
const requestBody = requestBodyMaterialization.value;
|
|
4801
4720
|
const requestParams = await applyPrivacyThenMaskAsync(
|
|
4802
4721
|
'request.params',
|
|
@@ -4825,15 +4744,18 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4825
4744
|
masking,
|
|
4826
4745
|
activePrivacy,
|
|
4827
4746
|
);
|
|
4828
|
-
const responseBodyMaterialization =
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4747
|
+
const responseBodyMaterialization = capturedBody === undefined
|
|
4748
|
+
? { value: undefined }
|
|
4749
|
+
: limitRawInlinePrivacyValue('response.body', capturedBody)
|
|
4750
|
+
?? await materializeInlinePrivacyValueAsync(
|
|
4751
|
+
'response.body',
|
|
4752
|
+
sanitizeRequestSnapshot(capturedBody),
|
|
4753
|
+
cfg,
|
|
4754
|
+
maskReq,
|
|
4755
|
+
endpointTraceCtx,
|
|
4756
|
+
masking,
|
|
4757
|
+
activePrivacy,
|
|
4758
|
+
);
|
|
4837
4759
|
const responseBody = responseBodyMaterialization.value;
|
|
4838
4760
|
const requestValueEntries: TraceValueBatchEntry[] = [];
|
|
4839
4761
|
const bodyValueCapture = requestBodyMaterialization.skipped
|
|
@@ -5390,7 +5312,11 @@ function sanitizeResultForMeta(value: any, options: TraceSanitizeOptions = {}) {
|
|
|
5390
5312
|
if (value === undefined) return undefined;
|
|
5391
5313
|
if (typeof value === 'function') return undefined;
|
|
5392
5314
|
try {
|
|
5393
|
-
return sanitizeTraceValue(value, 0, new WeakMap(),
|
|
5315
|
+
return sanitizeTraceValue(value, 0, new WeakMap(), {
|
|
5316
|
+
preserveLongStrings: true,
|
|
5317
|
+
disableTruncation: true,
|
|
5318
|
+
...options,
|
|
5319
|
+
});
|
|
5394
5320
|
} catch {
|
|
5395
5321
|
const fallback = safeJson(value);
|
|
5396
5322
|
return fallback === undefined ? undefined : fallback;
|
|
@@ -5471,7 +5397,7 @@ async function emitDbQueryAsync(cfg: any, sid?: string, aid?: string, payload?:
|
|
|
5471
5397
|
const afterPreview = afterMaterialization.value;
|
|
5472
5398
|
const resultMetaMaterialization = await materializeInlinePrivacyValueAsync('db.resultMeta', resultMetaSource ?? undefined, cfg, maskReq, null, null, privacy, dbContext);
|
|
5473
5399
|
const resultMetaPreviewRaw = resultMetaMaterialization.value;
|
|
5474
|
-
const resultMetaPreview =
|
|
5400
|
+
const resultMetaPreview = sanitizeMaterializedTraceValue(resultMetaPreviewRaw);
|
|
5475
5401
|
const errorMaterialization = await materializeInlinePrivacyValueAsync('db.error', payload.error ?? undefined, cfg, maskReq, null, null, privacy, dbContext);
|
|
5476
5402
|
const errorPreview = errorMaterialization.value;
|
|
5477
5403
|
const capture: FullValueCaptureContext = {
|
|
@@ -5894,27 +5820,111 @@ function normalizeKafkaHeadersForPayload(headers: any): Record<string, string> {
|
|
|
5894
5820
|
return out;
|
|
5895
5821
|
}
|
|
5896
5822
|
|
|
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
5823
|
function kafkaValueSize(value: any): number | null {
|
|
5912
5824
|
if (value === null || value === undefined) return null;
|
|
5913
5825
|
if (typeof value === 'string') return Buffer.byteLength(value, 'utf8');
|
|
5914
5826
|
if (Buffer.isBuffer(value)) return value.length;
|
|
5827
|
+
if (value instanceof Uint8Array) return value.byteLength;
|
|
5915
5828
|
return null;
|
|
5916
5829
|
}
|
|
5917
5830
|
|
|
5831
|
+
function decodeKafkaValue(value: any): any {
|
|
5832
|
+
if (value === null || value === undefined) return null;
|
|
5833
|
+
if (typeof value === 'string') return value;
|
|
5834
|
+
if (Buffer.isBuffer(value)) return value.toString('utf8');
|
|
5835
|
+
if (value instanceof Uint8Array) return Buffer.from(value).toString('utf8');
|
|
5836
|
+
if (typeof value === 'number' || typeof value === 'boolean') return value;
|
|
5837
|
+
return sanitizeResultForMeta(value);
|
|
5838
|
+
}
|
|
5839
|
+
|
|
5840
|
+
function parseKafkaJsonString(value: string): { parsed: true; value: any } | { parsed: false; value: string } {
|
|
5841
|
+
const trimmed = value.trim();
|
|
5842
|
+
if (!trimmed || !'{['.includes(trimmed[0]!)) {
|
|
5843
|
+
return { parsed: false, value };
|
|
5844
|
+
}
|
|
5845
|
+
try {
|
|
5846
|
+
return { parsed: true, value: JSON.parse(value) };
|
|
5847
|
+
} catch {
|
|
5848
|
+
return { parsed: false, value };
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5851
|
+
|
|
5852
|
+
function decodeKafkaMessageValue(value: any): {
|
|
5853
|
+
value: any;
|
|
5854
|
+
rawValue?: string;
|
|
5855
|
+
valueEncoding: string | null;
|
|
5856
|
+
} {
|
|
5857
|
+
const decoded = decodeKafkaValue(value);
|
|
5858
|
+
if (typeof decoded !== 'string') {
|
|
5859
|
+
return {
|
|
5860
|
+
value: decoded,
|
|
5861
|
+
valueEncoding: decoded === null ? null : typeof decoded,
|
|
5862
|
+
};
|
|
5863
|
+
}
|
|
5864
|
+
const parsed = parseKafkaJsonString(decoded);
|
|
5865
|
+
if (parsed.parsed) {
|
|
5866
|
+
return {
|
|
5867
|
+
value: parsed.value,
|
|
5868
|
+
rawValue: decoded,
|
|
5869
|
+
valueEncoding: 'json',
|
|
5870
|
+
};
|
|
5871
|
+
}
|
|
5872
|
+
return {
|
|
5873
|
+
value: decoded,
|
|
5874
|
+
valueEncoding: 'utf8',
|
|
5875
|
+
};
|
|
5876
|
+
}
|
|
5877
|
+
|
|
5878
|
+
function buildKafkaMessagePayload(message: any): Record<string, any> {
|
|
5879
|
+
const msgObj = message && typeof message === 'object' ? message : { value: message };
|
|
5880
|
+
const decoded = decodeKafkaMessageValue(msgObj.value);
|
|
5881
|
+
const payload: Record<string, any> = {
|
|
5882
|
+
key: decodeKafkaValue(msgObj.key),
|
|
5883
|
+
value: decoded.value,
|
|
5884
|
+
valueEncoding: decoded.valueEncoding,
|
|
5885
|
+
valueBytes: kafkaValueSize(msgObj.value),
|
|
5886
|
+
headers: normalizeKafkaHeadersForPayload(msgObj.headers),
|
|
5887
|
+
};
|
|
5888
|
+
if (decoded.rawValue !== undefined) {
|
|
5889
|
+
payload.rawValue = decoded.rawValue;
|
|
5890
|
+
}
|
|
5891
|
+
if (msgObj.partition !== undefined) {
|
|
5892
|
+
payload.partition = msgObj.partition;
|
|
5893
|
+
}
|
|
5894
|
+
if (msgObj.timestamp !== undefined) {
|
|
5895
|
+
payload.timestamp = msgObj.timestamp;
|
|
5896
|
+
}
|
|
5897
|
+
if (msgObj.offset !== undefined) {
|
|
5898
|
+
payload.offset = msgObj.offset;
|
|
5899
|
+
}
|
|
5900
|
+
return payload;
|
|
5901
|
+
}
|
|
5902
|
+
|
|
5903
|
+
function cloneKafkaTelemetryValue(value: any): any {
|
|
5904
|
+
if (value === undefined || value === null) return value;
|
|
5905
|
+
try {
|
|
5906
|
+
return JSON.parse(JSON.stringify(value));
|
|
5907
|
+
} catch {
|
|
5908
|
+
return value;
|
|
5909
|
+
}
|
|
5910
|
+
}
|
|
5911
|
+
|
|
5912
|
+
async function materializeKafkaRequestBody(
|
|
5913
|
+
cfg: KafkaJsPatchConfig,
|
|
5914
|
+
maskReq: MaskRequestContext,
|
|
5915
|
+
body: Record<string, any>,
|
|
5916
|
+
): Promise<InlinePrivacyMaterializationResult> {
|
|
5917
|
+
return materializeInlinePrivacyValueAsync(
|
|
5918
|
+
'request.body',
|
|
5919
|
+
sanitizeTraceValueForPrivacy(body),
|
|
5920
|
+
cfg,
|
|
5921
|
+
maskReq,
|
|
5922
|
+
null,
|
|
5923
|
+
normalizeMaskingConfig(cfg.masking),
|
|
5924
|
+
getRuntimePrivacyState(cfg).policy ?? null,
|
|
5925
|
+
);
|
|
5926
|
+
}
|
|
5927
|
+
|
|
5918
5928
|
function summarizeKafkaError(error: any): Record<string, unknown> {
|
|
5919
5929
|
const message =
|
|
5920
5930
|
typeof error?.message === 'string'
|
|
@@ -6005,6 +6015,7 @@ function recordKafkaTraceEvent(
|
|
|
6005
6015
|
library: inferLibraryNameFromFile(sourceFileForLibrary ?? raw.file),
|
|
6006
6016
|
};
|
|
6007
6017
|
if (shouldDropTraceEvent(candidate)) return;
|
|
6018
|
+
const omitJsonBuiltinValues = shouldOmitJsonBuiltinTraceValues(candidate);
|
|
6008
6019
|
|
|
6009
6020
|
const evt: TraceEventRecord = {
|
|
6010
6021
|
t: alignTimestamp(typeof raw.t === 'number' ? raw.t : Date.now()),
|
|
@@ -6020,8 +6031,8 @@ function recordKafkaTraceEvent(
|
|
|
6020
6031
|
|
|
6021
6032
|
const privacy = getRuntimePrivacyState(cfg).policy ?? null;
|
|
6022
6033
|
const masking = normalizeMaskingConfig(cfg.masking);
|
|
6023
|
-
if (raw.args !== undefined) {
|
|
6024
|
-
evt.args =
|
|
6034
|
+
if (!omitJsonBuiltinValues && raw.args !== undefined) {
|
|
6035
|
+
evt.args = sanitizeMaterializedTraceValue(materializeTracePrivacyValue(
|
|
6025
6036
|
'trace.args',
|
|
6026
6037
|
sanitizeTraceArgsForPrivacy(raw.args),
|
|
6027
6038
|
cfg,
|
|
@@ -6031,8 +6042,8 @@ function recordKafkaTraceEvent(
|
|
|
6031
6042
|
privacy,
|
|
6032
6043
|
));
|
|
6033
6044
|
}
|
|
6034
|
-
if (raw.returnValue !== undefined) {
|
|
6035
|
-
evt.returnValue =
|
|
6045
|
+
if (!omitJsonBuiltinValues && raw.returnValue !== undefined) {
|
|
6046
|
+
evt.returnValue = sanitizeMaterializedTraceValue(materializeTracePrivacyValue(
|
|
6036
6047
|
'trace.returnValue',
|
|
6037
6048
|
sanitizeTraceValueForPrivacy(raw.returnValue),
|
|
6038
6049
|
cfg,
|
|
@@ -6042,8 +6053,8 @@ function recordKafkaTraceEvent(
|
|
|
6042
6053
|
privacy,
|
|
6043
6054
|
));
|
|
6044
6055
|
}
|
|
6045
|
-
if (raw.error
|
|
6046
|
-
evt.error =
|
|
6056
|
+
if (hasMeaningfulRawTraceError(raw.error)) {
|
|
6057
|
+
evt.error = sanitizeMaterializedTraceValue(materializeTracePrivacyValue(
|
|
6047
6058
|
'trace.error',
|
|
6048
6059
|
sanitizeTraceValueForPrivacy(raw.error),
|
|
6049
6060
|
cfg,
|
|
@@ -6079,6 +6090,7 @@ function buildPendingKafkaTraceEvent(raw: any): PendingTraceEventRecord | null {
|
|
|
6079
6090
|
library: inferLibraryNameFromFile(sourceFileForLibrary ?? raw.file),
|
|
6080
6091
|
};
|
|
6081
6092
|
if (shouldDropTraceEvent(candidate)) return null;
|
|
6093
|
+
const omitJsonBuiltinValues = shouldOmitJsonBuiltinTraceValues(candidate);
|
|
6082
6094
|
|
|
6083
6095
|
const evt: PendingTraceEventRecord = {
|
|
6084
6096
|
t: alignTimestamp(typeof raw.t === 'number' ? raw.t : Date.now()),
|
|
@@ -6094,19 +6106,20 @@ function buildPendingKafkaTraceEvent(raw: any): PendingTraceEventRecord | null {
|
|
|
6094
6106
|
if (typeof raw.sourceFile === 'string' && raw.sourceFile) {
|
|
6095
6107
|
evt.__reproSourceFile = String(raw.sourceFile);
|
|
6096
6108
|
}
|
|
6097
|
-
|
|
6109
|
+
const hasErrorPayload = hasMeaningfulRawTraceError(raw.error);
|
|
6110
|
+
if ((!omitJsonBuiltinValues && (raw.args !== undefined || raw.returnValue !== undefined)) || hasErrorPayload) {
|
|
6098
6111
|
evt.__reproPending = {
|
|
6099
|
-
...(raw.args !== undefined
|
|
6112
|
+
...(!omitJsonBuiltinValues && raw.args !== undefined
|
|
6100
6113
|
? {
|
|
6101
6114
|
argsRaw: normalizeRawTraceArgs(raw.args),
|
|
6102
6115
|
}
|
|
6103
6116
|
: {}),
|
|
6104
|
-
...(raw.returnValue !== undefined
|
|
6117
|
+
...(!omitJsonBuiltinValues && raw.returnValue !== undefined
|
|
6105
6118
|
? {
|
|
6106
6119
|
returnValueRaw: raw.returnValue,
|
|
6107
6120
|
}
|
|
6108
6121
|
: {}),
|
|
6109
|
-
...(
|
|
6122
|
+
...(hasErrorPayload
|
|
6110
6123
|
? {
|
|
6111
6124
|
errorRaw: raw.error,
|
|
6112
6125
|
}
|
|
@@ -6251,14 +6264,26 @@ function patchKafkaProducerInstance(producer: any, cfg: KafkaJsPatchConfig) {
|
|
|
6251
6264
|
throw err;
|
|
6252
6265
|
} finally {
|
|
6253
6266
|
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
6267
|
const totalValueBytes = propagatedMessages.reduce((sum: number, message: any) => {
|
|
6259
6268
|
const size = kafkaValueSize(message?.value);
|
|
6260
6269
|
return sum + (size ?? 0);
|
|
6261
6270
|
}, 0);
|
|
6271
|
+
const requestKey = `KAFKA_PUBLISH ${topic}`;
|
|
6272
|
+
const maskReq: MaskRequestContext = {
|
|
6273
|
+
method: 'KAFKA_PUBLISH',
|
|
6274
|
+
path: `kafka://${topic}`,
|
|
6275
|
+
key: requestKey,
|
|
6276
|
+
};
|
|
6277
|
+
const bodyMaterialization = await materializeKafkaRequestBody(cfg, maskReq, {
|
|
6278
|
+
transport: 'kafka',
|
|
6279
|
+
topic,
|
|
6280
|
+
messageCount: propagatedMessages.length,
|
|
6281
|
+
totalValueBytes,
|
|
6282
|
+
messages: propagatedMessages.map((message: any) => buildKafkaMessagePayload(message)),
|
|
6283
|
+
traceId: span?.traceId ?? null,
|
|
6284
|
+
spanId: span?.spanId ?? null,
|
|
6285
|
+
parentSpanId: span?.parentSpanId ?? null,
|
|
6286
|
+
});
|
|
6262
6287
|
|
|
6263
6288
|
const requestPayload: Record<string, any> = {
|
|
6264
6289
|
rid: publishRid,
|
|
@@ -6267,22 +6292,14 @@ function patchKafkaProducerInstance(producer: any, cfg: KafkaJsPatchConfig) {
|
|
|
6267
6292
|
path: `kafka://${topic}`,
|
|
6268
6293
|
status: threw ? 500 : 200,
|
|
6269
6294
|
durMs: Date.now() - t0,
|
|
6270
|
-
key:
|
|
6295
|
+
key: requestKey,
|
|
6271
6296
|
headers: {
|
|
6272
6297
|
transport: 'kafka',
|
|
6273
6298
|
topic,
|
|
6274
6299
|
messageCount: propagatedMessages.length,
|
|
6275
6300
|
},
|
|
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
|
-
},
|
|
6301
|
+
body: bodyMaterialization.value,
|
|
6302
|
+
bodyMaterialization: bodyMaterialization.skipped,
|
|
6286
6303
|
respBody: threw
|
|
6287
6304
|
? { error: summarizeKafkaError(error) }
|
|
6288
6305
|
: summarizeKafkaProducerResult(result),
|
|
@@ -6373,30 +6390,51 @@ function patchKafkaProducerInstance(producer: any, cfg: KafkaJsPatchConfig) {
|
|
|
6373
6390
|
const count = Array.isArray(entry.messages) ? entry.messages.length : 0;
|
|
6374
6391
|
return sum + count;
|
|
6375
6392
|
}, 0);
|
|
6393
|
+
const totalValueBytes = patchedTopicMessages.reduce((sum: number, entry: any) => {
|
|
6394
|
+
const messages = Array.isArray(entry.messages) ? entry.messages : [];
|
|
6395
|
+
return sum + messages.reduce((messageSum: number, message: any) => {
|
|
6396
|
+
const size = kafkaValueSize(message?.value);
|
|
6397
|
+
return messageSum + (size ?? 0);
|
|
6398
|
+
}, 0);
|
|
6399
|
+
}, 0);
|
|
6400
|
+
const requestKey = uniqueTopics.length === 1
|
|
6401
|
+
? `KAFKA_PUBLISH_BATCH ${uniqueTopics[0]}`
|
|
6402
|
+
: 'KAFKA_PUBLISH_BATCH';
|
|
6403
|
+
const requestPath = uniqueTopics.length === 1 ? `kafka://${uniqueTopics[0]}` : 'kafka://batch';
|
|
6404
|
+
const maskReq: MaskRequestContext = {
|
|
6405
|
+
method: 'KAFKA_PUBLISH_BATCH',
|
|
6406
|
+
path: requestPath,
|
|
6407
|
+
key: requestKey,
|
|
6408
|
+
};
|
|
6409
|
+
const bodyMaterialization = await materializeKafkaRequestBody(cfg, maskReq, {
|
|
6410
|
+
transport: 'kafka',
|
|
6411
|
+
topics: uniqueTopics,
|
|
6412
|
+
topicMessages: patchedTopicMessages.map((entry: any) => ({
|
|
6413
|
+
topic: sanitizeKafkaTopic(entry.topic),
|
|
6414
|
+
messages: (Array.isArray(entry.messages) ? entry.messages : [])
|
|
6415
|
+
.map((message: any) => buildKafkaMessagePayload(message)),
|
|
6416
|
+
})),
|
|
6417
|
+
messageCount,
|
|
6418
|
+
totalValueBytes,
|
|
6419
|
+
traceId: span?.traceId ?? null,
|
|
6420
|
+
spanId: span?.spanId ?? null,
|
|
6421
|
+
parentSpanId: span?.parentSpanId ?? null,
|
|
6422
|
+
});
|
|
6376
6423
|
const requestPayload: Record<string, any> = {
|
|
6377
6424
|
rid: publishRid,
|
|
6378
6425
|
method: 'KAFKA_PUBLISH_BATCH',
|
|
6379
|
-
url:
|
|
6380
|
-
path:
|
|
6426
|
+
url: requestPath,
|
|
6427
|
+
path: requestPath,
|
|
6381
6428
|
status: threw ? 500 : 200,
|
|
6382
6429
|
durMs: Date.now() - t0,
|
|
6383
|
-
key:
|
|
6384
|
-
uniqueTopics.length === 1
|
|
6385
|
-
? `KAFKA_PUBLISH_BATCH ${uniqueTopics[0]}`
|
|
6386
|
-
: 'KAFKA_PUBLISH_BATCH',
|
|
6430
|
+
key: requestKey,
|
|
6387
6431
|
headers: {
|
|
6388
6432
|
transport: 'kafka',
|
|
6389
6433
|
topicCount: uniqueTopics.length,
|
|
6390
6434
|
messageCount,
|
|
6391
6435
|
},
|
|
6392
|
-
body:
|
|
6393
|
-
|
|
6394
|
-
topics: uniqueTopics,
|
|
6395
|
-
messageCount,
|
|
6396
|
-
traceId: span?.traceId ?? null,
|
|
6397
|
-
spanId: span?.spanId ?? null,
|
|
6398
|
-
parentSpanId: span?.parentSpanId ?? null,
|
|
6399
|
-
},
|
|
6436
|
+
body: bodyMaterialization.value,
|
|
6437
|
+
bodyMaterialization: bodyMaterialization.skipped,
|
|
6400
6438
|
respBody: threw
|
|
6401
6439
|
? { error: summarizeKafkaError(error) }
|
|
6402
6440
|
: summarizeKafkaProducerResult(result),
|
|
@@ -6478,6 +6516,24 @@ function wrapKafkaEachMessageHandler(eachMessage: any, cfg: KafkaJsPatchConfig,
|
|
|
6478
6516
|
throw err;
|
|
6479
6517
|
} finally {
|
|
6480
6518
|
if (!sid) return;
|
|
6519
|
+
const messagePayload = buildKafkaMessagePayload(message);
|
|
6520
|
+
const bodyMaterialization = await materializeKafkaRequestBody(cfg, maskReq, {
|
|
6521
|
+
transport: 'kafka',
|
|
6522
|
+
topic,
|
|
6523
|
+
partition,
|
|
6524
|
+
offset: message?.offset ?? null,
|
|
6525
|
+
timestamp: message?.timestamp ?? null,
|
|
6526
|
+
groupId: consumer?.__repro_group_id ?? null,
|
|
6527
|
+
parentRequestRid: runtime.requestRid,
|
|
6528
|
+
parentTraceId: runtime.traceId,
|
|
6529
|
+
parentSpanId: runtime.parentSpanId,
|
|
6530
|
+
messageKey: messagePayload.key,
|
|
6531
|
+
value: cloneKafkaTelemetryValue(messagePayload.value),
|
|
6532
|
+
rawValue: messagePayload.rawValue,
|
|
6533
|
+
valueEncoding: messagePayload.valueEncoding,
|
|
6534
|
+
valueBytes: messagePayload.valueBytes,
|
|
6535
|
+
message: messagePayload,
|
|
6536
|
+
});
|
|
6481
6537
|
const requestPayload: Record<string, any> = {
|
|
6482
6538
|
rid: consumeRid,
|
|
6483
6539
|
method: 'KAFKA_CONSUME',
|
|
@@ -6487,19 +6543,8 @@ function wrapKafkaEachMessageHandler(eachMessage: any, cfg: KafkaJsPatchConfig,
|
|
|
6487
6543
|
durMs: Date.now() - t0,
|
|
6488
6544
|
key: `KAFKA_CONSUME ${topic}`,
|
|
6489
6545
|
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
|
-
},
|
|
6546
|
+
body: bodyMaterialization.value,
|
|
6547
|
+
bodyMaterialization: bodyMaterialization.skipped,
|
|
6503
6548
|
respBody: threw ? { error: summarizeKafkaError(error) } : undefined,
|
|
6504
6549
|
};
|
|
6505
6550
|
|
|
@@ -6580,6 +6625,20 @@ function wrapKafkaEachBatchHandler(eachBatch: any, cfg: KafkaJsPatchConfig, cons
|
|
|
6580
6625
|
if (!sid) return;
|
|
6581
6626
|
const firstOffset = messages.length ? messages[0]?.offset ?? null : null;
|
|
6582
6627
|
const lastOffset = messages.length ? messages[messages.length - 1]?.offset ?? null : null;
|
|
6628
|
+
const firstMessagePayload = firstMessage ? buildKafkaMessagePayload(firstMessage) : null;
|
|
6629
|
+
const bodyMaterialization = await materializeKafkaRequestBody(cfg, maskReq, {
|
|
6630
|
+
transport: 'kafka',
|
|
6631
|
+
topic,
|
|
6632
|
+
groupId: consumer?.__repro_group_id ?? null,
|
|
6633
|
+
messageCount: messages.length,
|
|
6634
|
+
firstOffset,
|
|
6635
|
+
lastOffset,
|
|
6636
|
+
parentRequestRid: runtime.requestRid,
|
|
6637
|
+
parentTraceId: runtime.traceId,
|
|
6638
|
+
parentSpanId: runtime.parentSpanId,
|
|
6639
|
+
firstMessageKey: firstMessagePayload?.key ?? null,
|
|
6640
|
+
messages: messages.map((message: any) => buildKafkaMessagePayload(message)),
|
|
6641
|
+
});
|
|
6583
6642
|
const requestPayload: Record<string, any> = {
|
|
6584
6643
|
rid: consumeRid,
|
|
6585
6644
|
method: 'KAFKA_CONSUME_BATCH',
|
|
@@ -6589,18 +6648,8 @@ function wrapKafkaEachBatchHandler(eachBatch: any, cfg: KafkaJsPatchConfig, cons
|
|
|
6589
6648
|
durMs: Date.now() - t0,
|
|
6590
6649
|
key: `KAFKA_CONSUME_BATCH ${topic}`,
|
|
6591
6650
|
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
|
-
},
|
|
6651
|
+
body: bodyMaterialization.value,
|
|
6652
|
+
bodyMaterialization: bodyMaterialization.skipped,
|
|
6604
6653
|
respBody: threw ? { error: summarizeKafkaError(error) } : undefined,
|
|
6605
6654
|
};
|
|
6606
6655
|
|
|
@@ -7476,5 +7525,7 @@ export const __reproTestHooks = {
|
|
|
7476
7525
|
getRuntimePrivacyState(cfg).policy = policy;
|
|
7477
7526
|
},
|
|
7478
7527
|
recordKafkaTraceEventAsyncForTest: recordKafkaTraceEventAsync,
|
|
7528
|
+
materializeInlinePrivacyValueAsyncForTest: materializeInlinePrivacyValueAsync,
|
|
7529
|
+
patchKafkaProducerInstanceForTest: patchKafkaProducerInstance,
|
|
7479
7530
|
wrapKafkaEachMessageHandlerForTest: wrapKafkaEachMessageHandler,
|
|
7480
7531
|
};
|