@senzops/apm-node 1.1.12 → 1.1.15
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.global.js +1 -1
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/core/client.ts +133 -2
- package/src/instrumentation/bullmq.ts +175 -50
- package/src/instrumentation/cron.ts +190 -27
- package/src/instrumentation/hook.ts +240 -17
- package/src/utils/sdkMeta.ts +6 -0
- package/tsconfig.json +1 -0
|
@@ -1,70 +1,195 @@
|
|
|
1
1
|
import type { SenzorClient } from '../core/client';
|
|
2
2
|
import { hookRequire } from './hook';
|
|
3
|
+
import { Context } from '../core/context';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
const PATCHED =
|
|
6
|
+
Symbol.for(
|
|
7
|
+
'senzor.bullmq.patched'
|
|
8
|
+
);
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
function patchWorker(
|
|
11
|
+
target: any,
|
|
12
|
+
client: SenzorClient,
|
|
13
|
+
debug: boolean
|
|
14
|
+
) {
|
|
9
15
|
|
|
10
|
-
|
|
16
|
+
if (
|
|
17
|
+
!target?.Worker?.prototype
|
|
18
|
+
) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
11
21
|
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
const proto =
|
|
23
|
+
target.Worker.prototype;
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const currentAttempt = (job.attemptsMade || 0) + 1;
|
|
18
|
-
const maxAttempts = job.opts?.attempts || 1;
|
|
25
|
+
const original =
|
|
26
|
+
proto.processJob;
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
if (
|
|
29
|
+
typeof original !==
|
|
30
|
+
'function' ||
|
|
31
|
+
original[PATCHED]
|
|
32
|
+
) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
22
35
|
|
|
23
|
-
|
|
36
|
+
proto.processJob =
|
|
37
|
+
async function (
|
|
38
|
+
job: any
|
|
39
|
+
) {
|
|
40
|
+
|
|
41
|
+
const queueDelay =
|
|
42
|
+
job.timestamp
|
|
43
|
+
? Date.now() -
|
|
44
|
+
job.timestamp
|
|
45
|
+
: 0;
|
|
46
|
+
|
|
47
|
+
const currentAttempt =
|
|
48
|
+
(job.attemptsMade || 0)
|
|
49
|
+
+ 1;
|
|
50
|
+
|
|
51
|
+
const maxAttempts =
|
|
52
|
+
job.opts?.attempts
|
|
53
|
+
?? 1;
|
|
54
|
+
|
|
55
|
+
const isFinal =
|
|
56
|
+
currentAttempt >=
|
|
57
|
+
maxAttempts;
|
|
58
|
+
|
|
59
|
+
const taskName =
|
|
60
|
+
job.name ===
|
|
61
|
+
'__default__'
|
|
62
|
+
? job.queueName
|
|
63
|
+
: `${job.queueName}:${job.name}`;
|
|
64
|
+
|
|
65
|
+
return client.startTask(
|
|
66
|
+
|
|
67
|
+
taskName,
|
|
68
|
+
|
|
69
|
+
'queue',
|
|
70
|
+
|
|
71
|
+
{
|
|
72
|
+
queueDelay,
|
|
73
|
+
attempts:
|
|
74
|
+
currentAttempt,
|
|
75
|
+
isDeadLetter: false,
|
|
76
|
+
metadata: {
|
|
77
|
+
jobId: job.id,
|
|
78
|
+
queueName:
|
|
79
|
+
job.queueName,
|
|
80
|
+
maxAttempts
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
async () => {
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
|
|
88
|
+
const result =
|
|
89
|
+
await original.call(
|
|
90
|
+
this,
|
|
91
|
+
job
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
client.endTask(
|
|
95
|
+
'success'
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
return result;
|
|
99
|
+
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
24
102
|
|
|
25
|
-
return client.startTask(
|
|
26
|
-
taskName,
|
|
27
|
-
'queue',
|
|
28
|
-
{
|
|
29
|
-
queueDelay,
|
|
30
|
-
attempts: currentAttempt,
|
|
31
|
-
// We preset isDeadLetter to false, but if it throws an error and isFinalAttempt is true,
|
|
32
|
-
// we will mutate this in the catch block.
|
|
33
|
-
isDeadLetter: false,
|
|
34
|
-
metadata: { jobId: job.id, queueName: job.queueName, maxAttempts }
|
|
35
|
-
},
|
|
36
|
-
async () => {
|
|
37
103
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
104
|
+
|
|
105
|
+
const ctx =
|
|
106
|
+
Context.current();
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
ctx &&
|
|
110
|
+
ctx.contextType === 'task' &&
|
|
111
|
+
isFinal
|
|
112
|
+
) {
|
|
113
|
+
|
|
114
|
+
ctx.data
|
|
115
|
+
.isDeadLetter =
|
|
116
|
+
true;
|
|
117
|
+
|
|
45
118
|
}
|
|
46
119
|
|
|
47
|
-
|
|
48
|
-
|
|
120
|
+
}
|
|
121
|
+
catch { }
|
|
122
|
+
|
|
123
|
+
client.captureError(
|
|
124
|
+
error,
|
|
125
|
+
{
|
|
126
|
+
queueName:
|
|
127
|
+
job.queueName,
|
|
49
128
|
jobId: job.id,
|
|
50
|
-
isDeadLetter:
|
|
51
|
-
|
|
129
|
+
isDeadLetter:
|
|
130
|
+
isFinal
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
client.endTask(
|
|
135
|
+
'failed'
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
throw error;
|
|
52
139
|
|
|
53
|
-
client.endTask('failed');
|
|
54
|
-
throw error;
|
|
55
|
-
}
|
|
56
140
|
}
|
|
57
|
-
);
|
|
58
|
-
};
|
|
59
141
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
);
|
|
63
145
|
|
|
64
|
-
|
|
146
|
+
};
|
|
65
147
|
|
|
66
|
-
|
|
67
|
-
|
|
148
|
+
Object.defineProperty(
|
|
149
|
+
proto.processJob,
|
|
150
|
+
PATCHED,
|
|
151
|
+
{
|
|
152
|
+
value: true
|
|
68
153
|
}
|
|
69
|
-
|
|
70
|
-
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (debug) {
|
|
157
|
+
|
|
158
|
+
console.log(
|
|
159
|
+
'[Senzor] BullMQ instrumented'
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const instrumentBullMQ =
|
|
167
|
+
(
|
|
168
|
+
client: SenzorClient,
|
|
169
|
+
debug: boolean
|
|
170
|
+
) => {
|
|
171
|
+
|
|
172
|
+
hookRequire(
|
|
173
|
+
'bullmq',
|
|
174
|
+
(exports: any) => {
|
|
175
|
+
|
|
176
|
+
patchWorker(
|
|
177
|
+
exports,
|
|
178
|
+
client,
|
|
179
|
+
debug
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
if (exports?.default) {
|
|
183
|
+
|
|
184
|
+
patchWorker(
|
|
185
|
+
exports.default,
|
|
186
|
+
client,
|
|
187
|
+
debug
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
};
|
|
@@ -1,41 +1,204 @@
|
|
|
1
1
|
import type { SenzorClient } from '../core/client';
|
|
2
2
|
import { hookRequire } from './hook';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const PATCHED =
|
|
5
|
+
Symbol.for('senzor.cron.patched');
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (!target || typeof target.schedule !== 'function' || target.__senzorPatched) return;
|
|
7
|
+
type CronHandler =
|
|
8
|
+
(...args: unknown[]) => unknown;
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
type CronSchedule =
|
|
11
|
+
(
|
|
12
|
+
expression: string,
|
|
13
|
+
handler: CronHandler,
|
|
14
|
+
options?: unknown
|
|
15
|
+
) => unknown;
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const taskName = optsObj?.name || `cron: ${expression}`;
|
|
17
|
+
function normalizeOptions(
|
|
18
|
+
options: unknown
|
|
19
|
+
): Record<string, unknown> {
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
if (
|
|
22
|
+
typeof options === 'object' &&
|
|
23
|
+
options !== null
|
|
24
|
+
) {
|
|
25
|
+
return options as Record<
|
|
26
|
+
string,
|
|
27
|
+
unknown
|
|
28
|
+
>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// backward compatibility with:
|
|
32
|
+
// cron.schedule(expr, fn, "UTC")
|
|
33
|
+
if (options) {
|
|
34
|
+
return { timezone: options };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {};
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function patchTarget(
|
|
42
|
+
target: Record<string, unknown>,
|
|
43
|
+
client: SenzorClient,
|
|
44
|
+
debug: boolean
|
|
45
|
+
): void {
|
|
46
|
+
|
|
47
|
+
const schedule =
|
|
48
|
+
target.schedule as
|
|
49
|
+
CronSchedule | undefined;
|
|
50
|
+
|
|
51
|
+
if (
|
|
52
|
+
typeof schedule !== 'function' ||
|
|
53
|
+
(schedule as any)[PATCHED]
|
|
54
|
+
) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const original =
|
|
59
|
+
schedule;
|
|
60
|
+
|
|
61
|
+
const wrapped: CronSchedule =
|
|
62
|
+
function (
|
|
63
|
+
this: unknown,
|
|
64
|
+
expression,
|
|
65
|
+
handler,
|
|
66
|
+
options
|
|
67
|
+
) {
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
typeof handler !==
|
|
71
|
+
'function'
|
|
72
|
+
) {
|
|
73
|
+
|
|
74
|
+
return original.call(
|
|
75
|
+
this,
|
|
76
|
+
expression,
|
|
77
|
+
handler,
|
|
78
|
+
options
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
|
|
85
|
+
const opts =
|
|
86
|
+
normalizeOptions(
|
|
87
|
+
options
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const taskName =
|
|
91
|
+
(opts as any)?.name ??
|
|
92
|
+
`cron: ${expression}`;
|
|
93
|
+
|
|
94
|
+
const wrappedHandler =
|
|
95
|
+
client.wrapTask(
|
|
96
|
+
taskName,
|
|
97
|
+
'cron',
|
|
98
|
+
{
|
|
99
|
+
expression,
|
|
100
|
+
metadata: opts
|
|
101
|
+
},
|
|
102
|
+
handler
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return original.call(
|
|
106
|
+
this,
|
|
107
|
+
expression,
|
|
108
|
+
wrappedHandler,
|
|
109
|
+
options
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
|
|
115
|
+
if (debug) {
|
|
116
|
+
|
|
117
|
+
console.error(
|
|
118
|
+
'[Senzor] cron wrap failed',
|
|
119
|
+
err
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return original.call(
|
|
125
|
+
this,
|
|
126
|
+
expression,
|
|
127
|
+
handler,
|
|
128
|
+
options
|
|
23
129
|
);
|
|
24
130
|
|
|
25
|
-
|
|
26
|
-
};
|
|
131
|
+
}
|
|
27
132
|
|
|
28
|
-
// Safely mark as patched to prevent infinite loops
|
|
29
|
-
Object.defineProperty(target, '__senzorPatched', { value: true, enumerable: false, writable: true });
|
|
30
|
-
if (debug) console.log('[Senzor] Node-Cron successfully instrumented');
|
|
31
133
|
};
|
|
32
134
|
|
|
33
|
-
|
|
34
|
-
|
|
135
|
+
Object.defineProperty(
|
|
136
|
+
wrapped,
|
|
137
|
+
PATCHED,
|
|
138
|
+
{
|
|
139
|
+
value: true,
|
|
140
|
+
enumerable: false
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Some ESM namespace exports are frozen
|
|
145
|
+
try {
|
|
146
|
+
|
|
147
|
+
target.schedule =
|
|
148
|
+
wrapped;
|
|
149
|
+
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
|
|
153
|
+
if (debug) {
|
|
154
|
+
|
|
155
|
+
console.warn(
|
|
156
|
+
'[Senzor] unable to patch cron schedule (readonly export)'
|
|
157
|
+
);
|
|
35
158
|
|
|
36
|
-
// Apply patch to default (for import cron from 'node-cron')
|
|
37
|
-
if (cronExports.default) {
|
|
38
|
-
patchSchedule(cronExports.default);
|
|
39
159
|
}
|
|
40
|
-
|
|
41
|
-
}
|
|
160
|
+
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (debug) {
|
|
164
|
+
|
|
165
|
+
console.log(
|
|
166
|
+
'[Senzor] node-cron instrumented'
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export const instrumentNodeCron =
|
|
174
|
+
(
|
|
175
|
+
client: SenzorClient,
|
|
176
|
+
debug: boolean
|
|
177
|
+
): void => {
|
|
178
|
+
|
|
179
|
+
hookRequire(
|
|
180
|
+
'node-cron',
|
|
181
|
+
(exports: any) => {
|
|
182
|
+
|
|
183
|
+
if (!exports) return;
|
|
184
|
+
|
|
185
|
+
patchTarget(
|
|
186
|
+
exports,
|
|
187
|
+
client,
|
|
188
|
+
debug
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
if (exports.default) {
|
|
192
|
+
|
|
193
|
+
patchTarget(
|
|
194
|
+
exports.default,
|
|
195
|
+
client,
|
|
196
|
+
debug
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
};
|