@reproapp/node-sdk 0.0.46 → 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
@@ -817,16 +817,45 @@ function attachSpanContext(target, span) {
817
817
  }
818
818
  return target;
819
819
  }
820
- 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';
821
827
  return {
822
828
  requestRid,
823
829
  transport,
824
- 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,
825
833
  traceBatchCount: 0,
826
834
  dbChangeCount: 0,
827
835
  emailEventCount: 0,
836
+ captureFailures: [],
837
+ privacyMode,
838
+ privacyPolicyFingerprint: privacy?.fingerprint,
839
+ privacyStatementCount: privacy?.statements.length ?? 0,
828
840
  };
829
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),
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
+ }
858
+ }
830
859
  function buildRequestCaptureManifestPayload(scope, traceBatchTotal, closedAtMs) {
831
860
  const expectedTraceBatches = Math.max(0, Math.trunc(traceBatchTotal));
832
861
  const closedAt = typeof closedAtMs === 'number' && Number.isFinite(closedAtMs)
@@ -848,6 +877,19 @@ function buildRequestCaptureManifestPayload(scope, traceBatchTotal, closedAtMs)
848
877
  to: expectedTraceBatches - 1,
849
878
  }
850
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,
851
893
  closedAt,
852
894
  };
853
895
  }
@@ -1024,12 +1066,6 @@ const SDK_BACKGROUND_MAX_DEFER_MS = (() => {
1024
1066
  return Math.trunc(env);
1025
1067
  return Number.POSITIVE_INFINITY;
1026
1068
  })();
1027
- const SDK_BACKGROUND_WORK_TIMEOUT_MS = (() => {
1028
- const env = Number(process.env.REPRO_SDK_BACKGROUND_WORK_TIMEOUT_MS);
1029
- if (Number.isFinite(env) && env >= 0)
1030
- return Math.trunc(env);
1031
- return 20000;
1032
- })();
1033
1069
  const TRACE_MATERIALIZE_OFFTHREAD_ENABLED = (() => {
1034
1070
  const env = String(process.env.REPRO_TRACE_MATERIALIZE_OFFTHREAD || '').trim().toLowerCase();
1035
1071
  if (!env)
@@ -1378,28 +1414,9 @@ function scheduleSdkBackgroundDrain(delayMs = 0) {
1378
1414
  catch { }
1379
1415
  }
1380
1416
  async function runSdkBackgroundWork(work) {
1381
- if (!Number.isFinite(SDK_BACKGROUND_WORK_TIMEOUT_MS) || SDK_BACKGROUND_WORK_TIMEOUT_MS <= 0) {
1382
- await work();
1383
- return;
1384
- }
1385
- let timeout = null;
1386
- const workPromise = Promise.resolve()
1417
+ await Promise.resolve()
1387
1418
  .then(work)
1388
1419
  .catch(() => undefined);
1389
- const timeoutPromise = new Promise((resolve) => {
1390
- timeout = setTimeout(resolve, SDK_BACKGROUND_WORK_TIMEOUT_MS);
1391
- try {
1392
- timeout.unref?.();
1393
- }
1394
- catch { }
1395
- });
1396
- await Promise.race([workPromise, timeoutPromise]);
1397
- if (timeout) {
1398
- try {
1399
- clearTimeout(timeout);
1400
- }
1401
- catch { }
1402
- }
1403
1420
  }
1404
1421
  async function drainQueuedIngestBeforeBackgroundWork() {
1405
1422
  (0, client_1.kickIngestQueueDrain)();
@@ -2258,7 +2275,7 @@ const TRACE_MAX_BATCHES_PER_REQUEST = (() => {
2258
2275
  const env = Number(process.env.REPRO_TRACE_MAX_BATCHES_PER_REQUEST);
2259
2276
  if (Number.isFinite(env) && env > 0)
2260
2277
  return Math.trunc(env);
2261
- return 8;
2278
+ return Math.ceil(TRACE_MAX_EVENTS_PER_REQUEST / TRACE_BATCH_SIZE);
2262
2279
  })();
2263
2280
  const TRACE_FLUSH_DELAY_MS = 20;
2264
2281
  // Choose how to order trace events in payloads.
@@ -2828,7 +2845,7 @@ async function materializePendingTraceEvents(events, params) {
2828
2845
  previewValue: evt.args,
2829
2846
  capture: captureContext,
2830
2847
  metadata: traceValueMetadata,
2831
- });
2848
+ }, params.captureScope);
2832
2849
  if (argsValueCapture?.entry) {
2833
2850
  attachTraceValueEntry(evt, argsValueCapture.entry);
2834
2851
  }
