@reproapp/node-sdk 0.0.45 → 0.0.47

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.js CHANGED
@@ -332,6 +332,7 @@ function refreshDisabledFunctionTraceRules() {
332
332
  let disabledFunctionTraceRules = computeDisabledFunctionTraceRules();
333
333
  let disabledFunctionTypePatterns = [];
334
334
  let disabledTraceFilePatterns = [];
335
+ let excludedInstrumentationFilePatterns = [];
335
336
  let __TRACE_LOG_PREF = null;
336
337
  let __FULL_VALUE_CAPTURE_ENABLED = true;
337
338
  function setInterceptorTracingEnabled(enabled) {
@@ -415,7 +416,7 @@ function matchesRule(rule, event) {
415
416
  const wrapperPattern = rule.wrapper ?? rule.wrapperClass ?? rule.className ?? rule.owner;
416
417
  if (!matchesPattern(event.wrapperClass, wrapperPattern))
417
418
  return false;
418
- if (!matchesPattern(event.file, rule.file))
419
+ if (!matchesTraceFile(event, rule.file))
419
420
  return false;
420
421
  if (!matchesPattern(event.line == null ? null : String(event.line), rule.line))
421
422
  return false;
@@ -430,9 +431,23 @@ function matchesRule(rule, event) {
430
431
  return false;
431
432
  return true;
432
433
  }
434
+ function matchesTraceFile(event, pattern, defaultWhenEmpty = true) {
435
+ if (pattern === undefined || pattern === null)
436
+ return defaultWhenEmpty;
437
+ const candidates = [event.file, event.sourceFile].filter((value) => typeof value === 'string' && value.length > 0);
438
+ if (!candidates.length) {
439
+ return matchesPattern(null, pattern, defaultWhenEmpty);
440
+ }
441
+ return candidates.some((value) => matchesPattern(value, pattern, false));
442
+ }
433
443
  function shouldDropTraceEvent(event) {
434
444
  if (disabledTraceFilePatterns.length) {
435
- if (matchesPattern(event.file, disabledTraceFilePatterns, false)) {
445
+ if (matchesTraceFile(event, disabledTraceFilePatterns, false)) {
446
+ return true;
447
+ }
448
+ }
449
+ if (excludedInstrumentationFilePatterns.length) {
450
+ if (matchesTraceFile(event, excludedInstrumentationFilePatterns, false)) {
436
451
  return true;
437
452
  }
438
453
  }
@@ -599,6 +614,9 @@ function initReproTracing(opts) {
599
614
  if (hasOwn(options, 'disableTraceFiles')) {
600
615
  setDisabledTraceFiles(options.disableTraceFiles ?? null);
601
616
  }
617
+ if (hasOwn(options, 'exclude')) {
618
+ excludedInstrumentationFilePatterns = normalizePatternArray(options.exclude);
619
+ }
602
620
  if (hasOwn(options, 'logFunctionCalls') && typeof options.logFunctionCalls === 'boolean') {
603
621
  setReproTraceLogsEnabled(options.logFunctionCalls);
604
622
  }
@@ -611,7 +629,17 @@ function initReproTracing(opts) {
611
629
  __TRACER__ = tracerPkg;
612
630
  applyTraceLogPreference(tracerPkg);
613
631
  const { disableFunctionTraces: _disableFunctionTraces, disableFunctionTypes: _disableFunctionTypes, disableTraceFiles: _disableTraceFiles, fullValueCapture: _fullValueCapture, logFunctionCalls: _logFunctionCalls, traceInterceptors: _traceInterceptors, ...rest } = options;
614
- const initOpts = { ...defaultTracerInitOpts(), ...rest };
632
+ const defaults = defaultTracerInitOpts();
633
+ const overrides = rest;
634
+ const initOpts = {
635
+ ...defaults,
636
+ ...overrides,
637
+ include: overrides.include ?? defaults.include,
638
+ exclude: [
639
+ ...(defaults.exclude ?? []),
640
+ ...(overrides.exclude ?? []),
641
+ ],
642
+ };
615
643
  tracerPkg.init?.(initOpts);
616
644
  tracerPkg.patchHttp?.();
617
645
  applyTraceLogPreference(tracerPkg);
@@ -736,6 +764,7 @@ function traceEventCandidateFromRecord(event) {
736
764
  fn: event.fn,
737
765
  wrapperClass: inferWrapperClassFromFn(event.fn),
738
766
  file: event.file ?? null,
767
+ sourceFile: sourceFileForLibrary,
739
768
  line: event.line ?? null,
740
769
  depth: event.depth,
741
770
  library: inferLibraryNameFromFile(sourceFileForLibrary ?? event.file),
@@ -788,15 +817,44 @@ function attachSpanContext(target, span) {
788
817
  }
789
818
  return target;
790
819
  }
791
- function createRequestCaptureScope(requestRid, transport) {
820
+ function createRequestCaptureScope(requestRid, transport, cfg) {
821
+ const privacy = cfg ? getRuntimePrivacyState(cfg).policy ?? null : null;
822
+ const privacyMode = cfg && shouldCapturePrivacyRaw(cfg)
823
+ ? 'raw'
824
+ : privacy
825
+ ? 'policy'
826
+ : 'fallback';
792
827
  return {
793
828
  requestRid,
794
829
  transport,
795
- backendRequestCount: 0,
830
+ // Every scope represents one captured transport request. This is an
831
+ // observed expectation, not a count of successful enqueue operations.
832
+ backendRequestCount: 1,
796
833
  traceBatchCount: 0,
797
834
  dbChangeCount: 0,
798
835
  emailEventCount: 0,
836
+ captureFailures: [],
837
+ privacyMode,
838
+ privacyPolicyFingerprint: privacy?.fingerprint,
839
+ privacyStatementCount: privacy?.statements.length ?? 0,
840
+ };
841
+ }
842
+ function recordRequestCaptureFailure(scope, stage, target, reason) {
843
+ if (!scope)
844
+ return;
845
+ const normalized = {
846
+ stage: String(stage || 'capture').slice(0, 80),
847
+ target: String(target || 'unknown').slice(0, 120),
848
+ reason: String(reason || 'capture_failed').slice(0, 120),
799
849
  };
850
+ if (scope.captureFailures.some((failure) => failure.stage === normalized.stage &&
851
+ failure.target === normalized.target &&
852
+ failure.reason === normalized.reason)) {
853
+ return;
854
+ }
855
+ if (scope.captureFailures.length < 25) {
856
+ scope.captureFailures.push(normalized);
857
+ }
800
858
  }
801
859
  function buildRequestCaptureManifestPayload(scope, traceBatchTotal, closedAtMs) {
802
860
  const expectedTraceBatches = Math.max(0, Math.trunc(traceBatchTotal));
@@ -819,6 +877,19 @@ function buildRequestCaptureManifestPayload(scope, traceBatchTotal, closedAtMs)
819
877
  to: expectedTraceBatches - 1,
820
878
  }
821
879
  : undefined,
880
+ privacy: {
881
+ mode: scope.privacyMode,
882
+ status: scope.captureFailures.length > 0
883
+ ? 'failed'
884
+ : scope.privacyMode === 'raw'
885
+ ? 'bypassed'
886
+ : 'applied',
887
+ policyFingerprint: scope.privacyPolicyFingerprint,
888
+ statementCount: scope.privacyStatementCount,
889
+ },
890
+ captureFailures: scope.captureFailures.length
891
+ ? scope.captureFailures.map((failure) => ({ ...failure }))
892
+ : undefined,
822
893
  closedAt,
823
894
  };
824
895
  }
@@ -995,12 +1066,6 @@ const SDK_BACKGROUND_MAX_DEFER_MS = (() => {
995
1066
  return Math.trunc(env);
996
1067
  return Number.POSITIVE_INFINITY;
997
1068
  })();
998
- const SDK_BACKGROUND_WORK_TIMEOUT_MS = (() => {
999
- const env = Number(process.env.REPRO_SDK_BACKGROUND_WORK_TIMEOUT_MS);
1000
- if (Number.isFinite(env) && env >= 0)
1001
- return Math.trunc(env);
1002
- return 20000;
1003
- })();
1004
1069
  const TRACE_MATERIALIZE_OFFTHREAD_ENABLED = (() => {
1005
1070
  const env = String(process.env.REPRO_TRACE_MATERIALIZE_OFFTHREAD || '').trim().toLowerCase();
1006
1071
  if (!env)
@@ -1349,28 +1414,9 @@ function scheduleSdkBackgroundDrain(delayMs = 0) {
1349
1414
  catch { }
1350
1415
  }
1351
1416
  async function runSdkBackgroundWork(work) {
1352
- if (!Number.isFinite(SDK_BACKGROUND_WORK_TIMEOUT_MS) || SDK_BACKGROUND_WORK_TIMEOUT_MS <= 0) {
1353
- await work();
1354
- return;
1355
- }
1356
- let timeout = null;
1357
- const workPromise = Promise.resolve()
1417
+ await Promise.resolve()
1358
1418
  .then(work)
1359
1419
  .catch(() => undefined);
1360
- const timeoutPromise = new Promise((resolve) => {
1361
- timeout = setTimeout(resolve, SDK_BACKGROUND_WORK_TIMEOUT_MS);
1362
- try {
1363
- timeout.unref?.();
1364
- }
1365
- catch { }
1366
- });
1367
- await Promise.race([workPromise, timeoutPromise]);
1368
- if (timeout) {
1369
- try {
1370
- clearTimeout(timeout);
1371
- }
1372
- catch { }
1373
- }
1374
1420
  }
1375
1421
  async function drainQueuedIngestBeforeBackgroundWork() {
1376
1422
  (0, client_1.kickIngestQueueDrain)();
@@ -2229,7 +2275,7 @@ const TRACE_MAX_BATCHES_PER_REQUEST = (() => {
2229
2275
  const env = Number(process.env.REPRO_TRACE_MAX_BATCHES_PER_REQUEST);
2230
2276
  if (Number.isFinite(env) && env > 0)
2231
2277
  return Math.trunc(env);
2232
- return 8;
2278
+ return Math.ceil(TRACE_MAX_EVENTS_PER_REQUEST / TRACE_BATCH_SIZE);
2233
2279
  })();
2234
2280
  const TRACE_FLUSH_DELAY_MS = 20;
2235
2281
  // Choose how to order trace events in payloads.
@@ -2799,7 +2845,7 @@ async function materializePendingTraceEvents(events, params) {
2799
2845
  previewValue: evt.args,
2800
2846
  capture: captureContext,
2801
2847
  metadata: traceValueMetadata,
2802
- });
2848
+ }, params.captureScope);
2803
2849
  if (argsValueCapture?.entry) {
2804
2850
  attachTraceValueEntry(evt, argsValueCapture.entry);
2805
2851
  }
@@ -2821,7 +2867,7 @@ async function materializePendingTraceEvents(events, params) {
2821
2867
  previewValue: evt.returnValue,
2822
2868
  capture: captureContext,
2823
2869
  metadata: traceValueMetadata,
2824
- });
2870
+ }, params.captureScope);
2825
2871
  if (returnValueCapture?.entry) {
2826
2872
  attachTraceValueEntry(evt, returnValueCapture.entry);
2827
2873
  }
@@ -2843,7 +2889,7 @@ async function materializePendingTraceEvents(events, params) {
2843
2889
  previewValue: evt.error,
2844
2890
  capture: captureContext,
2845
2891
  metadata: traceValueMetadata,
2846
- });
2892
+ }, params.captureScope);
2847
2893
  if (errorValueCapture?.entry) {
2848
2894
  attachTraceValueEntry(evt, errorValueCapture.entry);
2849
2895
  }
