@reproapp/node-sdk 0.0.6 → 0.0.7
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.js +175 -148
- package/package.json +2 -2
- package/src/index.ts +223 -182
- package/test/request-flush-timing.test.js +123 -0
package/dist/index.js
CHANGED
|
@@ -3695,6 +3695,7 @@ function reproMiddleware(cfg) {
|
|
|
3695
3695
|
let idleTimer = null;
|
|
3696
3696
|
let hardStopTimer = null;
|
|
3697
3697
|
let flushPayload = null;
|
|
3698
|
+
let requestCaptureScheduled = false;
|
|
3698
3699
|
let sessionDrainWait = null;
|
|
3699
3700
|
const activeSpans = new Set();
|
|
3700
3701
|
let anonymousSpanDepth = 0;
|
|
@@ -3784,6 +3785,179 @@ function reproMiddleware(cfg) {
|
|
|
3784
3785
|
}
|
|
3785
3786
|
scheduleIdleFlush();
|
|
3786
3787
|
};
|
|
3788
|
+
const chooseRequestEndpoint = () => {
|
|
3789
|
+
const pendingEvents = preparePendingTraceEventsForFlush(events.slice());
|
|
3790
|
+
const baseEvents = balanceTraceEvents(pendingEvents.slice());
|
|
3791
|
+
const orderedEvents = TRACE_ORDER_MODE === 'tree'
|
|
3792
|
+
? reorderTraceEvents(baseEvents)
|
|
3793
|
+
: sortTraceEventsChronologically(baseEvents);
|
|
3794
|
+
const summary = summarizeEndpointFromEvents(orderedEvents);
|
|
3795
|
+
return {
|
|
3796
|
+
chosenEndpoint: summary.endpointTrace
|
|
3797
|
+
?? summary.preferredAppTrace
|
|
3798
|
+
?? summary.firstAppTrace
|
|
3799
|
+
?? endpointTrace
|
|
3800
|
+
?? preferredAppTrace
|
|
3801
|
+
?? firstAppTrace
|
|
3802
|
+
?? { fn: null, file: null, line: null, functionType: null },
|
|
3803
|
+
hasTraceEvents: orderedEvents.length > 0,
|
|
3804
|
+
};
|
|
3805
|
+
};
|
|
3806
|
+
const buildRequestCapturePayloadAsync = async (chosenEndpoint, hasTraceEvents) => {
|
|
3807
|
+
const endpointTraceCtx = (() => {
|
|
3808
|
+
if (!chosenEndpoint?.fn && !chosenEndpoint?.file)
|
|
3809
|
+
return null;
|
|
3810
|
+
return {
|
|
3811
|
+
type: 'enter',
|
|
3812
|
+
eventType: 'enter',
|
|
3813
|
+
fn: chosenEndpoint.fn ?? undefined,
|
|
3814
|
+
wrapperClass: inferWrapperClassFromFn(chosenEndpoint.fn),
|
|
3815
|
+
file: chosenEndpoint.file ?? null,
|
|
3816
|
+
line: chosenEndpoint.line ?? null,
|
|
3817
|
+
functionType: chosenEndpoint.functionType ?? null,
|
|
3818
|
+
library: inferLibraryNameFromFile(chosenEndpoint.file),
|
|
3819
|
+
};
|
|
3820
|
+
})();
|
|
3821
|
+
const activePrivacy = resolvePrivacy();
|
|
3822
|
+
const requestBodyRaw = req.body;
|
|
3823
|
+
const requestBodyMaterialization = limitRawInlinePrivacyValue('request.body', requestBodyRaw)
|
|
3824
|
+
?? await materializeInlinePrivacyValueAsync('request.body', sanitizeRequestSnapshot(requestBodyRaw), cfg, maskReq, endpointTraceCtx, masking, activePrivacy);
|
|
3825
|
+
const requestBody = requestBodyMaterialization.value;
|
|
3826
|
+
const requestParams = await applyPrivacyThenMaskAsync('request.params', sanitizeRequestSnapshot(req.params), cfg, maskReq, endpointTraceCtx, masking, activePrivacy);
|
|
3827
|
+
const requestQuery = await applyPrivacyThenMaskAsync('request.query', sanitizeRequestSnapshot(req.query), cfg, maskReq, endpointTraceCtx, masking, activePrivacy);
|
|
3828
|
+
const maskedHeaders = await applyPrivacyThenMaskAsync('request.headers', requestHeaders, cfg, maskReq, endpointTraceCtx, masking, activePrivacy);
|
|
3829
|
+
const responseBodyMaterialization = capturedBody === undefined
|
|
3830
|
+
? { value: undefined }
|
|
3831
|
+
: limitRawInlinePrivacyValue('response.body', capturedBody)
|
|
3832
|
+
?? await materializeInlinePrivacyValueAsync('response.body', sanitizeRequestSnapshot(capturedBody), cfg, maskReq, endpointTraceCtx, masking, activePrivacy);
|
|
3833
|
+
const responseBody = responseBodyMaterialization.value;
|
|
3834
|
+
const requestValueEntries = [];
|
|
3835
|
+
const bodyValueCapture = requestBodyMaterialization.skipped
|
|
3836
|
+
? undefined
|
|
3837
|
+
: await maybeCaptureRequestValueAsync({
|
|
3838
|
+
target: 'request.body',
|
|
3839
|
+
rawValue: req.body,
|
|
3840
|
+
previewValue: requestBody,
|
|
3841
|
+
capture: {
|
|
3842
|
+
runtimeConfig: cfg,
|
|
3843
|
+
captureHeaders: cfg.captureHeaders,
|
|
3844
|
+
maskReq,
|
|
3845
|
+
trace: endpointTraceCtx,
|
|
3846
|
+
masking,
|
|
3847
|
+
privacy: activePrivacy,
|
|
3848
|
+
},
|
|
3849
|
+
}, requestValueEntries);
|
|
3850
|
+
const paramsValueCapture = await maybeCaptureRequestValueAsync({
|
|
3851
|
+
target: 'request.params',
|
|
3852
|
+
rawValue: req.params,
|
|
3853
|
+
previewValue: requestParams,
|
|
3854
|
+
capture: {
|
|
3855
|
+
runtimeConfig: cfg,
|
|
3856
|
+
captureHeaders: cfg.captureHeaders,
|
|
3857
|
+
maskReq,
|
|
3858
|
+
trace: endpointTraceCtx,
|
|
3859
|
+
masking,
|
|
3860
|
+
privacy: activePrivacy,
|
|
3861
|
+
},
|
|
3862
|
+
}, requestValueEntries);
|
|
3863
|
+
const queryValueCapture = await maybeCaptureRequestValueAsync({
|
|
3864
|
+
target: 'request.query',
|
|
3865
|
+
rawValue: req.query,
|
|
3866
|
+
previewValue: requestQuery,
|
|
3867
|
+
capture: {
|
|
3868
|
+
runtimeConfig: cfg,
|
|
3869
|
+
captureHeaders: cfg.captureHeaders,
|
|
3870
|
+
maskReq,
|
|
3871
|
+
trace: endpointTraceCtx,
|
|
3872
|
+
masking,
|
|
3873
|
+
privacy: activePrivacy,
|
|
3874
|
+
},
|
|
3875
|
+
}, requestValueEntries);
|
|
3876
|
+
const headersValueCapture = await maybeCaptureRequestValueAsync({
|
|
3877
|
+
target: 'request.headers',
|
|
3878
|
+
rawValue: req.headers,
|
|
3879
|
+
previewValue: maskedHeaders,
|
|
3880
|
+
capture: {
|
|
3881
|
+
runtimeConfig: cfg,
|
|
3882
|
+
captureHeaders: cfg.captureHeaders,
|
|
3883
|
+
maskReq,
|
|
3884
|
+
trace: endpointTraceCtx,
|
|
3885
|
+
masking,
|
|
3886
|
+
privacy: activePrivacy,
|
|
3887
|
+
},
|
|
3888
|
+
}, requestValueEntries);
|
|
3889
|
+
const respBodyValueCapture = responseBodyMaterialization.skipped
|
|
3890
|
+
? undefined
|
|
3891
|
+
: await maybeCaptureRequestValueAsync({
|
|
3892
|
+
target: 'response.body',
|
|
3893
|
+
rawValue: capturedBody,
|
|
3894
|
+
previewValue: responseBody,
|
|
3895
|
+
capture: {
|
|
3896
|
+
runtimeConfig: cfg,
|
|
3897
|
+
captureHeaders: cfg.captureHeaders,
|
|
3898
|
+
maskReq,
|
|
3899
|
+
trace: endpointTraceCtx,
|
|
3900
|
+
masking,
|
|
3901
|
+
privacy: activePrivacy,
|
|
3902
|
+
},
|
|
3903
|
+
}, requestValueEntries);
|
|
3904
|
+
const requestPayload = {
|
|
3905
|
+
rid,
|
|
3906
|
+
method: req.method,
|
|
3907
|
+
url,
|
|
3908
|
+
path,
|
|
3909
|
+
status: res.statusCode,
|
|
3910
|
+
durMs: Date.now() - t0,
|
|
3911
|
+
headers: maskedHeaders,
|
|
3912
|
+
key,
|
|
3913
|
+
respBody: responseBody,
|
|
3914
|
+
trace: hasTraceEvents ? undefined : [],
|
|
3915
|
+
};
|
|
3916
|
+
if (requestBody !== undefined)
|
|
3917
|
+
requestPayload.body = requestBody;
|
|
3918
|
+
if (bodyValueCapture)
|
|
3919
|
+
requestPayload.bodyValueCapture = bodyValueCapture;
|
|
3920
|
+
if (requestParams !== undefined)
|
|
3921
|
+
requestPayload.params = requestParams;
|
|
3922
|
+
if (paramsValueCapture)
|
|
3923
|
+
requestPayload.paramsValueCapture = paramsValueCapture;
|
|
3924
|
+
if (requestQuery !== undefined)
|
|
3925
|
+
requestPayload.query = requestQuery;
|
|
3926
|
+
if (queryValueCapture)
|
|
3927
|
+
requestPayload.queryValueCapture = queryValueCapture;
|
|
3928
|
+
if (headersValueCapture)
|
|
3929
|
+
requestPayload.headersValueCapture = headersValueCapture;
|
|
3930
|
+
if (respBodyValueCapture)
|
|
3931
|
+
requestPayload.respBodyValueCapture = respBodyValueCapture;
|
|
3932
|
+
if (requestBodyMaterialization.skipped) {
|
|
3933
|
+
requestPayload.bodyMaterialization = requestBodyMaterialization.skipped;
|
|
3934
|
+
}
|
|
3935
|
+
if (responseBodyMaterialization.skipped) {
|
|
3936
|
+
requestPayload.respBodyMaterialization = responseBodyMaterialization.skipped;
|
|
3937
|
+
}
|
|
3938
|
+
requestPayload.entryPoint = chosenEndpoint;
|
|
3939
|
+
return { requestPayload, requestValueEntries };
|
|
3940
|
+
};
|
|
3941
|
+
const emitRequestCaptureAsync = async () => {
|
|
3942
|
+
if (requestCaptureScheduled)
|
|
3943
|
+
return;
|
|
3944
|
+
requestCaptureScheduled = true;
|
|
3945
|
+
try {
|
|
3946
|
+
const { chosenEndpoint, hasTraceEvents } = chooseRequestEndpoint();
|
|
3947
|
+
const { requestPayload, requestValueEntries } = await buildRequestCapturePayloadAsync(chosenEndpoint, hasTraceEvents);
|
|
3948
|
+
post(cfg, sid, {
|
|
3949
|
+
entries: [{
|
|
3950
|
+
actionId: aid,
|
|
3951
|
+
request: requestPayload,
|
|
3952
|
+
requestValues: requestValueEntries.length ? requestValueEntries : undefined,
|
|
3953
|
+
t: requestEpochMs,
|
|
3954
|
+
}]
|
|
3955
|
+
});
|
|
3956
|
+
}
|
|
3957
|
+
catch {
|
|
3958
|
+
// never break user code
|
|
3959
|
+
}
|
|
3960
|
+
};
|
|
3787
3961
|
try {
|
|
3788
3962
|
if (__TRACER__?.tracer?.on) {
|
|
3789
3963
|
const getTid = __TRACER__?.getCurrentTraceId;
|
|
@@ -3876,6 +4050,7 @@ function reproMiddleware(cfg) {
|
|
|
3876
4050
|
: Buffer.from(chunks.map(String).join(''));
|
|
3877
4051
|
capturedBody = coerceBodyToStorable(buf, res.getHeader?.('content-type'));
|
|
3878
4052
|
}
|
|
4053
|
+
void emitRequestCaptureAsync();
|
|
3879
4054
|
if (!flushPayload) {
|
|
3880
4055
|
flushPayload = async () => {
|
|
3881
4056
|
try {
|
|
@@ -3890,155 +4065,7 @@ function reproMiddleware(cfg) {
|
|
|
3890
4065
|
const orderedEvents = TRACE_ORDER_MODE === 'tree'
|
|
3891
4066
|
? reorderTraceEvents(baseEvents)
|
|
3892
4067
|
: sortTraceEventsChronologically(baseEvents);
|
|
3893
|
-
const summary = summarizeEndpointFromEvents(orderedEvents);
|
|
3894
|
-
const chosenEndpoint = summary.endpointTrace
|
|
3895
|
-
?? summary.preferredAppTrace
|
|
3896
|
-
?? summary.firstAppTrace
|
|
3897
|
-
?? endpointTrace
|
|
3898
|
-
?? preferredAppTrace
|
|
3899
|
-
?? firstAppTrace
|
|
3900
|
-
?? { fn: null, file: null, line: null, functionType: null };
|
|
3901
4068
|
const traceBatches = chunkArray(orderedEvents, TRACE_BATCH_SIZE);
|
|
3902
|
-
const endpointTraceCtx = (() => {
|
|
3903
|
-
if (!chosenEndpoint?.fn && !chosenEndpoint?.file)
|
|
3904
|
-
return null;
|
|
3905
|
-
return {
|
|
3906
|
-
type: 'enter',
|
|
3907
|
-
eventType: 'enter',
|
|
3908
|
-
fn: chosenEndpoint.fn ?? undefined,
|
|
3909
|
-
wrapperClass: inferWrapperClassFromFn(chosenEndpoint.fn),
|
|
3910
|
-
file: chosenEndpoint.file ?? null,
|
|
3911
|
-
line: chosenEndpoint.line ?? null,
|
|
3912
|
-
functionType: chosenEndpoint.functionType ?? null,
|
|
3913
|
-
library: inferLibraryNameFromFile(chosenEndpoint.file),
|
|
3914
|
-
};
|
|
3915
|
-
})();
|
|
3916
|
-
const activePrivacy = resolvePrivacy();
|
|
3917
|
-
const requestBodyRaw = req.body;
|
|
3918
|
-
const requestBodyMaterialization = limitRawInlinePrivacyValue('request.body', requestBodyRaw)
|
|
3919
|
-
?? await materializeInlinePrivacyValueAsync('request.body', sanitizeRequestSnapshot(requestBodyRaw), cfg, maskReq, endpointTraceCtx, masking, activePrivacy);
|
|
3920
|
-
const requestBody = requestBodyMaterialization.value;
|
|
3921
|
-
const requestParams = await applyPrivacyThenMaskAsync('request.params', sanitizeRequestSnapshot(req.params), cfg, maskReq, endpointTraceCtx, masking, activePrivacy);
|
|
3922
|
-
const requestQuery = await applyPrivacyThenMaskAsync('request.query', sanitizeRequestSnapshot(req.query), cfg, maskReq, endpointTraceCtx, masking, activePrivacy);
|
|
3923
|
-
const maskedHeaders = await applyPrivacyThenMaskAsync('request.headers', requestHeaders, cfg, maskReq, endpointTraceCtx, masking, activePrivacy);
|
|
3924
|
-
const responseBodyMaterialization = capturedBody === undefined
|
|
3925
|
-
? { value: undefined }
|
|
3926
|
-
: limitRawInlinePrivacyValue('response.body', capturedBody)
|
|
3927
|
-
?? await materializeInlinePrivacyValueAsync('response.body', sanitizeRequestSnapshot(capturedBody), cfg, maskReq, endpointTraceCtx, masking, activePrivacy);
|
|
3928
|
-
const responseBody = responseBodyMaterialization.value;
|
|
3929
|
-
const requestValueEntries = [];
|
|
3930
|
-
const bodyValueCapture = requestBodyMaterialization.skipped
|
|
3931
|
-
? undefined
|
|
3932
|
-
: await maybeCaptureRequestValueAsync({
|
|
3933
|
-
target: 'request.body',
|
|
3934
|
-
rawValue: req.body,
|
|
3935
|
-
previewValue: requestBody,
|
|
3936
|
-
capture: {
|
|
3937
|
-
runtimeConfig: cfg,
|
|
3938
|
-
captureHeaders: cfg.captureHeaders,
|
|
3939
|
-
maskReq,
|
|
3940
|
-
trace: endpointTraceCtx,
|
|
3941
|
-
masking,
|
|
3942
|
-
privacy: activePrivacy,
|
|
3943
|
-
},
|
|
3944
|
-
}, requestValueEntries);
|
|
3945
|
-
const paramsValueCapture = await maybeCaptureRequestValueAsync({
|
|
3946
|
-
target: 'request.params',
|
|
3947
|
-
rawValue: req.params,
|
|
3948
|
-
previewValue: requestParams,
|
|
3949
|
-
capture: {
|
|
3950
|
-
runtimeConfig: cfg,
|
|
3951
|
-
captureHeaders: cfg.captureHeaders,
|
|
3952
|
-
maskReq,
|
|
3953
|
-
trace: endpointTraceCtx,
|
|
3954
|
-
masking,
|
|
3955
|
-
privacy: activePrivacy,
|
|
3956
|
-
},
|
|
3957
|
-
}, requestValueEntries);
|
|
3958
|
-
const queryValueCapture = await maybeCaptureRequestValueAsync({
|
|
3959
|
-
target: 'request.query',
|
|
3960
|
-
rawValue: req.query,
|
|
3961
|
-
previewValue: requestQuery,
|
|
3962
|
-
capture: {
|
|
3963
|
-
runtimeConfig: cfg,
|
|
3964
|
-
captureHeaders: cfg.captureHeaders,
|
|
3965
|
-
maskReq,
|
|
3966
|
-
trace: endpointTraceCtx,
|
|
3967
|
-
masking,
|
|
3968
|
-
privacy: activePrivacy,
|
|
3969
|
-
},
|
|
3970
|
-
}, requestValueEntries);
|
|
3971
|
-
const headersValueCapture = await maybeCaptureRequestValueAsync({
|
|
3972
|
-
target: 'request.headers',
|
|
3973
|
-
rawValue: req.headers,
|
|
3974
|
-
previewValue: maskedHeaders,
|
|
3975
|
-
capture: {
|
|
3976
|
-
runtimeConfig: cfg,
|
|
3977
|
-
captureHeaders: cfg.captureHeaders,
|
|
3978
|
-
maskReq,
|
|
3979
|
-
trace: endpointTraceCtx,
|
|
3980
|
-
masking,
|
|
3981
|
-
privacy: activePrivacy,
|
|
3982
|
-
},
|
|
3983
|
-
}, requestValueEntries);
|
|
3984
|
-
const respBodyValueCapture = responseBodyMaterialization.skipped
|
|
3985
|
-
? undefined
|
|
3986
|
-
: await maybeCaptureRequestValueAsync({
|
|
3987
|
-
target: 'response.body',
|
|
3988
|
-
rawValue: capturedBody,
|
|
3989
|
-
previewValue: responseBody,
|
|
3990
|
-
capture: {
|
|
3991
|
-
runtimeConfig: cfg,
|
|
3992
|
-
captureHeaders: cfg.captureHeaders,
|
|
3993
|
-
maskReq,
|
|
3994
|
-
trace: endpointTraceCtx,
|
|
3995
|
-
masking,
|
|
3996
|
-
privacy: activePrivacy,
|
|
3997
|
-
},
|
|
3998
|
-
}, requestValueEntries);
|
|
3999
|
-
const requestPayload = {
|
|
4000
|
-
rid,
|
|
4001
|
-
method: req.method,
|
|
4002
|
-
url,
|
|
4003
|
-
path,
|
|
4004
|
-
status: res.statusCode,
|
|
4005
|
-
durMs: Date.now() - t0,
|
|
4006
|
-
headers: maskedHeaders,
|
|
4007
|
-
key,
|
|
4008
|
-
respBody: responseBody,
|
|
4009
|
-
trace: traceBatches.length ? undefined : [],
|
|
4010
|
-
};
|
|
4011
|
-
if (requestBody !== undefined)
|
|
4012
|
-
requestPayload.body = requestBody;
|
|
4013
|
-
if (bodyValueCapture)
|
|
4014
|
-
requestPayload.bodyValueCapture = bodyValueCapture;
|
|
4015
|
-
if (requestParams !== undefined)
|
|
4016
|
-
requestPayload.params = requestParams;
|
|
4017
|
-
if (paramsValueCapture)
|
|
4018
|
-
requestPayload.paramsValueCapture = paramsValueCapture;
|
|
4019
|
-
if (requestQuery !== undefined)
|
|
4020
|
-
requestPayload.query = requestQuery;
|
|
4021
|
-
if (queryValueCapture)
|
|
4022
|
-
requestPayload.queryValueCapture = queryValueCapture;
|
|
4023
|
-
if (headersValueCapture)
|
|
4024
|
-
requestPayload.headersValueCapture = headersValueCapture;
|
|
4025
|
-
if (respBodyValueCapture)
|
|
4026
|
-
requestPayload.respBodyValueCapture = respBodyValueCapture;
|
|
4027
|
-
if (requestBodyMaterialization.skipped) {
|
|
4028
|
-
requestPayload.bodyMaterialization = requestBodyMaterialization.skipped;
|
|
4029
|
-
}
|
|
4030
|
-
if (responseBodyMaterialization.skipped) {
|
|
4031
|
-
requestPayload.respBodyMaterialization = responseBodyMaterialization.skipped;
|
|
4032
|
-
}
|
|
4033
|
-
requestPayload.entryPoint = chosenEndpoint;
|
|
4034
|
-
post(cfg, sid, {
|
|
4035
|
-
entries: [{
|
|
4036
|
-
actionId: aid,
|
|
4037
|
-
request: requestPayload,
|
|
4038
|
-
requestValues: requestValueEntries.length ? requestValueEntries : undefined,
|
|
4039
|
-
t: requestEpochMs,
|
|
4040
|
-
}]
|
|
4041
|
-
});
|
|
4042
4069
|
if (traceBatches.length) {
|
|
4043
4070
|
for (let i = 0; i < traceBatches.length; i++) {
|
|
4044
4071
|
const batch = traceBatches[i];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reproapp/node-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
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 -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 -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
|
@@ -4499,6 +4499,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4499
4499
|
let idleTimer: NodeJS.Timeout | null = null;
|
|
4500
4500
|
let hardStopTimer: NodeJS.Timeout | null = null;
|
|
4501
4501
|
let flushPayload: null | (() => Promise<void>) = null;
|
|
4502
|
+
let requestCaptureScheduled = false;
|
|
4502
4503
|
let sessionDrainWait: Promise<void> | null = null;
|
|
4503
4504
|
const activeSpans = new Set<string>();
|
|
4504
4505
|
let anonymousSpanDepth = 0;
|
|
@@ -4572,6 +4573,226 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4572
4573
|
scheduleIdleFlush();
|
|
4573
4574
|
};
|
|
4574
4575
|
|
|
4576
|
+
const chooseRequestEndpoint = (): {
|
|
4577
|
+
chosenEndpoint: EndpointTraceInfo;
|
|
4578
|
+
hasTraceEvents: boolean;
|
|
4579
|
+
} => {
|
|
4580
|
+
const pendingEvents = preparePendingTraceEventsForFlush(events.slice());
|
|
4581
|
+
const baseEvents = balanceTraceEvents(pendingEvents.slice() as TraceEventRecord[]);
|
|
4582
|
+
const orderedEvents = TRACE_ORDER_MODE === 'tree'
|
|
4583
|
+
? reorderTraceEvents(baseEvents)
|
|
4584
|
+
: sortTraceEventsChronologically(baseEvents);
|
|
4585
|
+
const summary = summarizeEndpointFromEvents(orderedEvents);
|
|
4586
|
+
return {
|
|
4587
|
+
chosenEndpoint: summary.endpointTrace
|
|
4588
|
+
?? summary.preferredAppTrace
|
|
4589
|
+
?? summary.firstAppTrace
|
|
4590
|
+
?? endpointTrace
|
|
4591
|
+
?? preferredAppTrace
|
|
4592
|
+
?? firstAppTrace
|
|
4593
|
+
?? { fn: null, file: null, line: null, functionType: null },
|
|
4594
|
+
hasTraceEvents: orderedEvents.length > 0,
|
|
4595
|
+
};
|
|
4596
|
+
};
|
|
4597
|
+
|
|
4598
|
+
const buildRequestCapturePayloadAsync = async (
|
|
4599
|
+
chosenEndpoint: EndpointTraceInfo,
|
|
4600
|
+
hasTraceEvents: boolean,
|
|
4601
|
+
): Promise<{
|
|
4602
|
+
requestPayload: Record<string, any>;
|
|
4603
|
+
requestValueEntries: TraceValueBatchEntry[];
|
|
4604
|
+
}> => {
|
|
4605
|
+
const endpointTraceCtx: TraceEventForFilter | null = (() => {
|
|
4606
|
+
if (!chosenEndpoint?.fn && !chosenEndpoint?.file) return null;
|
|
4607
|
+
return {
|
|
4608
|
+
type: 'enter',
|
|
4609
|
+
eventType: 'enter',
|
|
4610
|
+
fn: chosenEndpoint.fn ?? undefined,
|
|
4611
|
+
wrapperClass: inferWrapperClassFromFn(chosenEndpoint.fn),
|
|
4612
|
+
file: chosenEndpoint.file ?? null,
|
|
4613
|
+
line: chosenEndpoint.line ?? null,
|
|
4614
|
+
functionType: chosenEndpoint.functionType ?? null,
|
|
4615
|
+
library: inferLibraryNameFromFile(chosenEndpoint.file),
|
|
4616
|
+
};
|
|
4617
|
+
})();
|
|
4618
|
+
const activePrivacy = resolvePrivacy();
|
|
4619
|
+
|
|
4620
|
+
const requestBodyRaw = (req as any).body;
|
|
4621
|
+
const requestBodyMaterialization = limitRawInlinePrivacyValue('request.body', requestBodyRaw)
|
|
4622
|
+
?? await materializeInlinePrivacyValueAsync(
|
|
4623
|
+
'request.body',
|
|
4624
|
+
sanitizeRequestSnapshot(requestBodyRaw),
|
|
4625
|
+
cfg,
|
|
4626
|
+
maskReq,
|
|
4627
|
+
endpointTraceCtx,
|
|
4628
|
+
masking,
|
|
4629
|
+
activePrivacy,
|
|
4630
|
+
);
|
|
4631
|
+
const requestBody = requestBodyMaterialization.value;
|
|
4632
|
+
const requestParams = await applyPrivacyThenMaskAsync(
|
|
4633
|
+
'request.params',
|
|
4634
|
+
sanitizeRequestSnapshot((req as any).params),
|
|
4635
|
+
cfg,
|
|
4636
|
+
maskReq,
|
|
4637
|
+
endpointTraceCtx,
|
|
4638
|
+
masking,
|
|
4639
|
+
activePrivacy,
|
|
4640
|
+
);
|
|
4641
|
+
const requestQuery = await applyPrivacyThenMaskAsync(
|
|
4642
|
+
'request.query',
|
|
4643
|
+
sanitizeRequestSnapshot((req as any).query),
|
|
4644
|
+
cfg,
|
|
4645
|
+
maskReq,
|
|
4646
|
+
endpointTraceCtx,
|
|
4647
|
+
masking,
|
|
4648
|
+
activePrivacy,
|
|
4649
|
+
);
|
|
4650
|
+
const maskedHeaders = await applyPrivacyThenMaskAsync(
|
|
4651
|
+
'request.headers',
|
|
4652
|
+
requestHeaders,
|
|
4653
|
+
cfg,
|
|
4654
|
+
maskReq,
|
|
4655
|
+
endpointTraceCtx,
|
|
4656
|
+
masking,
|
|
4657
|
+
activePrivacy,
|
|
4658
|
+
);
|
|
4659
|
+
const responseBodyMaterialization = capturedBody === undefined
|
|
4660
|
+
? { value: undefined }
|
|
4661
|
+
: limitRawInlinePrivacyValue('response.body', capturedBody)
|
|
4662
|
+
?? await materializeInlinePrivacyValueAsync(
|
|
4663
|
+
'response.body',
|
|
4664
|
+
sanitizeRequestSnapshot(capturedBody),
|
|
4665
|
+
cfg,
|
|
4666
|
+
maskReq,
|
|
4667
|
+
endpointTraceCtx,
|
|
4668
|
+
masking,
|
|
4669
|
+
activePrivacy,
|
|
4670
|
+
);
|
|
4671
|
+
const responseBody = responseBodyMaterialization.value;
|
|
4672
|
+
const requestValueEntries: TraceValueBatchEntry[] = [];
|
|
4673
|
+
const bodyValueCapture = requestBodyMaterialization.skipped
|
|
4674
|
+
? undefined
|
|
4675
|
+
: await maybeCaptureRequestValueAsync({
|
|
4676
|
+
target: 'request.body',
|
|
4677
|
+
rawValue: (req as any).body,
|
|
4678
|
+
previewValue: requestBody,
|
|
4679
|
+
capture: {
|
|
4680
|
+
runtimeConfig: cfg,
|
|
4681
|
+
captureHeaders: cfg.captureHeaders,
|
|
4682
|
+
maskReq,
|
|
4683
|
+
trace: endpointTraceCtx,
|
|
4684
|
+
masking,
|
|
4685
|
+
privacy: activePrivacy,
|
|
4686
|
+
},
|
|
4687
|
+
}, requestValueEntries);
|
|
4688
|
+
const paramsValueCapture = await maybeCaptureRequestValueAsync({
|
|
4689
|
+
target: 'request.params',
|
|
4690
|
+
rawValue: (req as any).params,
|
|
4691
|
+
previewValue: requestParams,
|
|
4692
|
+
capture: {
|
|
4693
|
+
runtimeConfig: cfg,
|
|
4694
|
+
captureHeaders: cfg.captureHeaders,
|
|
4695
|
+
maskReq,
|
|
4696
|
+
trace: endpointTraceCtx,
|
|
4697
|
+
masking,
|
|
4698
|
+
privacy: activePrivacy,
|
|
4699
|
+
},
|
|
4700
|
+
}, requestValueEntries);
|
|
4701
|
+
const queryValueCapture = await maybeCaptureRequestValueAsync({
|
|
4702
|
+
target: 'request.query',
|
|
4703
|
+
rawValue: (req as any).query,
|
|
4704
|
+
previewValue: requestQuery,
|
|
4705
|
+
capture: {
|
|
4706
|
+
runtimeConfig: cfg,
|
|
4707
|
+
captureHeaders: cfg.captureHeaders,
|
|
4708
|
+
maskReq,
|
|
4709
|
+
trace: endpointTraceCtx,
|
|
4710
|
+
masking,
|
|
4711
|
+
privacy: activePrivacy,
|
|
4712
|
+
},
|
|
4713
|
+
}, requestValueEntries);
|
|
4714
|
+
const headersValueCapture = await maybeCaptureRequestValueAsync({
|
|
4715
|
+
target: 'request.headers',
|
|
4716
|
+
rawValue: req.headers,
|
|
4717
|
+
previewValue: maskedHeaders,
|
|
4718
|
+
capture: {
|
|
4719
|
+
runtimeConfig: cfg,
|
|
4720
|
+
captureHeaders: cfg.captureHeaders,
|
|
4721
|
+
maskReq,
|
|
4722
|
+
trace: endpointTraceCtx,
|
|
4723
|
+
masking,
|
|
4724
|
+
privacy: activePrivacy,
|
|
4725
|
+
},
|
|
4726
|
+
}, requestValueEntries);
|
|
4727
|
+
const respBodyValueCapture = responseBodyMaterialization.skipped
|
|
4728
|
+
? undefined
|
|
4729
|
+
: await maybeCaptureRequestValueAsync({
|
|
4730
|
+
target: 'response.body',
|
|
4731
|
+
rawValue: capturedBody,
|
|
4732
|
+
previewValue: responseBody,
|
|
4733
|
+
capture: {
|
|
4734
|
+
runtimeConfig: cfg,
|
|
4735
|
+
captureHeaders: cfg.captureHeaders,
|
|
4736
|
+
maskReq,
|
|
4737
|
+
trace: endpointTraceCtx,
|
|
4738
|
+
masking,
|
|
4739
|
+
privacy: activePrivacy,
|
|
4740
|
+
},
|
|
4741
|
+
}, requestValueEntries);
|
|
4742
|
+
|
|
4743
|
+
const requestPayload: Record<string, any> = {
|
|
4744
|
+
rid,
|
|
4745
|
+
method: req.method,
|
|
4746
|
+
url,
|
|
4747
|
+
path,
|
|
4748
|
+
status: res.statusCode,
|
|
4749
|
+
durMs: Date.now() - t0,
|
|
4750
|
+
headers: maskedHeaders,
|
|
4751
|
+
key,
|
|
4752
|
+
respBody: responseBody,
|
|
4753
|
+
trace: hasTraceEvents ? undefined : [],
|
|
4754
|
+
};
|
|
4755
|
+
if (requestBody !== undefined) requestPayload.body = requestBody;
|
|
4756
|
+
if (bodyValueCapture) requestPayload.bodyValueCapture = bodyValueCapture;
|
|
4757
|
+
if (requestParams !== undefined) requestPayload.params = requestParams;
|
|
4758
|
+
if (paramsValueCapture) requestPayload.paramsValueCapture = paramsValueCapture;
|
|
4759
|
+
if (requestQuery !== undefined) requestPayload.query = requestQuery;
|
|
4760
|
+
if (queryValueCapture) requestPayload.queryValueCapture = queryValueCapture;
|
|
4761
|
+
if (headersValueCapture) requestPayload.headersValueCapture = headersValueCapture;
|
|
4762
|
+
if (respBodyValueCapture) requestPayload.respBodyValueCapture = respBodyValueCapture;
|
|
4763
|
+
if (requestBodyMaterialization.skipped) {
|
|
4764
|
+
requestPayload.bodyMaterialization = requestBodyMaterialization.skipped;
|
|
4765
|
+
}
|
|
4766
|
+
if (responseBodyMaterialization.skipped) {
|
|
4767
|
+
requestPayload.respBodyMaterialization = responseBodyMaterialization.skipped;
|
|
4768
|
+
}
|
|
4769
|
+
requestPayload.entryPoint = chosenEndpoint;
|
|
4770
|
+
|
|
4771
|
+
return { requestPayload, requestValueEntries };
|
|
4772
|
+
};
|
|
4773
|
+
|
|
4774
|
+
const emitRequestCaptureAsync = async (): Promise<void> => {
|
|
4775
|
+
if (requestCaptureScheduled) return;
|
|
4776
|
+
requestCaptureScheduled = true;
|
|
4777
|
+
try {
|
|
4778
|
+
const { chosenEndpoint, hasTraceEvents } = chooseRequestEndpoint();
|
|
4779
|
+
const { requestPayload, requestValueEntries } = await buildRequestCapturePayloadAsync(
|
|
4780
|
+
chosenEndpoint,
|
|
4781
|
+
hasTraceEvents,
|
|
4782
|
+
);
|
|
4783
|
+
post(cfg, sid, {
|
|
4784
|
+
entries: [{
|
|
4785
|
+
actionId: aid,
|
|
4786
|
+
request: requestPayload,
|
|
4787
|
+
requestValues: requestValueEntries.length ? requestValueEntries : undefined,
|
|
4788
|
+
t: requestEpochMs,
|
|
4789
|
+
}]
|
|
4790
|
+
});
|
|
4791
|
+
} catch {
|
|
4792
|
+
// never break user code
|
|
4793
|
+
}
|
|
4794
|
+
};
|
|
4795
|
+
|
|
4575
4796
|
try {
|
|
4576
4797
|
if (__TRACER__?.tracer?.on) {
|
|
4577
4798
|
const getTid = __TRACER__?.getCurrentTraceId;
|
|
@@ -4667,6 +4888,8 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4667
4888
|
capturedBody = coerceBodyToStorable(buf, res.getHeader?.('content-type'));
|
|
4668
4889
|
}
|
|
4669
4890
|
|
|
4891
|
+
void emitRequestCaptureAsync();
|
|
4892
|
+
|
|
4670
4893
|
if (!flushPayload) {
|
|
4671
4894
|
flushPayload = async () => {
|
|
4672
4895
|
try {
|
|
@@ -4681,189 +4904,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4681
4904
|
const orderedEvents = TRACE_ORDER_MODE === 'tree'
|
|
4682
4905
|
? reorderTraceEvents(baseEvents)
|
|
4683
4906
|
: sortTraceEventsChronologically(baseEvents);
|
|
4684
|
-
const summary = summarizeEndpointFromEvents(orderedEvents);
|
|
4685
|
-
const chosenEndpoint = summary.endpointTrace
|
|
4686
|
-
?? summary.preferredAppTrace
|
|
4687
|
-
?? summary.firstAppTrace
|
|
4688
|
-
?? endpointTrace
|
|
4689
|
-
?? preferredAppTrace
|
|
4690
|
-
?? firstAppTrace
|
|
4691
|
-
?? { fn: null, file: null, line: null, functionType: null };
|
|
4692
4907
|
const traceBatches = chunkArray(orderedEvents, TRACE_BATCH_SIZE);
|
|
4693
|
-
const endpointTraceCtx: TraceEventForFilter | null = (() => {
|
|
4694
|
-
if (!chosenEndpoint?.fn && !chosenEndpoint?.file) return null;
|
|
4695
|
-
return {
|
|
4696
|
-
type: 'enter',
|
|
4697
|
-
eventType: 'enter',
|
|
4698
|
-
fn: chosenEndpoint.fn ?? undefined,
|
|
4699
|
-
wrapperClass: inferWrapperClassFromFn(chosenEndpoint.fn),
|
|
4700
|
-
file: chosenEndpoint.file ?? null,
|
|
4701
|
-
line: chosenEndpoint.line ?? null,
|
|
4702
|
-
functionType: chosenEndpoint.functionType ?? null,
|
|
4703
|
-
library: inferLibraryNameFromFile(chosenEndpoint.file),
|
|
4704
|
-
};
|
|
4705
|
-
})();
|
|
4706
|
-
const activePrivacy = resolvePrivacy();
|
|
4707
|
-
|
|
4708
|
-
const requestBodyRaw = (req as any).body;
|
|
4709
|
-
const requestBodyMaterialization = limitRawInlinePrivacyValue('request.body', requestBodyRaw)
|
|
4710
|
-
?? await materializeInlinePrivacyValueAsync(
|
|
4711
|
-
'request.body',
|
|
4712
|
-
sanitizeRequestSnapshot(requestBodyRaw),
|
|
4713
|
-
cfg,
|
|
4714
|
-
maskReq,
|
|
4715
|
-
endpointTraceCtx,
|
|
4716
|
-
masking,
|
|
4717
|
-
activePrivacy,
|
|
4718
|
-
);
|
|
4719
|
-
const requestBody = requestBodyMaterialization.value;
|
|
4720
|
-
const requestParams = await applyPrivacyThenMaskAsync(
|
|
4721
|
-
'request.params',
|
|
4722
|
-
sanitizeRequestSnapshot((req as any).params),
|
|
4723
|
-
cfg,
|
|
4724
|
-
maskReq,
|
|
4725
|
-
endpointTraceCtx,
|
|
4726
|
-
masking,
|
|
4727
|
-
activePrivacy,
|
|
4728
|
-
);
|
|
4729
|
-
const requestQuery = await applyPrivacyThenMaskAsync(
|
|
4730
|
-
'request.query',
|
|
4731
|
-
sanitizeRequestSnapshot((req as any).query),
|
|
4732
|
-
cfg,
|
|
4733
|
-
maskReq,
|
|
4734
|
-
endpointTraceCtx,
|
|
4735
|
-
masking,
|
|
4736
|
-
activePrivacy,
|
|
4737
|
-
);
|
|
4738
|
-
const maskedHeaders = await applyPrivacyThenMaskAsync(
|
|
4739
|
-
'request.headers',
|
|
4740
|
-
requestHeaders,
|
|
4741
|
-
cfg,
|
|
4742
|
-
maskReq,
|
|
4743
|
-
endpointTraceCtx,
|
|
4744
|
-
masking,
|
|
4745
|
-
activePrivacy,
|
|
4746
|
-
);
|
|
4747
|
-
const responseBodyMaterialization = capturedBody === undefined
|
|
4748
|
-
? { value: undefined }
|
|
4749
|
-
: limitRawInlinePrivacyValue('response.body', capturedBody)
|
|
4750
|
-
?? await materializeInlinePrivacyValueAsync(
|
|
4751
|
-
'response.body',
|
|
4752
|
-
sanitizeRequestSnapshot(capturedBody),
|
|
4753
|
-
cfg,
|
|
4754
|
-
maskReq,
|
|
4755
|
-
endpointTraceCtx,
|
|
4756
|
-
masking,
|
|
4757
|
-
activePrivacy,
|
|
4758
|
-
);
|
|
4759
|
-
const responseBody = responseBodyMaterialization.value;
|
|
4760
|
-
const requestValueEntries: TraceValueBatchEntry[] = [];
|
|
4761
|
-
const bodyValueCapture = requestBodyMaterialization.skipped
|
|
4762
|
-
? undefined
|
|
4763
|
-
: await maybeCaptureRequestValueAsync({
|
|
4764
|
-
target: 'request.body',
|
|
4765
|
-
rawValue: (req as any).body,
|
|
4766
|
-
previewValue: requestBody,
|
|
4767
|
-
capture: {
|
|
4768
|
-
runtimeConfig: cfg,
|
|
4769
|
-
captureHeaders: cfg.captureHeaders,
|
|
4770
|
-
maskReq,
|
|
4771
|
-
trace: endpointTraceCtx,
|
|
4772
|
-
masking,
|
|
4773
|
-
privacy: activePrivacy,
|
|
4774
|
-
},
|
|
4775
|
-
}, requestValueEntries);
|
|
4776
|
-
const paramsValueCapture = await maybeCaptureRequestValueAsync({
|
|
4777
|
-
target: 'request.params',
|
|
4778
|
-
rawValue: (req as any).params,
|
|
4779
|
-
previewValue: requestParams,
|
|
4780
|
-
capture: {
|
|
4781
|
-
runtimeConfig: cfg,
|
|
4782
|
-
captureHeaders: cfg.captureHeaders,
|
|
4783
|
-
maskReq,
|
|
4784
|
-
trace: endpointTraceCtx,
|
|
4785
|
-
masking,
|
|
4786
|
-
privacy: activePrivacy,
|
|
4787
|
-
},
|
|
4788
|
-
}, requestValueEntries);
|
|
4789
|
-
const queryValueCapture = await maybeCaptureRequestValueAsync({
|
|
4790
|
-
target: 'request.query',
|
|
4791
|
-
rawValue: (req as any).query,
|
|
4792
|
-
previewValue: requestQuery,
|
|
4793
|
-
capture: {
|
|
4794
|
-
runtimeConfig: cfg,
|
|
4795
|
-
captureHeaders: cfg.captureHeaders,
|
|
4796
|
-
maskReq,
|
|
4797
|
-
trace: endpointTraceCtx,
|
|
4798
|
-
masking,
|
|
4799
|
-
privacy: activePrivacy,
|
|
4800
|
-
},
|
|
4801
|
-
}, requestValueEntries);
|
|
4802
|
-
const headersValueCapture = await maybeCaptureRequestValueAsync({
|
|
4803
|
-
target: 'request.headers',
|
|
4804
|
-
rawValue: req.headers,
|
|
4805
|
-
previewValue: maskedHeaders,
|
|
4806
|
-
capture: {
|
|
4807
|
-
runtimeConfig: cfg,
|
|
4808
|
-
captureHeaders: cfg.captureHeaders,
|
|
4809
|
-
maskReq,
|
|
4810
|
-
trace: endpointTraceCtx,
|
|
4811
|
-
masking,
|
|
4812
|
-
privacy: activePrivacy,
|
|
4813
|
-
},
|
|
4814
|
-
}, requestValueEntries);
|
|
4815
|
-
const respBodyValueCapture = responseBodyMaterialization.skipped
|
|
4816
|
-
? undefined
|
|
4817
|
-
: await maybeCaptureRequestValueAsync({
|
|
4818
|
-
target: 'response.body',
|
|
4819
|
-
rawValue: capturedBody,
|
|
4820
|
-
previewValue: responseBody,
|
|
4821
|
-
capture: {
|
|
4822
|
-
runtimeConfig: cfg,
|
|
4823
|
-
captureHeaders: cfg.captureHeaders,
|
|
4824
|
-
maskReq,
|
|
4825
|
-
trace: endpointTraceCtx,
|
|
4826
|
-
masking,
|
|
4827
|
-
privacy: activePrivacy,
|
|
4828
|
-
},
|
|
4829
|
-
}, requestValueEntries);
|
|
4830
|
-
|
|
4831
|
-
const requestPayload: Record<string, any> = {
|
|
4832
|
-
rid,
|
|
4833
|
-
method: req.method,
|
|
4834
|
-
url,
|
|
4835
|
-
path,
|
|
4836
|
-
status: res.statusCode,
|
|
4837
|
-
durMs: Date.now() - t0,
|
|
4838
|
-
headers: maskedHeaders,
|
|
4839
|
-
key,
|
|
4840
|
-
respBody: responseBody,
|
|
4841
|
-
trace: traceBatches.length ? undefined : [],
|
|
4842
|
-
};
|
|
4843
|
-
if (requestBody !== undefined) requestPayload.body = requestBody;
|
|
4844
|
-
if (bodyValueCapture) requestPayload.bodyValueCapture = bodyValueCapture;
|
|
4845
|
-
if (requestParams !== undefined) requestPayload.params = requestParams;
|
|
4846
|
-
if (paramsValueCapture) requestPayload.paramsValueCapture = paramsValueCapture;
|
|
4847
|
-
if (requestQuery !== undefined) requestPayload.query = requestQuery;
|
|
4848
|
-
if (queryValueCapture) requestPayload.queryValueCapture = queryValueCapture;
|
|
4849
|
-
if (headersValueCapture) requestPayload.headersValueCapture = headersValueCapture;
|
|
4850
|
-
if (respBodyValueCapture) requestPayload.respBodyValueCapture = respBodyValueCapture;
|
|
4851
|
-
if (requestBodyMaterialization.skipped) {
|
|
4852
|
-
requestPayload.bodyMaterialization = requestBodyMaterialization.skipped;
|
|
4853
|
-
}
|
|
4854
|
-
if (responseBodyMaterialization.skipped) {
|
|
4855
|
-
requestPayload.respBodyMaterialization = responseBodyMaterialization.skipped;
|
|
4856
|
-
}
|
|
4857
|
-
requestPayload.entryPoint = chosenEndpoint;
|
|
4858
|
-
|
|
4859
|
-
post(cfg, sid, {
|
|
4860
|
-
entries: [{
|
|
4861
|
-
actionId: aid,
|
|
4862
|
-
request: requestPayload,
|
|
4863
|
-
requestValues: requestValueEntries.length ? requestValueEntries : undefined,
|
|
4864
|
-
t: requestEpochMs,
|
|
4865
|
-
}]
|
|
4866
|
-
});
|
|
4867
4908
|
|
|
4868
4909
|
if (traceBatches.length) {
|
|
4869
4910
|
for (let i = 0; i < traceBatches.length; i++) {
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS = '50';
|
|
2
|
+
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const { EventEmitter } = require('events');
|
|
5
|
+
const { initReproTracing, reproMiddleware } = require('../dist');
|
|
6
|
+
const { flushIngestQueue } = require('../dist/ingest/client');
|
|
7
|
+
|
|
8
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
|
|
10
|
+
async function waitFor(predicate, timeoutMs = 3000) {
|
|
11
|
+
const deadline = Date.now() + timeoutMs;
|
|
12
|
+
while (Date.now() < deadline) {
|
|
13
|
+
if (predicate()) return;
|
|
14
|
+
await sleep(25);
|
|
15
|
+
}
|
|
16
|
+
assert(predicate(), 'timed out waiting for expected condition');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function makeTaggedReqRes(sessionId, actionId) {
|
|
20
|
+
const req = new EventEmitter();
|
|
21
|
+
req.method = 'POST';
|
|
22
|
+
req.url = '/flush-timing';
|
|
23
|
+
req.headers = {
|
|
24
|
+
'content-type': 'application/json',
|
|
25
|
+
'x-bug-session-id': sessionId,
|
|
26
|
+
'x-bug-action-id': actionId,
|
|
27
|
+
'x-bug-request-start': String(Date.now()),
|
|
28
|
+
};
|
|
29
|
+
req.body = { subject: 'Avery Debugson' };
|
|
30
|
+
req.params = {};
|
|
31
|
+
req.query = {};
|
|
32
|
+
|
|
33
|
+
const res = new EventEmitter();
|
|
34
|
+
res.statusCode = 200;
|
|
35
|
+
res.getHeader = () => undefined;
|
|
36
|
+
res.setHeader = () => {};
|
|
37
|
+
res.json = function (body) { this.body = body; this.emit('finish'); return body; };
|
|
38
|
+
res.send = function (body) { this.body = body; this.emit('finish'); return body; };
|
|
39
|
+
res.write = () => true;
|
|
40
|
+
res.end = () => { res.emit('finish'); return true; };
|
|
41
|
+
|
|
42
|
+
return { req, res };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function flattenEvents(posts) {
|
|
46
|
+
return posts.flatMap((body) => Array.isArray(body?.events) ? body.events : []);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function countEvents(posts, eventType) {
|
|
50
|
+
return flattenEvents(posts).filter((event) => event?.event_type === eventType).length;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function lateAsyncTrace() {
|
|
54
|
+
await sleep(2500);
|
|
55
|
+
return { done: true };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function main() {
|
|
59
|
+
const capturedBodies = [];
|
|
60
|
+
const originalFetch = global.fetch;
|
|
61
|
+
global.fetch = async (_url, init) => {
|
|
62
|
+
capturedBodies.push(JSON.parse(String(init?.body || '{}')));
|
|
63
|
+
return { ok: true, status: 200, json: async () => ({ ok: true }) };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
initReproTracing({ instrument: false, logFunctionCalls: false });
|
|
68
|
+
const cfg = {
|
|
69
|
+
tenantId: 'TENANT_test',
|
|
70
|
+
appId: 'APP_test',
|
|
71
|
+
appSecret: 'secret',
|
|
72
|
+
captureHeaders: false,
|
|
73
|
+
privacy: { environment: 'dev' },
|
|
74
|
+
ingestBase: 'http://127.0.0.1:65535',
|
|
75
|
+
};
|
|
76
|
+
const { req, res } = makeTaggedReqRes('S_request_flush_timing', 'A_request_flush_timing');
|
|
77
|
+
|
|
78
|
+
await new Promise((resolve, reject) => {
|
|
79
|
+
void reproMiddleware(cfg)(req, res, async (err) => {
|
|
80
|
+
if (err) {
|
|
81
|
+
reject(err);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const pending = global.__repro_call(
|
|
87
|
+
lateAsyncTrace,
|
|
88
|
+
null,
|
|
89
|
+
[],
|
|
90
|
+
'app',
|
|
91
|
+
1,
|
|
92
|
+
'lateAsyncTrace',
|
|
93
|
+
true,
|
|
94
|
+
);
|
|
95
|
+
Promise.resolve(pending).catch(() => undefined);
|
|
96
|
+
res.json({ ok: true });
|
|
97
|
+
resolve();
|
|
98
|
+
} catch (error) {
|
|
99
|
+
reject(error);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
await waitFor(() => countEvents(capturedBodies, 'backend_request') === 1, 1500);
|
|
105
|
+
assert.strictEqual(countEvents(capturedBodies, 'backend_request'), 1, JSON.stringify(capturedBodies));
|
|
106
|
+
|
|
107
|
+
await sleep(3500);
|
|
108
|
+
await flushIngestQueue();
|
|
109
|
+
|
|
110
|
+
assert.strictEqual(countEvents(capturedBodies, 'backend_request'), 1, JSON.stringify(capturedBodies));
|
|
111
|
+
assert(countEvents(capturedBodies, 'trace_batch') >= 1, JSON.stringify(capturedBodies));
|
|
112
|
+
|
|
113
|
+
console.log('request flush timing OK');
|
|
114
|
+
} finally {
|
|
115
|
+
await flushIngestQueue();
|
|
116
|
+
global.fetch = originalFetch;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
main().catch((error) => {
|
|
121
|
+
console.error(error);
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
});
|