@@ -2850,7 +2867,7 @@ async function materializePendingTraceEvents(events, params) {
2850
2867
  previewValue: evt.returnValue,
2851
2868
  capture: captureContext,
2852
2869
  metadata: traceValueMetadata,
2853
- });
2870
+ }, params.captureScope);
2854
2871
  if (returnValueCapture?.entry) {
2855
2872
  attachTraceValueEntry(evt, returnValueCapture.entry);
2856
2873
  }
@@ -2872,7 +2889,7 @@ async function materializePendingTraceEvents(events, params) {
2872
2889
  previewValue: evt.error,
2873
2890
  capture: captureContext,
2874
2891
  metadata: traceValueMetadata,
2875
- });
2892
+ }, params.captureScope);
2876
2893
  if (errorValueCapture?.entry) {
2877
2894
  attachTraceValueEntry(evt, errorValueCapture.entry);
2878
2895
  }
@@ -2886,8 +2903,21 @@ async function materializePendingTraceEvents(events, params) {
2886
2903
  }
2887
2904
  function stripPendingTraceValues(events, reason) {
2888
2905
  for (const evt of events) {
2889
- if (!evt.__reproPending)
2906
+ const pending = evt.__reproPending;
2907
+ if (!pending)
2890
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
+ }
2891
2921
  delete evt.__reproPending;
2892
2922
  }
2893
2923
  return events;
@@ -2908,6 +2938,7 @@ async function materializePendingTraceEventsAsync(events, params) {
2908
2938
  });
2909
2939
  }
2910
2940
  catch {
2941
+ recordRequestCaptureFailure(params.captureScope ?? getCtx().captureScope, 'privacy.trace', 'trace.values', 'main_thread_materialization_failed');
2911
2942
  return stripPendingTraceValues(events, 'main_thread_failed');
2912
2943
  }
2913
2944
  }
@@ -2927,9 +2958,6 @@ async function materializePendingTraceEventsAsync(events, params) {
2927
2958
  });
2928
2959
  }
2929
2960
  catch (err) {
2930
- if (isTraceMaterializerTimeoutError(err)) {
2931
- return stripPendingTraceValues(events, 'worker_timeout');
2932
- }
2933
2961
  try {
2934
2962
  return await materializePendingTraceEvents(events, {
2935
2963
  ...params,
@@ -2937,7 +2965,10 @@ async function materializePendingTraceEventsAsync(events, params) {
2937
2965
  });
2938
2966
  }
2939
2967
  catch {
2940
- 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');
2941
2972
  }
2942
2973
  }
2943
2974
  }
@@ -3404,7 +3435,7 @@ function limitRawInlinePrivacyValue(target, value) {
3404
3435
  const limited = limitInlinePrivacyValue(target, value);
3405
3436
  return limited.skipped ? limited : null;
3406
3437
  }