@@ -2857,8 +2903,21 @@ async function materializePendingTraceEvents(events, params) {
2857
2903
  }
2858
2904
  function stripPendingTraceValues(events, reason) {
2859
2905
  for (const evt of events) {
2860
- if (!evt.__reproPending)
2906
+ const pending = evt.__reproPending;
2907
+ if (!pending)
2861
2908
  continue;
2909
+ if (pending.argsRaw !== undefined) {
2910
+ evt.args = makeOmittedValue(reason);
2911
+ evt.argsValueCapture = { status: 'failed-policy' };
2912
+ }
2913
+ if (pending.returnValueRaw !== undefined) {
2914
+ evt.returnValue = makeOmittedValue(reason);
2915
+ evt.returnValueCapture = { status: 'failed-policy' };
2916
+ }
2917
+ if (pending.errorRaw !== undefined) {
2918
+ evt.error = makeOmittedValue(reason);
2919
+ evt.errorValueCapture = { status: 'failed-policy' };
2920
+ }
2862
2921
  delete evt.__reproPending;
2863
2922
  }
2864
2923
  return events;
@@ -2879,6 +2938,7 @@ async function materializePendingTraceEventsAsync(events, params) {
2879
2938
  });
2880
2939
  }
2881
2940
  catch {
2941
+ recordRequestCaptureFailure(params.captureScope ?? getCtx().captureScope, 'privacy.trace', 'trace.values', 'main_thread_materialization_failed');
2882
2942
  return stripPendingTraceValues(events, 'main_thread_failed');
2883
2943
  }
