@reproapp/node-sdk 0.0.7 → 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 +99 -2
- package/package.json +2 -2
- package/src/index.ts +134 -2
- package/test/express-trace-http-args.test.js +126 -0
- package/test/fixtures/express-trace-http-args-controller.js +29 -0
- package/test/fixtures/express-trace-http-args-server.js +21 -0
- 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).
|
|
@@ -1929,6 +1935,50 @@ function sanitizeTraceValue(value, depth = 0, seen = new WeakMap(), options = {}
|
|
|
1929
1935
|
const mongoId = coerceMongoId(value);
|
|
1930
1936
|
if (mongoId !== null)
|
|
1931
1937
|
return mongoId;
|
|
1938
|
+
if (isHttpRequestLike(value)) {
|
|
1939
|
+
const projected = {
|
|
1940
|
+
__kind: 'http-request',
|
|
1941
|
+
};
|
|
1942
|
+
if (typeof value.method === 'string') {
|
|
1943
|
+
projected.method = value.method;
|
|
1944
|
+
}
|
|
1945
|
+
const url = typeof value.originalUrl === 'string'
|
|
1946
|
+
? value.originalUrl
|
|
1947
|
+
: typeof value.url === 'string'
|
|
1948
|
+
? value.url
|
|
1949
|
+
: undefined;
|
|
1950
|
+
if (url) {
|
|
1951
|
+
projected.url = url;
|
|
1952
|
+
}
|
|
1953
|
+
const headers = sanitizeHeaders(value.headers, true);
|
|
1954
|
+
if (headers !== undefined) {
|
|
1955
|
+
projected.headers = sanitizeTraceValue(headers, depth + 1, seen, options, childCapturePath(valuePath, 'headers'));
|
|
1956
|
+
}
|
|
1957
|
+
if (value.params !== undefined) {
|
|
1958
|
+
projected.params = sanitizeTraceValue(value.params, depth + 1, seen, options, childCapturePath(valuePath, 'params'));
|
|
1959
|
+
}
|
|
1960
|
+
if (value.query !== undefined) {
|
|
1961
|
+
projected.query = sanitizeTraceValue(value.query, depth + 1, seen, options, childCapturePath(valuePath, 'query'));
|
|
1962
|
+
}
|
|
1963
|
+
if (value.body !== undefined) {
|
|
1964
|
+
projected.body = sanitizeTraceValue(value.body, depth + 1, seen, options, childCapturePath(valuePath, 'body'));
|
|
1965
|
+
}
|
|
1966
|
+
return projected;
|
|
1967
|
+
}
|
|
1968
|
+
if (isHttpResponseLike(value)) {
|
|
1969
|
+
const projected = {
|
|
1970
|
+
__kind: 'http-response',
|
|
1971
|
+
statusCode: Number(value.statusCode) || 0,
|
|
1972
|
+
};
|
|
1973
|
+
const rawHeaders = typeof value.getHeaders === 'function'
|
|
1974
|
+
? value.getHeaders()
|
|
1975
|
+
: value._headers;
|
|
1976
|
+
const headers = sanitizeHeaders(rawHeaders, true);
|
|
1977
|
+
if (headers !== undefined) {
|
|
1978
|
+
projected.headers = sanitizeTraceValue(headers, depth + 1, seen, options, childCapturePath(valuePath, 'headers'));
|
|
1979
|
+
}
|
|
1980
|
+
return projected;
|
|
1981
|
+
}
|
|
1932
1982
|
if (isMongooseQueryLike(value)) {
|
|
1933
1983
|
const captured = value.__repro_result;
|
|
1934
1984
|
if (captured !== undefined) {
|
|
@@ -3482,6 +3532,51 @@ function collectBatchTraceValueEntries(batch, batchIndex) {
|
|
|
3482
3532
|
});
|
|
3483
3533
|
return collected;
|
|
3484
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
|
+
}
|
|
3485
3580
|
function createCapturedValueEntry(params) {
|
|
3486
3581
|
if (!__FULL_VALUE_CAPTURE_ENABLED)
|
|
3487
3582
|
return undefined;
|
|
@@ -4065,7 +4160,7 @@ function reproMiddleware(cfg) {
|
|
|
4065
4160
|
const orderedEvents = TRACE_ORDER_MODE === 'tree'
|
|
4066
4161
|
? reorderTraceEvents(baseEvents)
|
|
4067
4162
|
: sortTraceEventsChronologically(baseEvents);
|
|
4068
|
-
const traceBatches =
|
|
4163
|
+
const traceBatches = chunkTraceEventsForTransport(orderedEvents, rid, aid);
|
|
4069
4164
|
if (traceBatches.length) {
|
|
4070
4165
|
for (let i = 0; i < traceBatches.length; i++) {
|
|
4071
4166
|
const batch = traceBatches[i];
|
|
@@ -5268,7 +5363,7 @@ function buildKafkaTraceEntries(actionId, requestRid, events) {
|
|
|
5268
5363
|
const orderedEvents = TRACE_ORDER_MODE === 'tree'
|
|
5269
5364
|
? reorderTraceEvents(baseEvents)
|
|
5270
5365
|
: sortTraceEventsChronologically(baseEvents);
|
|
5271
|
-
const batches =
|
|
5366
|
+
const batches = chunkTraceEventsForTransport(orderedEvents, requestRid, actionId);
|
|
5272
5367
|
return batches.map((batch, index) => ({
|
|
5273
5368
|
actionId: actionId ?? null,
|
|
5274
5369
|
trace: batch,
|
|
@@ -6582,4 +6677,6 @@ exports.__reproTestHooks = {
|
|
|
6582
6677
|
materializeInlinePrivacyValueAsyncForTest: materializeInlinePrivacyValueAsync,
|
|
6583
6678
|
patchKafkaProducerInstanceForTest: patchKafkaProducerInstance,
|
|
6584
6679
|
wrapKafkaEachMessageHandlerForTest: wrapKafkaEachMessageHandler,
|
|
6680
|
+
chunkTraceEventsForTransportForTest: chunkTraceEventsForTransport,
|
|
6681
|
+
estimateTraceBatchSerializedBytesForTest: estimateTraceBatchSerializedBytes,
|
|
6585
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 -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).
|
|
@@ -2318,6 +2323,55 @@ function sanitizeTraceValue(
|
|
|
2318
2323
|
const mongoId = coerceMongoId(value);
|
|
2319
2324
|
if (mongoId !== null) return mongoId;
|
|
2320
2325
|
|
|
2326
|
+
if (isHttpRequestLike(value)) {
|
|
2327
|
+
const projected: Record<string, any> = {
|
|
2328
|
+
__kind: 'http-request',
|
|
2329
|
+
};
|
|
2330
|
+
if (typeof (value as any).method === 'string') {
|
|
2331
|
+
projected.method = (value as any).method;
|
|
2332
|
+
}
|
|
2333
|
+
const url =
|
|
2334
|
+
typeof (value as any).originalUrl === 'string'
|
|
2335
|
+
? (value as any).originalUrl
|
|
2336
|
+
: typeof (value as any).url === 'string'
|
|
2337
|
+
? (value as any).url
|
|
2338
|
+
: undefined;
|
|
2339
|
+
if (url) {
|
|
2340
|
+
projected.url = url;
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
const headers = sanitizeHeaders((value as any).headers, true);
|
|
2344
|
+
if (headers !== undefined) {
|
|
2345
|
+
projected.headers = sanitizeTraceValue(headers, depth + 1, seen, options, childCapturePath(valuePath, 'headers'));
|
|
2346
|
+
}
|
|
2347
|
+
if ((value as any).params !== undefined) {
|
|
2348
|
+
projected.params = sanitizeTraceValue((value as any).params, depth + 1, seen, options, childCapturePath(valuePath, 'params'));
|
|
2349
|
+
}
|
|
2350
|
+
if ((value as any).query !== undefined) {
|
|
2351
|
+
projected.query = sanitizeTraceValue((value as any).query, depth + 1, seen, options, childCapturePath(valuePath, 'query'));
|
|
2352
|
+
}
|
|
2353
|
+
if ((value as any).body !== undefined) {
|
|
2354
|
+
projected.body = sanitizeTraceValue((value as any).body, depth + 1, seen, options, childCapturePath(valuePath, 'body'));
|
|
2355
|
+
}
|
|
2356
|
+
return projected;
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
if (isHttpResponseLike(value)) {
|
|
2360
|
+
const projected: Record<string, any> = {
|
|
2361
|
+
__kind: 'http-response',
|
|
2362
|
+
statusCode: Number((value as any).statusCode) || 0,
|
|
2363
|
+
};
|
|
2364
|
+
const rawHeaders =
|
|
2365
|
+
typeof (value as any).getHeaders === 'function'
|
|
2366
|
+
? (value as any).getHeaders()
|
|
2367
|
+
: (value as any)._headers;
|
|
2368
|
+
const headers = sanitizeHeaders(rawHeaders, true);
|
|
2369
|
+
if (headers !== undefined) {
|
|
2370
|
+
projected.headers = sanitizeTraceValue(headers, depth + 1, seen, options, childCapturePath(valuePath, 'headers'));
|
|
2371
|
+
}
|
|
2372
|
+
return projected;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2321
2375
|
if (isMongooseQueryLike(value)) {
|
|
2322
2376
|
const captured = (value as any).__repro_result;
|
|
2323
2377
|
if (captured !== undefined) {
|
|
@@ -4166,6 +4220,74 @@ function collectBatchTraceValueEntries(batch: TraceEventRecord[], batchIndex: nu
|
|
|
4166
4220
|
return collected;
|
|
4167
4221
|
}
|
|
4168
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
|
+
|
|
4169
4291
|
function createCapturedValueEntry(
|
|
4170
4292
|
params: {
|
|
4171
4293
|
target:
|
|
@@ -4904,7 +5026,11 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4904
5026
|
const orderedEvents = TRACE_ORDER_MODE === 'tree'
|
|
4905
5027
|
? reorderTraceEvents(baseEvents)
|
|
4906
5028
|
: sortTraceEventsChronologically(baseEvents);
|
|
4907
|
-
const traceBatches =
|
|
5029
|
+
const traceBatches = chunkTraceEventsForTransport(
|
|
5030
|
+
orderedEvents,
|
|
5031
|
+
rid,
|
|
5032
|
+
aid,
|
|
5033
|
+
);
|
|
4908
5034
|
|
|
4909
5035
|
if (traceBatches.length) {
|
|
4910
5036
|
for (let i = 0; i < traceBatches.length; i++) {
|
|
@@ -6204,7 +6330,11 @@ function buildKafkaTraceEntries(
|
|
|
6204
6330
|
TRACE_ORDER_MODE === 'tree'
|
|
6205
6331
|
? reorderTraceEvents(baseEvents)
|
|
6206
6332
|
: sortTraceEventsChronologically(baseEvents);
|
|
6207
|
-
const batches =
|
|
6333
|
+
const batches = chunkTraceEventsForTransport(
|
|
6334
|
+
orderedEvents,
|
|
6335
|
+
requestRid,
|
|
6336
|
+
actionId,
|
|
6337
|
+
);
|
|
6208
6338
|
return batches.map((batch, index) => ({
|
|
6209
6339
|
actionId: actionId ?? null,
|
|
6210
6340
|
trace: batch,
|
|
@@ -7569,4 +7699,6 @@ export const __reproTestHooks = {
|
|
|
7569
7699
|
materializeInlinePrivacyValueAsyncForTest: materializeInlinePrivacyValueAsync,
|
|
7570
7700
|
patchKafkaProducerInstanceForTest: patchKafkaProducerInstance,
|
|
7571
7701
|
wrapKafkaEachMessageHandlerForTest: wrapKafkaEachMessageHandler,
|
|
7702
|
+
chunkTraceEventsForTransportForTest: chunkTraceEventsForTransport,
|
|
7703
|
+
estimateTraceBatchSerializedBytesForTest: estimateTraceBatchSerializedBytes,
|
|
7572
7704
|
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const http = require('http');
|
|
3
|
+
|
|
4
|
+
const originalFetch = global.fetch;
|
|
5
|
+
const capturedBodies = [];
|
|
6
|
+
|
|
7
|
+
global.fetch = async (url, init = {}) => {
|
|
8
|
+
const target = String(url || '');
|
|
9
|
+
if (!target.includes('/v1/ingest/events')) {
|
|
10
|
+
throw new Error(`unexpected fetch target: ${target}`);
|
|
11
|
+
}
|
|
12
|
+
capturedBodies.push({
|
|
13
|
+
at: Date.now(),
|
|
14
|
+
url: target,
|
|
15
|
+
body: JSON.parse(String(init.body || '{}')),
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
ok: true,
|
|
19
|
+
status: 200,
|
|
20
|
+
json: async () => ({ ok: true }),
|
|
21
|
+
text: async () => '{"ok":true}',
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const { initRepro } = require('../dist');
|
|
26
|
+
const { flushIngestQueue } = require('../dist/ingest/client');
|
|
27
|
+
|
|
28
|
+
function findEvents(eventType) {
|
|
29
|
+
return capturedBodies.flatMap((entry) => Array.isArray(entry.body?.events) ? entry.body.events : [])
|
|
30
|
+
.filter((event) => event?.event_type === eventType);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function sendGet(url) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const request = http.get(url, (response) => {
|
|
36
|
+
let text = '';
|
|
37
|
+
response.setEncoding('utf8');
|
|
38
|
+
response.on('data', (chunk) => {
|
|
39
|
+
text += chunk;
|
|
40
|
+
});
|
|
41
|
+
response.on('end', () => resolve({
|
|
42
|
+
statusCode: response.statusCode,
|
|
43
|
+
body: text,
|
|
44
|
+
}));
|
|
45
|
+
});
|
|
46
|
+
request.on('error', reject);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function main() {
|
|
51
|
+
await initRepro({
|
|
52
|
+
tenantId: 'TENANT_express_trace_http_args',
|
|
53
|
+
appId: 'APP_express_trace_http_args',
|
|
54
|
+
appSecret: 'secret',
|
|
55
|
+
appName: 'express-trace-http-args',
|
|
56
|
+
serviceName: 'express-trace-http-args',
|
|
57
|
+
ingestBase: 'http://127.0.0.1:65535',
|
|
58
|
+
tracing: {
|
|
59
|
+
disableFunctionTypes: ['constructor'],
|
|
60
|
+
logFunctionCalls: false,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const { startServer } = require('./fixtures/express-trace-http-args-server');
|
|
65
|
+
const server = await startServer({
|
|
66
|
+
tenantId: 'TENANT_express_trace_http_args',
|
|
67
|
+
appId: 'APP_express_trace_http_args',
|
|
68
|
+
appSecret: 'secret',
|
|
69
|
+
appName: 'express-trace-http-args',
|
|
70
|
+
serviceName: 'express-trace-http-args',
|
|
71
|
+
ingestBase: 'http://127.0.0.1:65535',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const port = server.address().port;
|
|
76
|
+
const requestUrl = `http://127.0.0.1:${port}/ping?name=Avery&__repro_sid=S_express_trace_http_args&__repro_aid=A_express_trace_http_args&__repro_start=${Date.now()}`;
|
|
77
|
+
const response = await sendGet(requestUrl);
|
|
78
|
+
assert.equal(response.statusCode, 200);
|
|
79
|
+
|
|
80
|
+
await new Promise((resolve) => setTimeout(resolve, 2500));
|
|
81
|
+
await flushIngestQueue();
|
|
82
|
+
|
|
83
|
+
const requestEvents = findEvents('backend_request');
|
|
84
|
+
const traceEvents = findEvents('trace_batch');
|
|
85
|
+
|
|
86
|
+
assert.equal(requestEvents.length, 1, JSON.stringify(capturedBodies));
|
|
87
|
+
assert.equal(traceEvents.length, 1, JSON.stringify(capturedBodies));
|
|
88
|
+
|
|
89
|
+
const batch = traceEvents[0]?.payload?.trace;
|
|
90
|
+
assert(Array.isArray(batch) && batch.length > 0, JSON.stringify(traceEvents[0]));
|
|
91
|
+
|
|
92
|
+
const handlePingEnter = batch.find((event) => event?.type === 'enter' && event?.fn === 'handlePing');
|
|
93
|
+
assert(handlePingEnter, JSON.stringify(batch));
|
|
94
|
+
assert(Array.isArray(handlePingEnter.args), JSON.stringify(handlePingEnter));
|
|
95
|
+
assert.equal(handlePingEnter.args[0]?.__kind, 'http-request', JSON.stringify(handlePingEnter.args[0]));
|
|
96
|
+
assert.equal(handlePingEnter.args[1]?.__kind, 'http-response', JSON.stringify(handlePingEnter.args[1]));
|
|
97
|
+
assert.equal(handlePingEnter.args[0]?.url, '/ping?name=Avery');
|
|
98
|
+
assert.equal(handlePingEnter.args[0]?.query?.name, 'Avery');
|
|
99
|
+
|
|
100
|
+
const serializedBatch = JSON.stringify(traceEvents[0]);
|
|
101
|
+
assert(serializedBatch.length < 250000, `trace batch still too large: ${serializedBatch.length}`);
|
|
102
|
+
|
|
103
|
+
// eslint-disable-next-line no-console
|
|
104
|
+
console.log('express trace http arg projection OK');
|
|
105
|
+
} finally {
|
|
106
|
+
await new Promise((resolve, reject) => {
|
|
107
|
+
server.close((error) => {
|
|
108
|
+
if (!error || error.code === 'ERR_SERVER_NOT_RUNNING') {
|
|
109
|
+
resolve();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
reject(error);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
main()
|
|
119
|
+
.catch((error) => {
|
|
120
|
+
// eslint-disable-next-line no-console
|
|
121
|
+
console.error(error);
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
})
|
|
124
|
+
.finally(() => {
|
|
125
|
+
global.fetch = originalFetch;
|
|
126
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
async function buildPayload(name) {
|
|
2
|
+
const normalized = normalizeName(name);
|
|
3
|
+
const emphasized = emphasize(normalized);
|
|
4
|
+
return {
|
|
5
|
+
original: name,
|
|
6
|
+
normalized,
|
|
7
|
+
emphasized,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeName(name) {
|
|
12
|
+
return String(name || 'anonymous').trim().toLowerCase();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function emphasize(name) {
|
|
16
|
+
return `${name.toUpperCase()}!`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function handlePing(req, res) {
|
|
20
|
+
const payload = await buildPayload(req.query?.name || 'Avery Debugson');
|
|
21
|
+
res.json({
|
|
22
|
+
ok: true,
|
|
23
|
+
payload,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
handlePing,
|
|
29
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const { reproMiddleware } = require('../../dist');
|
|
3
|
+
const { handlePing } = require('./express-trace-http-args-controller');
|
|
4
|
+
|
|
5
|
+
async function startServer(cfg) {
|
|
6
|
+
const app = express();
|
|
7
|
+
app.use(reproMiddleware(cfg));
|
|
8
|
+
app.get('/ping', handlePing);
|
|
9
|
+
|
|
10
|
+
const server = await new Promise((resolve, reject) => {
|
|
11
|
+
const instance = app.listen(0);
|
|
12
|
+
instance.once('listening', () => resolve(instance));
|
|
13
|
+
instance.once('error', reject);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return server;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
startServer,
|
|
21
|
+
};
|
|
@@ -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();
|