3407
- function materializeInlinePrivacyValue(target, value, cfg, req, trace, masking, privacy, db) {
3438
+ function materializeInlinePrivacyValue(target, value, cfg, req, trace, masking, privacy, db, captureScope) {
3408
3439
  if (value === undefined) {
3409
3440
  return { value };
3410
3441
  }
@@ -3416,6 +3447,7 @@ function materializeInlinePrivacyValue(target, value, cfg, req, trace, masking,
3416
3447
  return { value: valueAfterPrivacy };
3417
3448
  }
3418
3449
  catch {
3450
+ recordRequestCaptureFailure(captureScope ?? getCtx().captureScope, 'privacy.inline', target, 'privacy_transform_failed');
3419
3451
  return {
3420
3452
  value: undefined,
3421
3453
  skipped: {
@@ -3426,7 +3458,7 @@ function materializeInlinePrivacyValue(target, value, cfg, req, trace, masking,
3426
3458
  };
3427
3459
  }
3428
3460
  }
3429
- async function materializeInlinePrivacyValueAsync(target, value, cfg, req, trace, masking, privacy, db) {
3461
+ async function materializeInlinePrivacyValueAsync(target, value, cfg, req, trace, masking, privacy, db, captureScope) {
3430
3462
  if (value === undefined) {
3431
3463
  return { value };
3432
3464
  }
@@ -3438,6 +3470,7 @@ async function materializeInlinePrivacyValueAsync(target, value, cfg, req, trace
3438
3470
  return { value: valueAfterPrivacy };
3439
3471
  }
3440
3472
  catch {
3473
+ recordRequestCaptureFailure(captureScope ?? getCtx().captureScope, 'privacy.inline', target, 'privacy_transform_failed');
3441
3474
  return {
3442
3475
  value: undefined,
3443
3476
  skipped: {
@@ -4330,6 +4363,13 @@ function chunkTraceEventsForTransport(events, requestRid, actionId) {
4330
4363
  if (sizedBatches.length >= TRACE_MAX_BATCHES_PER_REQUEST) {
4331
4364
  break;
4332
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
+ }
4333
4373
  const candidate = current.concat(event);
4334
4374
  const batchIndex = sizedBatches.length;
4335
4375
  const estimatedBytes = estimateTraceBatchSerializedBytes(candidate, requestRid, actionId, batchIndex);
@@ -4461,8 +4501,15 @@ function maybeCaptureRequestValue(params, sink) {
4461
4501
  }
4462
4502
  return result.captureRef;
4463
4503
  }
4464
- async function maybeCaptureRequestValueAsync(params, sink) {
4465
- 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
+ }
4466
4513
  if (!result)
4467
4514
  return undefined;
4468
4515
  if (result.entry) {
@@ -4473,8 +4520,14 @@ async function maybeCaptureRequestValueAsync(params, sink) {
4473
4520
  function maybeCaptureTraceValue(params) {
4474
4521
  return createCapturedValueEntry(params);
4475
4522
  }
4476
- async function maybeCaptureTraceValueAsync(params) {
4477
- 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
+ }
4478
4531
  }
4479
4532
  function maybeCaptureDbValue(params, sink) {
4480
4533
  const result = createCapturedValueEntry(params);
@@ -4485,8 +4538,15 @@ function maybeCaptureDbValue(params, sink) {
4485
4538
  }
4486
4539
  return result.captureRef;
4487
4540
  }
4488
- async function maybeCaptureDbValueAsync(params, sink) {
4489
- 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
+ }
4490
4550
  if (!result)
4491
4551
  return undefined;
4492
4552
  if (result.entry) {
@@ -4512,15 +4572,22 @@ function buildResultMetaFullValueCandidates(resultMetaRaw, resultMetaSource) {
4512
4572
  pushUniqueCaptureCandidate(candidates, resultMetaSource);
4513
4573
  return candidates;
4514
4574
  }
4515
- async function maybeCaptureDbValueFromCandidatesAsync(params, sink) {
4575
+ async function maybeCaptureDbValueFromCandidatesAsync(params, sink, captureScope) {
4516
4576
  let skippedRef;
4517
4577
  for (const rawValue of params.rawValues) {
4518
- const result = await createCapturedValueEntryAsync({
4519
- target: params.target,
4520
- rawValue,
4521
- previewValue: params.previewValue,
4522
- capture: params.capture,
4523
- });
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
+ }
4524
4591
  if (!result)
4525
4592
  continue;
4526
4593
  if (result.entry) {
@@ -4690,7 +4757,7 @@ function reproMiddleware(cfg) {
4690
4757
  const resolvePrivacy = () => getRuntimePrivacyState(captureCfg).policy ?? null;
4691
4758
  const clockSkewMs = headerTs !== null ? headerTs - requestStartRaw : 0;
4692
4759
  const rid = nextRequestId(requestEpochMs);
4693
- const captureScope = createRequestCaptureScope(rid, 'http');
4760
+ const captureScope = createRequestCaptureScope(rid, 'http', captureCfg);
4694
4761
  emitRequestCaptureStart(captureCfg, sid, aid, captureScope, requestEpochMs);
4695
4762
  const t0 = requestStartRaw;
4696
4763
  const url = req.originalUrl || req.url || '/';
@@ -4893,15 +4960,18 @@ function reproMiddleware(cfg) {
4893
4960
  const activePrivacy = resolvePrivacy();
4894
4961
  const requestBodyRaw = req.body;
4895
4962
  const requestBodyMaterialization = limitRawInlinePrivacyValue('request.body', requestBodyRaw)
4896
- ?? 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);
4897
4964
  const requestBody = requestBodyMaterialization.value;
4898
- const requestParams = await applyPrivacyThenMaskAsync('request.params', sanitizeRequestSnapshot(req.params), captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4899
- const requestQuery = await applyPrivacyThenMaskAsync('request.query', sanitizeRequestSnapshot(req.query), captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4900
- 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;
4901
4971
  const responseBodyMaterialization = capturedBody === undefined
4902
4972
  ? { value: undefined }
4903
4973
  : limitRawInlinePrivacyValue('response.body', capturedBody)
4904
- ?? 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);
4905
4975
  const responseBody = responseBodyMaterialization.value;
4906
4976
  const requestValueEntries = [];
4907
4977
  const bodyValueCapture = requestBodyRaw === undefined
@@ -4918,7 +4988,7 @@ function reproMiddleware(cfg) {
4918
4988
  masking,
4919
4989
  privacy: activePrivacy,
4920
4990
  },
4921
- }, requestValueEntries);
4991
+ }, requestValueEntries, captureScope);
4922
4992
  const paramsValueCapture = await maybeCaptureRequestValueAsync({
4923
4993
  target: 'request.params',
4924
4994
  rawValue: req.params,
@@ -4931,7 +5001,7 @@ function reproMiddleware(cfg) {
4931
5001
  masking,
4932
5002
  privacy: activePrivacy,
4933
5003
  },
4934
- }, requestValueEntries);
5004
+ }, requestValueEntries, captureScope);
4935
5005
  const queryValueCapture = await maybeCaptureRequestValueAsync({
4936
5006
  target: 'request.query',
4937
5007
  rawValue: req.query,
@@ -4944,7 +5014,7 @@ function reproMiddleware(cfg) {
4944
5014
  masking,
4945
5015
  privacy: activePrivacy,
4946
5016
  },
4947
- }, requestValueEntries);
5017
+ }, requestValueEntries, captureScope);
4948
5018
  const headersValueCapture = await maybeCaptureRequestValueAsync({
4949
5019
  target: 'request.headers',
4950
5020
  rawValue: req.headers,
@@ -4957,7 +5027,7 @@ function reproMiddleware(cfg) {
4957
5027
  masking,
4958
5028
  privacy: activePrivacy,
4959
5029
  },
4960
- }, requestValueEntries);
5030
+ }, requestValueEntries, captureScope);
4961
5031
  const respBodyValueCapture = capturedBody === undefined
4962
5032
  ? undefined
4963
5033
  : await maybeCaptureRequestValueAsync({
@@ -4972,7 +5042,7 @@ function reproMiddleware(cfg) {
4972
5042
  masking,
4973
5043
  privacy: activePrivacy,
4974
5044
  },
4975
- }, requestValueEntries);
5045
+ }, requestValueEntries, captureScope);
4976
5046
  const responseDurationMs = finishedAt !== null && Number.isFinite(finishedAt)
4977
5047
  ? Math.max(0, finishedAt - t0)
4978
5048
  : Date.now() - t0;
@@ -5034,10 +5104,38 @@ function reproMiddleware(cfg) {
5034
5104
  t: requestEpochMs,
5035
5105
  }]
5036
5106
  });