2884
2944
  }
@@ -2898,9 +2958,6 @@ async function materializePendingTraceEventsAsync(events, params) {
2898
2958
  });
2899
2959
  }
2900
2960
  catch (err) {
2901
- if (isTraceMaterializerTimeoutError(err)) {
2902
- return stripPendingTraceValues(events, 'worker_timeout');
2903
- }
2904
2961
  try {
2905
2962
  return await materializePendingTraceEvents(events, {
2906
2963
  ...params,
@@ -2908,7 +2965,10 @@ async function materializePendingTraceEventsAsync(events, params) {
2908
2965
  });
2909
2966
  }
2910
2967
  catch {
2911
- return stripPendingTraceValues(events, 'worker_failed');
2968
+ recordRequestCaptureFailure(params.captureScope ?? getCtx().captureScope, 'privacy.trace', 'trace.values', isTraceMaterializerTimeoutError(err)
2969
+ ? 'worker_timeout_fallback_failed'
2970
+ : 'worker_and_fallback_failed');
2971
+ return stripPendingTraceValues(events, isTraceMaterializerTimeoutError(err) ? 'worker_timeout_fallback_failed' : 'worker_failed');
2912
2972
  }
2913
2973
  }
2914
2974
  }
@@ -3375,7 +3435,7 @@ function limitRawInlinePrivacyValue(target, value) {
3375
3435
  const limited = limitInlinePrivacyValue(target, value);
3376
3436
  return limited.skipped ? limited : null;
3377
3437
  }
