@reproapp/node-sdk 0.0.47 → 0.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +6 -1
- package/dist/index.js +95 -39
- package/dist/semantic-serialization.d.ts +9 -0
- package/dist/semantic-serialization.js +171 -0
- package/package.json +3 -3
- package/src/index.ts +161 -39
- package/src/semantic-serialization.ts +182 -0
- package/test/content-addressed-mongodb.testcontainers.test.js +348 -0
- package/test/content-addressed-trace-values.test.js +264 -0
- package/test/fixtures/semantic-serialization-trace-controller.js +85 -0
- package/test/fixtures/semantic-serialization-trace-server.js +19 -0
- package/test/semantic-serialization-trace.test.js +152 -0
- package/test/semantic-serialization.test.js +229 -0
- package/test/session-storage-budget.test.js +298 -0
- package/test/trace-batch-size.test.js +33 -0
package/dist/index.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ import type { Request, Response, NextFunction } from 'express';
|
|
|
27
27
|
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
|
+
import { projectSemanticInfrastructureValue } from './semantic-serialization';
|
|
30
31
|
type SpanContext = {
|
|
31
32
|
traceId: string | null;
|
|
32
33
|
spanId: string | number | null;
|
|
@@ -106,6 +107,7 @@ type TraceValueBatchEntry = {
|
|
|
106
107
|
eventIndex?: number;
|
|
107
108
|
};
|
|
108
109
|
type TraceMaterializationConfig = {
|
|
110
|
+
tenantId: string;
|
|
109
111
|
appId: string;
|
|
110
112
|
appSecret?: string;
|
|
111
113
|
appName?: string;
|
|
@@ -118,6 +120,7 @@ type TraceMaterializationWorkerPayload = {
|
|
|
118
120
|
masking: NormalizedMaskingConfig | null;
|
|
119
121
|
privacy: NormalizedRuntimePrivacyPolicy | null;
|
|
120
122
|
fullValueCaptureEnabled: boolean;
|
|
123
|
+
valueNamespace?: string;
|
|
121
124
|
};
|
|
122
125
|
export type HeaderRule = string | RegExp;
|
|
123
126
|
export type HeaderCaptureOptions = {
|
|
@@ -295,6 +298,7 @@ type RequestCaptureScopeState = {
|
|
|
295
298
|
privacyMode: 'policy' | 'fallback' | 'raw';
|
|
296
299
|
privacyPolicyFingerprint?: string;
|
|
297
300
|
privacyStatementCount: number;
|
|
301
|
+
emittedValueDefinitionIds: Set<string>;
|
|
298
302
|
};
|
|
299
303
|
type BackendLiveTimingConfig = {
|
|
300
304
|
sessionMaxDurationMs: number;
|
|
@@ -334,7 +338,7 @@ type InlinePrivacyMaterializationResult = {
|
|
|
334
338
|
};
|
|
335
339
|
declare function materializeInlinePrivacyValueAsync(target: RuntimePrivacySurface, value: any, cfg: ReproMiddlewareConfig, req: MaskRequestContext, trace: TraceEventForFilter | null, masking: NormalizedMaskingConfig | null, privacy: NormalizedRuntimePrivacyPolicy | null, db?: RuntimePrivacyDbContext | null, captureScope?: RequestCaptureScopeState | null): Promise<InlinePrivacyMaterializationResult>;
|
|
336
340
|
declare function getEventTraceValueEntries(event: TraceEventRecord | PendingTraceEventRecord): TraceValueBatchEntry[] | undefined;
|
|
337
|
-
declare function estimateTraceBatchSerializedBytes(batch: TraceEventRecord[], requestRid: string, actionId: string | null | undefined, batchIndex: number): number;
|
|
341
|
+
declare function estimateTraceBatchSerializedBytes(batch: TraceEventRecord[], requestRid: string, actionId: string | null | undefined, batchIndex: number, previouslyEmittedValueIds?: ReadonlySet<string>): number;
|
|
338
342
|
declare function chunkTraceEventsForTransport(events: TraceEventRecord[], requestRid: string, actionId: string | null | undefined): TraceEventRecord[][];
|
|
339
343
|
export type ReproMiddlewareConfig = IngestClientConfig & {
|
|
340
344
|
/** API base used for backend-owned live session documents. Defaults to REPRO_API_BASE_URL/REPRO_API_BASE/privacy.apiBase. */
|
|
@@ -491,6 +495,7 @@ export declare const __reproTestHooks: {
|
|
|
491
495
|
estimateTraceBatchSerializedBytesForTest: typeof estimateTraceBatchSerializedBytes;
|
|
492
496
|
sanitizeTraceValueForPrivacyForTest: typeof sanitizeTraceValueForPrivacy;
|
|
493
497
|
sanitizeMaterializedTraceValueForTest: typeof sanitizeMaterializedTraceValue;
|
|
498
|
+
projectSemanticInfrastructureValueForTest: typeof projectSemanticInfrastructureValue;
|
|
494
499
|
getEventTraceValueEntriesForTest: typeof getEventTraceValueEntries;
|
|
495
500
|
};
|
|
496
501
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const path_1 = require("path");
|
|
|
9
9
|
const url_1 = require("url");
|
|
10
10
|
const client_1 = require("./ingest/client");
|
|
11
11
|
const privacy_1 = require("./privacy");
|
|
12
|
+
const semantic_serialization_1 = require("./semantic-serialization");
|
|
12
13
|
/**
|
|
13
14
|
* ============================= IMPORTANT =============================
|
|
14
15
|
* We snapshot Mongoose core methods BEFORE tracer init, then re-apply them
|
|
@@ -837,6 +838,7 @@ function createRequestCaptureScope(requestRid, transport, cfg) {
|
|
|
837
838
|
privacyMode,
|
|
838
839
|
privacyPolicyFingerprint: privacy?.fingerprint,
|
|
839
840
|
privacyStatementCount: privacy?.statements.length ?? 0,
|
|
841
|
+
emittedValueDefinitionIds: new Set(),
|
|
840
842
|
};
|
|
841
843
|
}
|
|
842
844
|
function recordRequestCaptureFailure(scope, stage, target, reason) {
|
|
@@ -2275,7 +2277,11 @@ const TRACE_MAX_BATCHES_PER_REQUEST = (() => {
|
|
|
2275
2277
|
const env = Number(process.env.REPRO_TRACE_MAX_BATCHES_PER_REQUEST);
|
|
2276
2278
|
if (Number.isFinite(env) && env > 0)
|
|
2277
2279
|
return Math.trunc(env);
|
|
2278
|
-
|
|
2280
|
+
// Byte-based splitting can legitimately produce one batch per event when
|
|
2281
|
+
// every event owns a distinct large value. The event limit remains the
|
|
2282
|
+
// hard upper bound; deriving this from the count-only batch size silently
|
|
2283
|
+
// dropped otherwise valid events after ten batches.
|
|
2284
|
+
return TRACE_MAX_EVENTS_PER_REQUEST;
|
|
2279
2285
|
})();
|
|
2280
2286
|
const TRACE_FLUSH_DELAY_MS = 20;
|
|
2281
2287
|
// Choose how to order trace events in payloads.
|
|
@@ -2603,6 +2609,10 @@ function sanitizeTraceValue(value, depth = 0, seen = new WeakMap(), options = {}
|
|
|
2603
2609
|
const mongoPlaceholder = describeMongoPlaceholder(value);
|
|
2604
2610
|
if (mongoPlaceholder)
|
|
2605
2611
|
return mongoPlaceholder;
|
|
2612
|
+
const semanticProjection = (0, semantic_serialization_1.projectSemanticInfrastructureValue)(value);
|
|
2613
|
+
if (semanticProjection) {
|
|
2614
|
+
return sanitizeTraceValue(semanticProjection.value, depth, seen, options, valuePath);
|
|
2615
|
+
}
|
|
2606
2616
|
if (isThenable(value)) {
|
|
2607
2617
|
return { __type: 'Promise', state: 'pending' };
|
|
2608
2618
|
}
|
|
@@ -2824,6 +2834,7 @@ async function materializePendingTraceEvents(events, params) {
|
|
|
2824
2834
|
trace: candidate,
|
|
2825
2835
|
masking: params.masking,
|
|
2826
2836
|
privacy: activePrivacy,
|
|
2837
|
+
valueNamespace: params.valueNamespace,
|
|
2827
2838
|
};
|
|
2828
2839
|
const traceValueMetadata = {
|
|
2829
2840
|
fn: evt.fn,
|
|
@@ -2946,6 +2957,7 @@ async function materializePendingTraceEventsAsync(events, params) {
|
|
|
2946
2957
|
return await materializePendingTraceEventsOffThread(events, {
|
|
2947
2958
|
events,
|
|
2948
2959
|
cfg: {
|
|
2960
|
+
tenantId: params.cfg.tenantId,
|
|
2949
2961
|
appId: params.cfg.appId,
|
|
2950
2962
|
appSecret: params.cfg.appSecret,
|
|
2951
2963
|
appName: params.cfg.appName,
|
|
@@ -2955,6 +2967,7 @@ async function materializePendingTraceEventsAsync(events, params) {
|
|
|
2955
2967
|
masking: params.masking,
|
|
2956
2968
|
privacy,
|
|
2957
2969
|
fullValueCaptureEnabled: __FULL_VALUE_CAPTURE_ENABLED,
|
|
2970
|
+
valueNamespace: params.valueNamespace,
|
|
2958
2971
|
});
|
|
2959
2972
|
}
|
|
2960
2973
|
catch (err) {
|
|
@@ -2981,6 +2994,7 @@ async function __materializePendingTraceEventsForWorker(payload) {
|
|
|
2981
2994
|
maskReq: payload.maskReq,
|
|
2982
2995
|
masking: payload.masking,
|
|
2983
2996
|
resolvePrivacy: () => payload.privacy,
|
|
2997
|
+
valueNamespace: payload.valueNamespace,
|
|
2984
2998
|
});
|
|
2985
2999
|
return materialized.map((event) => {
|
|
2986
3000
|
const entries = getEventTraceValueEntries(event);
|
|
@@ -3982,6 +3996,10 @@ function cloneFullValueCandidate(value, ctx, limits, depth = 0, seen = new WeakM
|
|
|
3982
3996
|
const mongoPlaceholder = describeMongoPlaceholder(value);
|
|
3983
3997
|
if (mongoPlaceholder)
|
|
3984
3998
|
return undefined;
|
|
3999
|
+
const semanticProjection = (0, semantic_serialization_1.projectSemanticInfrastructureValue)(value);
|
|
4000
|
+
if (semanticProjection) {
|
|
4001
|
+
return cloneFullValueCandidate(semanticProjection.value, ctx, limits, depth, seen, valuePath);
|
|
4002
|
+
}
|
|
3985
4003
|
if (isThenable(value))
|
|
3986
4004
|
return undefined;
|
|
3987
4005
|
if (shouldSkipFullValueObject(value))
|
|
@@ -4166,6 +4184,10 @@ async function cloneFullValueCandidateAsync(value, ctx, limits, depth = 0, seen
|
|
|
4166
4184
|
const mongoPlaceholder = describeMongoPlaceholder(value);
|
|
4167
4185
|
if (mongoPlaceholder)
|
|
4168
4186
|
return undefined;
|
|
4187
|
+
const semanticProjection = (0, semantic_serialization_1.projectSemanticInfrastructureValue)(value);
|
|
4188
|
+
if (semanticProjection) {
|
|
4189
|
+
return cloneFullValueCandidateAsync(semanticProjection.value, ctx, limits, depth, seen, valuePath);
|
|
4190
|
+
}
|
|
4169
4191
|
if (isThenable(value))
|
|
4170
4192
|
return undefined;
|
|
4171
4193
|
if (shouldSkipFullValueObject(value))
|
|
@@ -4314,13 +4336,16 @@ function getEventTraceValueEntries(event) {
|
|
|
4314
4336
|
}
|
|
4315
4337
|
return undefined;
|
|
4316
4338
|
}
|
|
4317
|
-
function collectBatchTraceValueEntries(batch, batchIndex) {
|
|
4339
|
+
function collectBatchTraceValueEntries(batch, batchIndex, emittedValueIds = new Set()) {
|
|
4318
4340
|
const collected = [];
|
|
4319
4341
|
batch.forEach((event, eventIndex) => {
|
|
4320
4342
|
const entries = getEventTraceValueEntries(event);
|
|
4321
4343
|
if (!Array.isArray(entries) || !entries.length)
|
|
4322
4344
|
return;
|
|
4323
4345
|
entries.forEach((entry) => {
|
|
4346
|
+
if (emittedValueIds.has(entry.id))
|
|
4347
|
+
return;
|
|
4348
|
+
emittedValueIds.add(entry.id);
|
|
4324
4349
|
collected.push({
|
|
4325
4350
|
...entry,
|
|
4326
4351
|
batchIndex,
|
|
@@ -4338,8 +4363,25 @@ function serializedByteLength(value) {
|
|
|
4338
4363
|
return Number.MAX_SAFE_INTEGER;
|
|
4339
4364
|
}
|
|
4340
4365
|
}
|
|
4341
|
-
function
|
|
4342
|
-
const
|
|
4366
|
+
function contentAddressedValueId(target, candidateSerialized, capture) {
|
|
4367
|
+
const config = capture.runtimeConfig;
|
|
4368
|
+
const signingKey = String(config.appSecret ||
|
|
4369
|
+
`${config.tenantId || 'tenant'}:${config.appId || 'app'}`);
|
|
4370
|
+
const namespace = String(capture.valueNamespace ||
|
|
4371
|
+
`${config.tenantId || 'tenant'}:${config.appId || 'app'}`);
|
|
4372
|
+
const digest = (0, crypto_1.createHmac)('sha256', signingKey)
|
|
4373
|
+
.update('repro-captured-value-v1\0')
|
|
4374
|
+
.update(namespace)
|
|
4375
|
+
.update('\0')
|
|
4376
|
+
.update(target)
|
|
4377
|
+
.update('\0')
|
|
4378
|
+
.update(candidateSerialized)
|
|
4379
|
+
.digest('hex')
|
|
4380
|
+
.slice(0, 40);
|
|
4381
|
+
return `rv1_${digest}`;
|
|
4382
|
+
}
|
|
4383
|
+
function estimateTraceBatchSerializedBytes(batch, requestRid, actionId, batchIndex, previouslyEmittedValueIds = new Set()) {
|
|
4384
|
+
const traceValues = collectBatchTraceValueEntries(batch, batchIndex, new Set(previouslyEmittedValueIds));
|
|
4343
4385
|
return serializedByteLength({
|
|
4344
4386
|
actionId: actionId ?? null,
|
|
4345
4387
|
trace: batch,
|
|
@@ -4358,26 +4400,32 @@ function chunkTraceEventsForTransport(events, requestRid, actionId) {
|
|
|
4358
4400
|
const boundedEvents = boundTraceEventsForTransport(events);
|
|
4359
4401
|
const countBatches = chunkArray(boundedEvents, TRACE_BATCH_SIZE);
|
|
4360
4402
|
const sizedBatches = [];
|
|
4403
|
+
const emittedValueIds = new Set();
|
|
4361
4404
|
let current = [];
|
|
4405
|
+
const commitCurrent = () => {
|
|
4406
|
+
if (!current.length)
|
|
4407
|
+
return;
|
|
4408
|
+
collectBatchTraceValueEntries(current, sizedBatches.length, emittedValueIds);
|
|
4409
|
+
sizedBatches.push(current);
|
|
4410
|
+
current = [];
|
|
4411
|
+
};
|
|
4362
4412
|
for (const event of countBatches.flat()) {
|
|
4363
4413
|
if (sizedBatches.length >= TRACE_MAX_BATCHES_PER_REQUEST) {
|
|
4364
4414
|
break;
|
|
4365
4415
|
}
|
|
4366
4416
|
if (current.length >= TRACE_BATCH_SIZE) {
|
|
4367
|
-
|
|
4368
|
-
current = [];
|
|
4417
|
+
commitCurrent();
|
|
4369
4418
|
if (sizedBatches.length >= TRACE_MAX_BATCHES_PER_REQUEST) {
|
|
4370
4419
|
break;
|
|
4371
4420
|
}
|
|
4372
4421
|
}
|
|
4373
4422
|
const candidate = current.concat(event);
|
|
4374
4423
|
const batchIndex = sizedBatches.length;
|
|
4375
|
-
const estimatedBytes = estimateTraceBatchSerializedBytes(candidate, requestRid, actionId, batchIndex);
|
|
4424
|
+
const estimatedBytes = estimateTraceBatchSerializedBytes(candidate, requestRid, actionId, batchIndex, emittedValueIds);
|
|
4376
4425
|
if (current.length > 0 &&
|
|
4377
4426
|
estimatedBytes > TRACE_BATCH_MAX_SERIALIZED_BYTES) {
|
|
4378
|
-
|
|
4427
|
+
commitCurrent();
|
|
4379
4428
|
if (sizedBatches.length >= TRACE_MAX_BATCHES_PER_REQUEST) {
|
|
4380
|
-
current = [];
|
|
4381
4429
|
break;
|
|
4382
4430
|
}
|
|
4383
4431
|
current = [event];
|
|
@@ -4387,7 +4435,7 @@ function chunkTraceEventsForTransport(events, requestRid, actionId) {
|
|
|
4387
4435
|
}
|
|
4388
4436
|
if (current.length > 0 &&
|
|
4389
4437
|
sizedBatches.length < TRACE_MAX_BATCHES_PER_REQUEST) {
|
|
4390
|
-
|
|
4438
|
+
commitCurrent();
|
|
4391
4439
|
}
|
|
4392
4440
|
return sizedBatches;
|
|
4393
4441
|
}
|
|
@@ -4424,7 +4472,7 @@ function createCapturedValueEntry(params) {
|
|
|
4424
4472
|
if (!candidateSerialized || candidateSerialized === previewSerialized) {
|
|
4425
4473
|
return undefined;
|
|
4426
4474
|
}
|
|
4427
|
-
const valueId = (
|
|
4475
|
+
const valueId = contentAddressedValueId(params.target, candidateSerialized, params.capture);
|
|
4428
4476
|
const projectedKind = candidate && typeof candidate === 'object'
|
|
4429
4477
|
? (candidate.__kind || candidate.__type)
|
|
4430
4478
|
: undefined;
|
|
@@ -4471,7 +4519,7 @@ async function createCapturedValueEntryAsync(params) {
|
|
|
4471
4519
|
if (!candidateSerialized || candidateSerialized === previewSerialized) {
|
|
4472
4520
|
return undefined;
|
|
4473
4521
|
}
|
|
4474
|
-
const valueId = (
|
|
4522
|
+
const valueId = contentAddressedValueId(params.target, candidateSerialized, params.capture);
|
|
4475
4523
|
const projectedKind = candidate && typeof candidate === 'object'
|
|
4476
4524
|
? (candidate.__kind || candidate.__type)
|
|
4477
4525
|
: undefined;
|
|
@@ -4492,13 +4540,20 @@ async function createCapturedValueEntryAsync(params) {
|
|
|
4492
4540
|
entry,
|
|
4493
4541
|
};
|
|
4494
4542
|
}
|
|
4543
|
+
function appendCapturedValueDefinition(sink, entry, captureScope) {
|
|
4544
|
+
if (!entry)
|
|
4545
|
+
return;
|
|
4546
|
+
const emitted = captureScope?.emittedValueDefinitionIds;
|
|
4547
|
+
if (emitted?.has(entry.id))
|
|
4548
|
+
return;
|
|
4549
|
+
emitted?.add(entry.id);
|
|
4550
|
+
sink.push(entry);
|
|
4551
|
+
}
|
|
4495
4552
|
function maybeCaptureRequestValue(params, sink) {
|
|
4496
4553
|
const result = createCapturedValueEntry(params);
|
|
4497
4554
|
if (!result)
|
|
4498
4555
|
return undefined;
|
|
4499
|
-
|
|
4500
|
-
sink.push(result.entry);
|
|
4501
|
-
}
|
|
4556
|
+
appendCapturedValueDefinition(sink, result.entry);
|
|
4502
4557
|
return result.captureRef;
|
|
4503
4558
|
}
|
|
4504
4559
|
async function maybeCaptureRequestValueAsync(params, sink, captureScope) {
|
|
@@ -4512,9 +4567,7 @@ async function maybeCaptureRequestValueAsync(params, sink, captureScope) {
|
|
|
4512
4567
|
}
|
|
4513
4568
|
if (!result)
|
|
4514
4569
|
return undefined;
|
|
4515
|
-
|
|
4516
|
-
sink.push(result.entry);
|
|
4517
|
-
}
|
|
4570
|
+
appendCapturedValueDefinition(sink, result.entry, captureScope);
|
|
4518
4571
|
return result.captureRef;
|
|
4519
4572
|
}
|
|
4520
4573
|
function maybeCaptureTraceValue(params) {
|
|
@@ -4533,9 +4586,7 @@ function maybeCaptureDbValue(params, sink) {
|
|
|
4533
4586
|
const result = createCapturedValueEntry(params);
|
|
4534
4587
|
if (!result)
|
|
4535
4588
|
return undefined;
|
|
4536
|
-
|
|
4537
|
-
sink.push(result.entry);
|
|
4538
|
-
}
|
|
4589
|
+
appendCapturedValueDefinition(sink, result.entry);
|
|
4539
4590
|
return result.captureRef;
|
|
4540
4591
|
}
|
|
4541
4592
|
async function maybeCaptureDbValueAsync(params, sink, captureScope) {
|
|
@@ -4549,9 +4600,7 @@ async function maybeCaptureDbValueAsync(params, sink, captureScope) {
|
|
|
4549
4600
|
}
|
|
4550
4601
|
if (!result)
|
|
4551
4602
|
return undefined;
|
|
4552
|
-
|
|
4553
|
-
sink.push(result.entry);
|
|
4554
|
-
}
|
|
4603
|
+
appendCapturedValueDefinition(sink, result.entry, captureScope);
|
|
4555
4604
|
return result.captureRef;
|
|
4556
4605
|
}
|
|
4557
4606
|
function pushUniqueCaptureCandidate(candidates, value) {
|
|
@@ -4591,7 +4640,7 @@ async function maybeCaptureDbValueFromCandidatesAsync(params, sink, captureScope
|
|
|
4591
4640
|
if (!result)
|
|
4592
4641
|
continue;
|
|
4593
4642
|
if (result.entry) {
|
|
4594
|
-
sink
|
|
4643
|
+
appendCapturedValueDefinition(sink, result.entry, captureScope);
|
|
4595
4644
|
return result.captureRef;
|
|
4596
4645
|
}
|
|
4597
4646
|
if (!skippedRef) {
|
|
@@ -4987,6 +5036,7 @@ function reproMiddleware(cfg) {
|
|
|
4987
5036
|
trace: endpointTraceCtx,
|
|
4988
5037
|
masking,
|
|
4989
5038
|
privacy: activePrivacy,
|
|
5039
|
+
valueNamespace: sid,
|
|
4990
5040
|
},
|
|
4991
5041
|
}, requestValueEntries, captureScope);
|
|
4992
5042
|
const paramsValueCapture = await maybeCaptureRequestValueAsync({
|
|
@@ -5000,6 +5050,7 @@ function reproMiddleware(cfg) {
|
|
|
5000
5050
|
trace: endpointTraceCtx,
|
|
5001
5051
|
masking,
|
|
5002
5052
|
privacy: activePrivacy,
|
|
5053
|
+
valueNamespace: sid,
|
|
5003
5054
|
},
|
|
5004
5055
|
}, requestValueEntries, captureScope);
|
|
5005
5056
|
const queryValueCapture = await maybeCaptureRequestValueAsync({
|
|
@@ -5013,6 +5064,7 @@ function reproMiddleware(cfg) {
|
|
|
5013
5064
|
trace: endpointTraceCtx,
|
|
5014
5065
|
masking,
|
|
5015
5066
|
privacy: activePrivacy,
|
|
5067
|
+
valueNamespace: sid,
|
|
5016
5068
|
},
|
|
5017
5069
|
}, requestValueEntries, captureScope);
|
|
5018
5070
|
const headersValueCapture = await maybeCaptureRequestValueAsync({
|
|
@@ -5026,6 +5078,7 @@ function reproMiddleware(cfg) {
|
|
|
5026
5078
|
trace: endpointTraceCtx,
|
|
5027
5079
|
masking,
|
|
5028
5080
|
privacy: activePrivacy,
|
|
5081
|
+
valueNamespace: sid,
|
|
5029
5082
|
},
|
|
5030
5083
|
}, requestValueEntries, captureScope);
|
|
5031
5084
|
const respBodyValueCapture = capturedBody === undefined
|
|
@@ -5041,6 +5094,7 @@ function reproMiddleware(cfg) {
|
|
|
5041
5094
|
trace: endpointTraceCtx,
|
|
5042
5095
|
masking,
|
|
5043
5096
|
privacy: activePrivacy,
|
|
5097
|
+
valueNamespace: sid,
|
|
5044
5098
|
},
|
|
5045
5099
|
}, requestValueEntries, captureScope);
|
|
5046
5100
|
const responseDurationMs = finishedAt !== null && Number.isFinite(finishedAt)
|
|
@@ -5250,6 +5304,7 @@ function reproMiddleware(cfg) {
|
|
|
5250
5304
|
masking,
|
|
5251
5305
|
resolvePrivacy,
|
|
5252
5306
|
captureScope,
|
|
5307
|
+
valueNamespace: sid,
|
|
5253
5308
|
});
|
|
5254
5309
|
const baseEvents = balanceTraceEvents(materializedEvents.slice());
|
|
5255
5310
|
const orderedEvents = TRACE_ORDER_MODE === 'tree'
|
|
@@ -5273,9 +5328,10 @@ function reproMiddleware(cfg) {
|
|
|
5273
5328
|
// on the wire so a large trace queue cannot starve completion.
|
|
5274
5329
|
emitRequestCaptureManifest(traceBatches.length);
|
|
5275
5330
|
try {
|
|
5331
|
+
const emittedValueIds = new Set();
|
|
5276
5332
|
for (let i = 0; i < traceBatches.length; i++) {
|
|
5277
5333
|
const batch = traceBatches[i];
|
|
5278
|
-
const traceValues = collectBatchTraceValueEntries(batch, i);
|
|
5334
|
+
const traceValues = collectBatchTraceValueEntries(batch, i, emittedValueIds);
|
|
5279
5335
|
post(captureCfg, sid, {
|
|
5280
5336
|
entries: [{
|
|
5281
5337
|
actionId: aid,
|
|
@@ -5846,12 +5902,6 @@ function dehydrateComplexValue(value) {
|
|
|
5846
5902
|
}
|
|
5847
5903
|
}
|
|
5848
5904
|
catch { }
|
|
5849
|
-
const ctor = value?.constructor?.name;
|
|
5850
|
-
if (ctor && ctor !== 'Object') {
|
|
5851
|
-
const plain = safeJson(value);
|
|
5852
|
-
if (plain !== undefined)
|
|
5853
|
-
return plain;
|
|
5854
|
-
}
|
|
5855
5905
|
return value;
|
|
5856
5906
|
}
|
|
5857
5907
|
function emitDbQuery(cfg, sid, aid, payload) {
|
|
@@ -5922,6 +5972,7 @@ async function emitDbQueryAsync(cfg, sid, aid, payload) {
|
|
|
5922
5972
|
masking: null,
|
|
5923
5973
|
privacy,
|
|
5924
5974
|
db: dbContext,
|
|
5975
|
+
valueNamespace: sid,
|
|
5925
5976
|
};
|
|
5926
5977
|
const dbValues = [];
|
|
5927
5978
|
const pkValueCapture = pkMaterialization.skipped ? undefined : await maybeCaptureDbValueAsync({
|
|
@@ -5929,37 +5980,37 @@ async function emitDbQueryAsync(cfg, sid, aid, payload) {
|
|
|
5929
5980
|
rawValue: payload.pk,
|
|
5930
5981
|
previewValue: pkPreview,
|
|
5931
5982
|
capture,
|
|
5932
|
-
}, dbValues);
|
|
5983
|
+
}, dbValues, ctx.captureScope);
|
|
5933
5984
|
const beforeValueCapture = beforeMaterialization.skipped ? undefined : await maybeCaptureDbValueAsync({
|
|
5934
5985
|
target: 'db.before',
|
|
5935
5986
|
rawValue: payload.before,
|
|
5936
5987
|
previewValue: beforePreview,
|
|
5937
5988
|
capture,
|
|
5938
|
-
}, dbValues);
|
|
5989
|
+
}, dbValues, ctx.captureScope);
|
|
5939
5990
|
const afterValueCapture = afterMaterialization.skipped ? undefined : await maybeCaptureDbValueAsync({
|
|
5940
5991
|
target: 'db.after',
|
|
5941
5992
|
rawValue: payload.after,
|
|
5942
5993
|
previewValue: afterPreview,
|
|
5943
5994
|
capture,
|
|
5944
|
-
}, dbValues);
|
|
5995
|
+
}, dbValues, ctx.captureScope);
|
|
5945
5996
|
const queryValueCapture = queryMaterialization.skipped ? undefined : await maybeCaptureDbValueAsync({
|
|
5946
5997
|
target: 'db.query',
|
|
5947
5998
|
rawValue: querySource,
|
|
5948
5999
|
previewValue: queryPreview,
|
|
5949
6000
|
capture,
|
|
5950
|
-
}, dbValues);
|
|
6001
|
+
}, dbValues, ctx.captureScope);
|
|
5951
6002
|
const resultMetaValueCapture = resultMetaMaterialization.skipped ? undefined : await maybeCaptureDbValueFromCandidatesAsync({
|
|
5952
6003
|
target: 'db.resultMeta',
|
|
5953
6004
|
rawValues: buildResultMetaFullValueCandidates(payload.resultMetaRaw, resultMetaSource),
|
|
5954
6005
|
previewValue: resultMetaPreview,
|
|
5955
6006
|
capture,
|
|
5956
|
-
}, dbValues);
|
|
6007
|
+
}, dbValues, ctx.captureScope);
|
|
5957
6008
|
const errorValueCapture = errorMaterialization.skipped ? undefined : await maybeCaptureDbValueAsync({
|
|
5958
6009
|
target: 'db.error',
|
|
5959
6010
|
rawValue: payload.error,
|
|
5960
6011
|
previewValue: errorPreview,
|
|
5961
6012
|
capture,
|
|
5962
|
-
}, dbValues);
|
|
6013
|
+
}, dbValues, ctx.captureScope);
|
|
5963
6014
|
const dbEntry = attachSpanContext({
|
|
5964
6015
|
type: payload.type ?? 'mongo',
|
|
5965
6016
|
collection: payload.collection,
|
|
@@ -6501,6 +6552,7 @@ function recordKafkaTraceEvent(raw, sink, cfg, maskReq) {
|
|
|
6501
6552
|
trace: candidate,
|
|
6502
6553
|
masking,
|
|
6503
6554
|
privacy,
|
|
6555
|
+
valueNamespace: getCtx().sid,
|
|
6504
6556
|
};
|
|
6505
6557
|
const traceValueMetadata = {
|
|
6506
6558
|
fn: evt.fn,
|
|
@@ -6638,6 +6690,7 @@ async function recordKafkaTraceEventAsync(raw, sink, cfg, maskReq) {
|
|
|
6638
6690
|
maskReq,
|
|
6639
6691
|
masking,
|
|
6640
6692
|
resolvePrivacy: () => getRuntimePrivacyState(cfg).policy ?? null,
|
|
6693
|
+
valueNamespace: getCtx().sid,
|
|
6641
6694
|
});
|
|
6642
6695
|
sink.push(...materializedEvents);
|
|
6643
6696
|
}
|
|
@@ -6649,10 +6702,11 @@ function buildKafkaTraceEntries(actionId, requestRid, events) {
|
|
|
6649
6702
|
? reorderTraceEvents(baseEvents)
|
|
6650
6703
|
: sortTraceEventsChronologically(baseEvents);
|
|
6651
6704
|
const batches = chunkTraceEventsForTransport(orderedEvents, requestRid, actionId);
|
|
6705
|
+
const emittedValueIds = new Set();
|
|
6652
6706
|
return batches.map((batch, index) => ({
|
|
6653
6707
|
actionId: actionId ?? null,
|
|
6654
6708
|
trace: batch,
|
|
6655
|
-
traceValues: collectBatchTraceValueEntries(batch, index),
|
|
6709
|
+
traceValues: collectBatchTraceValueEntries(batch, index, emittedValueIds),
|
|
6656
6710
|
traceBatch: {
|
|
6657
6711
|
rid: requestRid,
|
|
6658
6712
|
index,
|
|
@@ -6686,6 +6740,7 @@ function scheduleKafkaTraceFlush(cfg, kafkaCtx, actionId, requestRid, rawTraceEv
|
|
|
6686
6740
|
masking,
|
|
6687
6741
|
resolvePrivacy: () => getRuntimePrivacyState(cfg).policy ?? null,
|
|
6688
6742
|
captureScope,
|
|
6743
|
+
valueNamespace: sid,
|
|
6689
6744
|
});
|
|
6690
6745
|
entries = buildKafkaTraceEntries(actionId, requestRid, materializedEvents);
|
|
6691
6746
|
}
|
|
@@ -10090,5 +10145,6 @@ exports.__reproTestHooks = {
|
|
|
10090
10145
|
estimateTraceBatchSerializedBytesForTest: estimateTraceBatchSerializedBytes,
|
|
10091
10146
|
sanitizeTraceValueForPrivacyForTest: sanitizeTraceValueForPrivacy,
|
|
10092
10147
|
sanitizeMaterializedTraceValueForTest: sanitizeMaterializedTraceValue,
|
|
10148
|
+
projectSemanticInfrastructureValueForTest: semantic_serialization_1.projectSemanticInfrastructureValue,
|
|
10093
10149
|
getEventTraceValueEntriesForTest: getEventTraceValueEntries,
|
|
10094
10150
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type SemanticProjection = {
|
|
2
|
+
matched: true;
|
|
3
|
+
value: Record<string, unknown>;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Projects known runtime machinery into stable, non-recursive descriptions.
|
|
7
|
+
* Unknown application values deliberately return null and remain fully captured.
|
|
8
|
+
*/
|
|
9
|
+
export declare function projectSemanticInfrastructureValue(value: unknown): SemanticProjection | null;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.projectSemanticInfrastructureValue = void 0;
|
|
4
|
+
function constructorName(value) {
|
|
5
|
+
if (!value || typeof value !== 'object')
|
|
6
|
+
return '';
|
|
7
|
+
try {
|
|
8
|
+
const prototype = Object.getPrototypeOf(value);
|
|
9
|
+
const constructor = prototype
|
|
10
|
+
? Object.getOwnPropertyDescriptor(prototype, 'constructor')?.value
|
|
11
|
+
: undefined;
|
|
12
|
+
const name = constructor?.name;
|
|
13
|
+
return typeof name === 'string' ? name : '';
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function ownDataDescriptors(value) {
|
|
20
|
+
const out = {};
|
|
21
|
+
let descriptors;
|
|
22
|
+
try {
|
|
23
|
+
descriptors = Object.getOwnPropertyDescriptors(value);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
29
|
+
if (Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
|
|
30
|
+
out[key] = descriptor;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function method(value, key) {
|
|
36
|
+
try {
|
|
37
|
+
let current = value;
|
|
38
|
+
while (current) {
|
|
39
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, key);
|
|
40
|
+
if (descriptor) {
|
|
41
|
+
return Object.prototype.hasOwnProperty.call(descriptor, 'value') &&
|
|
42
|
+
typeof descriptor.value === 'function';
|
|
43
|
+
}
|
|
44
|
+
current = Object.getPrototypeOf(current);
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function primitiveStateValue(value) {
|
|
53
|
+
if (value === null)
|
|
54
|
+
return null;
|
|
55
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
if (typeof value === 'bigint')
|
|
59
|
+
return value.toString();
|
|
60
|
+
if (value instanceof Date)
|
|
61
|
+
return value.toISOString();
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
function semanticValue(category, className, details = {}) {
|
|
65
|
+
return {
|
|
66
|
+
matched: true,
|
|
67
|
+
value: {
|
|
68
|
+
__kind: 'repro-semantic',
|
|
69
|
+
category,
|
|
70
|
+
...(className ? { className } : {}),
|
|
71
|
+
...details,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function projectConfigurationService(value, className) {
|
|
76
|
+
if (!/(^|Api|App|Runtime|Environment)ConfigService$/i.test(className))
|
|
77
|
+
return null;
|
|
78
|
+
if (!method(value, 'get') && !method(value, 'getOrThrow'))
|
|
79
|
+
return null;
|
|
80
|
+
return semanticValue('configuration-service', className);
|
|
81
|
+
}
|
|
82
|
+
function projectLogger(value, className) {
|
|
83
|
+
if (!/(Logger|LoggerService)$/i.test(className))
|
|
84
|
+
return null;
|
|
85
|
+
const descriptors = ownDataDescriptors(value);
|
|
86
|
+
const context = primitiveStateValue(descriptors.context?.value);
|
|
87
|
+
return semanticValue('logger', className, context !== undefined ? { context } : {});
|
|
88
|
+
}
|
|
89
|
+
function projectNestInfrastructure(value, className) {
|
|
90
|
+
if (!/(ModuleRef|ModulesContainer|InstanceWrapper|NestContainer|Injector|DiscoveryService|ExecutionContextHost|ArgumentsHost|HttpAdapterHost)$/i.test(className)) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return semanticValue('nestjs-infrastructure', className);
|
|
94
|
+
}
|
|
95
|
+
function projectRedis(value, className) {
|
|
96
|
+
const looksLikeRedis = /Redis|Commander|Cluster/i.test(className) && (method(value, 'get') || method(value, 'set') || method(value, 'pipeline') || method(value, 'multi'));
|
|
97
|
+
if (!looksLikeRedis)
|
|
98
|
+
return null;
|
|
99
|
+
const descriptors = ownDataDescriptors(value);
|
|
100
|
+
const status = primitiveStateValue(descriptors.status?.value);
|
|
101
|
+
return semanticValue('redis-client', className, status !== undefined ? { status } : {});
|
|
102
|
+
}
|
|
103
|
+
function projectKafka(value, className) {
|
|
104
|
+
if (!/Kafka/i.test(className))
|
|
105
|
+
return null;
|
|
106
|
+
if (method(value, 'send') && method(value, 'connect') && method(value, 'disconnect')) {
|
|
107
|
+
return semanticValue('kafka-producer', className || 'KafkaProducer');
|
|
108
|
+
}
|
|
109
|
+
if (method(value, 'subscribe') && method(value, 'run') && method(value, 'connect')) {
|
|
110
|
+
return semanticValue('kafka-consumer', className || 'KafkaConsumer');
|
|
111
|
+
}
|
|
112
|
+
if (method(value, 'listTopics') && method(value, 'connect') && method(value, 'disconnect')) {
|
|
113
|
+
return semanticValue('kafka-admin', className || 'KafkaAdmin');
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
function projectAmqp(value, className) {
|
|
118
|
+
if (!/(Amqp|Channel|ChannelModel|ConfirmChannel)/i.test(className))
|
|
119
|
+
return null;
|
|
120
|
+
if (method(value, 'publish') && method(value, 'sendToQueue') && method(value, 'ack')) {
|
|
121
|
+
return semanticValue('amqp-channel', className || 'AmqpChannel');
|
|
122
|
+
}
|
|
123
|
+
if (method(value, 'createChannel') && method(value, 'close')) {
|
|
124
|
+
return semanticValue('amqp-connection', className || 'AmqpConnection');
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
function projectPostgres(value, className) {
|
|
129
|
+
if (/^(Pool|BoundPool)$/i.test(className) && method(value, 'query') && method(value, 'connect')) {
|
|
130
|
+
return semanticValue('postgres-pool', className);
|
|
131
|
+
}
|
|
132
|
+
if (/^(Client|PoolClient)$/i.test(className) && method(value, 'query')) {
|
|
133
|
+
return semanticValue('postgres-client', className);
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
function projectTypeOrm(value, className) {
|
|
138
|
+
if (/^DataSource$/i.test(className) && method(value, 'createQueryRunner')) {
|
|
139
|
+
return semanticValue('typeorm-infrastructure', className);
|
|
140
|
+
}
|
|
141
|
+
if (/^EntityManager$/i.test(className) && method(value, 'getRepository') && method(value, 'query')) {
|
|
142
|
+
return semanticValue('typeorm-infrastructure', className);
|
|
143
|
+
}
|
|
144
|
+
if (/^QueryRunner$/i.test(className) && method(value, 'query') && method(value, 'release')) {
|
|
145
|
+
return semanticValue('typeorm-infrastructure', className);
|
|
146
|
+
}
|
|
147
|
+
if (/^Repository$/i.test(className) && method(value, 'find') && method(value, 'save')) {
|
|
148
|
+
return semanticValue('typeorm-infrastructure', className);
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Projects known runtime machinery into stable, non-recursive descriptions.
|
|
154
|
+
* Unknown application values deliberately return null and remain fully captured.
|
|
155
|
+
*/
|
|
156
|
+
function projectSemanticInfrastructureValue(value) {
|
|
157
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
158
|
+
return null;
|
|
159
|
+
const className = constructorName(value);
|
|
160
|
+
if (!className || className === 'Object')
|
|
161
|
+
return null;
|
|
162
|
+
return (projectConfigurationService(value, className) ||
|
|
163
|
+
projectLogger(value, className) ||
|
|
164
|
+
projectNestInfrastructure(value, className) ||
|
|
165
|
+
projectRedis(value, className) ||
|
|
166
|
+
projectKafka(value, className) ||
|
|
167
|
+
projectAmqp(value, className) ||
|
|
168
|
+
projectPostgres(value, className) ||
|
|
169
|
+
projectTypeOrm(value, className));
|
|
170
|
+
}
|
|
171
|
+
exports.projectSemanticInfrastructureValue = projectSemanticInfrastructureValue;
|