5037
- captureScope.backendRequestCount += 1;
5038
5107
  }
5039
5108
  catch {
5040
- // 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
+ });
5041
5139
  }
5042
5140
  };
5043
5141
  const emitRequestCaptureManifest = (traceBatchTotal) => {
@@ -5151,6 +5249,7 @@ function reproMiddleware(cfg) {
5151
5249
  maskReq,
5152
5250
  masking,
5153
5251
  resolvePrivacy,
5252
+ captureScope,
5154
5253
  });
5155
5254
  const baseEvents = balanceTraceEvents(materializedEvents.slice());
5156
5255
  const orderedEvents = TRACE_ORDER_MODE === 'tree'
@@ -5165,12 +5264,14 @@ function reproMiddleware(cfg) {
5165
5264
  }
5166
5265
  }
5167
5266
  catch {
5267
+ recordRequestCaptureFailure(captureScope, 'trace.capture', 'trace.batches', 'trace_capture_failed');
5168
5268
  captureScope.traceBatchCount = 0;
5169
5269
  traceBatches = [];
5170
5270
  }
5171
- finally {
5172
- emitRequestCaptureManifest(traceBatches.length);
5173
- }
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);
5174
5275
  try {
5175
5276
  for (let i = 0; i < traceBatches.length; i++) {
5176
5277
  const batch = traceBatches[i];
@@ -5191,7 +5292,7 @@ function reproMiddleware(cfg) {
5191
5292
  }
5192
5293
  }
5193
5294
  catch {
5194
- // never break user code
5295
+ recordRequestCaptureFailure(captureScope, 'trace.enqueue', 'trace.batches', 'trace_enqueue_failed');
5195
5296
  }
5196
5297
  };