3378
- function materializeInlinePrivacyValue(target, value, cfg, req, trace, masking, privacy, db) {
3438
+ function materializeInlinePrivacyValue(target, value, cfg, req, trace, masking, privacy, db, captureScope) {
3379
3439
  if (value === undefined) {
3380
3440
  return { value };
3381
3441
  }
@@ -3387,6 +3447,7 @@ function materializeInlinePrivacyValue(target, value, cfg, req, trace, masking,
3387
3447
  return { value: valueAfterPrivacy };
3388
3448
  }
3389
3449
  catch {
3450
+ recordRequestCaptureFailure(captureScope ?? getCtx().captureScope, 'privacy.inline', target, 'privacy_transform_failed');
3390
3451
  return {
3391
3452
  value: undefined,
3392
3453
  skipped: {
@@ -3397,7 +3458,7 @@ function materializeInlinePrivacyValue(target, value, cfg, req, trace, masking,
3397
3458
  };
3398
3459
  }
3399
3460
  }
3400
- async function materializeInlinePrivacyValueAsync(target, value, cfg, req, trace, masking, privacy, db) {
3461
+ async function materializeInlinePrivacyValueAsync(target, value, cfg, req, trace, masking, privacy, db, captureScope) {
3401
3462
  if (value === undefined) {
3402
3463
  return { value };
3403
3464
  }
@@ -3409,6 +3470,7 @@ async function materializeInlinePrivacyValueAsync(target, value, cfg, req, trace
3409
3470
  return { value: valueAfterPrivacy };
3410
3471
  }
3411
3472
  catch {
3473
+ recordRequestCaptureFailure(captureScope ?? getCtx().captureScope, 'privacy.inline', target, 'privacy_transform_failed');
3412
3474
  return {
3413
3475
  value: undefined,
3414
3476
  skipped: {
@@ -4301,6 +4363,13 @@ function chunkTraceEventsForTransport(events, requestRid, actionId) {
4301
4363
  if (sizedBatches.length >= TRACE_MAX_BATCHES_PER_REQUEST) {
4302
4364
  break;
4303
4365
  }
4366
+ if (current.length >= TRACE_BATCH_SIZE) {
4367
+ sizedBatches.push(current);
4368
+ current = [];
4369
+ if (sizedBatches.length >= TRACE_MAX_BATCHES_PER_REQUEST) {
4370
+ break;
4371
+ }
4372
+ }
4304
4373
  const candidate = current.concat(event);
4305
4374
  const batchIndex = sizedBatches.length;
4306
4375
  const estimatedBytes = estimateTraceBatchSerializedBytes(candidate, requestRid, actionId, batchIndex);
@@ -4432,8 +4501,15 @@ function maybeCaptureRequestValue(params, sink) {
4432
4501
  }
4433
4502
  return result.captureRef;
4434
4503
  }
4435
- async function maybeCaptureRequestValueAsync(params, sink) {
4436
- const result = await createCapturedValueEntryAsync(params);
4504
+ async function maybeCaptureRequestValueAsync(params, sink, captureScope) {
4505
+ let result;
4506
+ try {
4507
+ result = await createCapturedValueEntryAsync(params);
4508
+ }
4509
+ catch {
4510
+ recordRequestCaptureFailure(captureScope ?? getCtx().captureScope, 'privacy.full_value', params.target, 'privacy_transform_failed');
4511
+ return { status: 'failed-policy' };
4512
+ }
4437
4513
  if (!result)
4438
4514
  return undefined;
4439
4515
  if (result.entry) {
@@ -4444,8 +4520,14 @@ async function maybeCaptureRequestValueAsync(params, sink) {
4444
4520
  function maybeCaptureTraceValue(params) {
4445
4521
  return createCapturedValueEntry(params);
4446
4522
  }
4447
- async function maybeCaptureTraceValueAsync(params) {
4448
- return await createCapturedValueEntryAsync(params);
4523
+ async function maybeCaptureTraceValueAsync(params, captureScope) {
4524
+ try {
4525
+ return await createCapturedValueEntryAsync(params);
4526
+ }
4527
+ catch {
4528
+ recordRequestCaptureFailure(captureScope ?? getCtx().captureScope, 'privacy.full_value', params.target, 'privacy_transform_failed');
4529
+ return { captureRef: { status: 'failed-policy' } };
4530
+ }
4449
4531
  }
4450
4532
  function maybeCaptureDbValue(params, sink) {
4451
4533
  const result = createCapturedValueEntry(params);
@@ -4456,8 +4538,15 @@ function maybeCaptureDbValue(params, sink) {
4456
4538
  }
4457
4539
  return result.captureRef;
4458
4540
  }
4459
- async function maybeCaptureDbValueAsync(params, sink) {
4460
- const result = await createCapturedValueEntryAsync(params);
4541
+ async function maybeCaptureDbValueAsync(params, sink, captureScope) {
4542
+ let result;
4543
+ try {
4544
+ result = await createCapturedValueEntryAsync(params);
4545
+ }
4546
+ catch {
4547
+ recordRequestCaptureFailure(captureScope ?? getCtx().captureScope, 'privacy.full_value', params.target, 'privacy_transform_failed');
4548
+ return { status: 'failed-policy' };
4549
+ }
4461
4550
  if (!result)
4462
4551
  return undefined;
4463
4552
  if (result.entry) {
@@ -4483,15 +4572,22 @@ function buildResultMetaFullValueCandidates(resultMetaRaw, resultMetaSource) {
4483
4572
  pushUniqueCaptureCandidate(candidates, resultMetaSource);
4484
4573
  return candidates;
4485
4574
  }
4486
- async function maybeCaptureDbValueFromCandidatesAsync(params, sink) {
4575
+ async function maybeCaptureDbValueFromCandidatesAsync(params, sink, captureScope) {
4487
4576
  let skippedRef;
4488
4577
  for (const rawValue of params.rawValues) {
4489
- const result = await createCapturedValueEntryAsync({
4490
- target: params.target,
4491
- rawValue,
4492
- previewValue: params.previewValue,
4493
- capture: params.capture,
4494
- });
4578
+ let result;
4579
+ try {
4580
+ result = await createCapturedValueEntryAsync({
4581
+ target: params.target,
4582
+ rawValue,
4583
+ previewValue: params.previewValue,
4584
+ capture: params.capture,
4585
+ });
4586
+ }
4587
+ catch {
4588
+ recordRequestCaptureFailure(captureScope ?? getCtx().captureScope, 'privacy.full_value', params.target, 'privacy_transform_failed');
4589
+ return { status: 'failed-policy' };
4590
+ }
4495
4591
  if (!result)
4496
4592
  continue;
4497
4593
  if (result.entry) {
@@ -4661,7 +4757,7 @@ function reproMiddleware(cfg) {
4661
4757
  const resolvePrivacy = () => getRuntimePrivacyState(captureCfg).policy ?? null;
4662
4758
  const clockSkewMs = headerTs !== null ? headerTs - requestStartRaw : 0;
4663
4759
  const rid = nextRequestId(requestEpochMs);
4664
- const captureScope = createRequestCaptureScope(rid, 'http');
4760
+ const captureScope = createRequestCaptureScope(rid, 'http', captureCfg);
4665
4761
  emitRequestCaptureStart(captureCfg, sid, aid, captureScope, requestEpochMs);
4666
4762
  const t0 = requestStartRaw;
4667
4763
  const url = req.originalUrl || req.url || '/';
@@ -4864,15 +4960,18 @@ function reproMiddleware(cfg) {
4864
4960
  const activePrivacy = resolvePrivacy();
4865
4961
  const requestBodyRaw = req.body;
4866
4962
  const requestBodyMaterialization = limitRawInlinePrivacyValue('request.body', requestBodyRaw)
4867
- ?? await materializeInlinePrivacyValueAsync('request.body', sanitizeRequestSnapshot(requestBodyRaw), captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4963
+ ?? await materializeInlinePrivacyValueAsync('request.body', sanitizeRequestSnapshot(requestBodyRaw), captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy, undefined, captureScope);
4868
4964
  const requestBody = requestBodyMaterialization.value;
4869
- const requestParams = await applyPrivacyThenMaskAsync('request.params', sanitizeRequestSnapshot(req.params), captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4870
- const requestQuery = await applyPrivacyThenMaskAsync('request.query', sanitizeRequestSnapshot(req.query), captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4871
- const maskedHeaders = await applyPrivacyThenMaskAsync('request.headers', requestHeaders, captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4965
+ const requestParamsMaterialization = await materializeInlinePrivacyValueAsync('request.params', sanitizeRequestSnapshot(req.params), captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy, undefined, captureScope);
4966
+ const requestParams = requestParamsMaterialization.value;
4967
+ const requestQueryMaterialization = await materializeInlinePrivacyValueAsync('request.query', sanitizeRequestSnapshot(req.query), captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy, undefined, captureScope);
4968
+ const requestQuery = requestQueryMaterialization.value;
4969
+ const requestHeadersMaterialization = await materializeInlinePrivacyValueAsync('request.headers', requestHeaders, captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy, undefined, captureScope);
4970
+ const maskedHeaders = requestHeadersMaterialization.value;
4872
4971
  const responseBodyMaterialization = capturedBody === undefined
4873
4972
  ? { value: undefined }
4874
4973
  : limitRawInlinePrivacyValue('response.body', capturedBody)
4875
- ?? await materializeInlinePrivacyValueAsync('response.body', sanitizeRequestSnapshot(capturedBody), captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4974
+ ?? await materializeInlinePrivacyValueAsync('response.body', sanitizeRequestSnapshot(capturedBody), captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy, undefined, captureScope);
4876
4975
  const responseBody = responseBodyMaterialization.value;
4877
4976
  const requestValueEntries = [];
4878
4977
  const bodyValueCapture = requestBodyRaw === undefined
@@ -4889,7 +4988,7 @@ function reproMiddleware(cfg) {
4889
4988
  masking,
4890
4989
  privacy: activePrivacy,
4891
4990
  },
4892
- }, requestValueEntries);
4991
+ }, requestValueEntries, captureScope);
4893
4992
  const paramsValueCapture = await maybeCaptureRequestValueAsync({
4894
4993
  target: 'request.params',
4895
4994
  rawValue: req.params,
@@ -4902,7 +5001,7 @@ function reproMiddleware(cfg) {
4902
5001
  masking,
4903
5002
  privacy: activePrivacy,
4904
5003
  },
4905
- }, requestValueEntries);
5004
+ }, requestValueEntries, captureScope);
4906
5005
  const queryValueCapture = await maybeCaptureRequestValueAsync({
4907
5006
  target: 'request.query',
4908
5007
  rawValue: req.query,
@@ -4915,7 +5014,7 @@ function reproMiddleware(cfg) {
4915
5014
  masking,
4916
5015
  privacy: activePrivacy,
4917
5016
  },
4918
- }, requestValueEntries);
5017
+ }, requestValueEntries, captureScope);
4919
5018
  const headersValueCapture = await maybeCaptureRequestValueAsync({
4920
5019
  target: 'request.headers',
4921
5020
  rawValue: req.headers,
@@ -4928,7 +5027,7 @@ function reproMiddleware(cfg) {
4928
5027
  masking,
4929
5028
  privacy: activePrivacy,
4930
5029
  },
4931
- }, requestValueEntries);
5030
+ }, requestValueEntries, captureScope);
4932
5031
  const respBodyValueCapture = capturedBody === undefined
4933
5032
  ? undefined
4934
5033
  : await maybeCaptureRequestValueAsync({
@@ -4943,7 +5042,7 @@ function reproMiddleware(cfg) {
4943
5042
  masking,
4944
5043
  privacy: activePrivacy,
4945
5044
  },
4946
- }, requestValueEntries);
5045
+ }, requestValueEntries, captureScope);
4947
5046
  const responseDurationMs = finishedAt !== null && Number.isFinite(finishedAt)
4948
5047
  ? Math.max(0, finishedAt - t0)
4949
5048
  : Date.now() - t0;
@@ -5005,10 +5104,38 @@ function reproMiddleware(cfg) {
5005
5104
  t: requestEpochMs,
5006
5105
  }]
5007
5106
  });
5008
- captureScope.backendRequestCount += 1;
5009
5107
  }
5010
5108
  catch {
5011
- // never break user code
5109
+ recordRequestCaptureFailure(captureScope, 'request.capture', 'backend_request', 'request_materialization_failed');
5110
+ // Preserve non-sensitive request metadata so the manifest cannot
5111
+ // silently turn a failed policy capture into a zero-request scope.
5112
+ post(captureCfg, sid, {
5113
+ entries: [{
5114
+ actionId: aid,
5115
+ request: {
5116
+ rid,
5117
+ method: req.method,
5118
+ url,
5119
+ path,
5120
+ status: res.statusCode,
5121
+ durMs: finishedAt !== null && Number.isFinite(finishedAt)
5122
+ ? Math.max(0, finishedAt - t0)
5123
+ : Date.now() - t0,
5124
+ key,
5125
+ bodyMaterialization: {
5126
+ status: 'skipped',
5127
+ reason: 'request_materialization_failed',
5128
+ target: 'request.body',
5129
+ },
5130
+ respBodyMaterialization: {
5131
+ status: 'skipped',
5132
+ reason: 'request_materialization_failed',
5133
+ target: 'response.body',
5134
+ },
5135
+ },
5136
+ t: requestEpochMs,
5137
+ }],
5138
+ });
5012
5139
  }
5013
5140
  };
5014
5141
  const emitRequestCaptureManifest = (traceBatchTotal) => {
@@ -5122,6 +5249,7 @@ function reproMiddleware(cfg) {
5122
5249
  maskReq,
5123
5250
  masking,
5124
5251
  resolvePrivacy,
5252
+ captureScope,
5125
5253
  });
5126
5254
  const baseEvents = balanceTraceEvents(materializedEvents.slice());
5127
5255
  const orderedEvents = TRACE_ORDER_MODE === 'tree'
@@ -5136,12 +5264,14 @@ function reproMiddleware(cfg) {
5136
5264
  }
5137
5265
  }
5138
5266
  catch {
5267
+ recordRequestCaptureFailure(captureScope, 'trace.capture', 'trace.batches', 'trace_capture_failed');
5139
5268
  captureScope.traceBatchCount = 0;
5140
5269
  traceBatches = [];
5141
5270
  }
5142
- finally {
5143
- emitRequestCaptureManifest(traceBatches.length);
5144
- }
5271
+ // Finalize policy/materialization status before publishing the
5272
+ // high-priority manifest. The manifest must lead its evidence
5273
+ // on the wire so a large trace queue cannot starve completion.
5274
+ emitRequestCaptureManifest(traceBatches.length);
5145
5275
  try {
5146
5276
  for (let i = 0; i < traceBatches.length; i++) {
5147
5277
  const batch = traceBatches[i];
@@ -5162,7 +5292,7 @@ function reproMiddleware(cfg) {
5162
5292
  }
5163
5293
  }
5164
5294
  catch {
5165
- // never break user code
5295
+ recordRequestCaptureFailure(captureScope, 'trace.enqueue', 'trace.batches', 'trace_enqueue_failed');
5166
5296
  }
5167
5297
  };
5168
5298
  }
@@ -5729,6 +5859,11 @@ function emitDbQuery(cfg, sid, aid, payload) {
5729
5859
  const spanContext = payload?.spanContext ?? captureSpanContextFromTracer();
5730
5860
  if (!shouldCaptureDbSpan(spanContext))
5731
5861
  return;
5862
+ if (sid && ctx.captureScope) {
5863
+ // Count the observed database operation before asynchronous privacy
5864
+ // materialization. A later failure must remain visible to completeness.
5865
+ ctx.captureScope.dbChangeCount += 1;
5866
+ }
5732
5867
  const runCtx = {
5733
5868
  sid: ctx.sid,
5734
5869
  aid: ctx.aid,
@@ -5740,7 +5875,9 @@ function emitDbQuery(cfg, sid, aid, payload) {
5740
5875
  };
5741
5876
  scheduleSdkBackgroundWork(async () => {
5742
5877
  await als.run(runCtx, async () => {
5743
- await emitDbQueryAsync(cfg, sid, aid, payload).catch(() => undefined);
5878
+ await emitDbQueryAsync(cfg, sid, aid, payload).catch(() => {
5879
+ recordRequestCaptureFailure(runCtx.captureScope, 'db.capture', 'db.change', 'db_capture_failed');
5880
+ });
5744
5881
  });
5745
5882
  }, { priority: 'high' });
5746
5883
  }
@@ -5856,8 +5993,8 @@ async function emitDbQueryAsync(cfg, sid, aid, payload) {
5856
5993
  t: payload.t,
5857
5994
  }]
5858
5995
  });
5859
- if (enqueued && ctx.captureScope) {
5860
- ctx.captureScope.dbChangeCount += 1;
5996
+ if (!enqueued) {
5997
+ recordRequestCaptureFailure(ctx.captureScope, 'db.enqueue', 'db.change', 'db_enqueue_failed');
5861
5998
  }
5862
5999
  }
5863
6000
  function buildMinimalUpdate(before, after) {
@@ -6264,8 +6401,8 @@ function cloneKafkaTelemetryValue(value) {
6264
6401
  return value;
6265
6402
  }
6266
6403
  }
6267
- async function materializeKafkaRequestBody(cfg, maskReq, body) {
6268
- return materializeInlinePrivacyValueAsync('request.body', sanitizeInlinePrivacyValue(body), cfg, maskReq, null, normalizeMaskingConfig(cfg.masking), getRuntimePrivacyState(cfg).policy ?? null);
6404
+ async function materializeKafkaRequestBody(cfg, maskReq, body, captureScope) {
6405
+ return materializeInlinePrivacyValueAsync('request.body', sanitizeInlinePrivacyValue(body), cfg, maskReq, null, normalizeMaskingConfig(cfg.masking), getRuntimePrivacyState(cfg).policy ?? null, undefined, captureScope);
6269
6406
  }
6270
6407
  function summarizeKafkaError(error) {
6271
6408
  const message = typeof error?.message === 'string'
@@ -6336,6 +6473,7 @@ function recordKafkaTraceEvent(raw, sink, cfg, maskReq) {
6336
6473
  fn: raw.fn,
6337
6474
  wrapperClass: inferWrapperClassFromFn(raw.fn),
6338
6475
  file: raw.file ?? null,
6476
+ sourceFile: sourceFileForLibrary,
6339
6477
  line: typeof raw.line === 'number' ? raw.line : null,
6340
6478
  depth: typeof raw.depth === 'number' ? raw.depth : undefined,
6341
6479
  library: inferLibraryNameFromFile(sourceFileForLibrary ?? raw.file),
@@ -6441,6 +6579,7 @@ function buildPendingKafkaTraceEvent(raw) {
6441
6579
  fn: raw.fn,
6442
6580
  wrapperClass: inferWrapperClassFromFn(raw.fn),
6443
6581
  file: raw.file ?? null,
6582
+ sourceFile: sourceFileForLibrary,
6444
6583
  line: typeof raw.line === 'number' ? raw.line : null,
6445
6584
  depth: typeof raw.depth === 'number' ? raw.depth : undefined,
6446
6585
  library: inferLibraryNameFromFile(sourceFileForLibrary ?? raw.file),
@@ -6536,23 +6675,27 @@ function scheduleKafkaTraceFlush(cfg, kafkaCtx, actionId, requestRid, rawTraceEv
6536
6675
  if (evt)
6537
6676
  pendingEvents.push(evt);
6538
6677
  }
6539
- if (!pendingEvents.length)
6540
- return;
6541
- const masking = normalizeMaskingConfig(cfg.masking);
6542
- const preparedEvents = preparePendingTraceEventsForFlush(pendingEvents);
6543
- const materializedEvents = await materializePendingTraceEventsAsync(preparedEvents, {
6544
- cfg,
6545
- maskReq,
6546
- masking,
6547
- resolvePrivacy: () => getRuntimePrivacyState(cfg).policy ?? null,
6548
- });
6549
- const entries = buildKafkaTraceEntries(actionId, requestRid, materializedEvents);
6678
+ let entries = [];
6679
+ try {
6680
+ if (pendingEvents.length) {
6681
+ const masking = normalizeMaskingConfig(cfg.masking);
6682
+ const preparedEvents = preparePendingTraceEventsForFlush(pendingEvents);
6683
+ const materializedEvents = await materializePendingTraceEventsAsync(preparedEvents, {
6684
+ cfg,
6685
+ maskReq,
6686
+ masking,
6687
+ resolvePrivacy: () => getRuntimePrivacyState(cfg).policy ?? null,
6688
+ captureScope,
6689
+ });
6690
+ entries = buildKafkaTraceEntries(actionId, requestRid, materializedEvents);
6691
+ }
6692
+ }
6693
+ catch {
6694
+ recordRequestCaptureFailure(captureScope, 'trace.capture', 'trace.batches', 'trace_capture_failed');
6695
+ }
6550
6696
  if (captureScope) {
6551
6697
  captureScope.traceBatchCount = entries.length;
6552
6698
  }
6553
- if (entries.length) {
6554
- post(cfg, sid, { entries });
6555
- }
6556
6699
  if (captureScope) {
6557
6700
  post(cfg, sid, {
6558
6701
  entries: [{
@@ -6561,7 +6704,10 @@ function scheduleKafkaTraceFlush(cfg, kafkaCtx, actionId, requestRid, rawTraceEv
6561
6704
  requestCaptureManifest: buildRequestCaptureManifestPayload(captureScope, entries.length),
6562
6705
  t: alignedNow(),
6563
6706
  }],
6564
- });
6707
+ }, { priority: 'high' });
6708
+ }
6709
+ if (entries.length) {
6710
+ post(cfg, sid, { entries });
6565
6711
  }
6566
6712
  });
6567
6713
  }, { priority: 'high' });
@@ -6579,7 +6725,7 @@ function patchKafkaProducerInstance(producer, cfg) {
6579
6725
  const aid = ctx.aid ?? fallback.aid ?? null;
6580
6726
  const span = captureSpanContextFromTracer();
6581
6727
  const publishRid = nextRequestId(alignedNow());
6582
- const captureScope = createRequestCaptureScope(publishRid, 'kafka');
6728
+ const captureScope = createRequestCaptureScope(publishRid, 'kafka', cfg);
6583
6729
  emitRequestCaptureStart(cfg, sid, aid, captureScope);
6584
6730
  const payloadObj = payload && typeof payload === 'object' ? payload : {};
6585
6731
  const topic = sanitizeKafkaTopic(payloadObj.topic);
@@ -6638,7 +6784,7 @@ function patchKafkaProducerInstance(producer, cfg) {
6638
6784
  traceId: span?.traceId ?? null,
6639
6785
  spanId: span?.spanId ?? null,
6640
6786
  parentSpanId: span?.parentSpanId ?? null,
6641
- });
6787
+ }, captureScope);
6642
6788
  const requestPayload = {
6643
6789
  rid: publishRid,
6644
6790
  method: 'KAFKA_PUBLISH',
@@ -6667,7 +6813,6 @@ function patchKafkaProducerInstance(producer, cfg) {
6667
6813
  },
6668
6814
  ],
6669
6815
  });
6670
- captureScope.backendRequestCount += 1;
6671
6816
  post(cfg, sid, {
6672
6817
  entries: [{
6673
6818
  actionId: aid ?? null,
@@ -6688,7 +6833,7 @@ function patchKafkaProducerInstance(producer, cfg) {
6688
6833
  const aid = ctx.aid ?? fallback.aid ?? null;
6689
6834
  const span = captureSpanContextFromTracer();
6690
6835
  const publishRid = nextRequestId(alignedNow());
6691
- const captureScope = createRequestCaptureScope(publishRid, 'kafka');
6836
+ const captureScope = createRequestCaptureScope(publishRid, 'kafka', cfg);
6692
6837
  emitRequestCaptureStart(cfg, sid, aid, captureScope);
6693
6838
  const payloadObj = payload && typeof payload === 'object' ? payload : {};
6694
6839
  const topicMessages = Array.isArray(payloadObj.topicMessages)
@@ -6778,7 +6923,7 @@ function patchKafkaProducerInstance(producer, cfg) {
6778
6923
  traceId: span?.traceId ?? null,
6779
6924
  spanId: span?.spanId ?? null,
6780
6925
  parentSpanId: span?.parentSpanId ?? null,
6781
- });
6926
+ }, captureScope);
6782
6927
  const requestPayload = {
6783
6928
  rid: publishRid,
6784
6929
  method: 'KAFKA_PUBLISH_BATCH',
@@ -6807,7 +6952,6 @@ function patchKafkaProducerInstance(producer, cfg) {
6807
6952
  },
6808
6953
  ],
6809
6954
  });
6810
- captureScope.backendRequestCount += 1;
6811
6955
  post(cfg, sid, {
6812
6956
  entries: [{
6813
6957
  actionId: aid ?? null,
@@ -6830,7 +6974,7 @@ function wrapKafkaEachMessageHandler(eachMessage, cfg, consumer) {
6830
6974
  const aid = runtime.aid;
6831
6975
  const consumeRid = nextRequestId(`${topic}-${message?.offset ?? alignedNow()}`);
6832
6976
  const traceId = consumeRid;
6833
- const captureScope = createRequestCaptureScope(consumeRid, 'kafka');
6977
+ const captureScope = createRequestCaptureScope(consumeRid, 'kafka', cfg);
6834
6978
  emitRequestCaptureStart(cfg, sid, aid, captureScope);
6835
6979
  const rawTraceEvents = [];
6836
6980
  const maskReq = {
@@ -6908,7 +7052,7 @@ function wrapKafkaEachMessageHandler(eachMessage, cfg, consumer) {
6908
7052
  valueEncoding: messagePayload.valueEncoding,
6909
7053
  valueBytes: messagePayload.valueBytes,
6910
7054
  message: messagePayload,
6911
- });
7055
+ }, captureScope);
6912
7056
  const requestPayload = {
6913
7057
  rid: consumeRid,
6914
7058
  method: 'KAFKA_CONSUME',
@@ -6929,7 +7073,6 @@ function wrapKafkaEachMessageHandler(eachMessage, cfg, consumer) {
6929
7073
  t: alignedNow(),
6930
7074
  }],
6931
7075
  });
6932
- captureScope.backendRequestCount += 1;
6933
7076
  scheduleKafkaTraceFlush(cfg, kafkaCtx, aid, consumeRid, rawTraceEvents, maskReq, captureScope);
6934
7077
  }
6935
7078
  };
