@reproapp/node-sdk 0.0.5 → 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/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) pendingValueCount += 1;
1437
- if (pending.returnValueRaw !== undefined) pendingValueCount += 1;
1438
- if (pending.errorRaw !== undefined) pendingValueCount += 1;
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 = 8;
1991
- const TRACE_FULL_VALUE_MAX_KEYS = 200;
1992
- const TRACE_FULL_VALUE_MAX_ITEMS = 200;
1993
- const TRACE_FULL_VALUE_MAX_STRING = 32000;
1994
- const TRACE_FULL_VALUE_FALLBACK_DEPTH = 6;
1995
- const TRACE_FULL_VALUE_FALLBACK_KEYS = 100;
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 INLINE_PRIVACY_PREVIEW_MAX_DEPTH = 3;
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 out = value.slice(0, TRACE_VALUE_MAX_ITEMS)
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
- for (const key of keys.slice(0, TRACE_VALUE_MAX_KEYS)) {
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
- const argsPreview = sanitizeTraceArgsForPrivacy(pending.argsRaw);
2467
- const argsMaterialization = await materializeInlinePrivacyValueAsync(
2490
+ if (!omitJsonBuiltinValues && pending.argsRaw !== undefined) {
2491
+ evt.args = sanitizeMaterializedTraceValue(await materializeTracePrivacyValueAsync(
2468
2492
  'trace.args',
2469
- argsPreview,
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
- const returnValuePreview = sanitizeTraceValueForPrivacy(pending.returnValueRaw);
2494
- const returnValueMaterialization = await materializeInlinePrivacyValueAsync(
2501
+ if (!omitJsonBuiltinValues && pending.returnValueRaw !== undefined) {
2502
+ evt.returnValue = sanitizeMaterializedTraceValue(await materializeTracePrivacyValueAsync(
2495
2503
  'trace.returnValue',
2496
- returnValuePreview,
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
- const errorPreview = sanitizeTraceValueForPrivacy(pending.errorRaw);
2521
- const errorMaterialization = await materializeInlinePrivacyValueAsync(
2513
+ evt.error = sanitizeMaterializedTraceValue(await materializeTracePrivacyValueAsync(
2522
2514
  'trace.error',
2523
- errorPreview,
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 transformed = applyRuntimePrivacyPolicy(privacy, target, value, {
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 transformed = await applyRuntimePrivacyPolicyAsync(privacy, target, value, {
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 shouldBoundInlinePrivacyTarget(target: RuntimePrivacySurface): boolean {
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 buildOversizedInlinePreview(
3146
+ function limitInlinePrivacyValue(
3147
+ target: RuntimePrivacySurface,
3168
3148
  value: any,
3169
- depth: number = 0,
3170
- seen: WeakSet<object> = new WeakSet<object>(),
3171
- preserveScalarValue: boolean = true,
3172
- ): any {
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
- if (value instanceof Map) {
3237
- const out: Record<string, any> = { __type: 'Map', size: value.size };
3238
- let index = 0;
3239
- for (const [key, item] of value.entries()) {
3240
- if (index >= INLINE_PRIVACY_PREVIEW_MAX_ITEMS) break;
3241
- out[normalizeCollectionKeyForCapture(key, index)] = buildOversizedInlinePreview(item, depth + 1, seen, false);
3242
- index += 1;
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
- if (value instanceof Set) {
3251
- const items: any[] = [];
3252
- let index = 0;
3253
- for (const item of value.values()) {
3254
- if (index >= INLINE_PRIVACY_PREVIEW_MAX_ITEMS) break;
3255
- items.push(buildOversizedInlinePreview(item, depth + 1, seen, false));
3256
- index += 1;
3257
- }
3258
- if (value.size > INLINE_PRIVACY_PREVIEW_MAX_ITEMS) {
3259
- items.push({ __truncatedItems: value.size - INLINE_PRIVACY_PREVIEW_MAX_ITEMS });
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
- const keys = Object.keys(value);
3269
- const ctor = getCtorName(value);
3270
- if (depth >= INLINE_PRIVACY_PREVIEW_MAX_DEPTH) {
3176
+ try {
3177
+ const valueAfterPrivacy = applyPrivacyThenMask(target, value, cfg, req, trace, masking, privacy, db);
3178
+ return { value: valueAfterPrivacy };
3179
+ } catch {
3271
3180
  return {
3272
- ...(ctor && ctor !== 'Object' ? { __type: ctor } : { __type: 'Object' }),
3273
- __keys: keys.slice(0, INLINE_PRIVACY_PREVIEW_MAX_KEYS),
3274
- ...(keys.length > INLINE_PRIVACY_PREVIEW_MAX_KEYS
3275
- ? { __truncatedKeys: keys.length - INLINE_PRIVACY_PREVIEW_MAX_KEYS }
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
- return {
3332
- value: await applyPrivacyThenMaskAsync(target, value, cfg, req, trace, masking, privacy, db),
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: truncatedForStorage ? 'truncated-for-storage' : 'stored',
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: truncatedForStorage ? 'truncated-for-storage' : 'stored',
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
- if (ev.args !== undefined || ev.returnValue !== undefined || ev.error !== undefined) {
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
- ...(ev.error !== undefined
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 requestBodyMaterialization = await materializeInlinePrivacyValueAsync(
4792
- 'request.body',
4793
- sanitizeRequestSnapshot((req as any).body),
4794
- cfg,
4795
- maskReq,
4796
- endpointTraceCtx,
4797
- masking,
4798
- activePrivacy,
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 = await materializeInlinePrivacyValueAsync(
4829
- 'response.body',
4830
- capturedBody === undefined ? undefined : sanitizeRequestSnapshot(capturedBody),
4831
- cfg,
4832
- maskReq,
4833
- endpointTraceCtx,
4834
- masking,
4835
- activePrivacy,
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(), options);
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 = sanitizeTraceValue(resultMetaPreviewRaw);
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 = sanitizeTraceValue(applyPrivacyThenMask(
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 = sanitizeTraceValue(applyPrivacyThenMask(
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 !== undefined) {
6046
- evt.error = sanitizeTraceValue(applyPrivacyThenMask(
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
- if (raw.args !== undefined || raw.returnValue !== undefined || raw.error !== undefined) {
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
- ...(raw.error !== undefined
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: `KAFKA_PUBLISH ${topic}`,
6295
+ key: requestKey,
6271
6296
  headers: {
6272
6297
  transport: 'kafka',
6273
6298
  topic,
6274
6299
  messageCount: propagatedMessages.length,
6275
6300
  },
6276
- body: {
6277
- transport: 'kafka',
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: uniqueTopics.length === 1 ? `kafka://${uniqueTopics[0]}` : 'kafka://batch',
6380
- path: uniqueTopics.length === 1 ? `kafka://${uniqueTopics[0]}` : 'kafka://batch',
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
- transport: 'kafka',
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
- transport: 'kafka',
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
- transport: 'kafka',
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
  };