@reproapp/node-sdk 0.0.3 → 0.0.5
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/README.md +36 -2
- package/dist/index.d.ts +140 -15
- package/dist/index.js +4927 -927
- package/dist/ingest/client.d.ts +10 -0
- package/dist/ingest/client.js +158 -0
- package/dist/ingest/mapper.d.ts +2 -0
- package/dist/ingest/mapper.js +92 -0
- package/dist/ingest/types.d.ts +40 -0
- package/dist/ingest/types.js +2 -0
- package/dist/ingest/worker.js +19 -0
- package/dist/integrations/sendgrid.d.ts +2 -4
- package/dist/integrations/sendgrid.js +4 -14
- package/dist/privacy-fallback.d.ts +1 -0
- package/dist/privacy-fallback.js +27 -0
- package/dist/privacy-redaction.d.ts +3 -0
- package/dist/privacy-redaction.js +38 -0
- package/dist/privacy.d.ts +108 -0
- package/dist/privacy.js +2868 -0
- package/dist/trace-materializer-worker.d.ts +1 -0
- package/dist/trace-materializer-worker.js +33 -0
- package/docs/tracing.md +1 -0
- package/package.json +8 -2
- package/src/index.ts +5583 -954
- package/src/ingest/client.ts +194 -0
- package/src/ingest/mapper.ts +104 -0
- package/src/ingest/types.ts +42 -0
- package/src/integrations/sendgrid.ts +6 -19
- package/src/privacy-fallback.ts +25 -0
- package/src/privacy-redaction.ts +37 -0
- package/src/privacy.ts +3593 -0
- package/src/trace-materializer-worker.ts +39 -0
- package/test/circular-capture.test.js +111 -0
- package/test/disable-subtree.test.js +154 -0
- package/test/integration-unawaited.js +183 -0
- package/test/kafka-runtime-privacy-policy.test.js +285 -0
- package/test/privacy-runtime-policy.test.js +2043 -0
- package/test/promise-map.test.js +72 -0
- package/test/unawaited.test.js +163 -0
- package/test/wrap-plugin-arrow-args.test.js +80 -0
- package/tracer/cjs-hook.js +0 -1
- package/tracer/wrap-plugin.js +96 -10
- package/dist/redaction.d.ts +0 -44
- package/dist/redaction.js +0 -167
- package/dist/server.js +0 -26
- /package/dist/{server.d.ts → ingest/worker.d.ts} +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
process.env.TRACE_RELOAD_CACHE = '0';
|
|
2
|
+
require('../tracer/register');
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const { trace } = require('../tracer/runtime');
|
|
5
|
+
|
|
6
|
+
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
7
|
+
|
|
8
|
+
const handlers = [
|
|
9
|
+
{
|
|
10
|
+
getRecipients: async () => { await wait(10); return ['a']; },
|
|
11
|
+
getTemplate: () => 'tmpl1',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
getRecipients: async () => { await wait(1); return ['b']; },
|
|
15
|
+
getTemplate: () => 'tmpl2',
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
async function handlerWorker(handler) {
|
|
20
|
+
const recipients = await handler.getRecipients();
|
|
21
|
+
const template = await handler.getTemplate();
|
|
22
|
+
return { recipients, template };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function runScenario() {
|
|
26
|
+
const results = handlers.map(handler => handlerWorker(handler));
|
|
27
|
+
return Promise.all(results);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function main() {
|
|
31
|
+
const events = [];
|
|
32
|
+
const off = trace.on(ev => { if (ev && ev.traceId === 'map-loop') events.push(ev); });
|
|
33
|
+
|
|
34
|
+
await trace.withTrace('map-loop', async () => {
|
|
35
|
+
await runScenario();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Give any pending promise callbacks a moment to emit their exits.
|
|
39
|
+
await wait(10);
|
|
40
|
+
off();
|
|
41
|
+
|
|
42
|
+
const enters = (name) => events.filter(ev => ev && ev.type === 'enter' && ev.fn === name);
|
|
43
|
+
|
|
44
|
+
const workerEnters = enters('handlerWorker');
|
|
45
|
+
const recipientsEnters = enters('handler.getRecipients');
|
|
46
|
+
const templateEnters = enters('handler.getTemplate');
|
|
47
|
+
|
|
48
|
+
const uniq = (arr) => Array.from(new Set(arr));
|
|
49
|
+
|
|
50
|
+
const workerSpanIds = uniq(workerEnters.map(ev => ev.spanId).filter(Boolean));
|
|
51
|
+
const recipientSpanIds = new Set(recipientsEnters.map(ev => ev.spanId).filter(Boolean));
|
|
52
|
+
const templateParentIds = uniq(templateEnters.map(ev => ev.parentSpanId).filter(Boolean));
|
|
53
|
+
|
|
54
|
+
assert(workerSpanIds.length >= 2, 'expected at least two handlerWorker spans');
|
|
55
|
+
assert(recipientsEnters.length >= 2, 'expected two getRecipients entries');
|
|
56
|
+
assert(templateEnters.length >= 2, 'expected two getTemplate entries');
|
|
57
|
+
assert.strictEqual(templateParentIds.length, 2, 'expected templates to attach to two distinct handler spans');
|
|
58
|
+
|
|
59
|
+
templateEnters.forEach(ev => {
|
|
60
|
+
assert(workerSpanIds.includes(ev.parentSpanId),
|
|
61
|
+
'getTemplate should be parented to its map callback (handlerWorker)');
|
|
62
|
+
assert(!recipientSpanIds.has(ev.parentSpanId),
|
|
63
|
+
'getTemplate should not be nested under getRecipients span');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
console.log('promise map + Promise.all trace parenting OK');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
main().catch(err => {
|
|
70
|
+
console.error(err);
|
|
71
|
+
process.exitCode = 1;
|
|
72
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Integration-style sanity check for unawaited async call ordering.
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
const { trace, SYM_IS_APP } = require('../tracer/runtime');
|
|
4
|
+
|
|
5
|
+
function wrapFunction(name, fn, opts = {}) {
|
|
6
|
+
const { bodyTraced = false } = opts;
|
|
7
|
+
const wrapped = function wrappedFn(...args) {
|
|
8
|
+
trace.enter(name, { file: 'app', line: 1 });
|
|
9
|
+
let result;
|
|
10
|
+
let err = null;
|
|
11
|
+
let threw = false;
|
|
12
|
+
try {
|
|
13
|
+
result = fn.apply(this, args);
|
|
14
|
+
return result;
|
|
15
|
+
} catch (e) {
|
|
16
|
+
threw = true;
|
|
17
|
+
err = e;
|
|
18
|
+
throw e;
|
|
19
|
+
} finally {
|
|
20
|
+
trace.exit({ fn: name, file: 'app', line: 1 }, { returnValue: result, error: err, threw });
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
try {
|
|
24
|
+
wrapped[SYM_IS_APP] = true;
|
|
25
|
+
wrapped.__repro_instrumented = true;
|
|
26
|
+
if (bodyTraced) wrapped.__repro_body_traced = true;
|
|
27
|
+
} catch {}
|
|
28
|
+
return wrapped;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function testUnawaitedOrdering() {
|
|
32
|
+
const events = [];
|
|
33
|
+
const unsubscribe = trace.on(ev => { if (ev && ev.traceId) events.push(ev); });
|
|
34
|
+
|
|
35
|
+
// Mock inner async functions.
|
|
36
|
+
const findNotificationModule = wrapFunction('findNotificationModule', async () => 'config');
|
|
37
|
+
const loadStudyModuleConfig = wrapFunction('loadStudyModuleConfig', async () => 'studyConfig');
|
|
38
|
+
const loadStudyConfigUserModule = wrapFunction('loadStudyConfigUserModule', async () => 'userConfig');
|
|
39
|
+
|
|
40
|
+
const notifyAboutShipmentDispatch = wrapFunction('notifyAboutShipmentDispatch', async () => {
|
|
41
|
+
await findNotificationModule();
|
|
42
|
+
await loadStudyModuleConfig();
|
|
43
|
+
await loadStudyConfigUserModule();
|
|
44
|
+
return 'done';
|
|
45
|
+
}, { bodyTraced: true });
|
|
46
|
+
|
|
47
|
+
await trace.withTrace('t1', async () => {
|
|
48
|
+
const p = global.__repro_call(
|
|
49
|
+
notifyAboutShipmentDispatch,
|
|
50
|
+
null,
|
|
51
|
+
[],
|
|
52
|
+
'app',
|
|
53
|
+
1,
|
|
54
|
+
'notifyAboutShipmentDispatch',
|
|
55
|
+
true // mark unawaited
|
|
56
|
+
);
|
|
57
|
+
// Simulate unawaited usage, but wait so events flush.
|
|
58
|
+
await p.catch(() => {});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
unsubscribe();
|
|
62
|
+
|
|
63
|
+
const ordered = events.map(ev => `${ev.type}:${ev.fn}`);
|
|
64
|
+
const expected = [
|
|
65
|
+
'enter:notifyAboutShipmentDispatch',
|
|
66
|
+
'enter:findNotificationModule',
|
|
67
|
+
'exit:findNotificationModule',
|
|
68
|
+
'enter:loadStudyModuleConfig',
|
|
69
|
+
'exit:loadStudyModuleConfig',
|
|
70
|
+
'enter:loadStudyConfigUserModule',
|
|
71
|
+
'exit:loadStudyConfigUserModule',
|
|
72
|
+
'exit:notifyAboutShipmentDispatch',
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
assert.deepStrictEqual(ordered, expected, `Trace order mismatch.\nGot: ${ordered.join(' | ')}\nExpected: ${expected.join(' | ')}`);
|
|
76
|
+
console.log('unawaited async trace order OK');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function testUnawaitedSiblingParenting() {
|
|
80
|
+
const events = [];
|
|
81
|
+
const off = trace.on(ev => { if (ev && ev.traceId === 't2') events.push(ev); });
|
|
82
|
+
|
|
83
|
+
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
84
|
+
|
|
85
|
+
const unawaitedChild = wrapFunction('unawaitedChild', async () => {
|
|
86
|
+
await wait(5);
|
|
87
|
+
return 'child';
|
|
88
|
+
});
|
|
89
|
+
const siblingWork = wrapFunction('siblingWork', async () => {
|
|
90
|
+
await wait(1);
|
|
91
|
+
return 'sibling';
|
|
92
|
+
});
|
|
93
|
+
const parent = wrapFunction('parent', async () => {
|
|
94
|
+
const childPromise = global.__repro_call(
|
|
95
|
+
unawaitedChild,
|
|
96
|
+
null,
|
|
97
|
+
[],
|
|
98
|
+
'app',
|
|
99
|
+
21,
|
|
100
|
+
'unawaitedChild',
|
|
101
|
+
true
|
|
102
|
+
);
|
|
103
|
+
await global.__repro_call(
|
|
104
|
+
siblingWork,
|
|
105
|
+
null,
|
|
106
|
+
[],
|
|
107
|
+
'app',
|
|
108
|
+
22,
|
|
109
|
+
'siblingWork',
|
|
110
|
+
false
|
|
111
|
+
);
|
|
112
|
+
return { childPromise };
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const { childPromise } = await trace.withTrace('t2', async () => parent());
|
|
116
|
+
await childPromise.catch(() => {});
|
|
117
|
+
await wait(5);
|
|
118
|
+
off();
|
|
119
|
+
|
|
120
|
+
const enterByFn = {};
|
|
121
|
+
const exitByFn = {};
|
|
122
|
+
events.forEach(ev => {
|
|
123
|
+
if (!ev || !ev.fn) return;
|
|
124
|
+
if (ev.type === 'enter' && !enterByFn[ev.fn]) enterByFn[ev.fn] = ev;
|
|
125
|
+
if (ev.type === 'exit' && !exitByFn[ev.fn]) exitByFn[ev.fn] = ev;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const parentEnter = enterByFn.parent;
|
|
129
|
+
const childEnter = enterByFn.unawaitedChild;
|
|
130
|
+
const siblingEnter = enterByFn.siblingWork;
|
|
131
|
+
assert(parentEnter && childEnter && siblingEnter, 'Missing enter events for parent/child/sibling');
|
|
132
|
+
|
|
133
|
+
assert.strictEqual(
|
|
134
|
+
childEnter.parentSpanId,
|
|
135
|
+
parentEnter.spanId,
|
|
136
|
+
'Unawaited child should be parented to the caller span'
|
|
137
|
+
);
|
|
138
|
+
assert.strictEqual(
|
|
139
|
+
siblingEnter.parentSpanId,
|
|
140
|
+
parentEnter.spanId,
|
|
141
|
+
'Sibling after unawaited call should not inherit unawaited child as parent'
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const parentExit = exitByFn.parent;
|
|
145
|
+
assert(parentExit, 'Missing exit event for parent');
|
|
146
|
+
assert.strictEqual(
|
|
147
|
+
parentExit.spanId,
|
|
148
|
+
parentEnter.spanId,
|
|
149
|
+
'Parent exit should retain its original span id'
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
console.log('unawaited sibling parenting OK');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function main() {
|
|
156
|
+
await testUnawaitedOrdering();
|
|
157
|
+
await testUnawaitedSiblingParenting();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
main().catch(err => {
|
|
161
|
+
console.error(err);
|
|
162
|
+
process.exitCode = 1;
|
|
163
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const vm = require('vm');
|
|
3
|
+
const babel = require('@babel/core');
|
|
4
|
+
const makeWrapPlugin = require('../tracer/wrap-plugin');
|
|
5
|
+
const { trace } = require('../tracer/runtime');
|
|
6
|
+
|
|
7
|
+
function loadInstrumentedModule(source, filename) {
|
|
8
|
+
const transformed = babel.transformSync(source, {
|
|
9
|
+
filename,
|
|
10
|
+
plugins: [makeWrapPlugin(filename, {})],
|
|
11
|
+
parserOpts: { sourceType: 'script', plugins: ['typescript'] },
|
|
12
|
+
generatorOpts: { retainLines: true },
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const module = { exports: {} };
|
|
16
|
+
const context = {
|
|
17
|
+
module,
|
|
18
|
+
exports: module.exports,
|
|
19
|
+
require,
|
|
20
|
+
console,
|
|
21
|
+
__trace: global.__trace,
|
|
22
|
+
__repro_call: global.__repro_call,
|
|
23
|
+
Promise,
|
|
24
|
+
Buffer,
|
|
25
|
+
setTimeout,
|
|
26
|
+
clearTimeout,
|
|
27
|
+
setImmediate,
|
|
28
|
+
clearImmediate,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
vm.runInNewContext(transformed.code, context, { filename });
|
|
32
|
+
return module.exports;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function toPlainJson(value) {
|
|
36
|
+
return JSON.parse(JSON.stringify(value));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function main() {
|
|
40
|
+
const filename = '/tmp/wrap-plugin-arrow-args.fixture.js';
|
|
41
|
+
const mod = loadInstrumentedModule(`
|
|
42
|
+
module.exports = {
|
|
43
|
+
consumerConfig: {
|
|
44
|
+
eachMessage: async ({ message }) => {
|
|
45
|
+
return message.value;
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
`, filename);
|
|
50
|
+
|
|
51
|
+
const events = [];
|
|
52
|
+
const off = trace.on(ev => {
|
|
53
|
+
if (ev && ev.traceId === 'arrow-args' && ev.type === 'enter' && ev.functionType === 'arrow') {
|
|
54
|
+
events.push(ev);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const payload = {
|
|
59
|
+
topic: 'payments.events',
|
|
60
|
+
partition: 1,
|
|
61
|
+
message: { value: 'ok' },
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
await trace.withTrace('arrow-args', async () => {
|
|
65
|
+
const value = await mod.consumerConfig.eachMessage(payload);
|
|
66
|
+
assert.strictEqual(value, 'ok');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
off();
|
|
70
|
+
|
|
71
|
+
assert.strictEqual(events.length, 1, `expected one arrow enter event, got ${events.length}`);
|
|
72
|
+
assert.deepStrictEqual(toPlainJson(events[0].args), [payload]);
|
|
73
|
+
|
|
74
|
+
console.log('wrap-plugin arrow destructured args OK');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
main().catch(err => {
|
|
78
|
+
console.error(err);
|
|
79
|
+
process.exitCode = 1;
|
|
80
|
+
});
|
package/tracer/cjs-hook.js
CHANGED
|
@@ -67,7 +67,6 @@ function tagExports(value, filename, seen = new WeakSet(), depth = 0, instrument
|
|
|
67
67
|
|
|
68
68
|
if (typeof d.get === 'function') {
|
|
69
69
|
tagExports(d.get, filename, seen, depth + 1, instrumented);
|
|
70
|
-
try { tagExports(value[k], filename, seen, depth + 1, instrumented); } catch {}
|
|
71
70
|
}
|
|
72
71
|
if (typeof d.set === 'function') {
|
|
73
72
|
tagExports(d.set, filename, seen, depth + 1, instrumented);
|
package/tracer/wrap-plugin.js
CHANGED
|
@@ -155,6 +155,97 @@ module.exports = function makeWrapPlugin(filenameForMeta, opts = {}) {
|
|
|
155
155
|
return argIndex === call.arguments.length - 1;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
function normalizeArrowParam(param, path) {
|
|
159
|
+
if (t.isIdentifier(param)) {
|
|
160
|
+
return {
|
|
161
|
+
param: t.cloneNode(param, true),
|
|
162
|
+
argExpr: t.cloneNode(param, true),
|
|
163
|
+
prelude: null,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (t.isRestElement(param)) {
|
|
168
|
+
if (t.isIdentifier(param.argument)) {
|
|
169
|
+
return {
|
|
170
|
+
param: t.cloneNode(param, true),
|
|
171
|
+
argExpr: t.cloneNode(param.argument, true),
|
|
172
|
+
prelude: null,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const tempId = path.scope.generateUidIdentifier('arg');
|
|
177
|
+
return {
|
|
178
|
+
param: t.restElement(t.cloneNode(tempId, true)),
|
|
179
|
+
argExpr: t.cloneNode(tempId, true),
|
|
180
|
+
prelude: t.variableDeclaration('const', [
|
|
181
|
+
t.variableDeclarator(
|
|
182
|
+
t.cloneNode(param.argument, true),
|
|
183
|
+
t.cloneNode(tempId, true)
|
|
184
|
+
)
|
|
185
|
+
]),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (t.isAssignmentPattern(param)) {
|
|
190
|
+
if (t.isIdentifier(param.left)) {
|
|
191
|
+
return {
|
|
192
|
+
param: t.cloneNode(param, true),
|
|
193
|
+
argExpr: t.cloneNode(param.left, true),
|
|
194
|
+
prelude: null,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const tempId = path.scope.generateUidIdentifier('arg');
|
|
199
|
+
return {
|
|
200
|
+
param: t.assignmentPattern(
|
|
201
|
+
t.cloneNode(tempId, true),
|
|
202
|
+
t.cloneNode(param.right, true)
|
|
203
|
+
),
|
|
204
|
+
argExpr: t.cloneNode(tempId, true),
|
|
205
|
+
prelude: t.variableDeclaration('const', [
|
|
206
|
+
t.variableDeclarator(
|
|
207
|
+
t.cloneNode(param.left, true),
|
|
208
|
+
t.cloneNode(tempId, true)
|
|
209
|
+
)
|
|
210
|
+
]),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (t.isObjectPattern(param) || t.isArrayPattern(param)) {
|
|
215
|
+
const tempId = path.scope.generateUidIdentifier('arg');
|
|
216
|
+
return {
|
|
217
|
+
param: t.cloneNode(tempId, true),
|
|
218
|
+
argExpr: t.cloneNode(tempId, true),
|
|
219
|
+
prelude: t.variableDeclaration('const', [
|
|
220
|
+
t.variableDeclarator(
|
|
221
|
+
t.cloneNode(param, true),
|
|
222
|
+
t.cloneNode(tempId, true)
|
|
223
|
+
)
|
|
224
|
+
]),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
param: t.cloneNode(param, true),
|
|
230
|
+
argExpr: t.identifier('undefined'),
|
|
231
|
+
prelude: null,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function normalizeArrowParams(path) {
|
|
236
|
+
if (!path.isArrowFunctionExpression()) {
|
|
237
|
+
return { argExprs: [], preludes: [] };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const normalized = path.node.params.map(param => normalizeArrowParam(param, path));
|
|
241
|
+
path.node.params = normalized.map(item => item.param);
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
argExprs: normalized.map(item => item.argExpr),
|
|
245
|
+
preludes: normalized.map(item => item.prelude).filter(Boolean),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
158
249
|
function wrap(path){
|
|
159
250
|
const n = path.node;
|
|
160
251
|
if (n.__repro_internal) return;
|
|
@@ -190,6 +281,10 @@ module.exports = function makeWrapPlugin(filenameForMeta, opts = {}) {
|
|
|
190
281
|
const bodyPath = path.get('body');
|
|
191
282
|
if (!bodyPath.isBlockStatement()) return;
|
|
192
283
|
const body = bodyPath.node;
|
|
284
|
+
const arrowParams = normalizeArrowParams(path);
|
|
285
|
+
if (arrowParams.preludes.length) {
|
|
286
|
+
body.body.unshift(...arrowParams.preludes);
|
|
287
|
+
}
|
|
193
288
|
|
|
194
289
|
const argsId = path.scope.generateUidIdentifier('args');
|
|
195
290
|
const resultId = path.scope.generateUidIdentifier('result');
|
|
@@ -209,16 +304,7 @@ module.exports = function makeWrapPlugin(filenameForMeta, opts = {}) {
|
|
|
209
304
|
argsArray
|
|
210
305
|
)
|
|
211
306
|
: t.arrayExpression(
|
|
212
|
-
|
|
213
|
-
if (t.isIdentifier(param)) return t.cloneNode(param, true);
|
|
214
|
-
if (t.isRestElement(param) && t.isIdentifier(param.argument)) {
|
|
215
|
-
return t.cloneNode(param.argument, true);
|
|
216
|
-
}
|
|
217
|
-
if (t.isAssignmentPattern(param) && t.isIdentifier(param.left)) {
|
|
218
|
-
return t.cloneNode(param.left, true);
|
|
219
|
-
}
|
|
220
|
-
return t.identifier('undefined');
|
|
221
|
-
})
|
|
307
|
+
arrowParams.argExprs.map(argExpr => t.cloneNode(argExpr, true))
|
|
222
308
|
);
|
|
223
309
|
|
|
224
310
|
const argsDecl = t.variableDeclaration('const', [
|
package/dist/redaction.d.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
type RedactionStrategy = 'mask' | 'remove';
|
|
2
|
-
export type RedactionRuleInput = string | RegExp | {
|
|
3
|
-
match?: string | RegExp;
|
|
4
|
-
field?: string | RegExp;
|
|
5
|
-
exact?: boolean;
|
|
6
|
-
strategy?: RedactionStrategy;
|
|
7
|
-
maskWith?: string;
|
|
8
|
-
};
|
|
9
|
-
type RedactionFieldMapKey = 'any' | 'headers' | 'body' | 'query' | 'params' | 'respBody' | 'response' | 'responseBody' | 'responseHeaders';
|
|
10
|
-
export type TransportRedactionFields = Partial<Record<RedactionFieldMapKey, RedactionRuleInput[]>>;
|
|
11
|
-
export interface TransportRedactionOptions {
|
|
12
|
-
/**
|
|
13
|
-
* Custom mask string for masked values. Default: "*****".
|
|
14
|
-
*/
|
|
15
|
-
maskWith?: string;
|
|
16
|
-
/**
|
|
17
|
-
* Additional rules that apply to specific request/response sections.
|
|
18
|
-
* Provide field names (case-insensitive), dot-delimited paths, or
|
|
19
|
-
* regular expressions. Rules declared under `any` apply everywhere.
|
|
20
|
-
*/
|
|
21
|
-
fields?: TransportRedactionFields;
|
|
22
|
-
}
|
|
23
|
-
type SectionKey = 'any' | 'headers' | 'body' | 'query' | 'params' | 'respBody' | 'responseHeaders';
|
|
24
|
-
type NormalizedRule = {
|
|
25
|
-
kind: 'string';
|
|
26
|
-
needle: string;
|
|
27
|
-
exact: boolean;
|
|
28
|
-
action: RedactionStrategy;
|
|
29
|
-
maskWith?: string;
|
|
30
|
-
} | {
|
|
31
|
-
kind: 'regex';
|
|
32
|
-
regex: RegExp;
|
|
33
|
-
action: RedactionStrategy;
|
|
34
|
-
maskWith?: string;
|
|
35
|
-
};
|
|
36
|
-
export type TransportRedactionState = {
|
|
37
|
-
maskWith: string;
|
|
38
|
-
rules: Record<SectionKey, NormalizedRule[]>;
|
|
39
|
-
};
|
|
40
|
-
export declare function configureTransportRedaction(options?: TransportRedactionOptions | null): TransportRedactionState;
|
|
41
|
-
export declare function getTransportRedactionState(): TransportRedactionState;
|
|
42
|
-
export declare function redactPayload<T>(payload: T, stateOverride?: TransportRedactionState | null): T;
|
|
43
|
-
export declare function redactSection<T>(value: T, section: Exclude<SectionKey, 'any'>, stateOverride?: TransportRedactionState | null): T;
|
|
44
|
-
export {};
|
package/dist/redaction.js
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.redactSection = exports.redactPayload = exports.getTransportRedactionState = exports.configureTransportRedaction = void 0;
|
|
4
|
-
const SECTION_KEYS = ['any', 'headers', 'body', 'query', 'params', 'respBody', 'responseHeaders'];
|
|
5
|
-
const DEFAULT_MASK = '*****';
|
|
6
|
-
const DEFAULT_RULES = [
|
|
7
|
-
{ match: /pass(word|phrase)?/i },
|
|
8
|
-
{ match: /pwd/i },
|
|
9
|
-
{ match: /secret/i },
|
|
10
|
-
{ match: /token/i },
|
|
11
|
-
{ match: /authorization/i },
|
|
12
|
-
{ match: /api[_-]?key/i },
|
|
13
|
-
];
|
|
14
|
-
let CURRENT_STATE = buildState();
|
|
15
|
-
function configureTransportRedaction(options) {
|
|
16
|
-
CURRENT_STATE = buildState(options);
|
|
17
|
-
return CURRENT_STATE;
|
|
18
|
-
}
|
|
19
|
-
exports.configureTransportRedaction = configureTransportRedaction;
|
|
20
|
-
function getTransportRedactionState() {
|
|
21
|
-
return CURRENT_STATE;
|
|
22
|
-
}
|
|
23
|
-
exports.getTransportRedactionState = getTransportRedactionState;
|
|
24
|
-
function redactPayload(payload, stateOverride) {
|
|
25
|
-
const state = stateOverride ?? CURRENT_STATE;
|
|
26
|
-
applyRules(payload, gatherRules('any', state), state.maskWith);
|
|
27
|
-
return payload;
|
|
28
|
-
}
|
|
29
|
-
exports.redactPayload = redactPayload;
|
|
30
|
-
function redactSection(value, section, stateOverride) {
|
|
31
|
-
const state = stateOverride ?? CURRENT_STATE;
|
|
32
|
-
applyRules(value, gatherRules(section, state), state.maskWith);
|
|
33
|
-
return value;
|
|
34
|
-
}
|
|
35
|
-
exports.redactSection = redactSection;
|
|
36
|
-
function buildState(options) {
|
|
37
|
-
const state = {
|
|
38
|
-
maskWith: typeof options?.maskWith === 'string' && options.maskWith ? options.maskWith : DEFAULT_MASK,
|
|
39
|
-
rules: SECTION_KEYS.reduce((acc, key) => {
|
|
40
|
-
acc[key] = [];
|
|
41
|
-
return acc;
|
|
42
|
-
}, {}),
|
|
43
|
-
};
|
|
44
|
-
appendRules(state.rules.any, DEFAULT_RULES);
|
|
45
|
-
if (!options?.fields) {
|
|
46
|
-
return state;
|
|
47
|
-
}
|
|
48
|
-
appendRules(state.rules.any, options.fields.any);
|
|
49
|
-
appendRules(state.rules.headers, options.fields.headers);
|
|
50
|
-
appendRules(state.rules.body, options.fields.body);
|
|
51
|
-
appendRules(state.rules.query, options.fields.query);
|
|
52
|
-
appendRules(state.rules.params, options.fields.params);
|
|
53
|
-
appendRules(state.rules.respBody, options.fields.respBody);
|
|
54
|
-
appendRules(state.rules.respBody, options.fields.response);
|
|
55
|
-
appendRules(state.rules.respBody, options.fields.responseBody);
|
|
56
|
-
appendRules(state.rules.responseHeaders, options.fields.responseHeaders);
|
|
57
|
-
return state;
|
|
58
|
-
}
|
|
59
|
-
function appendRules(bucket, inputs) {
|
|
60
|
-
if (!inputs || !Array.isArray(inputs))
|
|
61
|
-
return;
|
|
62
|
-
for (const entry of inputs) {
|
|
63
|
-
const normalized = normalizeRule(entry);
|
|
64
|
-
if (normalized)
|
|
65
|
-
bucket.push(normalized);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function normalizeRule(input) {
|
|
69
|
-
if (!input)
|
|
70
|
-
return null;
|
|
71
|
-
if (typeof input === 'string') {
|
|
72
|
-
const needle = input.trim().toLowerCase();
|
|
73
|
-
if (!needle)
|
|
74
|
-
return null;
|
|
75
|
-
return { kind: 'string', needle, exact: false, action: 'mask' };
|
|
76
|
-
}
|
|
77
|
-
if (input instanceof RegExp) {
|
|
78
|
-
return { kind: 'regex', regex: cloneRegex(input), action: 'mask' };
|
|
79
|
-
}
|
|
80
|
-
if (typeof input === 'object') {
|
|
81
|
-
const pattern = input.match ?? input.field;
|
|
82
|
-
const exact = !!input.exact;
|
|
83
|
-
const action = input.strategy === 'remove' ? 'remove' : 'mask';
|
|
84
|
-
const maskWith = typeof input.maskWith === 'string' && input.maskWith ? input.maskWith : undefined;
|
|
85
|
-
if (pattern instanceof RegExp) {
|
|
86
|
-
return { kind: 'regex', regex: cloneRegex(pattern), action, maskWith };
|
|
87
|
-
}
|
|
88
|
-
if (typeof pattern === 'string') {
|
|
89
|
-
const needle = pattern.trim().toLowerCase();
|
|
90
|
-
if (!needle)
|
|
91
|
-
return null;
|
|
92
|
-
return { kind: 'string', needle, exact, action, maskWith };
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
function cloneRegex(rx) {
|
|
98
|
-
const flags = rx.flags.includes('g') ? rx.flags.replace(/g/g, '') : rx.flags;
|
|
99
|
-
return new RegExp(rx.source, flags);
|
|
100
|
-
}
|
|
101
|
-
function gatherRules(section, state) {
|
|
102
|
-
if (section === 'any')
|
|
103
|
-
return state.rules.any;
|
|
104
|
-
return state.rules.any.concat(state.rules[section] ?? []);
|
|
105
|
-
}
|
|
106
|
-
function applyRules(value, rules, defaultMask) {
|
|
107
|
-
if (!rules.length)
|
|
108
|
-
return;
|
|
109
|
-
if (Array.isArray(value)) {
|
|
110
|
-
for (let i = 0; i < value.length; i++) {
|
|
111
|
-
const item = value[i];
|
|
112
|
-
if (item && typeof item === 'object') {
|
|
113
|
-
applyRules(item, rules, defaultMask);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
if (!isRedactableObject(value))
|
|
119
|
-
return;
|
|
120
|
-
for (const key of Object.keys(value)) {
|
|
121
|
-
const decision = pickDecision(key, rules, defaultMask);
|
|
122
|
-
if (!decision) {
|
|
123
|
-
const child = value[key];
|
|
124
|
-
if (child && typeof child === 'object') {
|
|
125
|
-
applyRules(child, rules, defaultMask);
|
|
126
|
-
}
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
if (decision === 'remove') {
|
|
130
|
-
delete value[key];
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
value[key] = decision;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
function isRedactableObject(value) {
|
|
138
|
-
if (!value || typeof value !== 'object')
|
|
139
|
-
return false;
|
|
140
|
-
if (Buffer.isBuffer(value))
|
|
141
|
-
return false;
|
|
142
|
-
if (ArrayBuffer.isView(value))
|
|
143
|
-
return false;
|
|
144
|
-
if (value instanceof Date || value instanceof RegExp)
|
|
145
|
-
return false;
|
|
146
|
-
if (value instanceof Map || value instanceof Set)
|
|
147
|
-
return false;
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
|
-
function pickDecision(key, rules, defaultMask) {
|
|
151
|
-
const lower = key.toLowerCase();
|
|
152
|
-
for (const rule of rules) {
|
|
153
|
-
if (rule.kind === 'string') {
|
|
154
|
-
const match = rule.exact ? lower === rule.needle : lower.includes(rule.needle);
|
|
155
|
-
if (!match)
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
if (!rule.regex.test(key))
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
if (rule.action === 'remove')
|
|
163
|
-
return 'remove';
|
|
164
|
-
return rule.maskWith ?? defaultMask;
|
|
165
|
-
}
|
|
166
|
-
return null;
|
|
167
|
-
}
|
package/dist/server.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const index_1 = require("./index");
|
|
4
|
-
const express = require('express');
|
|
5
|
-
const mongoose = require('mongoose');
|
|
6
|
-
(0, index_1.initReproTracing)();
|
|
7
|
-
const app = express();
|
|
8
|
-
(async () => {
|
|
9
|
-
await mongoose.connect('mongodb://127.0.0.1:27018', { dbName: 'trace_demo' });
|
|
10
|
-
const userSchema = new mongoose.Schema({
|
|
11
|
-
name: String, email: String, age: Number
|
|
12
|
-
}, { timestamps: true });
|
|
13
|
-
const User = mongoose.model('User', userSchema);
|
|
14
|
-
// seed some data
|
|
15
|
-
// @ts-ignore
|
|
16
|
-
app.get('/seed', async (_req, res) => {
|
|
17
|
-
await User.deleteMany({});
|
|
18
|
-
await User.insertMany([
|
|
19
|
-
{ name: 'Ada', email: 'ada@example.com', age: 27 },
|
|
20
|
-
{ name: 'Lin', email: 'lin@example.com', age: 35 },
|
|
21
|
-
{ name: 'Sam', email: 'sam@example.com', age: 17 },
|
|
22
|
-
]);
|
|
23
|
-
res.json({ ok: true, count: await User.countDocuments() });
|
|
24
|
-
});
|
|
25
|
-
app.listen(3000, () => process.stdout.write('listening on http://localhost:3000\n'));
|
|
26
|
-
})();
|
|
File without changes
|