@@ -6945,7 +7088,7 @@ function wrapKafkaEachBatchHandler(eachBatch, cfg, consumer) {
6945
7088
  const aid = runtime.aid;
6946
7089
  const consumeRid = nextRequestId(`${topic}-batch-${alignedNow()}`);
6947
7090
  const traceId = consumeRid;
6948
- const captureScope = createRequestCaptureScope(consumeRid, 'kafka');
7091
+ const captureScope = createRequestCaptureScope(consumeRid, 'kafka', cfg);
6949
7092
  emitRequestCaptureStart(cfg, sid, aid, captureScope);
6950
7093
  const rawTraceEvents = [];
6951
7094
  const maskReq = {
@@ -7021,7 +7164,7 @@ function wrapKafkaEachBatchHandler(eachBatch, cfg, consumer) {
7021
7164
  parentSpanId: runtime.parentSpanId,
7022
7165
  firstMessageKey: firstMessagePayload?.key ?? null,
7023
7166
  messages: messages.map((message) => buildKafkaMessagePayload(message)),
7024
- });
7167
+ }, captureScope);
7025
7168
  const requestPayload = {
7026
7169
  rid: consumeRid,
7027
7170
  method: 'KAFKA_CONSUME_BATCH',
@@ -7042,7 +7185,6 @@ function wrapKafkaEachBatchHandler(eachBatch, cfg, consumer) {
7042
7185
  t: alignedNow(),
7043
7186
  }],