5197
5298
  }
@@ -5758,6 +5859,11 @@ function emitDbQuery(cfg, sid, aid, payload) {
5758
5859
  const spanContext = payload?.spanContext ?? captureSpanContextFromTracer();
5759
5860
  if (!shouldCaptureDbSpan(spanContext))
5760
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
+ }
5761
5867
  const runCtx = {
5762
5868
  sid: ctx.sid,
5763
5869
  aid: ctx.aid,
@@ -5769,7 +5875,9 @@ function emitDbQuery(cfg, sid, aid, payload) {
5769
5875
  };
5770
5876
  scheduleSdkBackgroundWork(async () => {
5771
5877
  await als.run(runCtx, async () => {
5772
- 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
+ });
5773
5881
  });
5774
5882
  }, { priority: 'high' });
5775
5883
  }
@@ -5885,8 +5993,8 @@ async function emitDbQueryAsync(cfg, sid, aid, payload) {
5885
5993
  t: payload.t,
5886
5994
  }]
5887
5995
  });
5888
- if (enqueued && ctx.captureScope) {
5889
- ctx.captureScope.dbChangeCount += 1;
5996
+ if (!enqueued) {
5997
+ recordRequestCaptureFailure(ctx.captureScope, 'db.enqueue', 'db.change', 'db_enqueue_failed');
5890
5998
  }
5891
5999
  }
5892
6000
  function buildMinimalUpdate(before, after) {
@@ -6293,8 +6401,8 @@ function cloneKafkaTelemetryValue(value) {
6293
6401
  return value;
6294
6402
  }
6295
6403
  }
6296
- async function materializeKafkaRequestBody(cfg, maskReq, body) {
6297
- 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);
6298
6406
  }
6299
6407
  function summarizeKafkaError(error) {
6300
6408
  const message = typeof error?.message === 'string'
@@ -6567,23 +6675,27 @@ function scheduleKafkaTraceFlush(cfg, kafkaCtx, actionId, requestRid, rawTraceEv
6567
6675
  if (evt)
6568
6676
  pendingEvents.push(evt);
6569
6677
  }
6570
- if (!pendingEvents.length)
6571
- return;
6572
- const masking = normalizeMaskingConfig(cfg.masking);
6573
- const preparedEvents = preparePendingTraceEventsForFlush(pendingEvents);
6574
- const materializedEvents = await materializePendingTraceEventsAsync(preparedEvents, {
6575
- cfg,
6576
- maskReq,
6577
- masking,
6578
- resolvePrivacy: () => getRuntimePrivacyState(cfg).policy ?? null,
6579
- });
6580
- 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
+ }
6581
6696
  if (captureScope) {
6582
6697
  captureScope.traceBatchCount = entries.length;
6583
6698
  }
