@reproapp/node-sdk 0.0.7 → 0.0.8
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
CHANGED
|
@@ -1929,6 +1929,50 @@ function sanitizeTraceValue(value, depth = 0, seen = new WeakMap(), options = {}
|
|
|
1929
1929
|
const mongoId = coerceMongoId(value);
|
|
1930
1930
|
if (mongoId !== null)
|
|
1931
1931
|
return mongoId;
|
|
1932
|
+
if (isHttpRequestLike(value)) {
|
|
1933
|
+
const projected = {
|
|
1934
|
+
__kind: 'http-request',
|
|
1935
|
+
};
|
|
1936
|
+
if (typeof value.method === 'string') {
|
|
1937
|
+
projected.method = value.method;
|
|
1938
|
+
}
|
|
1939
|
+
const url = typeof value.originalUrl === 'string'
|
|
1940
|
+
? value.originalUrl
|
|
1941
|
+
: typeof value.url === 'string'
|
|
1942
|
+
? value.url
|
|
1943
|
+
: undefined;
|
|
1944
|
+
if (url) {
|
|
1945
|
+
projected.url = url;
|
|
1946
|
+
}
|
|
1947
|
+
const headers = sanitizeHeaders(value.headers, true);
|
|
1948
|
+
if (headers !== undefined) {
|
|
1949
|
+
projected.headers = sanitizeTraceValue(headers, depth + 1, seen, options, childCapturePath(valuePath, 'headers'));
|
|
1950
|
+
}
|
|
1951
|
+
if (value.params !== undefined) {
|
|
1952
|
+
projected.params = sanitizeTraceValue(value.params, depth + 1, seen, options, childCapturePath(valuePath, 'params'));
|
|
1953
|
+
}
|
|
1954
|
+
if (value.query !== undefined) {
|
|
1955
|
+
projected.query = sanitizeTraceValue(value.query, depth + 1, seen, options, childCapturePath(valuePath, 'query'));
|
|
1956
|
+
}
|
|
1957
|
+
if (value.body !== undefined) {
|
|
1958
|
+
projected.body = sanitizeTraceValue(value.body, depth + 1, seen, options, childCapturePath(valuePath, 'body'));
|
|
1959
|
+
}
|
|
1960
|
+
return projected;
|
|
1961
|
+
}
|
|
1962
|
+
if (isHttpResponseLike(value)) {
|
|
1963
|
+
const projected = {
|
|
1964
|
+
__kind: 'http-response',
|
|
1965
|
+
statusCode: Number(value.statusCode) || 0,
|
|
1966
|
+
};
|
|
1967
|
+
const rawHeaders = typeof value.getHeaders === 'function'
|
|
1968
|
+
? value.getHeaders()
|
|
1969
|
+
: value._headers;
|
|
1970
|
+
const headers = sanitizeHeaders(rawHeaders, true);
|
|
1971
|
+
if (headers !== undefined) {
|
|
1972
|
+
projected.headers = sanitizeTraceValue(headers, depth + 1, seen, options, childCapturePath(valuePath, 'headers'));
|
|
1973
|
+
}
|
|
1974
|
+
return projected;
|
|
1975
|
+
}
|
|
1932
1976
|
if (isMongooseQueryLike(value)) {
|
|
1933
1977
|
const captured = value.__repro_result;
|
|
1934
1978
|
if (captured !== undefined) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reproapp/node-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
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 -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
|
@@ -2318,6 +2318,55 @@ function sanitizeTraceValue(
|
|
|
2318
2318
|
const mongoId = coerceMongoId(value);
|
|
2319
2319
|
if (mongoId !== null) return mongoId;
|
|
2320
2320
|
|
|
2321
|
+
if (isHttpRequestLike(value)) {
|
|
2322
|
+
const projected: Record<string, any> = {
|
|
2323
|
+
__kind: 'http-request',
|
|
2324
|
+
};
|
|
2325
|
+
if (typeof (value as any).method === 'string') {
|
|
2326
|
+
projected.method = (value as any).method;
|
|
2327
|
+
}
|
|
2328
|
+
const url =
|
|
2329
|
+
typeof (value as any).originalUrl === 'string'
|
|
2330
|
+
? (value as any).originalUrl
|
|
2331
|
+
: typeof (value as any).url === 'string'
|
|
2332
|
+
? (value as any).url
|
|
2333
|
+
: undefined;
|
|
2334
|
+
if (url) {
|
|
2335
|
+
projected.url = url;
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
const headers = sanitizeHeaders((value as any).headers, true);
|
|
2339
|
+
if (headers !== undefined) {
|
|
2340
|
+
projected.headers = sanitizeTraceValue(headers, depth + 1, seen, options, childCapturePath(valuePath, 'headers'));
|
|
2341
|
+
}
|
|
2342
|
+
if ((value as any).params !== undefined) {
|
|
2343
|
+
projected.params = sanitizeTraceValue((value as any).params, depth + 1, seen, options, childCapturePath(valuePath, 'params'));
|
|
2344
|
+
}
|
|
2345
|
+
if ((value as any).query !== undefined) {
|
|
2346
|
+
projected.query = sanitizeTraceValue((value as any).query, depth + 1, seen, options, childCapturePath(valuePath, 'query'));
|
|
2347
|
+
}
|
|
2348
|
+
if ((value as any).body !== undefined) {
|
|
2349
|
+
projected.body = sanitizeTraceValue((value as any).body, depth + 1, seen, options, childCapturePath(valuePath, 'body'));
|
|
2350
|
+
}
|
|
2351
|
+
return projected;
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
if (isHttpResponseLike(value)) {
|
|
2355
|
+
const projected: Record<string, any> = {
|
|
2356
|
+
__kind: 'http-response',
|
|
2357
|
+
statusCode: Number((value as any).statusCode) || 0,
|
|
2358
|
+
};
|
|
2359
|
+
const rawHeaders =
|
|
2360
|
+
typeof (value as any).getHeaders === 'function'
|
|
2361
|
+
? (value as any).getHeaders()
|
|
2362
|
+
: (value as any)._headers;
|
|
2363
|
+
const headers = sanitizeHeaders(rawHeaders, true);
|
|
2364
|
+
if (headers !== undefined) {
|
|
2365
|
+
projected.headers = sanitizeTraceValue(headers, depth + 1, seen, options, childCapturePath(valuePath, 'headers'));
|
|
2366
|
+
}
|
|
2367
|
+
return projected;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2321
2370
|
if (isMongooseQueryLike(value)) {
|
|
2322
2371
|
const captured = (value as any).__repro_result;
|
|
2323
2372
|
if (captured !== undefined) {
|
|
@@ -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
|
+
};
|