7044
7187
  });
7045
- captureScope.backendRequestCount += 1;
7046
7188
  scheduleKafkaTraceFlush(cfg, kafkaCtx, aid, consumeRid, rawTraceEvents, maskReq, captureScope);
7047
7189
  }
7048
7190
  };
@@ -7422,24 +7564,24 @@ function scheduleAmqpTraceFlush(cfg, amqpCtx, actionId, requestRid, rawTraceEven
7422
7564
  pendingEvents.push(evt);
7423
7565
  }
7424
7566
  let entries = [];
7425
- if (pendingEvents.length) {
7426
- const masking = normalizeMaskingConfig(cfg.masking);
7427
- const preparedEvents = preparePendingTraceEventsForFlush(pendingEvents);
7428
- const materializedEvents = await materializePendingTraceEventsAsync(preparedEvents, {
7429
- cfg,
7430
- maskReq,
7431
- masking,
7432
- resolvePrivacy: () => getRuntimePrivacyState(cfg).policy ?? null,
7433
- });
7434
- entries = buildAmqpTraceEntries(actionId, requestRid, materializedEvents);
7435
- captureScope.traceBatchCount = entries.length;
7436
- if (entries.length) {
7437
- post(cfg, sid, { entries });
7567
+ try {
7568
+ if (pendingEvents.length) {
7569
+ const masking = normalizeMaskingConfig(cfg.masking);
7570
+ const preparedEvents = preparePendingTraceEventsForFlush(pendingEvents);
7571
+ const materializedEvents = await materializePendingTraceEventsAsync(preparedEvents, {
7572
+ cfg,
7573
+ maskReq,
7574
+ masking,
7575
+ resolvePrivacy: () => getRuntimePrivacyState(cfg).policy ?? null,
7576
+ captureScope,
7577
+ });
7578
+ entries = buildAmqpTraceEntries(actionId, requestRid, materializedEvents);
7438
7579
  }
7439
7580
  }
7440
- else {
7441
- captureScope.traceBatchCount = 0;
7581
+ catch {
7582
+ recordRequestCaptureFailure(captureScope, 'trace.capture', 'trace.batches', 'trace_capture_failed');
7442
7583
  }
7584
+ captureScope.traceBatchCount = entries.length;
7443
7585
  post(cfg, sid, {
7444
7586
  entries: [{
7445
7587
  actionId: actionId ?? null,
@@ -7447,7 +7589,10 @@ function scheduleAmqpTraceFlush(cfg, amqpCtx, actionId, requestRid, rawTraceEven
7447
7589
  requestCaptureManifest: buildRequestCaptureManifestPayload(captureScope, entries.length),
7448
7590
  t: alignedNow(),
7449
7591
  }],
7450
- });
7592
+ }, { priority: 'high' });
7593
+ if (entries.length) {
7594
+ post(cfg, sid, { entries });
7595
+ }
7451
7596
  });