6584
- if (entries.length) {
6585
- post(cfg, sid, { entries });
6586
- }
6587
6699
  if (captureScope) {
6588
6700
  post(cfg, sid, {
6589
6701
  entries: [{
@@ -6592,7 +6704,10 @@ function scheduleKafkaTraceFlush(cfg, kafkaCtx, actionId, requestRid, rawTraceEv
6592
6704
  requestCaptureManifest: buildRequestCaptureManifestPayload(captureScope, entries.length),
6593
6705
  t: alignedNow(),
6594
6706
  }],
6595
- });
6707
+ }, { priority: 'high' });
6708
+ }
6709
+ if (entries.length) {
6710
+ post(cfg, sid, { entries });
6596
6711
  }
6597
6712
  });
6598
6713
  }, { priority: 'high' });
@@ -6610,7 +6725,7 @@ function patchKafkaProducerInstance(producer, cfg) {
6610
6725
  const aid = ctx.aid ?? fallback.aid ?? null;
6611
6726
  const span = captureSpanContextFromTracer();
6612
6727
  const publishRid = nextRequestId(alignedNow());
6613
- const captureScope = createRequestCaptureScope(publishRid, 'kafka');
6728
+ const captureScope = createRequestCaptureScope(publishRid, 'kafka', cfg);
6614
6729
  emitRequestCaptureStart(cfg, sid, aid, captureScope);
6615
6730
  const payloadObj = payload && typeof payload === 'object' ? payload : {};
6616
6731
  const topic = sanitizeKafkaTopic(payloadObj.topic);
@@ -6669,7 +6784,7 @@ function patchKafkaProducerInstance(producer, cfg) {
6669
6784
  traceId: span?.traceId ?? null,
6670
6785
  spanId: span?.spanId ?? null,
6671
6786
  parentSpanId: span?.parentSpanId ?? null,
6672
- });
6787
+ }, captureScope);
6673
6788
  const requestPayload = {
6674
6789
  rid: publishRid,
6675
6790
  method: 'KAFKA_PUBLISH',
@@ -6698,7 +6813,6 @@ function patchKafkaProducerInstance(producer, cfg) {
6698
6813
  },
6699
6814
  ],
6700
6815
  });
6701
- captureScope.backendRequestCount += 1;
6702
6816
  post(cfg, sid, {
6703
6817
  entries: [{
6704
6818
  actionId: aid ?? null,
@@ -6719,7 +6833,7 @@ function patchKafkaProducerInstance(producer, cfg) {
6719
6833
  const aid = ctx.aid ?? fallback.aid ?? null;
6720
6834
  const span = captureSpanContextFromTracer();
6721
6835
  const publishRid = nextRequestId(alignedNow());
6722
- const captureScope = createRequestCaptureScope(publishRid, 'kafka');
6836
+ const captureScope = createRequestCaptureScope(publishRid, 'kafka', cfg);
6723
6837
  emitRequestCaptureStart(cfg, sid, aid, captureScope);
6724
6838
  const payloadObj = payload && typeof payload === 'object' ? payload : {};
6725
6839
  const topicMessages = Array.isArray(payloadObj.topicMessages)
@@ -6809,7 +6923,7 @@ function patchKafkaProducerInstance(producer, cfg) {
6809
6923
  traceId: span?.traceId ?? null,
6810
6924
  spanId: span?.spanId ?? null,
6811
6925
  parentSpanId: span?.parentSpanId ?? null,
6812
- });
6926
+ }, captureScope);
6813
6927
  const requestPayload = {
6814
6928
  rid: publishRid,
6815
6929
  method: 'KAFKA_PUBLISH_BATCH',
@@ -6838,7 +6952,6 @@ function patchKafkaProducerInstance(producer, cfg) {
6838
6952
  },
6839
6953
  ],
6840
6954
  });
