@reproapp/node-sdk 0.0.8 → 0.0.9
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 +4 -0
- package/dist/index.js +55 -2
- package/package.json +2 -2
- package/src/index.ts +85 -2
- package/test/trace-batch-size.test.js +58 -0
package/dist/index.d.ts
CHANGED
|
@@ -283,6 +283,8 @@ type InlinePrivacyMaterializationResult = {
|
|
|
283
283
|
};
|
|
284
284
|
};
|
|
285
285
|
declare function materializeInlinePrivacyValueAsync(target: RuntimePrivacySurface, value: any, cfg: ReproMiddlewareConfig, req: MaskRequestContext, trace: TraceEventForFilter | null, masking: NormalizedMaskingConfig | null, privacy: NormalizedRuntimePrivacyPolicy | null, db?: RuntimePrivacyDbContext | null): Promise<InlinePrivacyMaterializationResult>;
|
|
286
|
+
declare function estimateTraceBatchSerializedBytes(batch: TraceEventRecord[], requestRid: string, actionId: string | null | undefined, batchIndex: number): number;
|
|
287
|
+
declare function chunkTraceEventsForTransport(events: TraceEventRecord[], requestRid: string, actionId: string | null | undefined): TraceEventRecord[][];
|
|
286
288
|
export type ReproMiddlewareConfig = IngestClientConfig & {
|
|
287
289
|
/** Configure header capture/masking. Defaults to capturing with sensitive headers masked. */
|
|
288
290
|
captureHeaders?: boolean | HeaderCaptureOptions;
|
|
@@ -348,5 +350,7 @@ export declare const __reproTestHooks: {
|
|
|
348
350
|
materializeInlinePrivacyValueAsyncForTest: typeof materializeInlinePrivacyValueAsync;
|
|
349
351
|
patchKafkaProducerInstanceForTest: typeof patchKafkaProducerInstance;
|
|
350
352
|
wrapKafkaEachMessageHandlerForTest: typeof wrapKafkaEachMessageHandler;
|
|
353
|
+
chunkTraceEventsForTransportForTest: typeof chunkTraceEventsForTransport;
|
|
354
|
+
estimateTraceBatchSerializedBytesForTest: typeof estimateTraceBatchSerializedBytes;
|
|
351
355
|
};
|
|
352
356
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1657,6 +1657,12 @@ const TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS = (() => {
|
|
|
1657
1657
|
})();
|
|
1658
1658
|
const TRACE_VALUE_SIZE_EXCEEDED = Symbol('trace-value-size-exceeded');
|
|
1659
1659
|
const TRACE_BATCH_SIZE = 100;
|
|
1660
|
+
const TRACE_BATCH_MAX_SERIALIZED_BYTES = (() => {
|
|
1661
|
+
const env = Number(process.env.REPRO_TRACE_BATCH_MAX_SERIALIZED_BYTES);
|
|
1662
|
+
if (Number.isFinite(env) && env > 0)
|
|
1663
|
+
return Math.trunc(env);
|
|
1664
|
+
return 512 * 1024;
|
|
1665
|
+
})();
|
|
1660
1666
|
const TRACE_FLUSH_DELAY_MS = 20;
|
|
1661
1667
|
// Choose how to order trace events in payloads.
|
|
1662
1668
|
// - "chronological" (default): preserve event arrival order (no reshuffle).
|
|
@@ -3526,6 +3532,51 @@ function collectBatchTraceValueEntries(batch, batchIndex) {
|
|
|
3526
3532
|
});
|
|
3527
3533
|
return collected;
|
|
3528
3534
|
}
|
|
3535
|
+
function serializedByteLength(value) {
|
|
3536
|
+
try {
|
|
3537
|
+
return Buffer.byteLength(JSON.stringify(value), 'utf8');
|
|
3538
|
+
}
|
|
3539
|
+
catch {
|
|
3540
|
+
return Number.MAX_SAFE_INTEGER;
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
function estimateTraceBatchSerializedBytes(batch, requestRid, actionId, batchIndex) {
|
|
3544
|
+
const traceValues = collectBatchTraceValueEntries(batch, batchIndex);
|
|
3545
|
+
return serializedByteLength({
|
|
3546
|
+
actionId: actionId ?? null,
|
|
3547
|
+
trace: batch,
|
|
3548
|
+
traceValues: traceValues.length ? traceValues : undefined,
|
|
3549
|
+
traceBatch: {
|
|
3550
|
+
rid: requestRid,
|
|
3551
|
+
index: batchIndex,
|
|
3552
|
+
total: 0,
|
|
3553
|
+
},
|
|
3554
|
+
t: 0,
|
|
3555
|
+
});
|
|
3556
|
+
}
|
|
3557
|
+
function chunkTraceEventsForTransport(events, requestRid, actionId) {
|
|
3558
|
+
if (!Array.isArray(events) || events.length === 0)
|
|
3559
|
+
return [];
|
|
3560
|
+
const countBatches = chunkArray(events, TRACE_BATCH_SIZE);
|
|
3561
|
+
const sizedBatches = [];
|
|
3562
|
+
let current = [];
|
|
3563
|
+
for (const event of countBatches.flat()) {
|
|
3564
|
+
const candidate = current.concat(event);
|
|
3565
|
+
const batchIndex = sizedBatches.length;
|
|
3566
|
+
const estimatedBytes = estimateTraceBatchSerializedBytes(candidate, requestRid, actionId, batchIndex);
|
|
3567
|
+
if (current.length > 0 &&
|
|
3568
|
+
estimatedBytes > TRACE_BATCH_MAX_SERIALIZED_BYTES) {
|
|
3569
|
+
sizedBatches.push(current);
|
|
3570
|
+
current = [event];
|
|
3571
|
+
continue;
|
|
3572
|
+
}
|
|
3573
|
+
current = candidate;
|
|
3574
|
+
}
|
|
3575
|
+
if (current.length > 0) {
|
|
3576
|
+
sizedBatches.push(current);
|
|
3577
|
+
}
|
|
3578
|
+
return sizedBatches;
|
|
3579
|
+
}
|
|
3529
3580
|
function createCapturedValueEntry(params) {
|
|
3530
3581
|
if (!__FULL_VALUE_CAPTURE_ENABLED)
|
|
3531
3582
|
return undefined;
|
|
@@ -4109,7 +4160,7 @@ function reproMiddleware(cfg) {
|
|
|
4109
4160
|
const orderedEvents = TRACE_ORDER_MODE === 'tree'
|
|
4110
4161
|
? reorderTraceEvents(baseEvents)
|
|
4111
4162
|
: sortTraceEventsChronologically(baseEvents);
|
|
4112
|
-
const traceBatches =
|
|
4163
|
+
const traceBatches = chunkTraceEventsForTransport(orderedEvents, rid, aid);
|
|
4113
4164
|
if (traceBatches.length) {
|
|
4114
4165
|
for (let i = 0; i < traceBatches.length; i++) {
|
|
4115
4166
|
const batch = traceBatches[i];
|
|
@@ -5312,7 +5363,7 @@ function buildKafkaTraceEntries(actionId, requestRid, events) {
|
|
|
5312
5363
|
const orderedEvents = TRACE_ORDER_MODE === 'tree'
|
|
5313
5364
|
? reorderTraceEvents(baseEvents)
|
|
5314
5365
|
: sortTraceEventsChronologically(baseEvents);
|
|
5315
|
-
const batches =
|
|
5366
|
+
const batches = chunkTraceEventsForTransport(orderedEvents, requestRid, actionId);
|
|
5316
5367
|
return batches.map((batch, index) => ({
|
|
5317
5368
|
actionId: actionId ?? null,
|
|
5318
5369
|
trace: batch,
|
|
@@ -6626,4 +6677,6 @@ exports.__reproTestHooks = {
|
|
|
6626
6677
|
materializeInlinePrivacyValueAsyncForTest: materializeInlinePrivacyValueAsync,
|
|
6627
6678
|
patchKafkaProducerInstanceForTest: patchKafkaProducerInstance,
|
|
6628
6679
|
wrapKafkaEachMessageHandlerForTest: wrapKafkaEachMessageHandler,
|
|
6680
|
+
chunkTraceEventsForTransportForTest: chunkTraceEventsForTransport,
|
|
6681
|
+
estimateTraceBatchSerializedBytesForTest: estimateTraceBatchSerializedBytes,
|
|
6629
6682
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reproapp/node-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Repro Nest SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"build": "tsc -p tsconfig.json",
|
|
13
13
|
"dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
|
|
14
14
|
"prepublishOnly": "npm run build",
|
|
15
|
-
"test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/express-trace-http-args.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js"
|
|
15
|
+
"test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
18
|
"express": "^5.1.0",
|
package/src/index.ts
CHANGED
|
@@ -2028,6 +2028,11 @@ const TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS = (() => {
|
|
|
2028
2028
|
})();
|
|
2029
2029
|
const TRACE_VALUE_SIZE_EXCEEDED = Symbol('trace-value-size-exceeded');
|
|
2030
2030
|
const TRACE_BATCH_SIZE = 100;
|
|
2031
|
+
const TRACE_BATCH_MAX_SERIALIZED_BYTES = (() => {
|
|
2032
|
+
const env = Number(process.env.REPRO_TRACE_BATCH_MAX_SERIALIZED_BYTES);
|
|
2033
|
+
if (Number.isFinite(env) && env > 0) return Math.trunc(env);
|
|
2034
|
+
return 512 * 1024;
|
|
2035
|
+
})();
|
|
2031
2036
|
const TRACE_FLUSH_DELAY_MS = 20;
|
|
2032
2037
|
// Choose how to order trace events in payloads.
|
|
2033
2038
|
// - "chronological" (default): preserve event arrival order (no reshuffle).
|
|
@@ -4215,6 +4220,74 @@ function collectBatchTraceValueEntries(batch: TraceEventRecord[], batchIndex: nu
|
|
|
4215
4220
|
return collected;
|
|
4216
4221
|
}
|
|
4217
4222
|
|
|
4223
|
+
function serializedByteLength(value: any): number {
|
|
4224
|
+
try {
|
|
4225
|
+
return Buffer.byteLength(JSON.stringify(value), 'utf8');
|
|
4226
|
+
} catch {
|
|
4227
|
+
return Number.MAX_SAFE_INTEGER;
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
4230
|
+
|
|
4231
|
+
function estimateTraceBatchSerializedBytes(
|
|
4232
|
+
batch: TraceEventRecord[],
|
|
4233
|
+
requestRid: string,
|
|
4234
|
+
actionId: string | null | undefined,
|
|
4235
|
+
batchIndex: number,
|
|
4236
|
+
): number {
|
|
4237
|
+
const traceValues = collectBatchTraceValueEntries(batch, batchIndex);
|
|
4238
|
+
return serializedByteLength({
|
|
4239
|
+
actionId: actionId ?? null,
|
|
4240
|
+
trace: batch,
|
|
4241
|
+
traceValues: traceValues.length ? traceValues : undefined,
|
|
4242
|
+
traceBatch: {
|
|
4243
|
+
rid: requestRid,
|
|
4244
|
+
index: batchIndex,
|
|
4245
|
+
total: 0,
|
|
4246
|
+
},
|
|
4247
|
+
t: 0,
|
|
4248
|
+
});
|
|
4249
|
+
}
|
|
4250
|
+
|
|
4251
|
+
function chunkTraceEventsForTransport(
|
|
4252
|
+
events: TraceEventRecord[],
|
|
4253
|
+
requestRid: string,
|
|
4254
|
+
actionId: string | null | undefined,
|
|
4255
|
+
): TraceEventRecord[][] {
|
|
4256
|
+
if (!Array.isArray(events) || events.length === 0) return [];
|
|
4257
|
+
|
|
4258
|
+
const countBatches = chunkArray(events, TRACE_BATCH_SIZE);
|
|
4259
|
+
const sizedBatches: TraceEventRecord[][] = [];
|
|
4260
|
+
let current: TraceEventRecord[] = [];
|
|
4261
|
+
|
|
4262
|
+
for (const event of countBatches.flat()) {
|
|
4263
|
+
const candidate = current.concat(event);
|
|
4264
|
+
const batchIndex = sizedBatches.length;
|
|
4265
|
+
const estimatedBytes = estimateTraceBatchSerializedBytes(
|
|
4266
|
+
candidate,
|
|
4267
|
+
requestRid,
|
|
4268
|
+
actionId,
|
|
4269
|
+
batchIndex,
|
|
4270
|
+
);
|
|
4271
|
+
|
|
4272
|
+
if (
|
|
4273
|
+
current.length > 0 &&
|
|
4274
|
+
estimatedBytes > TRACE_BATCH_MAX_SERIALIZED_BYTES
|
|
4275
|
+
) {
|
|
4276
|
+
sizedBatches.push(current);
|
|
4277
|
+
current = [event];
|
|
4278
|
+
continue;
|
|
4279
|
+
}
|
|
4280
|
+
|
|
4281
|
+
current = candidate;
|
|
4282
|
+
}
|
|
4283
|
+
|
|
4284
|
+
if (current.length > 0) {
|
|
4285
|
+
sizedBatches.push(current);
|
|
4286
|
+
}
|
|
4287
|
+
|
|
4288
|
+
return sizedBatches;
|
|
4289
|
+
}
|
|
4290
|
+
|
|
4218
4291
|
function createCapturedValueEntry(
|
|
4219
4292
|
params: {
|
|
4220
4293
|
target:
|
|
@@ -4953,7 +5026,11 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4953
5026
|
const orderedEvents = TRACE_ORDER_MODE === 'tree'
|
|
4954
5027
|
? reorderTraceEvents(baseEvents)
|
|
4955
5028
|
: sortTraceEventsChronologically(baseEvents);
|
|
4956
|
-
const traceBatches =
|
|
5029
|
+
const traceBatches = chunkTraceEventsForTransport(
|
|
5030
|
+
orderedEvents,
|
|
5031
|
+
rid,
|
|
5032
|
+
aid,
|
|
5033
|
+
);
|
|
4957
5034
|
|
|
4958
5035
|
if (traceBatches.length) {
|
|
4959
5036
|
for (let i = 0; i < traceBatches.length; i++) {
|
|
@@ -6253,7 +6330,11 @@ function buildKafkaTraceEntries(
|
|
|
6253
6330
|
TRACE_ORDER_MODE === 'tree'
|
|
6254
6331
|
? reorderTraceEvents(baseEvents)
|
|
6255
6332
|
: sortTraceEventsChronologically(baseEvents);
|
|
6256
|
-
const batches =
|
|
6333
|
+
const batches = chunkTraceEventsForTransport(
|
|
6334
|
+
orderedEvents,
|
|
6335
|
+
requestRid,
|
|
6336
|
+
actionId,
|
|
6337
|
+
);
|
|
6257
6338
|
return batches.map((batch, index) => ({
|
|
6258
6339
|
actionId: actionId ?? null,
|
|
6259
6340
|
trace: batch,
|
|
@@ -7618,4 +7699,6 @@ export const __reproTestHooks = {
|
|
|
7618
7699
|
materializeInlinePrivacyValueAsyncForTest: materializeInlinePrivacyValueAsync,
|
|
7619
7700
|
patchKafkaProducerInstanceForTest: patchKafkaProducerInstance,
|
|
7620
7701
|
wrapKafkaEachMessageHandlerForTest: wrapKafkaEachMessageHandler,
|
|
7702
|
+
chunkTraceEventsForTransportForTest: chunkTraceEventsForTransport,
|
|
7703
|
+
estimateTraceBatchSerializedBytesForTest: estimateTraceBatchSerializedBytes,
|
|
7621
7704
|
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const { __reproTestHooks } = require('../dist');
|
|
3
|
+
|
|
4
|
+
function makeEvent(index, payload) {
|
|
5
|
+
return {
|
|
6
|
+
t: index,
|
|
7
|
+
type: 'enter',
|
|
8
|
+
fn: `fn${index}`,
|
|
9
|
+
file: '/app/src/controllers/subjects/index.ts',
|
|
10
|
+
line: index + 1,
|
|
11
|
+
depth: 1,
|
|
12
|
+
spanId: index + 1,
|
|
13
|
+
parentSpanId: null,
|
|
14
|
+
args: [payload],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function main() {
|
|
19
|
+
const payload = {
|
|
20
|
+
__kind: 'http-request',
|
|
21
|
+
method: 'POST',
|
|
22
|
+
url: '/api/v1/subject_visits/697001aaac70cc3d60f21273/subjects/createNewSubject?tenantId=tgtherapeutics',
|
|
23
|
+
headers: {
|
|
24
|
+
x_http_user: 'x'.repeat(12000),
|
|
25
|
+
authorization: '[dropped]',
|
|
26
|
+
},
|
|
27
|
+
body: {
|
|
28
|
+
value: 'y'.repeat(12000),
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const events = Array.from({ length: 120 }, (_, index) => makeEvent(index, payload));
|
|
33
|
+
const batches = __reproTestHooks.chunkTraceEventsForTransportForTest(
|
|
34
|
+
events,
|
|
35
|
+
'RID_test_trace_batch_size',
|
|
36
|
+
'A_test_trace_batch_size',
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
assert(batches.length > 1, `expected more than one batch, got ${batches.length}`);
|
|
40
|
+
|
|
41
|
+
batches.forEach((batch, index) => {
|
|
42
|
+
const size = __reproTestHooks.estimateTraceBatchSerializedBytesForTest(
|
|
43
|
+
batch,
|
|
44
|
+
'RID_test_trace_batch_size',
|
|
45
|
+
'A_test_trace_batch_size',
|
|
46
|
+
index,
|
|
47
|
+
);
|
|
48
|
+
assert(
|
|
49
|
+
size <= 512 * 1024,
|
|
50
|
+
`batch ${index} too large: ${size}`,
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// eslint-disable-next-line no-console
|
|
55
|
+
console.log('trace batch size chunking OK');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
main();
|