7452
7597
  }, { priority: 'high' });
7453
7598
  }
@@ -7493,7 +7638,7 @@ function scheduleAmqpPublishCapture(params) {
7493
7638
  parentSpanId: params.span?.parentSpanId ?? null,
7494
7639
  };
7495
7640
  scheduleSdkBackgroundWork(async () => {
7496
- const bodyMaterialization = await materializeKafkaRequestBody(params.cfg, maskReq, body);
7641
+ const bodyMaterialization = await materializeKafkaRequestBody(params.cfg, maskReq, body, params.captureScope);
7497
7642
  const requestPayload = {
7498
7643
  rid: params.publishRid,
7499
7644
  method: params.method,
@@ -7523,7 +7668,6 @@ function scheduleAmqpPublishCapture(params) {
7523
7668
  t: alignedNow(),
7524
7669
  }],
7525
7670
  });
7526
- params.captureScope.backendRequestCount += 1;
7527
7671
  post(params.cfg, params.sid, {
7528
7672
  entries: [{
7529
7673
  actionId: params.aid ?? null,
@@ -7574,7 +7718,7 @@ function patchAmqplibPublishMethod(channel, cfg, methodName) {
7574
7718
  : `amqp://queue/${queue}`;
7575
7719
  const key = `${method} ${methodName === 'publish' ? `${exchange || 'default'}/${routingKey}` : queue}`;
7576
7720
  const content = methodName === 'publish' ? args[2] : args[1];
7577
- const captureScope = createRequestCaptureScope(publishRid, 'amqp');
7721
+ const captureScope = createRequestCaptureScope(publishRid, 'amqp', cfg);
7578
7722
  emitRequestCaptureStart(cfg, sid, aid, captureScope);
7579
7723
  const t0 = Date.now();
7580
7724
  let finished = false;
@@ -7661,7 +7805,7 @@ function wrapAmqplibConsumeHandler(onMessage, cfg, queue) {
7661
7805
  const deliveryTag = message?.fields?.deliveryTag ?? alignedNow();
7662
7806
  const consumeRid = nextRequestId(`${queueName}-${deliveryTag}`);
7663
7807
  const traceId = consumeRid;
7664
- const captureScope = createRequestCaptureScope(consumeRid, 'amqp');
7808
+ const captureScope = createRequestCaptureScope(consumeRid, 'amqp', cfg);
7665
7809
  emitRequestCaptureStart(cfg, sid, aid, captureScope);
7666
7810
  const rawTraceEvents = [];
7667
7811
  const maskReq = {
@@ -7751,7 +7895,7 @@ function wrapAmqplibConsumeHandler(onMessage, cfg, queue) {
7751
7895
  properties: buildAmqpMessagePropertiesPayload(properties),
7752
7896
  };
7753
7897
  scheduleSdkBackgroundWork(async () => {
7754
- const bodyMaterialization = await materializeKafkaRequestBody(cfg, maskReq, body);
7898
+ const bodyMaterialization = await materializeKafkaRequestBody(cfg, maskReq, body, captureScope);
7755
7899
  const requestPayload = {
7756
7900
  rid: consumeRid,
7757
7901
  method: 'AMQP_CONSUME',
@@ -7772,7 +7916,6 @@ function wrapAmqplibConsumeHandler(onMessage, cfg, queue) {
7772
7916
  t: alignedNow(),
7773
7917
  }],
7774
7918
  });
7775
- captureScope.backendRequestCount += 1;
7776
7919
  scheduleAmqpTraceFlush(cfg, amqpCtx, aid, consumeRid, rawTraceEvents, maskReq, captureScope);
7777
7920
  }, { priority: 'high' });
7778
7921
  };