@reproapp/node-sdk 0.0.44 → 0.0.46
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 +1 -0
- package/dist/index.js +37 -6
- package/package.json +2 -2
- package/src/index.ts +44 -6
- package/test/client-performance-regression.test.js +38 -0
- package/test/kafka-runtime-privacy-policy.test.js +24 -9
- package/test/trace-source-exclude.test.js +222 -0
- package/tracer/cjs-hook.js +56 -37
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -332,6 +332,7 @@ function refreshDisabledFunctionTraceRules() {
|
|
|
332
332
|
let disabledFunctionTraceRules = computeDisabledFunctionTraceRules();
|
|
333
333
|
let disabledFunctionTypePatterns = [];
|
|
334
334
|
let disabledTraceFilePatterns = [];
|
|
335
|
+
let excludedInstrumentationFilePatterns = [];
|
|
335
336
|
let __TRACE_LOG_PREF = null;
|
|
336
337
|
let __FULL_VALUE_CAPTURE_ENABLED = true;
|
|
337
338
|
function setInterceptorTracingEnabled(enabled) {
|
|
@@ -415,7 +416,7 @@ function matchesRule(rule, event) {
|
|
|
415
416
|
const wrapperPattern = rule.wrapper ?? rule.wrapperClass ?? rule.className ?? rule.owner;
|
|
416
417
|
if (!matchesPattern(event.wrapperClass, wrapperPattern))
|
|
417
418
|
return false;
|
|
418
|
-
if (!
|
|
419
|
+
if (!matchesTraceFile(event, rule.file))
|
|
419
420
|
return false;
|
|
420
421
|
if (!matchesPattern(event.line == null ? null : String(event.line), rule.line))
|
|
421
422
|
return false;
|
|
@@ -430,9 +431,23 @@ function matchesRule(rule, event) {
|
|
|
430
431
|
return false;
|
|
431
432
|
return true;
|
|
432
433
|
}
|
|
434
|
+
function matchesTraceFile(event, pattern, defaultWhenEmpty = true) {
|
|
435
|
+
if (pattern === undefined || pattern === null)
|
|
436
|
+
return defaultWhenEmpty;
|
|
437
|
+
const candidates = [event.file, event.sourceFile].filter((value) => typeof value === 'string' && value.length > 0);
|
|
438
|
+
if (!candidates.length) {
|
|
439
|
+
return matchesPattern(null, pattern, defaultWhenEmpty);
|
|
440
|
+
}
|
|
441
|
+
return candidates.some((value) => matchesPattern(value, pattern, false));
|
|
442
|
+
}
|
|
433
443
|
function shouldDropTraceEvent(event) {
|
|
434
444
|
if (disabledTraceFilePatterns.length) {
|
|
435
|
-
if (
|
|
445
|
+
if (matchesTraceFile(event, disabledTraceFilePatterns, false)) {
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (excludedInstrumentationFilePatterns.length) {
|
|
450
|
+
if (matchesTraceFile(event, excludedInstrumentationFilePatterns, false)) {
|
|
436
451
|
return true;
|
|
437
452
|
}
|
|
438
453
|
}
|
|
@@ -599,6 +614,9 @@ function initReproTracing(opts) {
|
|
|
599
614
|
if (hasOwn(options, 'disableTraceFiles')) {
|
|
600
615
|
setDisabledTraceFiles(options.disableTraceFiles ?? null);
|
|
601
616
|
}
|
|
617
|
+
if (hasOwn(options, 'exclude')) {
|
|
618
|
+
excludedInstrumentationFilePatterns = normalizePatternArray(options.exclude);
|
|
619
|
+
}
|
|
602
620
|
if (hasOwn(options, 'logFunctionCalls') && typeof options.logFunctionCalls === 'boolean') {
|
|
603
621
|
setReproTraceLogsEnabled(options.logFunctionCalls);
|
|
604
622
|
}
|
|
@@ -611,7 +629,17 @@ function initReproTracing(opts) {
|
|
|
611
629
|
__TRACER__ = tracerPkg;
|
|
612
630
|
applyTraceLogPreference(tracerPkg);
|
|
613
631
|
const { disableFunctionTraces: _disableFunctionTraces, disableFunctionTypes: _disableFunctionTypes, disableTraceFiles: _disableTraceFiles, fullValueCapture: _fullValueCapture, logFunctionCalls: _logFunctionCalls, traceInterceptors: _traceInterceptors, ...rest } = options;
|
|
614
|
-
const
|
|
632
|
+
const defaults = defaultTracerInitOpts();
|
|
633
|
+
const overrides = rest;
|
|
634
|
+
const initOpts = {
|
|
635
|
+
...defaults,
|
|
636
|
+
...overrides,
|
|
637
|
+
include: overrides.include ?? defaults.include,
|
|
638
|
+
exclude: [
|
|
639
|
+
...(defaults.exclude ?? []),
|
|
640
|
+
...(overrides.exclude ?? []),
|
|
641
|
+
],
|
|
642
|
+
};
|
|
615
643
|
tracerPkg.init?.(initOpts);
|
|
616
644
|
tracerPkg.patchHttp?.();
|
|
617
645
|
applyTraceLogPreference(tracerPkg);
|
|
@@ -736,6 +764,7 @@ function traceEventCandidateFromRecord(event) {
|
|
|
736
764
|
fn: event.fn,
|
|
737
765
|
wrapperClass: inferWrapperClassFromFn(event.fn),
|
|
738
766
|
file: event.file ?? null,
|
|
767
|
+
sourceFile: sourceFileForLibrary,
|
|
739
768
|
line: event.line ?? null,
|
|
740
769
|
depth: event.depth,
|
|
741
770
|
library: inferLibraryNameFromFile(sourceFileForLibrary ?? event.file),
|
|
@@ -4875,11 +4904,11 @@ function reproMiddleware(cfg) {
|
|
|
4875
4904
|
?? await materializeInlinePrivacyValueAsync('response.body', sanitizeRequestSnapshot(capturedBody), captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy);
|
|
4876
4905
|
const responseBody = responseBodyMaterialization.value;
|
|
4877
4906
|
const requestValueEntries = [];
|
|
4878
|
-
const bodyValueCapture =
|
|
4907
|
+
const bodyValueCapture = requestBodyRaw === undefined
|
|
4879
4908
|
? undefined
|
|
4880
4909
|
: await maybeCaptureRequestValueAsync({
|
|
4881
4910
|
target: 'request.body',
|
|
4882
|
-
rawValue:
|
|
4911
|
+
rawValue: requestBodyRaw,
|
|
4883
4912
|
previewValue: requestBody,
|
|
4884
4913
|
capture: {
|
|
4885
4914
|
runtimeConfig: captureCfg,
|
|
@@ -4929,7 +4958,7 @@ function reproMiddleware(cfg) {
|
|
|
4929
4958
|
privacy: activePrivacy,
|
|
4930
4959
|
},
|
|
4931
4960
|
}, requestValueEntries);
|
|
4932
|
-
const respBodyValueCapture =
|
|
4961
|
+
const respBodyValueCapture = capturedBody === undefined
|
|
4933
4962
|
? undefined
|
|
4934
4963
|
: await maybeCaptureRequestValueAsync({
|
|
4935
4964
|
target: 'response.body',
|
|
@@ -6336,6 +6365,7 @@ function recordKafkaTraceEvent(raw, sink, cfg, maskReq) {
|
|
|
6336
6365
|
fn: raw.fn,
|
|
6337
6366
|
wrapperClass: inferWrapperClassFromFn(raw.fn),
|
|
6338
6367
|
file: raw.file ?? null,
|
|
6368
|
+
sourceFile: sourceFileForLibrary,
|
|
6339
6369
|
line: typeof raw.line === 'number' ? raw.line : null,
|
|
6340
6370
|
depth: typeof raw.depth === 'number' ? raw.depth : undefined,
|
|
6341
6371
|
library: inferLibraryNameFromFile(sourceFileForLibrary ?? raw.file),
|
|
@@ -6441,6 +6471,7 @@ function buildPendingKafkaTraceEvent(raw) {
|
|
|
6441
6471
|
fn: raw.fn,
|
|
6442
6472
|
wrapperClass: inferWrapperClassFromFn(raw.fn),
|
|
6443
6473
|
file: raw.file ?? null,
|
|
6474
|
+
sourceFile: sourceFileForLibrary,
|
|
6444
6475
|
line: typeof raw.line === 'number' ? raw.line : null,
|
|
6445
6476
|
depth: typeof raw.depth === 'number' ? raw.depth : undefined,
|
|
6446
6477
|
library: inferLibraryNameFromFile(sourceFileForLibrary ?? raw.file),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reproapp/node-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.46",
|
|
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/request-capture-background.test.js && node test/init-auto-http-server.test.js && node test/backend-live-mode.test.js && node test/client-performance-regression.test.js && node test/request-manifest-priority.test.js && node test/runtime-no-double-invoke.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/runtime-service-registration.test.js && node test/ingest-queue-retention.test.js && node test/ingest-high-priority-retention.test.js && node test/ingest-critical-retention.test.js && node test/ingest-critical-retry-retention.test.js && node test/ingest-batching.test.js && node test/ingest-timeout.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/wrap-plugin-super-call.test.js && node test/function-arg-identity.test.js && node test/privacy-service-scope.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js && node test/amqplib-integration.test.js && node test/mongodb-integration.test.js && node test/mongodb-esm-import-hook.test.js && node test/mongodb-suppression-cleanup.test.js && node test/postgresql-integration.test.js",
|
|
15
|
+
"test": "npm run build && node test/trace-source-exclude.test.js && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/request-capture-background.test.js && node test/init-auto-http-server.test.js && node test/backend-live-mode.test.js && node test/client-performance-regression.test.js && node test/request-manifest-priority.test.js && node test/runtime-no-double-invoke.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/runtime-service-registration.test.js && node test/ingest-queue-retention.test.js && node test/ingest-high-priority-retention.test.js && node test/ingest-critical-retention.test.js && node test/ingest-critical-retry-retention.test.js && node test/ingest-batching.test.js && node test/ingest-timeout.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/wrap-plugin-super-call.test.js && node test/function-arg-identity.test.js && node test/privacy-service-scope.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js && node test/amqplib-integration.test.js && node test/mongodb-integration.test.js && node test/mongodb-esm-import-hook.test.js && node test/mongodb-suppression-cleanup.test.js && node test/postgresql-integration.test.js",
|
|
16
16
|
"test:containers": "npm run build && node test/runtime-no-double-invoke.testcontainers.test.js && node test/wrap-plugin-super-call.testcontainers.test.js && node test/function-arg-identity.testcontainers.test.js && node test/mongodb-mongoose-native-suppression.testcontainers.test.js && node test/postgresql-integrations.testcontainers.test.js"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -341,6 +341,7 @@ export type TraceEventForFilter = {
|
|
|
341
341
|
fn?: string;
|
|
342
342
|
wrapperClass?: string | null;
|
|
343
343
|
file?: string | null;
|
|
344
|
+
sourceFile?: string | null;
|
|
344
345
|
line?: number | null;
|
|
345
346
|
depth?: number;
|
|
346
347
|
library?: string | null;
|
|
@@ -606,6 +607,7 @@ function refreshDisabledFunctionTraceRules() {
|
|
|
606
607
|
let disabledFunctionTraceRules: DisableFunctionTraceConfig[] = computeDisabledFunctionTraceRules();
|
|
607
608
|
let disabledFunctionTypePatterns: Array<string | number | RegExp> = [];
|
|
608
609
|
let disabledTraceFilePatterns: Array<string | number | RegExp> = [];
|
|
610
|
+
let excludedInstrumentationFilePatterns: Array<string | number | RegExp> = [];
|
|
609
611
|
let __TRACE_LOG_PREF: boolean | null = null;
|
|
610
612
|
let __FULL_VALUE_CAPTURE_ENABLED = true;
|
|
611
613
|
|
|
@@ -684,7 +686,7 @@ function matchesRule(rule: DisableFunctionTraceRule, event: TraceEventForFilter)
|
|
|
684
686
|
const wrapperPattern = rule.wrapper ?? rule.wrapperClass ?? rule.className ?? rule.owner;
|
|
685
687
|
if (!matchesPattern(event.wrapperClass, wrapperPattern)) return false;
|
|
686
688
|
|
|
687
|
-
if (!
|
|
689
|
+
if (!matchesTraceFile(event, rule.file)) return false;
|
|
688
690
|
if (!matchesPattern(event.line == null ? null : String(event.line), rule.line)) return false;
|
|
689
691
|
|
|
690
692
|
const libPattern = rule.lib ?? rule.library;
|
|
@@ -699,9 +701,29 @@ function matchesRule(rule: DisableFunctionTraceRule, event: TraceEventForFilter)
|
|
|
699
701
|
return true;
|
|
700
702
|
}
|
|
701
703
|
|
|
704
|
+
function matchesTraceFile(
|
|
705
|
+
event: Pick<TraceEventForFilter, 'file' | 'sourceFile'>,
|
|
706
|
+
pattern?: TraceRulePattern,
|
|
707
|
+
defaultWhenEmpty: boolean = true,
|
|
708
|
+
): boolean {
|
|
709
|
+
if (pattern === undefined || pattern === null) return defaultWhenEmpty;
|
|
710
|
+
const candidates = [event.file, event.sourceFile].filter(
|
|
711
|
+
(value): value is string => typeof value === 'string' && value.length > 0,
|
|
712
|
+
);
|
|
713
|
+
if (!candidates.length) {
|
|
714
|
+
return matchesPattern(null, pattern, defaultWhenEmpty);
|
|
715
|
+
}
|
|
716
|
+
return candidates.some((value) => matchesPattern(value, pattern, false));
|
|
717
|
+
}
|
|
718
|
+
|
|
702
719
|
function shouldDropTraceEvent(event: TraceEventForFilter): boolean {
|
|
703
720
|
if (disabledTraceFilePatterns.length) {
|
|
704
|
-
if (
|
|
721
|
+
if (matchesTraceFile(event, disabledTraceFilePatterns, false)) {
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if (excludedInstrumentationFilePatterns.length) {
|
|
726
|
+
if (matchesTraceFile(event, excludedInstrumentationFilePatterns, false)) {
|
|
705
727
|
return true;
|
|
706
728
|
}
|
|
707
729
|
}
|
|
@@ -948,6 +970,9 @@ export function initReproTracing(opts?: ReproTracingInitOptions) {
|
|
|
948
970
|
if (hasOwn(options, 'disableTraceFiles')) {
|
|
949
971
|
setDisabledTraceFiles(options.disableTraceFiles ?? null);
|
|
950
972
|
}
|
|
973
|
+
if (hasOwn(options, 'exclude')) {
|
|
974
|
+
excludedInstrumentationFilePatterns = normalizePatternArray(options.exclude);
|
|
975
|
+
}
|
|
951
976
|
if (hasOwn(options, 'logFunctionCalls') && typeof options.logFunctionCalls === 'boolean') {
|
|
952
977
|
setReproTraceLogsEnabled(options.logFunctionCalls);
|
|
953
978
|
}
|
|
@@ -971,7 +996,17 @@ export function initReproTracing(opts?: ReproTracingInitOptions) {
|
|
|
971
996
|
traceInterceptors: _traceInterceptors,
|
|
972
997
|
...rest
|
|
973
998
|
} = options;
|
|
974
|
-
const
|
|
999
|
+
const defaults = defaultTracerInitOpts();
|
|
1000
|
+
const overrides = rest as TracerInitOpts;
|
|
1001
|
+
const initOpts = {
|
|
1002
|
+
...defaults,
|
|
1003
|
+
...overrides,
|
|
1004
|
+
include: overrides.include ?? defaults.include,
|
|
1005
|
+
exclude: [
|
|
1006
|
+
...(defaults.exclude ?? []),
|
|
1007
|
+
...(overrides.exclude ?? []),
|
|
1008
|
+
],
|
|
1009
|
+
};
|
|
975
1010
|
tracerPkg.init?.(initOpts);
|
|
976
1011
|
tracerPkg.patchHttp?.();
|
|
977
1012
|
applyTraceLogPreference(tracerPkg);
|
|
@@ -1094,6 +1129,7 @@ function traceEventCandidateFromRecord(event: PendingTraceEventRecord): TraceEve
|
|
|
1094
1129
|
fn: event.fn,
|
|
1095
1130
|
wrapperClass: inferWrapperClassFromFn(event.fn),
|
|
1096
1131
|
file: event.file ?? null,
|
|
1132
|
+
sourceFile: sourceFileForLibrary,
|
|
1097
1133
|
line: event.line ?? null,
|
|
1098
1134
|
depth: event.depth,
|
|
1099
1135
|
library: inferLibraryNameFromFile(sourceFileForLibrary ?? event.file),
|
|
@@ -6036,11 +6072,11 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
6036
6072
|
);
|
|
6037
6073
|
const responseBody = responseBodyMaterialization.value;
|
|
6038
6074
|
const requestValueEntries: TraceValueBatchEntry[] = [];
|
|
6039
|
-
const bodyValueCapture =
|
|
6075
|
+
const bodyValueCapture = requestBodyRaw === undefined
|
|
6040
6076
|
? undefined
|
|
6041
6077
|
: await maybeCaptureRequestValueAsync({
|
|
6042
6078
|
target: 'request.body',
|
|
6043
|
-
rawValue:
|
|
6079
|
+
rawValue: requestBodyRaw,
|
|
6044
6080
|
previewValue: requestBody,
|
|
6045
6081
|
capture: {
|
|
6046
6082
|
runtimeConfig: captureCfg,
|
|
@@ -6090,7 +6126,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
6090
6126
|
privacy: activePrivacy,
|
|
6091
6127
|
},
|
|
6092
6128
|
}, requestValueEntries);
|
|
6093
|
-
const respBodyValueCapture =
|
|
6129
|
+
const respBodyValueCapture = capturedBody === undefined
|
|
6094
6130
|
? undefined
|
|
6095
6131
|
: await maybeCaptureRequestValueAsync({
|
|
6096
6132
|
target: 'response.body',
|
|
@@ -7595,6 +7631,7 @@ function recordKafkaTraceEvent(
|
|
|
7595
7631
|
fn: raw.fn,
|
|
7596
7632
|
wrapperClass: inferWrapperClassFromFn(raw.fn),
|
|
7597
7633
|
file: raw.file ?? null,
|
|
7634
|
+
sourceFile: sourceFileForLibrary,
|
|
7598
7635
|
line: typeof raw.line === 'number' ? raw.line : null,
|
|
7599
7636
|
depth: typeof raw.depth === 'number' ? raw.depth : undefined,
|
|
7600
7637
|
library: inferLibraryNameFromFile(sourceFileForLibrary ?? raw.file),
|
|
@@ -7727,6 +7764,7 @@ function buildPendingKafkaTraceEvent(raw: any): PendingTraceEventRecord | null {
|
|
|
7727
7764
|
fn: raw.fn,
|
|
7728
7765
|
wrapperClass: inferWrapperClassFromFn(raw.fn),
|
|
7729
7766
|
file: raw.file ?? null,
|
|
7767
|
+
sourceFile: sourceFileForLibrary,
|
|
7730
7768
|
line: typeof raw.line === 'number' ? raw.line : null,
|
|
7731
7769
|
depth: typeof raw.depth === 'number' ? raw.depth : undefined,
|
|
7732
7770
|
library: inferLibraryNameFromFile(sourceFileForLibrary ?? raw.file),
|
|
@@ -142,6 +142,8 @@ async function main() {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
const large = makeReqRes('S_perf_large_payload', 'A_perf_large_payload', '/large-payload');
|
|
145
|
+
large.req.method = 'POST';
|
|
146
|
+
large.req.body = largePayload();
|
|
145
147
|
await invokeMiddleware(middleware, large.req, large.res, async () => {
|
|
146
148
|
const traced = global.__repro_call(
|
|
147
149
|
largeTraceReturn,
|
|
@@ -160,12 +162,48 @@ async function main() {
|
|
|
160
162
|
|
|
161
163
|
const backendRequest = eventsOf(capturedBodies, 'backend_request')[0]?.payload?.request;
|
|
162
164
|
assert(backendRequest, JSON.stringify(capturedBodies));
|
|
165
|
+
assert.equal(backendRequest.body, undefined, JSON.stringify(backendRequest));
|
|
166
|
+
assert.equal(
|
|
167
|
+
backendRequest.bodyMaterialization?.reason,
|
|
168
|
+
'inline-size-limit',
|
|
169
|
+
JSON.stringify(backendRequest),
|
|
170
|
+
);
|
|
171
|
+
assert.equal(
|
|
172
|
+
backendRequest.bodyValueCapture?.status,
|
|
173
|
+
'stored',
|
|
174
|
+
JSON.stringify(backendRequest),
|
|
175
|
+
);
|
|
163
176
|
assert.equal(backendRequest.respBody, undefined, JSON.stringify(backendRequest));
|
|
164
177
|
assert.equal(
|
|
165
178
|
backendRequest.respBodyMaterialization?.reason,
|
|
166
179
|
'inline-size-limit',
|
|
167
180
|
JSON.stringify(backendRequest),
|
|
168
181
|
);
|
|
182
|
+
assert.equal(
|
|
183
|
+
backendRequest.respBodyValueCapture?.status,
|
|
184
|
+
'stored',
|
|
185
|
+
JSON.stringify(backendRequest),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const requestValues = eventsOf(capturedBodies, 'backend_request')
|
|
189
|
+
.flatMap((event) => Array.isArray(event?.payload?.requestValues) ? event.payload.requestValues : []);
|
|
190
|
+
const capturedRequestBody = requestValues.find((entry) =>
|
|
191
|
+
entry?.id === backendRequest.bodyValueCapture.valueId &&
|
|
192
|
+
entry?.target === 'request.body'
|
|
193
|
+
);
|
|
194
|
+
const capturedResponseBody = requestValues.find((entry) =>
|
|
195
|
+
entry?.id === backendRequest.respBodyValueCapture.valueId &&
|
|
196
|
+
entry?.target === 'response.body'
|
|
197
|
+
);
|
|
198
|
+
assert(capturedRequestBody, JSON.stringify(requestValues));
|
|
199
|
+
assert(capturedResponseBody, JSON.stringify(requestValues));
|
|
200
|
+
assert.equal(capturedRequestBody.value?.rows?.length, 400, JSON.stringify(capturedRequestBody));
|
|
201
|
+
assert.equal(capturedResponseBody.value?.rows?.length, 400, JSON.stringify(capturedResponseBody));
|
|
202
|
+
assert.equal(
|
|
203
|
+
capturedResponseBody.value.rows[399].depot,
|
|
204
|
+
'depot-399',
|
|
205
|
+
JSON.stringify(capturedResponseBody),
|
|
206
|
+
);
|
|
169
207
|
|
|
170
208
|
const traceEvents = eventsOf(capturedBodies, 'trace_batch')
|
|
171
209
|
.flatMap((event) => Array.isArray(event?.payload?.trace) ? event.payload.trace : []);
|
|
@@ -307,7 +307,7 @@ async function testKafkaTraceFlushIsNotBlockedForeverByActiveHttpRequest() {
|
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
-
async function
|
|
310
|
+
async function testLargeHttpResponseBodyKeepsFullValueOutOfBand() {
|
|
311
311
|
const marker = 'OVERSIZED_HTTP_RESPONSE_MARKER';
|
|
312
312
|
const capturedBodies = [];
|
|
313
313
|
const originalFetch = global.fetch;
|
|
@@ -344,13 +344,28 @@ async function testLargeHttpResponseBodyKeepsRequestMetadataWithoutInlineBody()
|
|
|
344
344
|
return text.includes('"event_type":"backend_request"') &&
|
|
345
345
|
text.includes('"reason":"inline-size-limit"');
|
|
346
346
|
}));
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
assert(
|
|
353
|
-
assert(
|
|
347
|
+
const backendEvent = capturedBodies
|
|
348
|
+
.flatMap((body) => Array.isArray(body?.events) ? body.events : [])
|
|
349
|
+
.find((event) => event?.event_type === 'backend_request');
|
|
350
|
+
const request = backendEvent?.payload?.request;
|
|
351
|
+
const requestValues = backendEvent?.payload?.requestValues;
|
|
352
|
+
assert(request, JSON.stringify(capturedBodies));
|
|
353
|
+
assert.strictEqual(request.respBody, undefined, JSON.stringify(request));
|
|
354
|
+
assert.strictEqual(request.respBodyMaterialization?.reason, 'inline-size-limit');
|
|
355
|
+
assert.strictEqual(request.respBodyValueCapture?.status, 'stored');
|
|
356
|
+
assert(!JSON.stringify(request).includes(marker), JSON.stringify(request));
|
|
357
|
+
const capturedResponse = requestValues?.find((entry) =>
|
|
358
|
+
entry?.id === request.respBodyValueCapture.valueId &&
|
|
359
|
+
entry?.target === 'response.body'
|
|
360
|
+
);
|
|
361
|
+
assert(capturedResponse, JSON.stringify(requestValues));
|
|
362
|
+
assert.strictEqual(
|
|
363
|
+
capturedResponse.value?.payload,
|
|
364
|
+
`${marker}:${'x'.repeat(12 * 1024)}`,
|
|
365
|
+
);
|
|
366
|
+
assert(!JSON.stringify(request).includes('__type":"json-string'), JSON.stringify(request));
|
|
367
|
+
assert(!JSON.stringify(request).includes('__truncatedKeys'), JSON.stringify(request));
|
|
368
|
+
assert(!JSON.stringify(request).includes('more chars'), JSON.stringify(request));
|
|
354
369
|
} finally {
|
|
355
370
|
await flushIngestQueue();
|
|
356
371
|
global.fetch = originalFetch;
|
|
@@ -746,7 +761,7 @@ async function main() {
|
|
|
746
761
|
await testKafkaConsumerTraceUsesConsumeTraceId();
|
|
747
762
|
await testKafkaProducerCapturesFullPublishedMessageValue();
|
|
748
763
|
await testKafkaTraceFlushIsNotBlockedForeverByActiveHttpRequest();
|
|
749
|
-
await
|
|
764
|
+
await testLargeHttpResponseBodyKeepsFullValueOutOfBand();
|
|
750
765
|
await testJsonBuiltinTracePayloadsAreOmitted();
|
|
751
766
|
await testLargeStringTraceValuesStayIngestibleAndCaptureFullValueOutOfBand();
|
|
752
767
|
await testLargeStructuredTraceValuesStayIngestibleAndCaptureFullValueOutOfBand();
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
process.env.TRACE_LINGER_AFTER_FINISH_MS = '0';
|
|
4
|
+
process.env.TRACE_IDLE_FLUSH_MS = '25';
|
|
5
|
+
process.env.SESSION_DRAIN_TIMEOUT_MS = '0';
|
|
6
|
+
process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS = '25';
|
|
7
|
+
|
|
8
|
+
const assert = require('node:assert');
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const { EventEmitter } = require('node:events');
|
|
12
|
+
const {
|
|
13
|
+
initReproTracing,
|
|
14
|
+
reproMiddleware,
|
|
15
|
+
} = require('../dist');
|
|
16
|
+
|
|
17
|
+
const excludeTenantModule =
|
|
18
|
+
/src[\\/]modules[\\/]core[\\/]database[\\/]modules[\\/]tenant[\\/]tenant-database\.module\.ts$/;
|
|
19
|
+
|
|
20
|
+
function writeFixture(root) {
|
|
21
|
+
const distRoot = path.join(root, 'dist');
|
|
22
|
+
const tenantDistDir = path.join(
|
|
23
|
+
distRoot,
|
|
24
|
+
'modules/core/database/modules/tenant',
|
|
25
|
+
);
|
|
26
|
+
const tenantSourceFile = path.join(
|
|
27
|
+
root,
|
|
28
|
+
'src/modules/core/database/modules/tenant/tenant-database.module.ts',
|
|
29
|
+
);
|
|
30
|
+
const tenantDistFile = path.join(tenantDistDir, 'tenant-database.module.js');
|
|
31
|
+
const tenantMapFile = `${tenantDistFile}.map`;
|
|
32
|
+
const callerFile = path.join(distRoot, 'caller.js');
|
|
33
|
+
|
|
34
|
+
fs.mkdirSync(tenantDistDir, { recursive: true });
|
|
35
|
+
fs.writeFileSync(
|
|
36
|
+
tenantDistFile,
|
|
37
|
+
[
|
|
38
|
+
"'use strict';",
|
|
39
|
+
'function connectTenantDatabase() {',
|
|
40
|
+
" return 'connected';",
|
|
41
|
+
'}',
|
|
42
|
+
'module.exports = { connectTenantDatabase };',
|
|
43
|
+
'//# sourceMappingURL=tenant-database.module.js.map',
|
|
44
|
+
'',
|
|
45
|
+
].join('\n'),
|
|
46
|
+
);
|
|
47
|
+
fs.writeFileSync(
|
|
48
|
+
tenantMapFile,
|
|
49
|
+
JSON.stringify({
|
|
50
|
+
version: 3,
|
|
51
|
+
file: path.basename(tenantDistFile),
|
|
52
|
+
sourceRoot: '',
|
|
53
|
+
sources: [tenantSourceFile],
|
|
54
|
+
names: [],
|
|
55
|
+
mappings: '',
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
fs.writeFileSync(
|
|
59
|
+
callerFile,
|
|
60
|
+
[
|
|
61
|
+
"'use strict';",
|
|
62
|
+
"const tenant = require('./modules/core/database/modules/tenant/tenant-database.module');",
|
|
63
|
+
'function invokeTenantDatabase() {',
|
|
64
|
+
' return tenant.connectTenantDatabase();',
|
|
65
|
+
'}',
|
|
66
|
+
'module.exports = { invokeTenantDatabase, tenant };',
|
|
67
|
+
'',
|
|
68
|
+
].join('\n'),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return { callerFile, tenantSourceFile };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function makeReqRes() {
|
|
75
|
+
const req = new EventEmitter();
|
|
76
|
+
req.method = 'GET';
|
|
77
|
+
req.url = '/source-exclude';
|
|
78
|
+
req.headers = {
|
|
79
|
+
'x-bug-session-id': 'S_trace_source_exclude',
|
|
80
|
+
'x-bug-action-id': 'A_trace_source_exclude',
|
|
81
|
+
'x-bug-request-start': String(Date.now()),
|
|
82
|
+
};
|
|
83
|
+
req.params = {};
|
|
84
|
+
req.query = {};
|
|
85
|
+
|
|
86
|
+
const res = new EventEmitter();
|
|
87
|
+
res.statusCode = 200;
|
|
88
|
+
res.getHeader = () => undefined;
|
|
89
|
+
res.setHeader = () => {};
|
|
90
|
+
res.json = function json(body) {
|
|
91
|
+
this.body = body;
|
|
92
|
+
this.emit('finish');
|
|
93
|
+
return body;
|
|
94
|
+
};
|
|
95
|
+
res.send = res.json;
|
|
96
|
+
res.write = () => true;
|
|
97
|
+
res.end = () => {
|
|
98
|
+
res.emit('finish');
|
|
99
|
+
return true;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return { req, res };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function traceEventsFromPosts(posts) {
|
|
106
|
+
const traces = [];
|
|
107
|
+
const append = (value) => {
|
|
108
|
+
if (Array.isArray(value)) {
|
|
109
|
+
traces.push(...value);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (typeof value !== 'string') return;
|
|
113
|
+
try {
|
|
114
|
+
const parsed = JSON.parse(value);
|
|
115
|
+
if (Array.isArray(parsed)) traces.push(...parsed);
|
|
116
|
+
} catch {}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
for (const post of posts) {
|
|
120
|
+
let payload;
|
|
121
|
+
try {
|
|
122
|
+
payload = JSON.parse(String(post.body || ''));
|
|
123
|
+
} catch {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
for (const entry of Array.isArray(payload.entries) ? payload.entries : []) {
|
|
127
|
+
append(entry?.trace);
|
|
128
|
+
append(entry?.request?.trace);
|
|
129
|
+
}
|
|
130
|
+
for (const event of Array.isArray(payload.events) ? payload.events : []) {
|
|
131
|
+
append(event?.payload?.trace);
|
|
132
|
+
append(event?.payload?.request?.trace);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return traces;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function main() {
|
|
140
|
+
const fixtureRoot = fs.mkdtempSync(path.join(__dirname, '.tmp-source-exclude-'));
|
|
141
|
+
const posts = [];
|
|
142
|
+
const originalFetch = global.fetch;
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const fixture = writeFixture(fixtureRoot);
|
|
146
|
+
const includeFixture = new RegExp(
|
|
147
|
+
`^${fixtureRoot.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
global.fetch = async (url, options = {}) => {
|
|
151
|
+
posts.push({ url: String(url), body: options.body });
|
|
152
|
+
return {
|
|
153
|
+
ok: true,
|
|
154
|
+
status: 200,
|
|
155
|
+
json: async () => ({ ok: true }),
|
|
156
|
+
text: async () => '{"ok":true}',
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
initReproTracing({
|
|
161
|
+
include: [includeFixture],
|
|
162
|
+
exclude: [excludeTenantModule],
|
|
163
|
+
logFunctionCalls: false,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const caller = require(fixture.callerFile);
|
|
167
|
+
assert.strictEqual(
|
|
168
|
+
caller.tenant.connectTenantDatabase.__repro_instrumented,
|
|
169
|
+
undefined,
|
|
170
|
+
'source-mapped excluded module must not be instrumented',
|
|
171
|
+
);
|
|
172
|
+
assert.strictEqual(
|
|
173
|
+
caller.invokeTenantDatabase.__repro_instrumented,
|
|
174
|
+
true,
|
|
175
|
+
'non-excluded caller must remain instrumented',
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const middleware = reproMiddleware({
|
|
179
|
+
appId: 'APP_trace_source_exclude',
|
|
180
|
+
tenantId: 'TENANT_trace_source_exclude',
|
|
181
|
+
appSecret: 'secret',
|
|
182
|
+
captureHeaders: false,
|
|
183
|
+
});
|
|
184
|
+
const { req, res } = makeReqRes();
|
|
185
|
+
await new Promise((resolve, reject) => {
|
|
186
|
+
middleware(req, res, () => {
|
|
187
|
+
try {
|
|
188
|
+
assert.strictEqual(caller.invokeTenantDatabase(), 'connected');
|
|
189
|
+
res.json({ ok: true });
|
|
190
|
+
resolve();
|
|
191
|
+
} catch (error) {
|
|
192
|
+
reject(error);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
197
|
+
|
|
198
|
+
const traces = traceEventsFromPosts(posts);
|
|
199
|
+
assert(
|
|
200
|
+
traces.some((event) => event?.fn === 'invokeTenantDatabase'),
|
|
201
|
+
`expected caller trace, received ${JSON.stringify(traces)}`,
|
|
202
|
+
);
|
|
203
|
+
assert(
|
|
204
|
+
!traces.some(
|
|
205
|
+
(event) =>
|
|
206
|
+
event?.fn === 'connectTenantDatabase' ||
|
|
207
|
+
event?.sourceFile === fixture.tenantSourceFile,
|
|
208
|
+
),
|
|
209
|
+
`excluded source leaked into trace payload: ${JSON.stringify(traces)}`,
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
console.log('source-mapped tracing exclusion OK');
|
|
213
|
+
} finally {
|
|
214
|
+
global.fetch = originalFetch;
|
|
215
|
+
fs.rmSync(fixtureRoot, { recursive: true, force: true });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
main().catch((error) => {
|
|
220
|
+
console.error(error);
|
|
221
|
+
process.exitCode = 1;
|
|
222
|
+
});
|
package/tracer/cjs-hook.js
CHANGED
|
@@ -31,16 +31,19 @@ function tagExports(value, filename, seen = new WeakSet(), depth = 0, instrument
|
|
|
31
31
|
|
|
32
32
|
if (typeof value === 'function') {
|
|
33
33
|
try {
|
|
34
|
-
|
|
34
|
+
const existingSourceFile = value[SYM_SRC_FILE];
|
|
35
|
+
const belongsToCurrentModule =
|
|
36
|
+
!existingSourceFile || toPosix(existingSourceFile) === toPosix(filename);
|
|
37
|
+
if (!existingSourceFile) {
|
|
35
38
|
Object.defineProperty(value, SYM_SRC_FILE, { value: filename, configurable: true });
|
|
36
39
|
}
|
|
37
|
-
if (value[SYM_IS_APP] !== isApp) {
|
|
40
|
+
if (belongsToCurrentModule && value[SYM_IS_APP] !== isApp) {
|
|
38
41
|
Object.defineProperty(value, SYM_IS_APP, { value: isApp, configurable: true });
|
|
39
42
|
}
|
|
40
|
-
if (instrumented && value.__repro_instrumented !== true) {
|
|
43
|
+
if (instrumented && belongsToCurrentModule && value.__repro_instrumented !== true) {
|
|
41
44
|
Object.defineProperty(value, '__repro_instrumented', { value: true, configurable: true });
|
|
42
45
|
}
|
|
43
|
-
if (instrumented && value[SYM_BODY_TRACED] !== true) {
|
|
46
|
+
if (instrumented && belongsToCurrentModule && value[SYM_BODY_TRACED] !== true) {
|
|
44
47
|
Object.defineProperty(value, SYM_BODY_TRACED, { value: true, configurable: true });
|
|
45
48
|
}
|
|
46
49
|
} catch {}
|
|
@@ -82,10 +85,24 @@ function installCJS({ include, exclude, parserPlugins } = {}) {
|
|
|
82
85
|
: [ new RegExp('^' + escapeRx(CWD + '/')) ];
|
|
83
86
|
const exc = Array.isArray(exclude) ? exclude : [];
|
|
84
87
|
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
const matchesAny = (patterns, file) => {
|
|
89
|
+
const normalized = toPosix(file);
|
|
90
|
+
return patterns.some((rx) => {
|
|
91
|
+
try {
|
|
92
|
+
rx.lastIndex = 0;
|
|
93
|
+
const matched = rx.test(normalized);
|
|
94
|
+
rx.lastIndex = 0;
|
|
95
|
+
return matched;
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const shouldHandle = (physicalFile, sourceFile) => {
|
|
103
|
+
const candidates = [physicalFile, sourceFile].filter(Boolean);
|
|
104
|
+
if (candidates.some(file => matchesAny(exc, file))) return false;
|
|
105
|
+
return candidates.some(file => matchesAny(inc, file));
|
|
89
106
|
};
|
|
90
107
|
|
|
91
108
|
// ---- Global hook: intercept *all* compiles, regardless of how .ts was compiled ----
|
|
@@ -100,8 +117,7 @@ function installCJS({ include, exclude, parserPlugins } = {}) {
|
|
|
100
117
|
if (transformedFiles.has(filename)) {
|
|
101
118
|
// Already transformed once; avoid double-transforming code that may already include __repro_call wrappers.
|
|
102
119
|
out = code;
|
|
103
|
-
} else if (
|
|
104
|
-
try { process.stderr.write(`[trace-debug] compile: ${filename}\n`); } catch {}
|
|
120
|
+
} else if (isAppFile(filename)) {
|
|
105
121
|
const sourceInfo = getSourceInfo(code, filename);
|
|
106
122
|
if (sourceInfo?.metaFilename) {
|
|
107
123
|
metaFilename = sourceInfo.metaFilename;
|
|
@@ -109,35 +125,38 @@ function installCJS({ include, exclude, parserPlugins } = {}) {
|
|
|
109
125
|
if (typeof sourceInfo?.mapOriginalPosition === 'function') {
|
|
110
126
|
mapOriginalPosition = sourceInfo.mapOriginalPosition;
|
|
111
127
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
sourceMaps: 'inline',
|
|
118
|
-
parserOpts: {
|
|
128
|
+
if (shouldHandle(filename, metaFilename)) {
|
|
129
|
+
try { process.stderr.write(`[trace-debug] compile: ${filename}\n`); } catch {}
|
|
130
|
+
// Transform the already-compiled JS (keeps Nest’s decorator metadata intact).
|
|
131
|
+
const res = babel.transformSync(code, {
|
|
132
|
+
filename,
|
|
119
133
|
sourceType: 'unambiguous',
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
134
|
+
retainLines: true,
|
|
135
|
+
sourceMaps: 'inline',
|
|
136
|
+
parserOpts: {
|
|
137
|
+
sourceType: 'unambiguous',
|
|
138
|
+
// We’re parsing JS here; TS was compiled by ts-node/Nest.
|
|
139
|
+
plugins: parserPlugins || [
|
|
140
|
+
'jsx', 'classProperties', 'classPrivateProperties',
|
|
141
|
+
'classPrivateMethods', 'dynamicImport', 'topLevelAwait',
|
|
142
|
+
'optionalChaining', 'nullishCoalescingOperator',
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
// only the wrap plugin; do NOT run TS transform here
|
|
146
|
+
plugins: [
|
|
147
|
+
[ makeWrap(metaFilename, {
|
|
148
|
+
mode: 'all',
|
|
149
|
+
wrapGettersSetters: false,
|
|
150
|
+
skipAnonymous: false,
|
|
151
|
+
mapOriginalPosition,
|
|
152
|
+
}) ],
|
|
125
153
|
],
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
skipAnonymous: false,
|
|
133
|
-
mapOriginalPosition,
|
|
134
|
-
}) ],
|
|
135
|
-
],
|
|
136
|
-
compact: false,
|
|
137
|
-
comments: true,
|
|
138
|
-
});
|
|
139
|
-
out = res?.code || code;
|
|
140
|
-
wasInstrumented = !!res?.code;
|
|
154
|
+
compact: false,
|
|
155
|
+
comments: true,
|
|
156
|
+
});
|
|
157
|
+
out = res?.code || code;
|
|
158
|
+
wasInstrumented = !!res?.code;
|
|
159
|
+
}
|
|
141
160
|
transformedFiles.add(filename);
|
|
142
161
|
}
|
|
143
162
|
} catch (err) {
|