6841
- captureScope.backendRequestCount += 1;
6842
6955
  post(cfg, sid, {
6843
6956
  entries: [{
6844
6957
  actionId: aid ?? null,
@@ -6861,7 +6974,7 @@ function wrapKafkaEachMessageHandler(eachMessage, cfg, consumer) {
6861
6974
  const aid = runtime.aid;
6862
6975
  const consumeRid = nextRequestId(`${topic}-${message?.offset ?? alignedNow()}`);
6863
6976
  const traceId = consumeRid;
6864
- const captureScope = createRequestCaptureScope(consumeRid, 'kafka');
6977
+ const captureScope = createRequestCaptureScope(consumeRid, 'kafka', cfg);
6865
6978
  emitRequestCaptureStart(cfg, sid, aid, captureScope);
6866
6979
  const rawTraceEvents = [];
6867
6980
  const maskReq = {
@@ -6939,7 +7052,7 @@ function wrapKafkaEachMessageHandler(eachMessage, cfg, consumer) {
6939
7052
  valueEncoding: messagePayload.valueEncoding,
6940
7053
  valueBytes: messagePayload.valueBytes,
6941
7054
  message: messagePayload,
6942
- });
7055
+ }, captureScope);
6943
7056
  const requestPayload = {
6944
7057
  rid: consumeRid,
6945
7058
  method: 'KAFKA_CONSUME',
@@ -6960,7 +7073,6 @@ function wrapKafkaEachMessageHandler(eachMessage, cfg, consumer) {
6960
7073
  t: alignedNow(),
6961
7074
  }],
6962
7075
  });
6963
- captureScope.backendRequestCount += 1;
6964
7076
  scheduleKafkaTraceFlush(cfg, kafkaCtx, aid, consumeRid, rawTraceEvents, maskReq, captureScope);
6965
7077
  }
6966
7078
  };
@@ -6976,7 +7088,7 @@ function wrapKafkaEachBatchHandler(eachBatch, cfg, consumer) {
6976
7088
  const aid = runtime.aid;
6977
7089
  const consumeRid = nextRequestId(`${topic}-batch-${alignedNow()}`);
6978
7090
  const traceId = consumeRid;
6979
- const captureScope = createRequestCaptureScope(consumeRid, 'kafka');
7091
+ const captureScope = createRequestCaptureScope(consumeRid, 'kafka', cfg);
6980
7092
  emitRequestCaptureStart(cfg, sid, aid, captureScope);
6981
7093
  const rawTraceEvents = [];
6982
7094
  const maskReq = {
@@ -7052,7 +7164,7 @@ function wrapKafkaEachBatchHandler(eachBatch, cfg, consumer) {
7052
7164
  parentSpanId: runtime.parentSpanId,
7053
7165
  firstMessageKey: firstMessagePayload?.key ?? null,
7054
7166
  messages: messages.map((message) => buildKafkaMessagePayload(message)),
7055
- });
7167
+ }, captureScope);
7056
7168
  const requestPayload = {
7057
7169
  rid: consumeRid,
7058
7170
  method: 'KAFKA_CONSUME_BATCH',
@@ -7073,7 +7185,6 @@ function wrapKafkaEachBatchHandler(eachBatch, cfg, consumer) {
7073
7185
  t: alignedNow(),
7074
7186
  }],
7075
7187
  });
7076
- captureScope.backendRequestCount += 1;
7077
7188
  scheduleKafkaTraceFlush(cfg, kafkaCtx, aid, consumeRid, rawTraceEvents, maskReq, captureScope);
7078
7189
  }
7079
7190
  };
@@ -7453,24 +7564,24 @@ function scheduleAmqpTraceFlush(cfg, amqpCtx, actionId, requestRid, rawTraceEven
7453
7564
  pendingEvents.push(evt);
7454
7565
  }
7455
7566
  let entries = [];
7456
- if (pendingEvents.length) {
7457
- const masking = normalizeMaskingConfig(cfg.masking);
7458
- const preparedEvents = preparePendingTraceEventsForFlush(pendingEvents);
7459
- const materializedEvents = await materializePendingTraceEventsAsync(preparedEvents, {
7460
- cfg,
7461
- maskReq,
7462
- masking,
7463
- resolvePrivacy: () => getRuntimePrivacyState(cfg).policy ?? null,
7464
- });
7465
- entries = buildAmqpTraceEntries(actionId, requestRid, materializedEvents);
7466
- captureScope.traceBatchCount = entries.length;
7467
- if (entries.length) {
7468
- 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);
7469
7579
  }
7470
7580
  }
