@reproapp/node-sdk 0.0.11 → 0.0.13

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 CHANGED
@@ -67,9 +67,12 @@ type TraceEventRecord = {
67
67
  spanId?: string | number | null;
68
68
  parentSpanId?: string | number | null;
69
69
  args?: any;
70
+ argsValueCapture?: TraceValueCaptureRef;
70
71
  returnValue?: any;
72
+ returnValueCapture?: TraceValueCaptureRef;
71
73
  threw?: boolean;
72
74
  error?: any;
75
+ errorValueCapture?: TraceValueCaptureRef;
73
76
  unawaited?: boolean;
74
77
  };
75
78
  type PendingTraceEventRecord = TraceEventRecord & {
@@ -82,9 +85,14 @@ type PendingTraceEventRecord = TraceEventRecord & {
82
85
  __reproSourceFile?: string | null;
83
86
  __reproTraceValueEntries?: TraceValueBatchEntry[];
84
87
  };
88
+ type TraceValueCaptureRef = {
89
+ status: 'stored' | 'truncated-for-storage' | 'skipped-non-meaningful';
90
+ valueId?: string;
91
+ projectedKind?: string;
92
+ };
85
93
  type TraceValueBatchEntry = {
86
94
  id: string;
87
- target: 'db.pk' | 'db.before' | 'db.after' | 'db.query' | 'db.resultMeta' | 'db.error' | 'request.headers' | 'request.body' | 'request.params' | 'request.query' | 'response.body';
95
+ target: 'db.pk' | 'db.before' | 'db.after' | 'db.query' | 'db.resultMeta' | 'db.error' | 'request.headers' | 'request.body' | 'request.params' | 'request.query' | 'response.body' | 'trace.args' | 'trace.returnValue' | 'trace.error';
88
96
  value: any;
89
97
  truncatedForStorage?: boolean;
90
98
  projectedKind?: string;
@@ -285,6 +293,7 @@ type InlinePrivacyMaterializationResult = {
285
293
  };
286
294
  };
287
295
  declare function materializeInlinePrivacyValueAsync(target: RuntimePrivacySurface, value: any, cfg: ReproMiddlewareConfig, req: MaskRequestContext, trace: TraceEventForFilter | null, masking: NormalizedMaskingConfig | null, privacy: NormalizedRuntimePrivacyPolicy | null, db?: RuntimePrivacyDbContext | null): Promise<InlinePrivacyMaterializationResult>;
296
+ declare function getEventTraceValueEntries(event: TraceEventRecord | PendingTraceEventRecord): TraceValueBatchEntry[] | undefined;
288
297
  declare function estimateTraceBatchSerializedBytes(batch: TraceEventRecord[], requestRid: string, actionId: string | null | undefined, batchIndex: number): number;
289
298
  declare function chunkTraceEventsForTransport(events: TraceEventRecord[], requestRid: string, actionId: string | null | undefined): TraceEventRecord[][];
290
299
  export type ReproMiddlewareConfig = IngestClientConfig & {
@@ -362,5 +371,6 @@ export declare const __reproTestHooks: {
362
371
  estimateTraceBatchSerializedBytesForTest: typeof estimateTraceBatchSerializedBytes;
363
372
  sanitizeTraceValueForPrivacyForTest: typeof sanitizeTraceValueForPrivacy;
364
373
  sanitizeMaterializedTraceValueForTest: typeof sanitizeMaterializedTraceValue;
374
+ getEventTraceValueEntriesForTest: typeof getEventTraceValueEntries;
365
375
  };
366
376
  export {};
package/dist/index.js CHANGED
@@ -2129,6 +2129,56 @@ function sanitizeInlinePrivacyValue(value) {
2129
2129
  function sanitizeMaterializedTraceValue(value) {
2130
2130
  return sanitizeTraceValue(value, 0, new WeakMap(), { preserveLongStrings: true, disableTruncation: true });
2131
2131
  }
2132
+ function compactMaterializedTracePreview(value, depth = 0) {
2133
+ if (value === undefined || value === null)
2134
+ return value;
2135
+ if (depth >= TRACE_VALUE_MAX_DEPTH) {
2136
+ return makeOmittedValue(`depth>${TRACE_VALUE_MAX_DEPTH}`);
2137
+ }
2138
+ const type = typeof value;
2139
+ if (type === 'string') {
2140
+ if (value.length <= TRACE_VALUE_MAX_STRING)
2141
+ return value;
2142
+ return value.slice(0, TRACE_VALUE_MAX_STRING);
2143
+ }
2144
+ if (type === 'number' || type === 'boolean')
2145
+ return value;
2146
+ if (type === 'bigint')
2147
+ return value.toString();
2148
+ if (type === 'symbol')
2149
+ return value.toString();
2150
+ if (type === 'function')
2151
+ return `[Function${value.name ? ` ${value.name}` : ''}]`;
2152
+ if (type !== 'object')
2153
+ return String(value);
2154
+ if (Array.isArray(value)) {
2155
+ return value
2156
+ .slice(0, TRACE_VALUE_MAX_ITEMS)
2157
+ .map((item) => compactMaterializedTracePreview(item, depth + 1));
2158
+ }
2159
+ const out = {};
2160
+ const keys = Object.keys(value).slice(0, TRACE_VALUE_MAX_KEYS);
2161
+ for (const key of keys) {
2162
+ try {
2163
+ out[key] = compactMaterializedTracePreview(value[key], depth + 1);
2164
+ }
2165
+ catch (err) {
2166
+ out[key] = `[Cannot serialize: ${err?.message || 'unknown error'}]`;
2167
+ }
2168
+ }
2169
+ return out;
2170
+ }
2171
+ function limitMaterializedTraceInlineValue(value) {
2172
+ const inlineValue = sanitizeMaterializedTraceValue(value);
2173
+ if (!traceInlineValueExceedsLimit(inlineValue)) {
2174
+ return inlineValue;
2175
+ }
2176
+ const compactPreview = compactMaterializedTracePreview(inlineValue);
2177
+ if (!traceInlineValueExceedsLimit(compactPreview)) {
2178
+ return compactPreview;
2179
+ }
2180
+ return makeOmittedValue('inline-size-limit');
2181
+ }
2132
2182
  function sanitizeTraceArgsForPrivacy(values) {
2133
2183
  if (!Array.isArray(values))
2134
2184
  return [sanitizeTraceValueForPrivacy(values)];
@@ -2142,6 +2192,14 @@ function normalizeRawTraceArgs(values) {
2142
2192
  function hasMeaningfulRawTraceError(error) {
2143
2193
  return error !== undefined && error !== null;
2144
2194
  }
2195
+ function attachTraceValueEntry(event, entry) {
2196
+ const current = event[TRACE_FULL_VALUE_ENTRY_SYMBOL];
2197
+ if (Array.isArray(current)) {
2198
+ current.push(entry);
2199
+ return;
2200
+ }
2201
+ event[TRACE_FULL_VALUE_ENTRY_SYMBOL] = [entry];
2202
+ }
2145
2203
  async function materializePendingTraceEvents(events, params) {
2146
2204
  for (const evt of events) {
2147
2205
  const pending = evt.__reproPending;
@@ -2151,14 +2209,71 @@ async function materializePendingTraceEvents(events, params) {
2151
2209
  const candidate = pending.candidate ?? traceEventCandidateFromRecord(evt);
2152
2210
  const omitJsonBuiltinValues = shouldOmitJsonBuiltinTraceValues(candidate);
2153
2211
  const activePrivacy = params.resolvePrivacy();
2212
+ const captureContext = {
2213
+ runtimeConfig: params.cfg,
2214
+ captureHeaders: params.cfg.captureHeaders,
2215
+ maskReq: params.maskReq,
2216
+ trace: candidate,
2217
+ masking: params.masking,
2218
+ privacy: activePrivacy,
2219
+ };
2220
+ const traceValueMetadata = {
2221
+ fn: evt.fn,
2222
+ file: evt.file,
2223
+ line: evt.line ?? null,
2224
+ spanId: evt.spanId ?? null,
2225
+ parentSpanId: evt.parentSpanId ?? null,
2226
+ };
2154
2227
  if (!omitJsonBuiltinValues && pending.argsRaw !== undefined) {
2155
- evt.args = sanitizeMaterializedTraceValue(await materializeTracePrivacyValueAsync('trace.args', sanitizeTraceArgsForPrivacy(pending.argsRaw), params.cfg, params.maskReq, candidate, params.masking, activePrivacy));
2228
+ const argsMaterialized = await materializeTracePrivacyValueAsync('trace.args', sanitizeTraceArgsForPrivacy(pending.argsRaw), params.cfg, params.maskReq, candidate, params.masking, activePrivacy);
2229
+ evt.args = limitMaterializedTraceInlineValue(argsMaterialized);
2230
+ const argsValueCapture = await maybeCaptureTraceValueAsync({
2231
+ target: 'trace.args',
2232
+ rawValue: pending.argsRaw,
2233
+ previewValue: evt.args,
2234
+ capture: captureContext,
2235
+ metadata: traceValueMetadata,
2236
+ });
2237
+ if (argsValueCapture?.entry) {
2238
+ attachTraceValueEntry(evt, argsValueCapture.entry);
2239
+ }
2240
+ if (argsValueCapture?.captureRef) {
2241
+ evt.argsValueCapture = argsValueCapture.captureRef;
2242
+ }
2156
2243
  }
2157
2244
  if (!omitJsonBuiltinValues && pending.returnValueRaw !== undefined) {
2158
- evt.returnValue = sanitizeMaterializedTraceValue(await materializeTracePrivacyValueAsync('trace.returnValue', sanitizeTraceValueForPrivacy(pending.returnValueRaw), params.cfg, params.maskReq, candidate, params.masking, activePrivacy));
2245
+ const returnValueMaterialized = await materializeTracePrivacyValueAsync('trace.returnValue', sanitizeTraceValueForPrivacy(pending.returnValueRaw), params.cfg, params.maskReq, candidate, params.masking, activePrivacy);
2246
+ evt.returnValue = limitMaterializedTraceInlineValue(returnValueMaterialized);
2247
+ const returnValueCapture = await maybeCaptureTraceValueAsync({
2248
+ target: 'trace.returnValue',
2249
+ rawValue: pending.returnValueRaw,
2250
+ previewValue: evt.returnValue,
2251
+ capture: captureContext,
2252
+ metadata: traceValueMetadata,
2253
+ });
2254
+ if (returnValueCapture?.entry) {
2255
+ attachTraceValueEntry(evt, returnValueCapture.entry);
2256
+ }
2257
+ if (returnValueCapture?.captureRef) {
2258
+ evt.returnValueCapture = returnValueCapture.captureRef;
2259
+ }
2159
2260
  }
2160
2261
  if (pending.errorRaw !== undefined) {
2161
- evt.error = sanitizeMaterializedTraceValue(await materializeTracePrivacyValueAsync('trace.error', sanitizeTraceValueForPrivacy(pending.errorRaw), params.cfg, params.maskReq, candidate, params.masking, activePrivacy));
2262
+ const errorMaterialized = await materializeTracePrivacyValueAsync('trace.error', sanitizeTraceValueForPrivacy(pending.errorRaw), params.cfg, params.maskReq, candidate, params.masking, activePrivacy);
2263
+ evt.error = limitMaterializedTraceInlineValue(errorMaterialized);
2264
+ const errorValueCapture = await maybeCaptureTraceValueAsync({
2265
+ target: 'trace.error',
2266
+ rawValue: pending.errorRaw,
2267
+ previewValue: evt.error,
2268
+ capture: captureContext,
2269
+ metadata: traceValueMetadata,
2270
+ });
2271
+ if (errorValueCapture?.entry) {
2272
+ attachTraceValueEntry(evt, errorValueCapture.entry);
2273
+ }
2274
+ if (errorValueCapture?.captureRef) {
2275
+ evt.errorValueCapture = errorValueCapture.captureRef;
2276
+ }
2162
2277
  }
2163
2278
  }
2164
2279
  return events;
@@ -3710,6 +3825,12 @@ async function maybeCaptureRequestValueAsync(params, sink) {
3710
3825
  }
3711
3826
  return result.captureRef;
3712
3827
  }
3828
+ function maybeCaptureTraceValue(params) {
3829
+ return createCapturedValueEntry(params);
3830
+ }
3831
+ async function maybeCaptureTraceValueAsync(params) {
3832
+ return await createCapturedValueEntryAsync(params);
3833
+ }
3713
3834
  function maybeCaptureDbValue(params, sink) {
3714
3835
  const result = createCapturedValueEntry(params);
3715
3836
  if (!result)
@@ -5288,14 +5409,71 @@ function recordKafkaTraceEvent(raw, sink, cfg, maskReq) {
5288
5409
  };
5289
5410
  const privacy = getRuntimePrivacyState(cfg).policy ?? null;
5290
5411
  const masking = normalizeMaskingConfig(cfg.masking);
5412
+ const captureContext = {
5413
+ runtimeConfig: cfg,
5414
+ captureHeaders: cfg.captureHeaders,
5415
+ maskReq,
5416
+ trace: candidate,
5417
+ masking,
5418
+ privacy,
5419
+ };
5420
+ const traceValueMetadata = {
5421
+ fn: evt.fn,
5422
+ file: evt.file,
5423
+ line: evt.line ?? null,
5424
+ spanId: evt.spanId ?? null,
5425
+ parentSpanId: evt.parentSpanId ?? null,
5426
+ };
5291
5427
  if (!omitJsonBuiltinValues && raw.args !== undefined) {
5292
- evt.args = sanitizeMaterializedTraceValue(materializeTracePrivacyValue('trace.args', sanitizeTraceArgsForPrivacy(raw.args), cfg, maskReq, candidate, masking, privacy));
5428
+ const argsMaterialized = materializeTracePrivacyValue('trace.args', sanitizeTraceArgsForPrivacy(raw.args), cfg, maskReq, candidate, masking, privacy);
5429
+ evt.args = limitMaterializedTraceInlineValue(argsMaterialized);
5430
+ const argsValueCapture = maybeCaptureTraceValue({
5431
+ target: 'trace.args',
5432
+ rawValue: normalizeRawTraceArgs(raw.args),
5433
+ previewValue: evt.args,
5434
+ capture: captureContext,
5435
+ metadata: traceValueMetadata,
5436
+ });
5437
+ if (argsValueCapture?.entry) {
5438
+ attachTraceValueEntry(evt, argsValueCapture.entry);
5439
+ }
5440
+ if (argsValueCapture?.captureRef) {
5441
+ evt.argsValueCapture = argsValueCapture.captureRef;
5442
+ }
5293
5443
  }
5294
5444
  if (!omitJsonBuiltinValues && raw.returnValue !== undefined) {
5295
- evt.returnValue = sanitizeMaterializedTraceValue(materializeTracePrivacyValue('trace.returnValue', sanitizeTraceValueForPrivacy(raw.returnValue), cfg, maskReq, candidate, masking, privacy));
5445
+ const returnValueMaterialized = materializeTracePrivacyValue('trace.returnValue', sanitizeTraceValueForPrivacy(raw.returnValue), cfg, maskReq, candidate, masking, privacy);
5446
+ evt.returnValue = limitMaterializedTraceInlineValue(returnValueMaterialized);
5447
+ const returnValueCapture = maybeCaptureTraceValue({
5448
+ target: 'trace.returnValue',
5449
+ rawValue: raw.returnValue,
5450
+ previewValue: evt.returnValue,
5451
+ capture: captureContext,
5452
+ metadata: traceValueMetadata,
5453
+ });
5454
+ if (returnValueCapture?.entry) {
5455
+ attachTraceValueEntry(evt, returnValueCapture.entry);
5456
+ }
5457
+ if (returnValueCapture?.captureRef) {
5458
+ evt.returnValueCapture = returnValueCapture.captureRef;
5459
+ }
5296
5460
  }
5297
5461
  if (hasMeaningfulRawTraceError(raw.error)) {
5298
- evt.error = sanitizeMaterializedTraceValue(materializeTracePrivacyValue('trace.error', sanitizeTraceValueForPrivacy(raw.error), cfg, maskReq, candidate, masking, privacy));
5462
+ const errorMaterialized = materializeTracePrivacyValue('trace.error', sanitizeTraceValueForPrivacy(raw.error), cfg, maskReq, candidate, masking, privacy);
5463
+ evt.error = limitMaterializedTraceInlineValue(errorMaterialized);
5464
+ const errorValueCapture = maybeCaptureTraceValue({
5465
+ target: 'trace.error',
5466
+ rawValue: raw.error,
5467
+ previewValue: evt.error,
5468
+ capture: captureContext,
5469
+ metadata: traceValueMetadata,
5470
+ });
5471
+ if (errorValueCapture?.entry) {
5472
+ attachTraceValueEntry(evt, errorValueCapture.entry);
5473
+ }
5474
+ if (errorValueCapture?.captureRef) {
5475
+ evt.errorValueCapture = errorValueCapture.captureRef;
5476
+ }
5299
5477
  }
5300
5478
  if (raw.threw !== undefined)
5301
5479
  evt.threw = raw.threw === true;
@@ -5388,6 +5566,7 @@ function buildKafkaTraceEntries(actionId, requestRid, events) {
5388
5566
  return batches.map((batch, index) => ({
5389
5567
  actionId: actionId ?? null,
5390
5568
  trace: batch,
5569
+ traceValues: collectBatchTraceValueEntries(batch, index),
5391
5570
  traceBatch: {
5392
5571
  rid: requestRid,
5393
5572
  index,
@@ -6727,4 +6906,5 @@ exports.__reproTestHooks = {
6727
6906
  estimateTraceBatchSerializedBytesForTest: estimateTraceBatchSerializedBytes,
6728
6907
  sanitizeTraceValueForPrivacyForTest: sanitizeTraceValueForPrivacy,
6729
6908
  sanitizeMaterializedTraceValueForTest: sanitizeMaterializedTraceValue,
6909
+ getEventTraceValueEntriesForTest: getEventTraceValueEntries,
6730
6910
  };
@@ -4,7 +4,13 @@ exports.flushIngestQueue = exports.enqueueIngestEntries = exports.postIngestEntr
4
4
  const mapper_1 = require("./mapper");
5
5
  const DEFAULT_INGEST_BASE = 'http://localhost:8080';
6
6
  const DEFAULT_INGEST_PATH = '/v1/ingest/events';
7
- const DEFAULT_QUEUE_MAX_ITEMS = 1000;
7
+ const DEFAULT_QUEUE_MAX_ITEMS = (() => {
8
+ const env = Number(typeof process !== 'undefined' ? process?.env?.REPRO_INGEST_QUEUE_MAX_ITEMS : undefined);
9
+ if (Number.isFinite(env) && env > 0) {
10
+ return Math.trunc(env);
11
+ }
12
+ return 0;
13
+ })();
8
14
  const DEFAULT_QUEUE_MAX_ATTEMPTS = 5;
9
15
  const DEFAULT_QUEUE_BACKOFF_MAX_MS = 4000;
10
16
  const DEFAULT_DEFER_DRAIN_MS = 25;
@@ -142,7 +148,7 @@ const enqueueIngestEntries = (config, sessionId, entries) => {
142
148
  entries: entries.slice(),
143
149
  attempts: 0,
144
150
  });
145
- if (ingestQueue.length > DEFAULT_QUEUE_MAX_ITEMS) {
151
+ if (DEFAULT_QUEUE_MAX_ITEMS > 0 && ingestQueue.length > DEFAULT_QUEUE_MAX_ITEMS) {
146
152
  ingestQueue.splice(0, ingestQueue.length - DEFAULT_QUEUE_MAX_ITEMS);
147
153
  }
148
154
  scheduleDrain(shouldDeferDrain?.() ? deferDrainMs : 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reproapp/node-sdk",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "Repro Nest SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,7 +12,7 @@
12
12
  "build": "tsc -p tsconfig.json",
13
13
  "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
14
14
  "prepublishOnly": "npm run build",
15
- "test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js"
15
+ "test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/ingest-queue-retention.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js"
16
16
  },
17
17
  "peerDependencies": {
18
18
  "express": "^5.1.0",
package/src/index.ts CHANGED
@@ -351,9 +351,12 @@ type TraceEventRecord = {
351
351
  spanId?: string | number | null;
352
352
  parentSpanId?: string | number | null;
353
353
  args?: any;
354
+ argsValueCapture?: TraceValueCaptureRef;
354
355
  returnValue?: any;
356
+ returnValueCapture?: TraceValueCaptureRef;
355
357
  threw?: boolean;
356
358
  error?: any;
359
+ errorValueCapture?: TraceValueCaptureRef;
357
360
  unawaited?: boolean;
358
361
  };
359
362
 
@@ -387,7 +390,10 @@ type TraceValueBatchEntry = {
387
390
  | 'request.body'
388
391
  | 'request.params'
389
392
  | 'request.query'
390
- | 'response.body';
393
+ | 'response.body'
394
+ | 'trace.args'
395
+ | 'trace.returnValue'
396
+ | 'trace.error';
391
397
  value: any;
392
398
  truncatedForStorage?: boolean;
393
399
  projectedKind?: string;
@@ -2533,6 +2539,53 @@ function sanitizeMaterializedTraceValue(value: any): any {
2533
2539
  return sanitizeTraceValue(value, 0, new WeakMap(), { preserveLongStrings: true, disableTruncation: true });
2534
2540
  }
2535
2541
 
2542
+ function compactMaterializedTracePreview(value: any, depth = 0): any {
2543
+ if (value === undefined || value === null) return value;
2544
+ if (depth >= TRACE_VALUE_MAX_DEPTH) {
2545
+ return makeOmittedValue(`depth>${TRACE_VALUE_MAX_DEPTH}`);
2546
+ }
2547
+
2548
+ const type = typeof value;
2549
+ if (type === 'string') {
2550
+ if (value.length <= TRACE_VALUE_MAX_STRING) return value;
2551
+ return value.slice(0, TRACE_VALUE_MAX_STRING);
2552
+ }
2553
+ if (type === 'number' || type === 'boolean') return value;
2554
+ if (type === 'bigint') return value.toString();
2555
+ if (type === 'symbol') return value.toString();
2556
+ if (type === 'function') return `[Function${value.name ? ` ${value.name}` : ''}]`;
2557
+ if (type !== 'object') return String(value);
2558
+
2559
+ if (Array.isArray(value)) {
2560
+ return value
2561
+ .slice(0, TRACE_VALUE_MAX_ITEMS)
2562
+ .map((item) => compactMaterializedTracePreview(item, depth + 1));
2563
+ }
2564
+
2565
+ const out: Record<string, any> = {};
2566
+ const keys = Object.keys(value).slice(0, TRACE_VALUE_MAX_KEYS);
2567
+ for (const key of keys) {
2568
+ try {
2569
+ out[key] = compactMaterializedTracePreview((value as Record<string, any>)[key], depth + 1);
2570
+ } catch (err) {
2571
+ out[key] = `[Cannot serialize: ${(err as Error)?.message || 'unknown error'}]`;
2572
+ }
2573
+ }
2574
+ return out;
2575
+ }
2576
+
2577
+ function limitMaterializedTraceInlineValue(value: any): any {
2578
+ const inlineValue = sanitizeMaterializedTraceValue(value);
2579
+ if (!traceInlineValueExceedsLimit(inlineValue)) {
2580
+ return inlineValue;
2581
+ }
2582
+ const compactPreview = compactMaterializedTracePreview(inlineValue);
2583
+ if (!traceInlineValueExceedsLimit(compactPreview)) {
2584
+ return compactPreview;
2585
+ }
2586
+ return makeOmittedValue('inline-size-limit');
2587
+ }
2588
+
2536
2589
  function sanitizeTraceArgsForPrivacy(values: any): any {
2537
2590
  if (!Array.isArray(values)) return [sanitizeTraceValueForPrivacy(values)];
2538
2591
  return values.map(v => sanitizeTraceValueForPrivacy(v));
@@ -2547,6 +2600,18 @@ function hasMeaningfulRawTraceError(error: any): boolean {
2547
2600
  return error !== undefined && error !== null;
2548
2601
  }
2549
2602
 
2603
+ function attachTraceValueEntry(
2604
+ event: TraceEventRecord | PendingTraceEventRecord,
2605
+ entry: TraceValueBatchEntry,
2606
+ ): void {
2607
+ const current = (event as any)[TRACE_FULL_VALUE_ENTRY_SYMBOL] as TraceValueBatchEntry[] | undefined;
2608
+ if (Array.isArray(current)) {
2609
+ current.push(entry);
2610
+ return;
2611
+ }
2612
+ (event as any)[TRACE_FULL_VALUE_ENTRY_SYMBOL] = [entry];
2613
+ }
2614
+
2550
2615
  async function materializePendingTraceEvents(
2551
2616
  events: PendingTraceEventRecord[],
2552
2617
  params: {
@@ -2564,9 +2629,24 @@ async function materializePendingTraceEvents(
2564
2629
  const candidate = pending.candidate ?? traceEventCandidateFromRecord(evt);
2565
2630
  const omitJsonBuiltinValues = shouldOmitJsonBuiltinTraceValues(candidate);
2566
2631
  const activePrivacy = params.resolvePrivacy();
2632
+ const captureContext: FullValueCaptureContext = {
2633
+ runtimeConfig: params.cfg,
2634
+ captureHeaders: params.cfg.captureHeaders,
2635
+ maskReq: params.maskReq,
2636
+ trace: candidate,
2637
+ masking: params.masking,
2638
+ privacy: activePrivacy,
2639
+ };
2640
+ const traceValueMetadata = {
2641
+ fn: evt.fn,
2642
+ file: evt.file,
2643
+ line: evt.line ?? null,
2644
+ spanId: evt.spanId ?? null,
2645
+ parentSpanId: evt.parentSpanId ?? null,
2646
+ };
2567
2647
 
2568
2648
  if (!omitJsonBuiltinValues && pending.argsRaw !== undefined) {
2569
- evt.args = sanitizeMaterializedTraceValue(await materializeTracePrivacyValueAsync(
2649
+ const argsMaterialized = await materializeTracePrivacyValueAsync(
2570
2650
  'trace.args',
2571
2651
  sanitizeTraceArgsForPrivacy(pending.argsRaw),
2572
2652
  params.cfg,
@@ -2574,10 +2654,24 @@ async function materializePendingTraceEvents(
2574
2654
  candidate,
2575
2655
  params.masking,
2576
2656
  activePrivacy,
2577
- ));
2657
+ );
2658
+ evt.args = limitMaterializedTraceInlineValue(argsMaterialized);
2659
+ const argsValueCapture = await maybeCaptureTraceValueAsync({
2660
+ target: 'trace.args',
2661
+ rawValue: pending.argsRaw,
2662
+ previewValue: evt.args,
2663
+ capture: captureContext,
2664
+ metadata: traceValueMetadata,
2665
+ });
2666
+ if (argsValueCapture?.entry) {
2667
+ attachTraceValueEntry(evt, argsValueCapture.entry);
2668
+ }
2669
+ if (argsValueCapture?.captureRef) {
2670
+ evt.argsValueCapture = argsValueCapture.captureRef;
2671
+ }
2578
2672
  }
2579
2673
  if (!omitJsonBuiltinValues && pending.returnValueRaw !== undefined) {
2580
- evt.returnValue = sanitizeMaterializedTraceValue(await materializeTracePrivacyValueAsync(
2674
+ const returnValueMaterialized = await materializeTracePrivacyValueAsync(
2581
2675
  'trace.returnValue',
2582
2676
  sanitizeTraceValueForPrivacy(pending.returnValueRaw),
2583
2677
  params.cfg,
@@ -2585,10 +2679,24 @@ async function materializePendingTraceEvents(
2585
2679
  candidate,
2586
2680
  params.masking,
2587
2681
  activePrivacy,
2588
- ));
2682
+ );
2683
+ evt.returnValue = limitMaterializedTraceInlineValue(returnValueMaterialized);
2684
+ const returnValueCapture = await maybeCaptureTraceValueAsync({
2685
+ target: 'trace.returnValue',
2686
+ rawValue: pending.returnValueRaw,
2687
+ previewValue: evt.returnValue,
2688
+ capture: captureContext,
2689
+ metadata: traceValueMetadata,
2690
+ });
2691
+ if (returnValueCapture?.entry) {
2692
+ attachTraceValueEntry(evt, returnValueCapture.entry);
2693
+ }
2694
+ if (returnValueCapture?.captureRef) {
2695
+ evt.returnValueCapture = returnValueCapture.captureRef;
2696
+ }
2589
2697
  }
2590
2698
  if (pending.errorRaw !== undefined) {
2591
- evt.error = sanitizeMaterializedTraceValue(await materializeTracePrivacyValueAsync(
2699
+ const errorMaterialized = await materializeTracePrivacyValueAsync(
2592
2700
  'trace.error',
2593
2701
  sanitizeTraceValueForPrivacy(pending.errorRaw),
2594
2702
  params.cfg,
@@ -2596,7 +2704,21 @@ async function materializePendingTraceEvents(
2596
2704
  candidate,
2597
2705
  params.masking,
2598
2706
  activePrivacy,
2599
- ));
2707
+ );
2708
+ evt.error = limitMaterializedTraceInlineValue(errorMaterialized);
2709
+ const errorValueCapture = await maybeCaptureTraceValueAsync({
2710
+ target: 'trace.error',
2711
+ rawValue: pending.errorRaw,
2712
+ previewValue: evt.error,
2713
+ capture: captureContext,
2714
+ metadata: traceValueMetadata,
2715
+ });
2716
+ if (errorValueCapture?.entry) {
2717
+ attachTraceValueEntry(evt, errorValueCapture.entry);
2718
+ }
2719
+ if (errorValueCapture?.captureRef) {
2720
+ evt.errorValueCapture = errorValueCapture.captureRef;
2721
+ }
2600
2722
  }
2601
2723
  }
2602
2724
  return events;
@@ -4325,7 +4447,10 @@ function createCapturedValueEntry(
4325
4447
  | 'request.body'
4326
4448
  | 'request.params'
4327
4449
  | 'request.query'
4328
- | 'response.body';
4450
+ | 'response.body'
4451
+ | 'trace.args'
4452
+ | 'trace.returnValue'
4453
+ | 'trace.error';
4329
4454
  rawValue: any;
4330
4455
  previewValue: any;
4331
4456
  capture: FullValueCaptureContext;
@@ -4404,7 +4529,10 @@ async function createCapturedValueEntryAsync(
4404
4529
  | 'request.body'
4405
4530
  | 'request.params'
4406
4531
  | 'request.query'
4407
- | 'response.body';
4532
+ | 'response.body'
4533
+ | 'trace.args'
4534
+ | 'trace.returnValue'
4535
+ | 'trace.error';
4408
4536
  rawValue: any;
4409
4537
  previewValue: any;
4410
4538
  capture: FullValueCaptureContext;
@@ -4504,6 +4632,30 @@ async function maybeCaptureRequestValueAsync(
4504
4632
  return result.captureRef;
4505
4633
  }
4506
4634
 
4635
+ function maybeCaptureTraceValue(
4636
+ params: {
4637
+ target: 'trace.args' | 'trace.returnValue' | 'trace.error';
4638
+ rawValue: any;
4639
+ previewValue: any;
4640
+ capture: FullValueCaptureContext;
4641
+ metadata?: Partial<TraceValueBatchEntry>;
4642
+ },
4643
+ ): { captureRef: TraceValueCaptureRef; entry?: TraceValueBatchEntry } | undefined {
4644
+ return createCapturedValueEntry(params);
4645
+ }
4646
+
4647
+ async function maybeCaptureTraceValueAsync(
4648
+ params: {
4649
+ target: 'trace.args' | 'trace.returnValue' | 'trace.error';
4650
+ rawValue: any;
4651
+ previewValue: any;
4652
+ capture: FullValueCaptureContext;
4653
+ metadata?: Partial<TraceValueBatchEntry>;
4654
+ },
4655
+ ): Promise<{ captureRef: TraceValueCaptureRef; entry?: TraceValueBatchEntry } | undefined> {
4656
+ return await createCapturedValueEntryAsync(params);
4657
+ }
4658
+
4507
4659
  function maybeCaptureDbValue(
4508
4660
  params: {
4509
4661
  target: 'db.pk' | 'db.before' | 'db.after' | 'db.query' | 'db.resultMeta' | 'db.error';
@@ -6222,8 +6374,23 @@ function recordKafkaTraceEvent(
6222
6374
 
6223
6375
  const privacy = getRuntimePrivacyState(cfg).policy ?? null;
6224
6376
  const masking = normalizeMaskingConfig(cfg.masking);
6377
+ const captureContext: FullValueCaptureContext = {
6378
+ runtimeConfig: cfg,
6379
+ captureHeaders: cfg.captureHeaders,
6380
+ maskReq,
6381
+ trace: candidate,
6382
+ masking,
6383
+ privacy,
6384
+ };
6385
+ const traceValueMetadata = {
6386
+ fn: evt.fn,
6387
+ file: evt.file,
6388
+ line: evt.line ?? null,
6389
+ spanId: evt.spanId ?? null,
6390
+ parentSpanId: evt.parentSpanId ?? null,
6391
+ };
6225
6392
  if (!omitJsonBuiltinValues && raw.args !== undefined) {
6226
- evt.args = sanitizeMaterializedTraceValue(materializeTracePrivacyValue(
6393
+ const argsMaterialized = materializeTracePrivacyValue(
6227
6394
  'trace.args',
6228
6395
  sanitizeTraceArgsForPrivacy(raw.args),
6229
6396
  cfg,
@@ -6231,10 +6398,24 @@ function recordKafkaTraceEvent(
6231
6398
  candidate,
6232
6399
  masking,
6233
6400
  privacy,
6234
- ));
6401
+ );
6402
+ evt.args = limitMaterializedTraceInlineValue(argsMaterialized);
6403
+ const argsValueCapture = maybeCaptureTraceValue({
6404
+ target: 'trace.args',
6405
+ rawValue: normalizeRawTraceArgs(raw.args),
6406
+ previewValue: evt.args,
6407
+ capture: captureContext,
6408
+ metadata: traceValueMetadata,
6409
+ });
6410
+ if (argsValueCapture?.entry) {
6411
+ attachTraceValueEntry(evt, argsValueCapture.entry);
6412
+ }
6413
+ if (argsValueCapture?.captureRef) {
6414
+ evt.argsValueCapture = argsValueCapture.captureRef;
6415
+ }
6235
6416
  }
6236
6417
  if (!omitJsonBuiltinValues && raw.returnValue !== undefined) {
6237
- evt.returnValue = sanitizeMaterializedTraceValue(materializeTracePrivacyValue(
6418
+ const returnValueMaterialized = materializeTracePrivacyValue(
6238
6419
  'trace.returnValue',
6239
6420
  sanitizeTraceValueForPrivacy(raw.returnValue),
6240
6421
  cfg,
@@ -6242,10 +6423,24 @@ function recordKafkaTraceEvent(
6242
6423
  candidate,
6243
6424
  masking,
6244
6425
  privacy,
6245
- ));
6426
+ );
6427
+ evt.returnValue = limitMaterializedTraceInlineValue(returnValueMaterialized);
6428
+ const returnValueCapture = maybeCaptureTraceValue({
6429
+ target: 'trace.returnValue',
6430
+ rawValue: raw.returnValue,
6431
+ previewValue: evt.returnValue,
6432
+ capture: captureContext,
6433
+ metadata: traceValueMetadata,
6434
+ });
6435
+ if (returnValueCapture?.entry) {
6436
+ attachTraceValueEntry(evt, returnValueCapture.entry);
6437
+ }
6438
+ if (returnValueCapture?.captureRef) {
6439
+ evt.returnValueCapture = returnValueCapture.captureRef;
6440
+ }
6246
6441
  }
6247
6442
  if (hasMeaningfulRawTraceError(raw.error)) {
6248
- evt.error = sanitizeMaterializedTraceValue(materializeTracePrivacyValue(
6443
+ const errorMaterialized = materializeTracePrivacyValue(
6249
6444
  'trace.error',
6250
6445
  sanitizeTraceValueForPrivacy(raw.error),
6251
6446
  cfg,
@@ -6253,7 +6448,21 @@ function recordKafkaTraceEvent(
6253
6448
  candidate,
6254
6449
  masking,
6255
6450
  privacy,
6256
- ));
6451
+ );
6452
+ evt.error = limitMaterializedTraceInlineValue(errorMaterialized);
6453
+ const errorValueCapture = maybeCaptureTraceValue({
6454
+ target: 'trace.error',
6455
+ rawValue: raw.error,
6456
+ previewValue: evt.error,
6457
+ capture: captureContext,
6458
+ metadata: traceValueMetadata,
6459
+ });
6460
+ if (errorValueCapture?.entry) {
6461
+ attachTraceValueEntry(evt, errorValueCapture.entry);
6462
+ }
6463
+ if (errorValueCapture?.captureRef) {
6464
+ evt.errorValueCapture = errorValueCapture.captureRef;
6465
+ }
6257
6466
  }
6258
6467
  if (raw.threw !== undefined) evt.threw = raw.threw === true;
6259
6468
  if (raw.unawaited !== undefined) evt.unawaited = raw.unawaited === true;
@@ -6362,6 +6571,7 @@ function buildKafkaTraceEntries(
6362
6571
  return batches.map((batch, index) => ({
6363
6572
  actionId: actionId ?? null,
6364
6573
  trace: batch,
6574
+ traceValues: collectBatchTraceValueEntries(batch, index),
6365
6575
  traceBatch: {
6366
6576
  rid: requestRid,
6367
6577
  index,
@@ -7752,4 +7962,5 @@ export const __reproTestHooks = {
7752
7962
  estimateTraceBatchSerializedBytesForTest: estimateTraceBatchSerializedBytes,
7753
7963
  sanitizeTraceValueForPrivacyForTest: sanitizeTraceValueForPrivacy,
7754
7964
  sanitizeMaterializedTraceValueForTest: sanitizeMaterializedTraceValue,
7965
+ getEventTraceValueEntriesForTest: getEventTraceValueEntries,
7755
7966
  };
@@ -3,7 +3,13 @@ import type { IngestClientConfig, LegacyEntry } from './types';
3
3
 
4
4
  const DEFAULT_INGEST_BASE = 'http://localhost:8080';
5
5
  const DEFAULT_INGEST_PATH = '/v1/ingest/events';
6
- const DEFAULT_QUEUE_MAX_ITEMS = 1000;
6
+ const DEFAULT_QUEUE_MAX_ITEMS = (() => {
7
+ const env = Number(typeof process !== 'undefined' ? (process as any)?.env?.REPRO_INGEST_QUEUE_MAX_ITEMS : undefined);
8
+ if (Number.isFinite(env) && env > 0) {
9
+ return Math.trunc(env);
10
+ }
11
+ return 0;
12
+ })();
7
13
  const DEFAULT_QUEUE_MAX_ATTEMPTS = 5;
8
14
  const DEFAULT_QUEUE_BACKOFF_MAX_MS = 4000;
9
15
  const DEFAULT_DEFER_DRAIN_MS = 25;
@@ -177,7 +183,7 @@ export const enqueueIngestEntries = (
177
183
  entries: entries.slice(),
178
184
  attempts: 0,
179
185
  });
180
- if (ingestQueue.length > DEFAULT_QUEUE_MAX_ITEMS) {
186
+ if (DEFAULT_QUEUE_MAX_ITEMS > 0 && ingestQueue.length > DEFAULT_QUEUE_MAX_ITEMS) {
181
187
  ingestQueue.splice(0, ingestQueue.length - DEFAULT_QUEUE_MAX_ITEMS);
182
188
  }
183
189
  scheduleDrain(shouldDeferDrain?.() ? deferDrainMs : 0);
@@ -0,0 +1,82 @@
1
+ const assert = require('assert');
2
+
3
+ const originalFetch = global.fetch;
4
+ const capturedBodies = [];
5
+
6
+ global.fetch = async (_url, init = {}) => {
7
+ capturedBodies.push(JSON.parse(String(init.body || '{}')));
8
+ return {
9
+ ok: true,
10
+ status: 200,
11
+ json: async () => ({ ok: true }),
12
+ text: async () => '{"ok":true}',
13
+ };
14
+ };
15
+
16
+ const { enqueueIngestEntries, flushIngestQueue } = require('../dist/ingest/client');
17
+
18
+ async function main() {
19
+ const cfg = {
20
+ tenantId: 'TENANT_test',
21
+ appId: 'APP_test',
22
+ appSecret: 'secret',
23
+ serviceName: 'queue-retention-test',
24
+ ingestBase: 'http://127.0.0.1:65535',
25
+ };
26
+
27
+ const sessionId = 'S_queue_retention';
28
+ const requestRid = 'R_queue_retention';
29
+ const totalBatches = 1414;
30
+
31
+ for (let index = 0; index < totalBatches; index += 1) {
32
+ enqueueIngestEntries(cfg, sessionId, [{
33
+ actionId: 'A_queue_retention',
34
+ trace: [{
35
+ t: Date.now(),
36
+ type: 'enter',
37
+ fn: `trace-${index}`,
38
+ }],
39
+ traceBatch: {
40
+ rid: requestRid,
41
+ index,
42
+ total: totalBatches,
43
+ },
44
+ t: Date.now(),
45
+ }]);
46
+ }
47
+
48
+ await flushIngestQueue();
49
+
50
+ const traceEvents = capturedBodies.flatMap((body) => Array.isArray(body?.events) ? body.events : [])
51
+ .filter((event) => event?.event_type === 'trace_batch');
52
+
53
+ assert.strictEqual(traceEvents.length, totalBatches, JSON.stringify({
54
+ got: traceEvents.length,
55
+ expected: totalBatches,
56
+ }));
57
+
58
+ const indexes = traceEvents
59
+ .map((event) => Number(event?.payload?.traceBatch?.index))
60
+ .filter((value) => Number.isFinite(value))
61
+ .sort((left, right) => left - right);
62
+
63
+ assert.strictEqual(indexes.length, totalBatches, JSON.stringify({
64
+ got: indexes.length,
65
+ expected: totalBatches,
66
+ }));
67
+ assert.strictEqual(indexes[0], 0);
68
+ assert.strictEqual(indexes[indexes.length - 1], totalBatches - 1);
69
+
70
+ // eslint-disable-next-line no-console
71
+ console.log('ingest queue retention OK');
72
+ }
73
+
74
+ main()
75
+ .catch((error) => {
76
+ // eslint-disable-next-line no-console
77
+ console.error(error);
78
+ process.exitCode = 1;
79
+ })
80
+ .finally(() => {
81
+ global.fetch = originalFetch;
82
+ });
@@ -419,7 +419,19 @@ async function testKafkaProducerCapturesFullPublishedMessageValue() {
419
419
  }
420
420
 
421
421
  function assertNoLargeJsonPreviewArtifacts(value) {
422
- const text = JSON.stringify(value);
422
+ const text = JSON.stringify(value, (key, currentValue) => {
423
+ if (
424
+ key === 'argsValueCapture' ||
425
+ key === 'returnValueCapture' ||
426
+ key === 'errorValueCapture' ||
427
+ key === 'argsMaterialization' ||
428
+ key === 'returnValueMaterialization' ||
429
+ key === 'errorMaterialization'
430
+ ) {
431
+ return undefined;
432
+ }
433
+ return currentValue;
434
+ });
423
435
  assert(!text.includes('__type":"json-string'), text);
424
436
  assert(!text.includes('__truncatedKeys'), text);
425
437
  assert(!text.includes('__truncatedItems'), text);
@@ -540,7 +552,7 @@ async function testJsonBuiltinTracePayloadsAreOmitted() {
540
552
  assertNoLargeJsonPreviewArtifacts(consumedSink[0]);
541
553
  }
542
554
 
543
- async function testLargeStringTraceValuesAreCapturedInlineWithoutTruncation() {
555
+ async function testLargeStringTraceValuesStayIngestibleAndCaptureFullValueOutOfBand() {
544
556
  const hugeMarker = 'HUGE_INLINE_VALUE_MARKER';
545
557
  const hugeJson = JSON.stringify({
546
558
  eventId: 'evt-huge',
@@ -586,13 +598,18 @@ async function testLargeStringTraceValuesAreCapturedInlineWithoutTruncation() {
586
598
  assert.strictEqual(sink[0].fn, 'parseGatewayPayload');
587
599
  assert.strictEqual(typeof sink[0].args[0], 'string');
588
600
  assert(sink[0].args[0].includes(hugeMarker), JSON.stringify(sink[0]));
589
- assert(sink[0].args[0].includes('x'.repeat(140 * 1024)), JSON.stringify(sink[0]));
590
- assert.strictEqual(sink[0].argsMaterialization, undefined, JSON.stringify(sink[0]));
591
- assert.strictEqual(sink[0].argsValueCapture, undefined, JSON.stringify(sink[0]));
592
- assertNoLargeJsonPreviewArtifacts(sink[0]);
601
+ assert(sink[0].args[0].length < 4096, JSON.stringify(sink[0]));
602
+ assert.strictEqual(sink[0].argsValueCapture?.status, 'stored', JSON.stringify(sink[0]));
603
+ const entries = __reproTestHooks.getEventTraceValueEntriesForTest(sink[0]);
604
+ assert(Array.isArray(entries) && entries.length === 1, JSON.stringify(entries));
605
+ assert.strictEqual(entries[0].target, 'trace.args');
606
+ assert.strictEqual(typeof entries[0].value?.[0], 'string');
607
+ assert(entries[0].value[0].includes(hugeMarker), JSON.stringify(entries[0]));
608
+ assert(entries[0].value[0].includes('x'.repeat(140 * 1024)), JSON.stringify(entries[0]));
609
+ assertNoLargeJsonPreviewArtifacts(sink[0].args);
593
610
  }
594
611
 
595
- async function testLargeStructuredTraceValuesAreCapturedInlineWithoutTruncation() {
612
+ async function testLargeStructuredTraceValuesStayIngestibleAndCaptureFullValueOutOfBand() {
596
613
  const hugeMarker = 'HUGE_STRUCTURED_TRACE_VALUE_MARKER';
597
614
  const hugeEnvelope = {
598
615
  eventId: 'evt-huge-structured',
@@ -641,19 +658,29 @@ async function testLargeStructuredTraceValuesAreCapturedInlineWithoutTruncation(
641
658
 
642
659
  assert.strictEqual(sink.length, 1);
643
660
  assert.strictEqual(sink[0].fn, 'recordConsumedEvent');
644
- assert(sink[0].args, 'expected args to be stored inline');
645
- assert.strictEqual(sink[0].args[0].providerResponse.rawNarrative, `${hugeMarker}:${'x'.repeat(40 * 1024)}`);
661
+ assert(sink[0].args, 'expected args preview to be stored inline');
662
+ assert(JSON.stringify(sink[0].args).length < 9000, JSON.stringify(sink[0]).slice(0, 12000));
663
+ assert.strictEqual(
664
+ sink[0].args[0]?.providerResponse?.rawNarrative?.__kind,
665
+ 'omitted',
666
+ JSON.stringify(sink[0]),
667
+ );
646
668
  assert.strictEqual(typeof sink[0].args[2], 'string');
647
- const parsedRawArg = JSON.parse(sink[0].args[2]);
669
+ assert(sink[0].args[2].includes('providerResponse'), JSON.stringify(sink[0]));
670
+ assert.strictEqual(sink[0].argsValueCapture?.status, 'stored', JSON.stringify(sink[0]));
671
+ const entries = __reproTestHooks.getEventTraceValueEntriesForTest(sink[0]);
672
+ assert(Array.isArray(entries) && entries.length === 1, JSON.stringify(entries));
673
+ assert.strictEqual(entries[0].target, 'trace.args');
674
+ assert.strictEqual(entries[0].value?.[0]?.providerResponse?.rawNarrative, `${hugeMarker}:${'x'.repeat(40 * 1024)}`);
675
+ assert.strictEqual(typeof entries[0].value?.[2], 'string');
676
+ const parsedRawArg = JSON.parse(entries[0].value[2]);
648
677
  assert.strictEqual(parsedRawArg.providerResponse.rawNarrative, `${hugeMarker}:${'x'.repeat(40 * 1024)}`);
649
678
  assert.strictEqual(parsedRawArg.providerResponse.credentials.apiKey, '[dropped]');
650
679
  assert.strictEqual(parsedRawArg.providerResponse.credentials.webhookSecret, '[dropped]');
651
- assert.strictEqual(sink[0].argsMaterialization, undefined, JSON.stringify(sink[0]));
652
- assert.strictEqual(sink[0].argsValueCapture, undefined, JSON.stringify(sink[0]));
653
- assertNoLargeJsonPreviewArtifacts(sink[0]);
680
+ assertNoLargeJsonPreviewArtifacts(sink[0].args);
654
681
  }
655
682
 
656
- async function testLargeTraceReturnValueIsCapturedInlineWithoutTruncation() {
683
+ async function testLargeTraceReturnValueStaysIngestibleAndCapturesFullValueOutOfBand() {
657
684
  const hugeMarker = 'HUGE_STRUCTURED_TRACE_RETURN_MARKER';
658
685
  const hugeResponse = {
659
686
  id: 'drill-1',
@@ -698,11 +725,17 @@ async function testLargeTraceReturnValueIsCapturedInlineWithoutTruncation() {
698
725
 
699
726
  assert.strictEqual(sink.length, 1);
700
727
  assert.strictEqual(sink[0].fn, 'toDrillResponse');
701
- assert(sink[0].returnValue, 'expected return value to be stored inline');
702
- assert.strictEqual(sink[0].returnValue.structuredSnapshot.payload, `${hugeMarker}:${'x'.repeat(40 * 1024)}`);
703
- assert.strictEqual(sink[0].returnValueMaterialization, undefined, JSON.stringify(sink[0]));
704
- assert.strictEqual(sink[0].returnValueCapture, undefined, JSON.stringify(sink[0]));
705
- assertNoLargeJsonPreviewArtifacts(sink[0]);
728
+ assert(sink[0].returnValue, 'expected return value preview to be stored inline');
729
+ assert(JSON.stringify(sink[0].returnValue).length < 9000, JSON.stringify(sink[0]).slice(0, 12000));
730
+ assert.strictEqual(typeof sink[0].returnValue?.structuredSnapshot?.payload, 'string');
731
+ assert(sink[0].returnValue.structuredSnapshot.payload.includes(hugeMarker), JSON.stringify(sink[0]));
732
+ assert(sink[0].returnValue.structuredSnapshot.payload.length < 4096, JSON.stringify(sink[0]));
733
+ assert.strictEqual(sink[0].returnValueCapture?.status, 'stored', JSON.stringify(sink[0]));
734
+ const entries = __reproTestHooks.getEventTraceValueEntriesForTest(sink[0]);
735
+ assert(Array.isArray(entries) && entries.length === 1, JSON.stringify(entries));
736
+ assert.strictEqual(entries[0].target, 'trace.returnValue');
737
+ assert.strictEqual(entries[0].value?.structuredSnapshot?.payload, `${hugeMarker}:${'x'.repeat(40 * 1024)}`);
738
+ assertNoLargeJsonPreviewArtifacts(sink[0].returnValue);
706
739
  }
707
740
 
708
741
  async function main() {
@@ -712,9 +745,9 @@ async function main() {
712
745
  await testKafkaTraceFlushIsNotBlockedForeverByActiveHttpRequest();
713
746
  await testLargeHttpResponseBodyIsCapturedWithoutDroppingRequest();
714
747
  await testJsonBuiltinTracePayloadsAreOmitted();
715
- await testLargeStringTraceValuesAreCapturedInlineWithoutTruncation();
716
- await testLargeStructuredTraceValuesAreCapturedInlineWithoutTruncation();
717
- await testLargeTraceReturnValueIsCapturedInlineWithoutTruncation();
748
+ await testLargeStringTraceValuesStayIngestibleAndCaptureFullValueOutOfBand();
749
+ await testLargeStructuredTraceValuesStayIngestibleAndCaptureFullValueOutOfBand();
750
+ await testLargeTraceReturnValueStaysIngestibleAndCapturesFullValueOutOfBand();
718
751
  console.log('kafka runtime privacy policy wiring OK');
719
752
  }
720
753