@reproapp/node-sdk 0.0.48 → 0.0.50
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/README.md +44 -1
- package/dist/index.d.ts +17 -0
- package/dist/index.js +140 -23
- package/dist/ingest/client.js +27 -2
- package/dist/live-capture-limits.d.ts +19 -0
- package/dist/live-capture-limits.js +207 -0
- package/dist/semantic-serialization.d.ts +1 -0
- package/dist/semantic-serialization.js +33 -1
- package/package.json +7 -2
- package/src/index.ts +211 -24
- package/src/ingest/client.ts +27 -3
- package/src/live-capture-limits.ts +177 -0
- package/src/semantic-serialization.ts +30 -0
- package/test/content-addressed-mongodb.testcontainers.test.js +23 -4
- package/test/fixtures/live-capture-limits-server.js +21 -0
- package/test/ingest-throttle-retention.test.js +41 -0
- package/test/kafka-runtime-privacy-policy.test.js +26 -2
- package/test/live-broker-limits.testcontainers.test.js +86 -0
- package/test/live-capture-limits.test.js +99 -0
- package/test/live-capture-limits.testcontainers.test.js +78 -0
- package/test/policy-session-completion.testcontainers.test.js +18 -1
- package/test/trace-observation-snapshots.test.js +403 -0
package/README.md
CHANGED
|
@@ -107,6 +107,7 @@ await initRepro({
|
|
|
107
107
|
appName: "Acme Web",
|
|
108
108
|
serviceName: "api",
|
|
109
109
|
apiBase: process.env.REPRO_API_BASE,
|
|
110
|
+
ingestBase: process.env.REPRO_INGEST_BASE!,
|
|
110
111
|
});
|
|
111
112
|
```
|
|
112
113
|
|
|
@@ -123,14 +124,56 @@ await initRepro({
|
|
|
123
124
|
liveMode: {
|
|
124
125
|
enabled: true,
|
|
125
126
|
backendOnly: true,
|
|
126
|
-
|
|
127
|
+
// Trust only a header that your authenticated gateway overwrites.
|
|
128
|
+
defineActorId: ({ req }) => req.headers["x-user-id"] ?? null,
|
|
127
129
|
shouldCapture: ({ path }) => path.startsWith("/api/") && !path.includes("/health"),
|
|
128
130
|
actorType: "user",
|
|
129
131
|
actorLabels: ({ path }) => ({ routeGroup: path.split("/").filter(Boolean)[0] ?? "api" }),
|
|
132
|
+
liveCapture: { sampleRate: 0.1, targets: [] },
|
|
130
133
|
},
|
|
131
134
|
});
|
|
132
135
|
```
|
|
133
136
|
|
|
137
|
+
### Sampling, limits and runtime targets
|
|
138
|
+
|
|
139
|
+
Backend-owned live capture accepts `liveMode.liveCapture.sampleRate` from 0 to 1
|
|
140
|
+
(default 1 for compatibility). A chain keeps its sample decision until five minutes
|
|
141
|
+
of inactivity. Actor identity separates chains; three-minute child segments admit
|
|
142
|
+
new work. Admitted requests and their queue/DB/trace tails stay in the original
|
|
143
|
+
segment even when delivery finishes later. Browser-correlated captures use the
|
|
144
|
+
browser's admission decision, rather than sampling a second time.
|
|
145
|
+
|
|
146
|
+
Keep the `liveCapture` object in your runtime configuration and replace its `targets`
|
|
147
|
+
array when a support case starts or ends. Example target entries:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
const liveCapture = {
|
|
151
|
+
sampleRate: 0.1,
|
|
152
|
+
targets: [
|
|
153
|
+
{ actorIds: ['user-123'], expiresAt: Date.now() + 3600000 },
|
|
154
|
+
{ labels: { routeGroup: 'reports' }, expiresAt: Date.now() + 3600000 },
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
// Pass this object as liveMode.liveCapture; update liveCapture.targets at runtime.
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Targets override sampling, including rate 0, but never quota admission. All labels
|
|
161
|
+
in one target must match; actor IDs and label selectors are alternatives. Use a
|
|
162
|
+
trusted identity source, not arbitrary client-controlled headers.
|
|
163
|
+
|
|
164
|
+
Live business values have depth 10, 100 keys per object, and 100 entries per array,
|
|
165
|
+
Map, Set or native collection. Partial captures carry explicit omitted path/reason/
|
|
166
|
+
count metadata. `captureRaw: true` bypasses privacy processing, not live limits.
|
|
167
|
+
Staging business values are unchanged. Both modes project recognized runtime
|
|
168
|
+
infrastructure (connections, pools, framework/configuration machinery) into compact
|
|
169
|
+
descriptions; business documents, queries, results and application payloads remain
|
|
170
|
+
evidence. Unknown objects are treated as business data.
|
|
171
|
+
|
|
172
|
+
HTTP 429 honors `Retry-After` without dropping critical uploads. Quota refusal (402)
|
|
173
|
+
and deleted/failed capture refusal (410) stop retrying that refused batch; business
|
|
174
|
+
requests continue. Quota-enabled installations must use the configured collector,
|
|
175
|
+
not the legacy direct API evidence endpoints.
|
|
176
|
+
|
|
134
177
|
## Manual Wiring
|
|
135
178
|
|
|
136
179
|
Use the lower-level exports when auto-attach is not appropriate or when you need
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ import type { Schema } from 'mongoose';
|
|
|
28
28
|
import { type IngestClientConfig } from './ingest/client';
|
|
29
29
|
import { type NormalizedRuntimePrivacyPolicy, type RuntimePrivacyConfig, type RuntimePrivacyDbContext, type RuntimePrivacySurface } from './privacy';
|
|
30
30
|
import { projectSemanticInfrastructureValue } from './semantic-serialization';
|
|
31
|
+
import { type CaptureOmission } from './live-capture-limits';
|
|
31
32
|
type SpanContext = {
|
|
32
33
|
traceId: string | null;
|
|
33
34
|
spanId: string | number | null;
|
|
@@ -88,6 +89,8 @@ type PendingTraceEventRecord = TraceEventRecord & {
|
|
|
88
89
|
__reproTraceValueEntries?: TraceValueBatchEntry[];
|
|
89
90
|
};
|
|
90
91
|
type TraceValueCaptureRef = {
|
|
92
|
+
complete?: boolean;
|
|
93
|
+
omissions?: CaptureOmission[];
|
|
91
94
|
status: 'stored' | 'truncated-for-storage' | 'skipped-non-meaningful' | 'failed-policy';
|
|
92
95
|
valueId?: string;
|
|
93
96
|
projectedKind?: string;
|
|
@@ -107,6 +110,7 @@ type TraceValueBatchEntry = {
|
|
|
107
110
|
eventIndex?: number;
|
|
108
111
|
};
|
|
109
112
|
type TraceMaterializationConfig = {
|
|
113
|
+
captureMode?: 'live' | 'staging';
|
|
110
114
|
tenantId: string;
|
|
111
115
|
appId: string;
|
|
112
116
|
appSecret?: string;
|
|
@@ -284,6 +288,7 @@ type NativeMongoSuppression = {
|
|
|
284
288
|
ownerStore?: Ctx;
|
|
285
289
|
};
|
|
286
290
|
type RequestCaptureScopeState = {
|
|
291
|
+
liveCapture?: boolean;
|
|
287
292
|
requestRid: string;
|
|
288
293
|
transport: 'http' | 'kafka' | 'amqp' | 'background';
|
|
289
294
|
backendRequestCount: number;
|
|
@@ -299,6 +304,7 @@ type RequestCaptureScopeState = {
|
|
|
299
304
|
privacyPolicyFingerprint?: string;
|
|
300
305
|
privacyStatementCount: number;
|
|
301
306
|
emittedValueDefinitionIds: Set<string>;
|
|
307
|
+
traceObservationSnapshots: Map<string, any>;
|
|
302
308
|
};
|
|
303
309
|
type BackendLiveTimingConfig = {
|
|
304
310
|
sessionMaxDurationMs: number;
|
|
@@ -341,6 +347,8 @@ declare function getEventTraceValueEntries(event: TraceEventRecord | PendingTrac
|
|
|
341
347
|
declare function estimateTraceBatchSerializedBytes(batch: TraceEventRecord[], requestRid: string, actionId: string | null | undefined, batchIndex: number, previouslyEmittedValueIds?: ReadonlySet<string>): number;
|
|
342
348
|
declare function chunkTraceEventsForTransport(events: TraceEventRecord[], requestRid: string, actionId: string | null | undefined): TraceEventRecord[][];
|
|
343
349
|
export type ReproMiddlewareConfig = IngestClientConfig & {
|
|
350
|
+
/** Propagated recording mode. A tagged staging request never inherits live limits. */
|
|
351
|
+
captureMode?: 'live' | 'staging';
|
|
344
352
|
/** API base used for backend-owned live session documents. Defaults to REPRO_API_BASE_URL/REPRO_API_BASE/privacy.apiBase. */
|
|
345
353
|
apiBase?: string;
|
|
346
354
|
/** Configure header capture/masking. Defaults to capturing with sensitive headers masked. */
|
|
@@ -369,6 +377,15 @@ export type ReproLiveActorDecision = ReproLiveActorArgs & {
|
|
|
369
377
|
actorLabels?: Record<string, string>;
|
|
370
378
|
};
|
|
371
379
|
export type ReproLiveModeConfig = {
|
|
380
|
+
/** Stable chain sampling. Runtime updates apply to new chains; targets are evaluated on each claim. */
|
|
381
|
+
liveCapture?: {
|
|
382
|
+
sampleRate?: number;
|
|
383
|
+
targets?: {
|
|
384
|
+
actorIds?: string[];
|
|
385
|
+
labels?: Record<string, string>;
|
|
386
|
+
expiresAt: number;
|
|
387
|
+
}[];
|
|
388
|
+
};
|
|
372
389
|
enabled?: boolean;
|
|
373
390
|
backendOnly?: boolean;
|
|
374
391
|
/**
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ const url_1 = require("url");
|
|
|
10
10
|
const client_1 = require("./ingest/client");
|
|
11
11
|
const privacy_1 = require("./privacy");
|
|
12
12
|
const semantic_serialization_1 = require("./semantic-serialization");
|
|
13
|
+
const live_capture_limits_1 = require("./live-capture-limits");
|
|
13
14
|
/**
|
|
14
15
|
* ============================= IMPORTANT =============================
|
|
15
16
|
* We snapshot Mongoose core methods BEFORE tracer init, then re-apply them
|
|
@@ -828,6 +829,7 @@ function createRequestCaptureScope(requestRid, transport, cfg) {
|
|
|
828
829
|
return {
|
|
829
830
|
requestRid,
|
|
830
831
|
transport,
|
|
832
|
+
liveCapture: cfg?.captureMode === 'live' || (!!cfg && cfg.captureMode !== 'staging' && !!normalizeLiveModeConfig(cfg)),
|
|
831
833
|
// Every scope represents one captured transport request. This is an
|
|
832
834
|
// observed expectation, not a count of successful enqueue operations.
|
|
833
835
|
backendRequestCount: 1,
|
|
@@ -839,6 +841,7 @@ function createRequestCaptureScope(requestRid, transport, cfg) {
|
|
|
839
841
|
privacyPolicyFingerprint: privacy?.fingerprint,
|
|
840
842
|
privacyStatementCount: privacy?.statements.length ?? 0,
|
|
841
843
|
emittedValueDefinitionIds: new Set(),
|
|
844
|
+
traceObservationSnapshots: new Map(),
|
|
842
845
|
};
|
|
843
846
|
}
|
|
844
847
|
function recordRequestCaptureFailure(scope, stage, target, reason) {
|
|
@@ -2053,6 +2056,7 @@ async function resolveBackendLiveContext(cfg, req, requestStart) {
|
|
|
2053
2056
|
}
|
|
2054
2057
|
const { actorId, actorType, actorLabels } = identity;
|
|
2055
2058
|
const claim = await postLiveApiJson(cfg, liveMode, '/v1/live-sessions/claim', {
|
|
2059
|
+
liveCapture: liveMode.liveCapture,
|
|
2056
2060
|
clientTime: requestStart,
|
|
2057
2061
|
captureMode: 'backend-only',
|
|
2058
2062
|
actorId,
|
|
@@ -2155,6 +2159,7 @@ function stripReproQueryFromUrl(rawUrl) {
|
|
|
2155
2159
|
parsed.searchParams.delete(REPRO_QUERY_SESSION_PARAM);
|
|
2156
2160
|
parsed.searchParams.delete(REPRO_QUERY_ACTION_PARAM);
|
|
2157
2161
|
parsed.searchParams.delete(REPRO_QUERY_START_PARAM);
|
|
2162
|
+
parsed.searchParams.delete('__repro_mode');
|
|
2158
2163
|
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
2159
2164
|
}
|
|
2160
2165
|
catch {
|
|
@@ -2168,6 +2173,7 @@ function stripReproQueryParams(req) {
|
|
|
2168
2173
|
delete query[REPRO_QUERY_SESSION_PARAM];
|
|
2169
2174
|
delete query[REPRO_QUERY_ACTION_PARAM];
|
|
2170
2175
|
delete query[REPRO_QUERY_START_PARAM];
|
|
2176
|
+
delete query.__repro_mode;
|
|
2171
2177
|
}
|
|
2172
2178
|
}
|
|
2173
2179
|
catch { }
|
|
@@ -2810,6 +2816,66 @@ function normalizeRawTraceArgs(values) {
|
|
|
2810
2816
|
function hasMeaningfulRawTraceError(error) {
|
|
2811
2817
|
return error !== undefined && error !== null;
|
|
2812
2818
|
}
|
|
2819
|
+
const TRACE_OBSERVATION_SNAPSHOT = Symbol('repro.traceObservationSnapshot');
|
|
2820
|
+
// Trace events can remain queued after user code mutates the observed objects.
|
|
2821
|
+
// Intern immutable semantic snapshots per request so temporal evidence stays
|
|
2822
|
+
// correct without retaining another full copy for repeated unchanged values.
|
|
2823
|
+
function internTraceObservationSnapshot(snapshot, captureScope) {
|
|
2824
|
+
if (!captureScope || snapshot === null || typeof snapshot !== 'object') {
|
|
2825
|
+
return snapshot;
|
|
2826
|
+
}
|
|
2827
|
+
const serialized = stableSerializeTraceValue(snapshot);
|
|
2828
|
+
if (serialized === null)
|
|
2829
|
+
return snapshot;
|
|
2830
|
+
const digest = (0, crypto_1.createHash)('sha256')
|
|
2831
|
+
.update('repro-trace-observation-v1\0')
|
|
2832
|
+
.update(serialized)
|
|
2833
|
+
.digest('hex');
|
|
2834
|
+
const existing = captureScope.traceObservationSnapshots.get(digest);
|
|
2835
|
+
if (existing !== undefined)
|
|
2836
|
+
return existing;
|
|
2837
|
+
captureScope.traceObservationSnapshots.set(digest, snapshot);
|
|
2838
|
+
return snapshot;
|
|
2839
|
+
}
|
|
2840
|
+
function snapshotTraceValueAtObservation(value, captureScope, valuePath = '$', target = 'trace.value') {
|
|
2841
|
+
try {
|
|
2842
|
+
if (captureScope?.liveCapture) {
|
|
2843
|
+
value = target === 'trace.args' && Array.isArray(value)
|
|
2844
|
+
? (0, live_capture_limits_1.limitLiveArguments)(value)
|
|
2845
|
+
: (0, live_capture_limits_1.limitLiveCapture)(value, valuePath).value;
|
|
2846
|
+
}
|
|
2847
|
+
const snapshot = sanitizeTraceValue(value, 0, new WeakMap(), { preserveLongStrings: true, disableTruncation: true }, valuePath);
|
|
2848
|
+
return internTraceObservationSnapshot(snapshot, captureScope);
|
|
2849
|
+
}
|
|
2850
|
+
catch {
|
|
2851
|
+
recordRequestCaptureFailure(captureScope, 'trace.snapshot', target, 'snapshot_failed');
|
|
2852
|
+
return makeOmittedValue('capture-snapshot-failed');
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
function snapshotTraceArgsAtObservation(values, captureScope) {
|
|
2856
|
+
const snapshot = snapshotTraceValueAtObservation(normalizeRawTraceArgs(values), captureScope, '$', 'trace.args');
|
|
2857
|
+
return Array.isArray(snapshot) ? snapshot : [snapshot];
|
|
2858
|
+
}
|
|
2859
|
+
function snapshotRawTraceEventAtObservation(raw, captureScope) {
|
|
2860
|
+
if (!raw || typeof raw !== 'object' || raw[TRACE_OBSERVATION_SNAPSHOT] === true) {
|
|
2861
|
+
return raw;
|
|
2862
|
+
}
|
|
2863
|
+
const snapshot = { ...raw };
|
|
2864
|
+
if (raw.args !== undefined) {
|
|
2865
|
+
snapshot.args = snapshotTraceArgsAtObservation(raw.args, captureScope);
|
|
2866
|
+
}
|
|
2867
|
+
if (raw.returnValue !== undefined) {
|
|
2868
|
+
snapshot.returnValue = snapshotTraceValueAtObservation(raw.returnValue, captureScope, '$', 'trace.returnValue');
|
|
2869
|
+
}
|
|
2870
|
+
if (hasMeaningfulRawTraceError(raw.error)) {
|
|
2871
|
+
snapshot.error = snapshotTraceValueAtObservation(raw.error, captureScope, '$', 'trace.error');
|
|
2872
|
+
}
|
|
2873
|
+
Object.defineProperty(snapshot, TRACE_OBSERVATION_SNAPSHOT, {
|
|
2874
|
+
value: true,
|
|
2875
|
+
enumerable: false,
|
|
2876
|
+
});
|
|
2877
|
+
return snapshot;
|
|
2878
|
+
}
|
|
2813
2879
|
function attachTraceValueEntry(event, entry) {
|
|
2814
2880
|
const current = event[TRACE_FULL_VALUE_ENTRY_SYMBOL];
|
|
2815
2881
|
if (Array.isArray(current)) {
|
|
@@ -2962,6 +3028,7 @@ async function materializePendingTraceEventsAsync(events, params) {
|
|
|
2962
3028
|
appSecret: params.cfg.appSecret,
|
|
2963
3029
|
appName: params.cfg.appName,
|
|
2964
3030
|
privacy: params.cfg.privacy,
|
|
3031
|
+
captureMode: params.captureScope?.liveCapture ? 'live' : params.cfg.captureMode,
|
|
2965
3032
|
},
|
|
2966
3033
|
maskReq: params.maskReq,
|
|
2967
3034
|
masking: params.masking,
|
|
@@ -3383,7 +3450,20 @@ async function initializeRuntimePrivacyPolicyAtStartup(cfg) {
|
|
|
3383
3450
|
}
|
|
3384
3451
|
throw new Error(`Runtime privacy policy failed to load during startup from ${apiBase} for env ${state.environment} (${lastFailure}).`);
|
|
3385
3452
|
}
|
|
3453
|
+
function isLiveCapture(cfg, scope) {
|
|
3454
|
+
if (scope)
|
|
3455
|
+
return scope.liveCapture === true;
|
|
3456
|
+
return cfg.captureMode === 'live' || (cfg.captureMode !== 'staging' && getCtx().captureScope?.liveCapture === true);
|
|
3457
|
+
}
|
|
3458
|
+
function constrainLiveCapture(value, cfg, target, scope) {
|
|
3459
|
+
if (!isLiveCapture(cfg, scope))
|
|
3460
|
+
return value;
|
|
3461
|
+
return target === 'trace.args' && Array.isArray(value)
|
|
3462
|
+
? (0, live_capture_limits_1.limitLiveArguments)(value)
|
|
3463
|
+
: (0, live_capture_limits_1.limitLiveCapture)(value).value;
|
|
3464
|
+
}
|
|
3386
3465
|
function applyPrivacyThenMask(target, value, cfg, req, trace, masking, privacy, db) {
|
|
3466
|
+
value = constrainLiveCapture(value, cfg, target);
|
|
3387
3467
|
if (shouldCapturePrivacyRaw(cfg)) {
|
|
3388
3468
|
return value;
|
|
3389
3469
|
}
|
|
@@ -3404,6 +3484,7 @@ function applyPrivacyThenMask(target, value, cfg, req, trace, masking, privacy,
|
|
|
3404
3484
|
return applyMasking(target, transformed, req, trace, masking);
|
|
3405
3485
|
}
|
|
3406
3486
|
async function applyPrivacyThenMaskAsync(target, value, cfg, req, trace, masking, privacy, db) {
|
|
3487
|
+
value = constrainLiveCapture(value, cfg, target);
|
|
3407
3488
|
if (shouldCapturePrivacyRaw(cfg)) {
|
|
3408
3489
|
return value;
|
|
3409
3490
|
}
|
|
@@ -3450,6 +3531,7 @@ function limitRawInlinePrivacyValue(target, value) {
|
|
|
3450
3531
|
return limited.skipped ? limited : null;
|
|
3451
3532
|
}
|
|
3452
3533
|
function materializeInlinePrivacyValue(target, value, cfg, req, trace, masking, privacy, db, captureScope) {
|
|
3534
|
+
value = constrainLiveCapture(value, cfg, target, captureScope);
|
|
3453
3535
|
if (value === undefined) {
|
|
3454
3536
|
return { value };
|
|
3455
3537
|
}
|
|
@@ -3473,6 +3555,7 @@ function materializeInlinePrivacyValue(target, value, cfg, req, trace, masking,
|
|
|
3473
3555
|
}
|
|
3474
3556
|
}
|
|
3475
3557
|
async function materializeInlinePrivacyValueAsync(target, value, cfg, req, trace, masking, privacy, db, captureScope) {
|
|
3558
|
+
value = constrainLiveCapture(value, cfg, target, captureScope);
|
|
3476
3559
|
if (value === undefined) {
|
|
3477
3560
|
return { value };
|
|
3478
3561
|
}
|
|
@@ -4449,6 +4532,8 @@ function boundTraceEventsForTransport(events) {
|
|
|
4449
4532
|
function createCapturedValueEntry(params) {
|
|
4450
4533
|
if (!__FULL_VALUE_CAPTURE_ENABLED)
|
|
4451
4534
|
return undefined;
|
|
4535
|
+
const limitedRaw = constrainLiveCapture(params.rawValue, params.capture.runtimeConfig, params.target);
|
|
4536
|
+
const omissions = isLiveCapture(params.capture.runtimeConfig) ? (0, live_capture_limits_1.collectCaptureOmissions)(limitedRaw) : [];
|
|
4452
4537
|
const preferredLimits = {
|
|
4453
4538
|
maxDepth: TRACE_FULL_VALUE_MAX_DEPTH,
|
|
4454
4539
|
maxKeys: TRACE_FULL_VALUE_MAX_KEYS,
|
|
@@ -4458,7 +4543,7 @@ function createCapturedValueEntry(params) {
|
|
|
4458
4543
|
const buildMaskedCandidate = (limits) => {
|
|
4459
4544
|
const projected = params.target === 'request.headers'
|
|
4460
4545
|
? sanitizeHeaders(params.rawValue, params.capture.captureHeaders)
|
|
4461
|
-
: cloneFullValueCandidate(
|
|
4546
|
+
: cloneFullValueCandidate(limitedRaw, params.capture, limits);
|
|
4462
4547
|
if (projected === undefined)
|
|
4463
4548
|
return undefined;
|
|
4464
4549
|
return applyPrivacyThenMask(params.target, projected, params.capture.runtimeConfig, params.capture.maskReq, params.capture.trace, params.capture.masking, params.capture.privacy, params.capture.db);
|
|
@@ -4469,7 +4554,7 @@ function createCapturedValueEntry(params) {
|
|
|
4469
4554
|
return { captureRef: { status: 'skipped-non-meaningful' } };
|
|
4470
4555
|
}
|
|
4471
4556
|
const candidateSerialized = stableSerializeTraceValue(candidate);
|
|
4472
|
-
if (!candidateSerialized || candidateSerialized === previewSerialized) {
|
|
4557
|
+
if (!candidateSerialized || (candidateSerialized === previewSerialized && !omissions.length)) {
|
|
4473
4558
|
return undefined;
|
|
4474
4559
|
}
|
|
4475
4560
|
const valueId = contentAddressedValueId(params.target, candidateSerialized, params.capture);
|
|
@@ -4489,6 +4574,7 @@ function createCapturedValueEntry(params) {
|
|
|
4489
4574
|
status: 'stored',
|
|
4490
4575
|
valueId,
|
|
4491
4576
|
projectedKind: entry.projectedKind,
|
|
4577
|
+
...(omissions.length ? { complete: false, omissions } : {}),
|
|
4492
4578
|
},
|
|
4493
4579
|
entry,
|
|
4494
4580
|
};
|
|
@@ -4496,6 +4582,8 @@ function createCapturedValueEntry(params) {
|
|
|
4496
4582
|
async function createCapturedValueEntryAsync(params) {
|
|
4497
4583
|
if (!__FULL_VALUE_CAPTURE_ENABLED)
|
|
4498
4584
|
return undefined;
|
|
4585
|
+
const limitedRaw = constrainLiveCapture(params.rawValue, params.capture.runtimeConfig, params.target);
|
|
4586
|
+
const omissions = isLiveCapture(params.capture.runtimeConfig) ? (0, live_capture_limits_1.collectCaptureOmissions)(limitedRaw) : [];
|
|
4499
4587
|
const preferredLimits = {
|
|
4500
4588
|
maxDepth: TRACE_FULL_VALUE_MAX_DEPTH,
|
|
4501
4589
|
maxKeys: TRACE_FULL_VALUE_MAX_KEYS,
|
|
@@ -4505,7 +4593,7 @@ async function createCapturedValueEntryAsync(params) {
|
|
|
4505
4593
|
const buildMaskedCandidate = async (limits) => {
|
|
4506
4594
|
const projected = params.target === 'request.headers'
|
|
4507
4595
|
? sanitizeHeaders(params.rawValue, params.capture.captureHeaders)
|
|
4508
|
-
: await cloneFullValueCandidateAsync(
|
|
4596
|
+
: await cloneFullValueCandidateAsync(limitedRaw, params.capture, limits);
|
|
4509
4597
|
if (projected === undefined)
|
|
4510
4598
|
return undefined;
|
|
4511
4599
|
return applyPrivacyThenMaskAsync(params.target, projected, params.capture.runtimeConfig, params.capture.maskReq, params.capture.trace, params.capture.masking, params.capture.privacy, params.capture.db);
|
|
@@ -4516,7 +4604,7 @@ async function createCapturedValueEntryAsync(params) {
|
|
|
4516
4604
|
return { captureRef: { status: 'skipped-non-meaningful' } };
|
|
4517
4605
|
}
|
|
4518
4606
|
const candidateSerialized = stableSerializeTraceValue(candidate);
|
|
4519
|
-
if (!candidateSerialized || candidateSerialized === previewSerialized) {
|
|
4607
|
+
if (!candidateSerialized || (candidateSerialized === previewSerialized && !omissions.length)) {
|
|
4520
4608
|
return undefined;
|
|
4521
4609
|
}
|
|
4522
4610
|
const valueId = contentAddressedValueId(params.target, candidateSerialized, params.capture);
|
|
@@ -4536,6 +4624,7 @@ async function createCapturedValueEntryAsync(params) {
|
|
|
4536
4624
|
status: 'stored',
|
|
4537
4625
|
valueId,
|
|
4538
4626
|
projectedKind: entry.projectedKind,
|
|
4627
|
+
...(omissions.length ? { complete: false, omissions } : {}),
|
|
4539
4628
|
},
|
|
4540
4629
|
entry,
|
|
4541
4630
|
};
|
|
@@ -4738,6 +4827,7 @@ function reproMiddleware(cfg) {
|
|
|
4738
4827
|
const querySid = readRequestQueryParam(req, REPRO_QUERY_SESSION_PARAM);
|
|
4739
4828
|
const queryAid = readRequestQueryParam(req, REPRO_QUERY_ACTION_PARAM);
|
|
4740
4829
|
const queryTs = readRequestQueryNumber(req, REPRO_QUERY_START_PARAM);
|
|
4830
|
+
const queryCaptureMode = readRequestQueryParam(req, '__repro_mode');
|
|
4741
4831
|
if (querySid || queryAid || queryTs !== null) {
|
|
4742
4832
|
stripReproQueryParams(req);
|
|
4743
4833
|
}
|
|
@@ -4774,21 +4864,25 @@ function reproMiddleware(cfg) {
|
|
|
4774
4864
|
res.on('close', endTrackedClientRequest);
|
|
4775
4865
|
return next(); // only capture tagged requests
|
|
4776
4866
|
}
|
|
4777
|
-
|
|
4778
|
-
|
|
4867
|
+
let captureCfg = cfg;
|
|
4868
|
+
if (backendLiveChild) {
|
|
4869
|
+
captureCfg = {
|
|
4779
4870
|
...cfg,
|
|
4780
4871
|
actorId: backendLiveChild.actorId,
|
|
4781
4872
|
actorType: backendLiveChild.actorType,
|
|
4782
4873
|
actorLabels: backendLiveChild.actorLabels,
|
|
4783
|
-
}
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
:
|
|
4874
|
+
};
|
|
4875
|
+
shareRuntimePrivacyState(cfg, captureCfg);
|
|
4876
|
+
}
|
|
4877
|
+
else if (taggedActorIdentity) {
|
|
4878
|
+
captureCfg = {
|
|
4879
|
+
...cfg,
|
|
4880
|
+
actorId: taggedActorIdentity.actorId,
|
|
4881
|
+
actorType: taggedActorIdentity.actorType,
|
|
4882
|
+
actorLabels: taggedActorIdentity.actorLabels,
|
|
4883
|
+
};
|
|
4884
|
+
shareRuntimePrivacyState(cfg, captureCfg);
|
|
4885
|
+
}
|
|
4792
4886
|
if (taggedActorIdentity) {
|
|
4793
4887
|
post(captureCfg, sid, {
|
|
4794
4888
|
entries: [{
|
|
@@ -4803,6 +4897,11 @@ function reproMiddleware(cfg) {
|
|
|
4803
4897
|
}],
|
|
4804
4898
|
}, { priority: 'high' });
|
|
4805
4899
|
}
|
|
4900
|
+
const captureMode = backendLiveContext || req.headers['x-repro-capture-mode'] === 'live' || queryCaptureMode === 'live'
|
|
4901
|
+
? 'live' : 'staging';
|
|
4902
|
+
const modeConfig = { ...captureCfg, captureMode };
|
|
4903
|
+
shareRuntimePrivacyState(captureCfg, modeConfig);
|
|
4904
|
+
captureCfg = modeConfig;
|
|
4806
4905
|
const resolvePrivacy = () => getRuntimePrivacyState(captureCfg).policy ?? null;
|
|
4807
4906
|
const clockSkewMs = headerTs !== null ? headerTs - requestStartRaw : 0;
|
|
4808
4907
|
const rid = nextRequestId(requestEpochMs);
|
|
@@ -5262,17 +5361,17 @@ function reproMiddleware(cfg) {
|
|
|
5262
5361
|
evt.__reproPending = {
|
|
5263
5362
|
...(ev.args !== undefined
|
|
5264
5363
|
? {
|
|
5265
|
-
argsRaw:
|
|
5364
|
+
argsRaw: snapshotTraceArgsAtObservation(ev.args, captureScope),
|
|
5266
5365
|
}
|
|
5267
5366
|
: {}),
|
|
5268
5367
|
...(ev.returnValue !== undefined
|
|
5269
5368
|
? {
|
|
5270
|
-
returnValueRaw: ev.returnValue,
|
|
5369
|
+
returnValueRaw: snapshotTraceValueAtObservation(ev.returnValue, captureScope, '$', 'trace.returnValue'),
|
|
5271
5370
|
}
|
|
5272
5371
|
: {}),
|
|
5273
5372
|
...(hasErrorPayload
|
|
5274
5373
|
? {
|
|
5275
|
-
errorRaw: ev.error,
|
|
5374
|
+
errorRaw: snapshotTraceValueAtObservation(ev.error, captureScope, '$', 'trace.error'),
|
|
5276
5375
|
}
|
|
5277
5376
|
: {}),
|
|
5278
5377
|
};
|
|
@@ -6227,6 +6326,8 @@ function patchNodeFetchForPropagation() {
|
|
|
6227
6326
|
if (runtime.aid && !hasHttpHeader(headers, KAFKA_HEADER_ACTION_ID)) {
|
|
6228
6327
|
setHttpHeader(headers, KAFKA_HEADER_ACTION_ID, runtime.aid);
|
|
6229
6328
|
}
|
|
6329
|
+
if (runtime.sid)
|
|
6330
|
+
setHttpHeader(headers, 'x-repro-capture-mode', getCtx().captureScope?.liveCapture ? 'live' : 'staging');
|
|
6230
6331
|
if (runtime.traceId && !hasHttpHeader(headers, KAFKA_HEADER_TRACE_ID)) {
|
|
6231
6332
|
setHttpHeader(headers, KAFKA_HEADER_TRACE_ID, runtime.traceId);
|
|
6232
6333
|
}
|
|
@@ -6277,6 +6378,8 @@ function patchNodeHttpModuleRequest(mod) {
|
|
|
6277
6378
|
maybeSet(REQUEST_START_HEADER, String(alignedNow()));
|
|
6278
6379
|
maybeSet(KAFKA_HEADER_SESSION_ID, runtime.sid);
|
|
6279
6380
|
maybeSet(KAFKA_HEADER_ACTION_ID, runtime.aid);
|
|
6381
|
+
if (runtime.sid)
|
|
6382
|
+
maybeSet('x-repro-capture-mode', getCtx().captureScope?.liveCapture ? 'live' : 'staging');
|
|
6280
6383
|
maybeSet(KAFKA_HEADER_TRACE_ID, runtime.traceId);
|
|
6281
6384
|
maybeSet(KAFKA_HEADER_PARENT_SPAN_ID, runtime.parentSpanId);
|
|
6282
6385
|
maybeSet(KAFKA_HEADER_REQUEST_ID, runtime.requestRid);
|
|
@@ -6453,6 +6556,11 @@ function cloneKafkaTelemetryValue(value) {
|
|
|
6453
6556
|
}
|
|
6454
6557
|
}
|
|
6455
6558
|
async function materializeKafkaRequestBody(cfg, maskReq, body, captureScope) {
|
|
6559
|
+
// Broker bodies have no separate full-value reference. Returning the trace
|
|
6560
|
+
// inline preview here would permanently replace message arrays with 20 items.
|
|
6561
|
+
if (shouldCapturePrivacyRaw(cfg)) {
|
|
6562
|
+
return { value: constrainLiveCapture(sanitizeInlinePrivacyValue(body), cfg, 'request.body', captureScope) };
|
|
6563
|
+
}
|
|
6456
6564
|
return materializeInlinePrivacyValueAsync('request.body', sanitizeInlinePrivacyValue(body), cfg, maskReq, null, normalizeMaskingConfig(cfg.masking), getRuntimePrivacyState(cfg).policy ?? null, undefined, captureScope);
|
|
6457
6565
|
}
|
|
6458
6566
|
function summarizeKafkaError(error) {
|
|
@@ -6497,7 +6605,8 @@ function resolveKafkaContext(cfg, headers) {
|
|
|
6497
6605
|
readKafkaHeader(headers, KAFKA_HEADER_PARENT_SPAN_ID) ??
|
|
6498
6606
|
null;
|
|
6499
6607
|
const requestRid = readKafkaHeader(headers, KAFKA_HEADER_REQUEST_ID);
|
|
6500
|
-
return { sid, aid, traceId, parentSpanId, requestRid
|
|
6608
|
+
return { sid, aid, traceId, parentSpanId, requestRid,
|
|
6609
|
+
captureMode: readKafkaHeader(headers, 'x-repro-capture-mode') === 'live' || ctx.captureScope?.liveCapture ? 'live' : 'staging' };
|
|
6501
6610
|
}
|
|
6502
6611
|
function withOptionalTraceContext(traceId, fn) {
|
|
6503
6612
|
const tracerWithTrace = __TRACER__?.withTrace;
|
|
@@ -6621,6 +6730,7 @@ function recordKafkaTraceEvent(raw, sink, cfg, maskReq) {
|
|
|
6621
6730
|
function buildPendingKafkaTraceEvent(raw) {
|
|
6622
6731
|
if (!raw || (raw.type !== 'enter' && raw.type !== 'exit'))
|
|
6623
6732
|
return null;
|
|
6733
|
+
raw = snapshotRawTraceEventAtObservation(raw, getCtx().captureScope);
|
|
6624
6734
|
const sourceFileForLibrary = typeof raw.sourceFile === 'string' && raw.sourceFile
|
|
6625
6735
|
? String(raw.sourceFile)
|
|
6626
6736
|
: null;
|
|
@@ -6796,6 +6906,7 @@ function patchKafkaProducerInstance(producer, cfg) {
|
|
|
6796
6906
|
[KAFKA_HEADER_REQUEST_ID]: publishRid,
|
|
6797
6907
|
[KAFKA_HEADER_SESSION_ID]: sid,
|
|
6798
6908
|
[KAFKA_HEADER_ACTION_ID]: aid,
|
|
6909
|
+
'x-repro-capture-mode': ctx.captureScope?.liveCapture ? 'live' : 'staging',
|
|
6799
6910
|
}),
|
|
6800
6911
|
};
|
|
6801
6912
|
});
|
|
@@ -6918,6 +7029,7 @@ function patchKafkaProducerInstance(producer, cfg) {
|
|
|
6918
7029
|
[KAFKA_HEADER_REQUEST_ID]: publishRid,
|
|
6919
7030
|
[KAFKA_HEADER_SESSION_ID]: sid,
|
|
6920
7031
|
[KAFKA_HEADER_ACTION_ID]: aid,
|
|
7032
|
+
'x-repro-capture-mode': ctx.captureScope?.liveCapture ? 'live' : 'staging',
|
|
6921
7033
|
}),
|
|
6922
7034
|
};
|
|
6923
7035
|
}),
|
|
@@ -7030,6 +7142,7 @@ function wrapKafkaEachMessageHandler(eachMessage, cfg, consumer) {
|
|
|
7030
7142
|
const consumeRid = nextRequestId(`${topic}-${message?.offset ?? alignedNow()}`);
|
|
7031
7143
|
const traceId = consumeRid;
|
|
7032
7144
|
const captureScope = createRequestCaptureScope(consumeRid, 'kafka', cfg);
|
|
7145
|
+
captureScope.liveCapture = runtime.captureMode === 'live';
|
|
7033
7146
|
emitRequestCaptureStart(cfg, sid, aid, captureScope);
|
|
7034
7147
|
const rawTraceEvents = [];
|
|
7035
7148
|
const maskReq = {
|
|
@@ -7054,7 +7167,7 @@ function wrapKafkaEachMessageHandler(eachMessage, cfg, consumer) {
|
|
|
7054
7167
|
unsubscribe = __TRACER__.tracer.on((event) => {
|
|
7055
7168
|
if (!event || event.traceId !== traceId)
|
|
7056
7169
|
return;
|
|
7057
|
-
rawTraceEvents.push(event);
|
|
7170
|
+
rawTraceEvents.push(snapshotRawTraceEventAtObservation(event, captureScope));
|
|
7058
7171
|
});
|
|
7059
7172
|
}
|
|
7060
7173
|
catch { }
|
|
@@ -7144,6 +7257,7 @@ function wrapKafkaEachBatchHandler(eachBatch, cfg, consumer) {
|
|
|
7144
7257
|
const consumeRid = nextRequestId(`${topic}-batch-${alignedNow()}`);
|
|
7145
7258
|
const traceId = consumeRid;
|
|
7146
7259
|
const captureScope = createRequestCaptureScope(consumeRid, 'kafka', cfg);
|
|
7260
|
+
captureScope.liveCapture = runtime.captureMode === 'live';
|
|
7147
7261
|
emitRequestCaptureStart(cfg, sid, aid, captureScope);
|
|
7148
7262
|
const rawTraceEvents = [];
|
|
7149
7263
|
const maskReq = {
|
|
@@ -7168,7 +7282,7 @@ function wrapKafkaEachBatchHandler(eachBatch, cfg, consumer) {
|
|
|
7168
7282
|
unsubscribe = __TRACER__.tracer.on((event) => {
|
|
7169
7283
|
if (!event || event.traceId !== traceId)
|
|
7170
7284
|
return;
|
|
7171
|
-
rawTraceEvents.push(event);
|
|
7285
|
+
rawTraceEvents.push(snapshotRawTraceEventAtObservation(event, captureScope));
|
|
7172
7286
|
});
|
|
7173
7287
|
}
|
|
7174
7288
|
catch { }
|
|
@@ -7581,7 +7695,8 @@ function resolveAmqpContext(cfg, headers) {
|
|
|
7581
7695
|
readAmqpHeader(headers, KAFKA_HEADER_PARENT_SPAN_ID) ??
|
|
7582
7696
|
null;
|
|
7583
7697
|
const requestRid = readAmqpHeader(headers, KAFKA_HEADER_REQUEST_ID);
|
|
7584
|
-
return { sid, aid, traceId, parentSpanId, requestRid
|
|
7698
|
+
return { sid, aid, traceId, parentSpanId, requestRid,
|
|
7699
|
+
captureMode: readAmqpHeader(headers, 'x-repro-capture-mode') === 'live' || ctx.captureScope?.liveCapture ? 'live' : 'staging' };
|
|
7585
7700
|
}
|
|
7586
7701
|
function withOptionalTraceContextSync(traceId, fn) {
|
|
7587
7702
|
const tracerWithTrace = __TRACER__?.withTrace;
|
|
@@ -7756,6 +7871,7 @@ function patchAmqplibPublishMethod(channel, cfg, methodName) {
|
|
|
7756
7871
|
[KAFKA_HEADER_REQUEST_ID]: publishRid,
|
|
7757
7872
|
[KAFKA_HEADER_SESSION_ID]: sid,
|
|
7758
7873
|
[KAFKA_HEADER_ACTION_ID]: aid,
|
|
7874
|
+
'x-repro-capture-mode': ctx.captureScope?.liveCapture ? 'live' : 'staging',
|
|
7759
7875
|
};
|
|
7760
7876
|
const shouldPropagate = hasAmqpPropagationValues(propagationValues);
|
|
7761
7877
|
const callbackIndex = args.length > 0 && typeof args[args.length - 1] === 'function'
|
|
@@ -7861,6 +7977,7 @@ function wrapAmqplibConsumeHandler(onMessage, cfg, queue) {
|
|
|
7861
7977
|
const consumeRid = nextRequestId(`${queueName}-${deliveryTag}`);
|
|
7862
7978
|
const traceId = consumeRid;
|
|
7863
7979
|
const captureScope = createRequestCaptureScope(consumeRid, 'amqp', cfg);
|
|
7980
|
+
captureScope.liveCapture = runtime.captureMode === 'live';
|
|
7864
7981
|
emitRequestCaptureStart(cfg, sid, aid, captureScope);
|
|
7865
7982
|
const rawTraceEvents = [];
|
|
7866
7983
|
const maskReq = {
|
|
@@ -7886,7 +8003,7 @@ function wrapAmqplibConsumeHandler(onMessage, cfg, queue) {
|
|
|
7886
8003
|
unsubscribe = __TRACER__.tracer.on((event) => {
|
|
7887
8004
|
if (!event || event.traceId !== traceId)
|
|
7888
8005
|
return;
|
|
7889
|
-
rawTraceEvents.push(event);
|
|
8006
|
+
rawTraceEvents.push(snapshotRawTraceEventAtObservation(event, captureScope));
|
|
7890
8007
|
});
|
|
7891
8008
|
}
|
|
7892
8009
|
catch { }
|
package/dist/ingest/client.js
CHANGED
|
@@ -47,6 +47,8 @@ let backoffMs = 0;
|
|
|
47
47
|
let criticalBackoffMs = 0;
|
|
48
48
|
let shouldDeferDrain = null;
|
|
49
49
|
let deferDrainMs = DEFAULT_DEFER_DRAIN_MS;
|
|
50
|
+
const throttledUntil = new Map();
|
|
51
|
+
const throttleDelay = (config) => Math.max(0, (throttledUntil.get(resolveIngestUrl(config)) ?? 0) - Date.now());
|
|
50
52
|
const configureIngestQueueDrain = (options) => {
|
|
51
53
|
shouldDeferDrain = options.shouldDefer ?? null;
|
|
52
54
|
if (Number.isFinite(options.deferMs) && Number(options.deferMs) >= 0) {
|
|
@@ -166,6 +168,9 @@ const postIngestEntries = async (config, sessionId, entries) => {
|
|
|
166
168
|
};
|
|
167
169
|
exports.postIngestEntries = postIngestEntries;
|
|
168
170
|
const sendIngestEntries = async (config, sessionId, entries) => {
|
|
171
|
+
if (throttleDelay(config) > 0)
|
|
172
|
+
return 'throttled';
|
|
173
|
+
throttledUntil.delete(resolveIngestUrl(config));
|
|
169
174
|
if (!sessionId || !Array.isArray(entries) || entries.length === 0) {
|
|
170
175
|
return true;
|
|
171
176
|
}
|
|
@@ -197,6 +202,18 @@ const sendIngestEntries = async (config, sessionId, entries) => {
|
|
|
197
202
|
body: JSON.stringify({ events }),
|
|
198
203
|
...(controller ? { signal: controller.signal } : {}),
|
|
199
204
|
});
|
|
205
|
+
if (response.status === 429) {
|
|
206
|
+
const retryAfter = response.headers?.get?.('retry-after');
|
|
207
|
+
const seconds = retryAfter ? Number(retryAfter) : NaN;
|
|
208
|
+
const date = retryAfter ? Date.parse(retryAfter) : NaN;
|
|
209
|
+
const delay = Number.isFinite(seconds) ? seconds * 1000 : Number.isFinite(date) ? date - Date.now() : 1000;
|
|
210
|
+
throttledUntil.set(resolveIngestUrl(config), Date.now() + Math.max(1000, Math.min(86400000, delay)));
|
|
211
|
+
return 'throttled';
|
|
212
|
+
}
|
|
213
|
+
if (response.status === 402 || response.status === 410) {
|
|
214
|
+
console.warn(`[repro] Upload refused for ${sessionId}: ${response.status === 402 ? 'capture quota exhausted' : 'capture deleted, expired or failed'}. This batch will not be retried.`);
|
|
215
|
+
return 'denied';
|
|
216
|
+
}
|
|
200
217
|
return response.ok;
|
|
201
218
|
}
|
|
202
219
|
catch {
|
|
@@ -257,7 +274,11 @@ const drainCriticalIngestQueue = async () => {
|
|
|
257
274
|
return;
|
|
258
275
|
}
|
|
259
276
|
const ok = await sendIngestEntries(selected.batch.config, selected.batch.sessionId, selected.batch.entries);
|
|
260
|
-
if (ok) {
|
|
277
|
+
if (ok === 'throttled') {
|
|
278
|
+
scheduleCriticalDrain(throttleDelay(selected.batch.config));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (ok === true || ok === 'denied') {
|
|
261
282
|
criticalIngestQueue.splice(0, selected.queueItems);
|
|
262
283
|
criticalBackoffMs = 0;
|
|
263
284
|
continue;
|
|
@@ -298,7 +319,11 @@ const drainIngestQueue = async () => {
|
|
|
298
319
|
}
|
|
299
320
|
const batch = selected.batch;
|
|
300
321
|
const ok = await sendIngestEntries(batch.config, batch.sessionId, batch.entries);
|
|
301
|
-
if (ok) {
|
|
322
|
+
if (ok === 'throttled') {
|
|
323
|
+
scheduleDrain(throttleDelay(batch.config));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (ok === true || ok === 'denied') {
|
|
302
327
|
ingestQueue.splice(0, selected.queueItems);
|
|
303
328
|
backoffMs = 0;
|
|
304
329
|
continue;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type CaptureOmission = {
|
|
2
|
+
reason: 'max-depth' | 'max-keys' | 'max-entries' | 'accessor' | 'uninspectable';
|
|
3
|
+
path: string;
|
|
4
|
+
limit?: number;
|
|
5
|
+
originalCount?: number;
|
|
6
|
+
capturedCount: number;
|
|
7
|
+
};
|
|
8
|
+
export declare const LIVE_CAPTURE_LIMITS: Readonly<{
|
|
9
|
+
maxDepth: 10;
|
|
10
|
+
maxKeys: 100;
|
|
11
|
+
maxEntries: 100;
|
|
12
|
+
}>;
|
|
13
|
+
/** Snapshot only data descriptors: capture must never execute application getters. */
|
|
14
|
+
export declare function limitLiveCapture(value: unknown, rootPath?: string): {
|
|
15
|
+
value: any;
|
|
16
|
+
omissions: CaptureOmission[];
|
|
17
|
+
};
|
|
18
|
+
export declare function collectCaptureOmissions(value: any): CaptureOmission[];
|
|
19
|
+
export declare function limitLiveArguments(args: any[]): any[];
|