7471
- else {
7472
- captureScope.traceBatchCount = 0;
7581
+ catch {
7582
+ recordRequestCaptureFailure(captureScope, 'trace.capture', 'trace.batches', 'trace_capture_failed');
7473
7583
  }
7584
+ captureScope.traceBatchCount = entries.length;
7474
7585
  post(cfg, sid, {
7475
7586
  entries: [{
7476
7587
  actionId: actionId ?? null,
@@ -7478,7 +7589,10 @@ function scheduleAmqpTraceFlush(cfg, amqpCtx, actionId, requestRid, rawTraceEven
7478
7589
  requestCaptureManifest: buildRequestCaptureManifestPayload(captureScope, entries.length),
7479
7590
  t: alignedNow(),
7480
7591
  }],
7481
- });
7592
+ }, { priority: 'high' });
7593
+ if (entries.length) {
7594
+ post(cfg, sid, { entries });
7595
+ }
7482
7596
  });
7483
7597
  }, { priority: 'high' });
7484
7598
  }
@@ -7524,7 +7638,7 @@ function scheduleAmqpPublishCapture(params) {
7524
7638
  parentSpanId: params.span?.parentSpanId ?? null,
7525
7639
  };
7526
7640
  scheduleSdkBackgroundWork(async () => {
7527
- const bodyMaterialization = await materializeKafkaRequestBody(params.cfg, maskReq, body);
7641
+ const bodyMaterialization = await materializeKafkaRequestBody(params.cfg, maskReq, body, params.captureScope);
7528
7642
  const requestPayload = {
7529
7643
  rid: params.publishRid,
7530
7644
  method: params.method,
@@ -7554,7 +7668,6 @@ function scheduleAmqpPublishCapture(params) {
7554
7668
  t: alignedNow(),
7555
7669
  }],
7556
7670
  });
7557
- params.captureScope.backendRequestCount += 1;
7558
7671
  post(params.cfg, params.sid, {
7559
7672
  entries: [{
7560
7673
  actionId: params.aid ?? null,
@@ -7605,7 +7718,7 @@ function patchAmqplibPublishMethod(channel, cfg, methodName) {
7605
7718
  : `amqp://queue/${queue}`;
7606
7719
  const key = `${method} ${methodName === 'publish' ? `${exchange || 'default'}/${routingKey}` : queue}`;
7607
7720
  const content = methodName === 'publish' ? args[2] : args[1];
7608
- const captureScope = createRequestCaptureScope(publishRid, 'amqp');
7721
+ const captureScope = createRequestCaptureScope(publishRid, 'amqp', cfg);
7609
7722
  emitRequestCaptureStart(cfg, sid, aid, captureScope);
7610
7723
  const t0 = Date.now();
7611
7724
  let finished = false;
@@ -7692,7 +7805,7 @@ function wrapAmqplibConsumeHandler(onMessage, cfg, queue) {
7692
7805
  const deliveryTag = message?.fields?.deliveryTag ?? alignedNow();
7693
7806
  const consumeRid = nextRequestId(`${queueName}-${deliveryTag}`);
7694
7807
  const traceId = consumeRid;
7695
- const captureScope = createRequestCaptureScope(consumeRid, 'amqp');
7808
+ const captureScope = createRequestCaptureScope(consumeRid, 'amqp', cfg);
7696
7809
  emitRequestCaptureStart(cfg, sid, aid, captureScope);
7697
7810
  const rawTraceEvents = [];
7698
7811
  const maskReq = {
@@ -7782,7 +7895,7 @@ function wrapAmqplibConsumeHandler(onMessage, cfg, queue) {
7782
7895
  properties: buildAmqpMessagePropertiesPayload(properties),
7783
7896
  };
7784
7897
  scheduleSdkBackgroundWork(async () => {
7785
- const bodyMaterialization = await materializeKafkaRequestBody(cfg, maskReq, body);
7898
+ const bodyMaterialization = await materializeKafkaRequestBody(cfg, maskReq, body, captureScope);
7786
7899
  const requestPayload = {
7787
7900
  rid: consumeRid,
7788
7901
  method: 'AMQP_CONSUME',
@@ -7803,7 +7916,6 @@ function wrapAmqplibConsumeHandler(onMessage, cfg, queue) {
7803
7916
  t: alignedNow(),
7804
7917
  }],
7805
7918
  });
7806
- captureScope.backendRequestCount += 1;
7807
7919
  scheduleAmqpTraceFlush(cfg, amqpCtx, aid, consumeRid, rawTraceEvents, maskReq, captureScope);
7808
7920
  }, { priority: 'high' });
7809
7921
  };