@senzops/apm-node 1.2.0 → 1.2.2
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/.claude/worktrees/infallible-chatelet-f3fb36/.claude/settings.local.json +9 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/CHANGELOG.md +49 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/README.md +398 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/package-lock.json +1494 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/package.json +42 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/client.ts +451 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/context.ts +48 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/normalizer.ts +44 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/sanitizer.ts +203 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/transport.ts +273 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/types.ts +106 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/index.ts +36 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/bullmq.ts +195 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/cron.ts +204 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/express.ts +338 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/fastify.ts +296 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/framework.ts +301 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/hook.ts +134 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/http.ts +530 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/koa.ts +173 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mongo.ts +202 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mongoose.ts +156 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mysql.ts +169 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/patch.ts +56 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/pg.ts +131 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/redis.ts +109 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/span.ts +73 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/undici.ts +189 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/middleware/express.ts +48 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/register.ts +58 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/getClientIp.ts +175 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/ids.ts +7 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/internal.ts +1 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/sdkMeta.ts +6 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/traceContext.ts +44 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/fastify.ts +35 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/h3.ts +59 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/next.ts +131 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/tsconfig.json +15 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/tsup.config.ts +21 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/wiki.md +852 -0
- package/CHANGELOG.md +8 -0
- package/README.md +12 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- 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/dist/register.js +1 -1
- package/dist/register.js.map +1 -1
- package/dist/register.mjs +1 -1
- package/dist/register.mjs.map +1 -1
- package/package.json +1 -1
- package/src/core/client.ts +8 -2
- package/src/core/types.ts +5 -0
- package/src/instrumentation/express.ts +338 -0
- package/src/instrumentation/fastify.ts +296 -0
- package/src/instrumentation/framework.ts +301 -0
- package/src/instrumentation/hook.ts +79 -192
- package/src/instrumentation/koa.ts +173 -0
- package/src/register.ts +16 -0
- package/src/wrappers/fastify.ts +10 -7
- package/src/wrappers/h3.ts +40 -16
- package/src/wrappers/next.ts +68 -21
- package/wiki.md +8 -0
package/package.json
CHANGED
package/src/core/client.ts
CHANGED
|
@@ -2,8 +2,11 @@ import { Transport } from './transport';
|
|
|
2
2
|
import { Context } from './context';
|
|
3
3
|
import { SenzorOptions, ActiveTrace, TaskRun, SenzorLog } from './types';
|
|
4
4
|
import { randomUUID } from 'crypto';
|
|
5
|
-
import { instrumentHttp, instrumentFetch } from '../instrumentation/http';
|
|
6
|
-
import {
|
|
5
|
+
import { instrumentHttp, instrumentFetch } from '../instrumentation/http';
|
|
6
|
+
import { instrumentExpress } from '../instrumentation/express';
|
|
7
|
+
import { instrumentFastify } from '../instrumentation/fastify';
|
|
8
|
+
import { instrumentKoa } from '../instrumentation/koa';
|
|
9
|
+
import { instrumentMongo } from '../instrumentation/mongo';
|
|
7
10
|
import { instrumentPg } from '../instrumentation/pg';
|
|
8
11
|
import { instrumentUndici } from '../instrumentation/undici';
|
|
9
12
|
import { instrumentRedis } from '../instrumentation/redis';
|
|
@@ -74,6 +77,9 @@ export class SenzorClient {
|
|
|
74
77
|
this.setupLogInterception(); // Fire up Auto Log Instrumentation
|
|
75
78
|
|
|
76
79
|
try { if (this.isInstrumentationEnabled('http')) instrumentHttp(this, endpoint, this.options || undefined); } catch (e) { }
|
|
80
|
+
try { if (this.isInstrumentationEnabled('express')) instrumentExpress(this.options || undefined); } catch (e) { }
|
|
81
|
+
try { if (this.isInstrumentationEnabled('fastify')) instrumentFastify(this.options || undefined); } catch (e) { }
|
|
82
|
+
try { if (this.isInstrumentationEnabled('koa')) instrumentKoa(this.options || undefined); } catch (e) { }
|
|
77
83
|
try { if (this.isInstrumentationEnabled('fetch')) instrumentFetch(endpoint, this.options || undefined); } catch (e) { }
|
|
78
84
|
try { if (this.isInstrumentationEnabled('undici')) instrumentUndici(this.options || undefined); } catch (e) { }
|
|
79
85
|
try { if (this.isInstrumentationEnabled('mongo')) instrumentMongo(this.options || undefined); } catch (e) { }
|
package/src/core/types.ts
CHANGED
|
@@ -11,6 +11,11 @@ export interface SenzorOptions {
|
|
|
11
11
|
captureHeaders?: boolean;
|
|
12
12
|
captureDbStatement?: boolean;
|
|
13
13
|
instrumentations?: boolean | string[];
|
|
14
|
+
frameworkSpans?: boolean;
|
|
15
|
+
captureMiddlewareSpans?: boolean;
|
|
16
|
+
captureRouterSpans?: boolean;
|
|
17
|
+
captureLifecycleHookSpans?: boolean;
|
|
18
|
+
ignoreFrameworkSpanTypes?: string[];
|
|
14
19
|
debug?: boolean;
|
|
15
20
|
autoLogs?: boolean;
|
|
16
21
|
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { normalizePath } from '../core/normalizer';
|
|
2
|
+
import { SenzorOptions } from '../core/types';
|
|
3
|
+
import { hookRequire } from './hook';
|
|
4
|
+
import { patchMethod } from './patch';
|
|
5
|
+
import { invokeWithFrameworkSpan } from './framework';
|
|
6
|
+
|
|
7
|
+
const LAYER_PATCHED = Symbol.for('senzor.express.layer.patched');
|
|
8
|
+
|
|
9
|
+
const routeMethods = new Set([
|
|
10
|
+
'checkout',
|
|
11
|
+
'copy',
|
|
12
|
+
'delete',
|
|
13
|
+
'get',
|
|
14
|
+
'head',
|
|
15
|
+
'lock',
|
|
16
|
+
'merge',
|
|
17
|
+
'mkactivity',
|
|
18
|
+
'mkcol',
|
|
19
|
+
'move',
|
|
20
|
+
'm-search',
|
|
21
|
+
'notify',
|
|
22
|
+
'options',
|
|
23
|
+
'patch',
|
|
24
|
+
'post',
|
|
25
|
+
'purge',
|
|
26
|
+
'put',
|
|
27
|
+
'report',
|
|
28
|
+
'search',
|
|
29
|
+
'subscribe',
|
|
30
|
+
'trace',
|
|
31
|
+
'unlock',
|
|
32
|
+
'unsubscribe'
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const stringifyPath = (value: unknown): string | undefined => {
|
|
36
|
+
if (typeof value === 'string') return value;
|
|
37
|
+
if (value instanceof RegExp) return value.toString();
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
return value.map(stringifyPath).filter(Boolean).join(',');
|
|
40
|
+
}
|
|
41
|
+
if (typeof value === 'number') return String(value);
|
|
42
|
+
return undefined;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const getLayerPath = (args: any[]): string | undefined => {
|
|
46
|
+
for (const arg of args) {
|
|
47
|
+
if (typeof arg === 'function') return undefined;
|
|
48
|
+
const path = stringifyPath(arg);
|
|
49
|
+
if (path) return path;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return undefined;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const getRequestRoute = (
|
|
56
|
+
req: any,
|
|
57
|
+
layer: any,
|
|
58
|
+
layerPath?: string
|
|
59
|
+
): string | undefined => {
|
|
60
|
+
const routePath = stringifyPath(layer?.route?.path);
|
|
61
|
+
if (routePath) {
|
|
62
|
+
const baseUrl = req?.baseUrl || '';
|
|
63
|
+
return `${baseUrl}${routePath}` || routePath;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (req?.route?.path) {
|
|
67
|
+
return `${req.baseUrl || ''}${req.route.path}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (layerPath) {
|
|
71
|
+
const baseUrl = req?.baseUrl || '';
|
|
72
|
+
return `${baseUrl}${layerPath}` || layerPath;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const path = req?.originalUrl || req?.url || req?.path;
|
|
76
|
+
return path ? normalizePath(String(path).split('?')[0]) : undefined;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const getLayerType = (
|
|
80
|
+
layer: any,
|
|
81
|
+
original: Function,
|
|
82
|
+
forcedType?: 'middleware' | 'router' | 'request_handler' | 'error_handler'
|
|
83
|
+
) => {
|
|
84
|
+
if (forcedType) return forcedType;
|
|
85
|
+
if (original.length === 4) return 'error_handler' as const;
|
|
86
|
+
if (layer?.route) return 'request_handler' as const;
|
|
87
|
+
if (layer?.name === 'router' || layer?.handle?.stack || layer?.handle?.name === 'router') {
|
|
88
|
+
return 'router' as const;
|
|
89
|
+
}
|
|
90
|
+
return 'middleware' as const;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const getRouteMethod = (layer: any, req: any): string | undefined => {
|
|
94
|
+
if (layer?.route?.methods) {
|
|
95
|
+
const method = Object.keys(layer.route.methods).find(
|
|
96
|
+
(candidate) => layer.route.methods[candidate]
|
|
97
|
+
);
|
|
98
|
+
if (method) return method.toUpperCase();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return req?.method;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const copyEnumerableProperties = (
|
|
105
|
+
source: Function,
|
|
106
|
+
target: Function
|
|
107
|
+
) => {
|
|
108
|
+
for (const key in source as any) {
|
|
109
|
+
try {
|
|
110
|
+
Object.defineProperty(target, key, {
|
|
111
|
+
configurable: true,
|
|
112
|
+
enumerable: true,
|
|
113
|
+
get() {
|
|
114
|
+
return (source as any)[key];
|
|
115
|
+
},
|
|
116
|
+
set(value) {
|
|
117
|
+
(source as any)[key] = value;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
} catch { }
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const patchLayer = (
|
|
125
|
+
layer: any,
|
|
126
|
+
layerPath: string | undefined,
|
|
127
|
+
options?: SenzorOptions,
|
|
128
|
+
forcedType?: 'middleware' | 'router' | 'request_handler' | 'error_handler'
|
|
129
|
+
) => {
|
|
130
|
+
if (!layer || layer[LAYER_PATCHED] || typeof layer.handle !== 'function') {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
Object.defineProperty(layer, LAYER_PATCHED, {
|
|
135
|
+
value: true,
|
|
136
|
+
enumerable: false
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
patchMethod(
|
|
140
|
+
layer,
|
|
141
|
+
'handle',
|
|
142
|
+
'senzor.express.layer.handle',
|
|
143
|
+
(original) => {
|
|
144
|
+
const layerType = getLayerType(layer, original, forcedType);
|
|
145
|
+
const handlerName =
|
|
146
|
+
original.name ||
|
|
147
|
+
layer.name ||
|
|
148
|
+
layerType;
|
|
149
|
+
|
|
150
|
+
if (original.length === 4) {
|
|
151
|
+
const wrapped = function senzorExpressErrorHandler(
|
|
152
|
+
this: unknown,
|
|
153
|
+
err: any,
|
|
154
|
+
req: any,
|
|
155
|
+
res: any,
|
|
156
|
+
next: Function
|
|
157
|
+
) {
|
|
158
|
+
const route = getRequestRoute(req, layer, layerPath);
|
|
159
|
+
return invokeWithFrameworkSpan(
|
|
160
|
+
original,
|
|
161
|
+
this,
|
|
162
|
+
[err, req, res, next],
|
|
163
|
+
{
|
|
164
|
+
framework: 'express',
|
|
165
|
+
type: 'error_handler',
|
|
166
|
+
name: `express.error_handler ${route || handlerName}`,
|
|
167
|
+
route,
|
|
168
|
+
method: getRouteMethod(layer, req),
|
|
169
|
+
layerPath,
|
|
170
|
+
handlerName,
|
|
171
|
+
request: req,
|
|
172
|
+
response: res,
|
|
173
|
+
attributes: {
|
|
174
|
+
'express.type': 'error_handler',
|
|
175
|
+
'express.layer.name': layer.name,
|
|
176
|
+
error: err?.message,
|
|
177
|
+
'error.type': err?.name || typeof err
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
options,
|
|
181
|
+
{
|
|
182
|
+
callbackIndex: 3,
|
|
183
|
+
callbackCompletesSpan: true,
|
|
184
|
+
responseEndsSpan: true
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
copyEnumerableProperties(original, wrapped);
|
|
190
|
+
return wrapped;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const wrapped = function senzorExpressLayer(
|
|
194
|
+
this: unknown,
|
|
195
|
+
req: any,
|
|
196
|
+
res: any,
|
|
197
|
+
next: Function
|
|
198
|
+
) {
|
|
199
|
+
const route = getRequestRoute(req, layer, layerPath);
|
|
200
|
+
const method = getRouteMethod(layer, req);
|
|
201
|
+
const displayRoute = route || layerPath || handlerName;
|
|
202
|
+
const name =
|
|
203
|
+
layerType === 'request_handler'
|
|
204
|
+
? `express.request_handler ${method || ''} ${displayRoute}`.trim()
|
|
205
|
+
: `express.${layerType} ${displayRoute}`;
|
|
206
|
+
|
|
207
|
+
return invokeWithFrameworkSpan(
|
|
208
|
+
original,
|
|
209
|
+
this,
|
|
210
|
+
[req, res, next],
|
|
211
|
+
{
|
|
212
|
+
framework: 'express',
|
|
213
|
+
type: layerType,
|
|
214
|
+
name,
|
|
215
|
+
route,
|
|
216
|
+
method,
|
|
217
|
+
layerPath,
|
|
218
|
+
handlerName,
|
|
219
|
+
request: req,
|
|
220
|
+
response: res,
|
|
221
|
+
attributes: {
|
|
222
|
+
'express.type': layerType,
|
|
223
|
+
'express.layer.name': layer.name,
|
|
224
|
+
'http.route': route
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
options,
|
|
228
|
+
{
|
|
229
|
+
callbackIndex: 2,
|
|
230
|
+
callbackCompletesSpan: true,
|
|
231
|
+
responseEndsSpan: true
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
copyEnumerableProperties(original, wrapped);
|
|
237
|
+
return wrapped;
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const patchRouteMethodHandlers = (
|
|
243
|
+
route: any,
|
|
244
|
+
routePath: string | undefined,
|
|
245
|
+
options?: SenzorOptions
|
|
246
|
+
) => {
|
|
247
|
+
if (!route || route.__senzorRouteMethodsPatched) return;
|
|
248
|
+
|
|
249
|
+
Object.defineProperty(route, '__senzorRouteMethodsPatched', {
|
|
250
|
+
value: true,
|
|
251
|
+
enumerable: false
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
for (const method of routeMethods) {
|
|
255
|
+
if (typeof route[method] !== 'function') continue;
|
|
256
|
+
|
|
257
|
+
patchMethod(
|
|
258
|
+
route,
|
|
259
|
+
method,
|
|
260
|
+
`senzor.express.route.${method}`,
|
|
261
|
+
(original) =>
|
|
262
|
+
function patchedExpressRouteMethod(this: any, ...args: any[]) {
|
|
263
|
+
const result = original.apply(this, args);
|
|
264
|
+
const stack = this?.stack || [];
|
|
265
|
+
|
|
266
|
+
for (const layer of stack) {
|
|
267
|
+
patchLayer(layer, routePath, options, 'request_handler');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const patchExpress = (
|
|
277
|
+
expressModule: any,
|
|
278
|
+
options?: SenzorOptions
|
|
279
|
+
) => {
|
|
280
|
+
if (!expressModule) return;
|
|
281
|
+
|
|
282
|
+
const routerProto =
|
|
283
|
+
typeof expressModule?.Router?.prototype?.route === 'function'
|
|
284
|
+
? expressModule.Router.prototype
|
|
285
|
+
: expressModule.Router;
|
|
286
|
+
|
|
287
|
+
patchMethod(
|
|
288
|
+
routerProto,
|
|
289
|
+
'route',
|
|
290
|
+
'senzor.express.router.route',
|
|
291
|
+
(original) =>
|
|
292
|
+
function patchedExpressRoute(this: any, ...args: any[]) {
|
|
293
|
+
const route = original.apply(this, args);
|
|
294
|
+
const routePath = getLayerPath(args);
|
|
295
|
+
const layer = this?.stack?.[this.stack.length - 1];
|
|
296
|
+
|
|
297
|
+
patchLayer(layer, routePath, options, 'router');
|
|
298
|
+
patchRouteMethodHandlers(route, routePath, options);
|
|
299
|
+
|
|
300
|
+
return route;
|
|
301
|
+
}
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
patchMethod(
|
|
305
|
+
routerProto,
|
|
306
|
+
'use',
|
|
307
|
+
'senzor.express.router.use',
|
|
308
|
+
(original) =>
|
|
309
|
+
function patchedExpressRouterUse(this: any, ...args: any[]) {
|
|
310
|
+
const result = original.apply(this, args);
|
|
311
|
+
const layer = this?.stack?.[this.stack.length - 1];
|
|
312
|
+
patchLayer(layer, getLayerPath(args), options);
|
|
313
|
+
return result;
|
|
314
|
+
}
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
patchMethod(
|
|
318
|
+
expressModule.application,
|
|
319
|
+
'use',
|
|
320
|
+
'senzor.express.application.use',
|
|
321
|
+
(original) =>
|
|
322
|
+
function patchedExpressApplicationUse(this: any, ...args: any[]) {
|
|
323
|
+
const router = this?.router || this?._router;
|
|
324
|
+
const result = original.apply(this, args);
|
|
325
|
+
const activeRouter = this?.router || this?._router || router;
|
|
326
|
+
const layer = activeRouter?.stack?.[activeRouter.stack.length - 1];
|
|
327
|
+
patchLayer(layer, getLayerPath(args), options);
|
|
328
|
+
return result;
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
export const instrumentExpress = (options?: SenzorOptions) => {
|
|
334
|
+
hookRequire('express', (exports: any) => {
|
|
335
|
+
patchExpress(exports, options);
|
|
336
|
+
if (exports?.default) patchExpress(exports.default, options);
|
|
337
|
+
});
|
|
338
|
+
};
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { SenzorOptions } from '../core/types';
|
|
2
|
+
import { hookRequire } from './hook';
|
|
3
|
+
import { patchMethod } from './patch';
|
|
4
|
+
import { wrapFrameworkHandlerWithArity } from './framework';
|
|
5
|
+
|
|
6
|
+
const FACTORY_PATCHED = Symbol.for('senzor.fastify.factory.patched');
|
|
7
|
+
const INSTANCE_PATCHED = Symbol.for('senzor.fastify.instance.patched');
|
|
8
|
+
|
|
9
|
+
const lifecycleHookNames = new Set([
|
|
10
|
+
'onRequest',
|
|
11
|
+
'preParsing',
|
|
12
|
+
'preValidation',
|
|
13
|
+
'preHandler',
|
|
14
|
+
'preSerialization',
|
|
15
|
+
'onSend',
|
|
16
|
+
'onResponse',
|
|
17
|
+
'onError',
|
|
18
|
+
'onTimeout',
|
|
19
|
+
'onRequestAbort'
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
const routeLifecycleKeys = [
|
|
23
|
+
'onRequest',
|
|
24
|
+
'preParsing',
|
|
25
|
+
'preValidation',
|
|
26
|
+
'preHandler',
|
|
27
|
+
'preSerialization',
|
|
28
|
+
'onSend',
|
|
29
|
+
'onResponse',
|
|
30
|
+
'onError'
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const getRoute = (request: any, fallback?: string): string | undefined =>
|
|
34
|
+
request?.routeOptions?.url ||
|
|
35
|
+
request?.routerPath ||
|
|
36
|
+
request?.context?.config?.url ||
|
|
37
|
+
fallback;
|
|
38
|
+
|
|
39
|
+
const wrapHook = (
|
|
40
|
+
hookName: string,
|
|
41
|
+
handler: any,
|
|
42
|
+
options?: SenzorOptions,
|
|
43
|
+
route?: string
|
|
44
|
+
) => {
|
|
45
|
+
if (typeof handler !== 'function') return handler;
|
|
46
|
+
|
|
47
|
+
return wrapFrameworkHandlerWithArity(
|
|
48
|
+
handler,
|
|
49
|
+
(_thisArg, args) => {
|
|
50
|
+
const request = args[0];
|
|
51
|
+
const reply = args[1];
|
|
52
|
+
const currentRoute = getRoute(request, route);
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
framework: 'fastify',
|
|
56
|
+
type: hookName === 'onError' ? 'error_handler' : 'lifecycle_hook',
|
|
57
|
+
name: `fastify.${hookName} ${currentRoute || request?.url || ''}`.trim(),
|
|
58
|
+
route: currentRoute,
|
|
59
|
+
method: request?.method,
|
|
60
|
+
handlerName: handler.name || hookName,
|
|
61
|
+
request,
|
|
62
|
+
response: reply?.raw || reply,
|
|
63
|
+
attributes: {
|
|
64
|
+
'fastify.hook': hookName,
|
|
65
|
+
'fastify.type': hookName === 'onError' ? 'error_handler' : 'lifecycle_hook',
|
|
66
|
+
'http.route': currentRoute,
|
|
67
|
+
url: request?.url
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
options,
|
|
72
|
+
{
|
|
73
|
+
callbackCompletesSpan: true,
|
|
74
|
+
responseEndsSpan: false
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const wrapRouteHandler = (
|
|
80
|
+
handler: any,
|
|
81
|
+
options?: SenzorOptions,
|
|
82
|
+
route?: string
|
|
83
|
+
) => {
|
|
84
|
+
if (typeof handler !== 'function') return handler;
|
|
85
|
+
|
|
86
|
+
return wrapFrameworkHandlerWithArity(
|
|
87
|
+
handler,
|
|
88
|
+
(_thisArg, args) => {
|
|
89
|
+
const request = args[0];
|
|
90
|
+
const reply = args[1];
|
|
91
|
+
const currentRoute = getRoute(request, route);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
framework: 'fastify',
|
|
95
|
+
type: 'route_handler',
|
|
96
|
+
name: `fastify.route_handler ${request?.method || ''} ${currentRoute || request?.url || ''}`.trim(),
|
|
97
|
+
route: currentRoute,
|
|
98
|
+
method: request?.method,
|
|
99
|
+
handlerName: handler.name || 'handler',
|
|
100
|
+
request,
|
|
101
|
+
response: reply?.raw || reply,
|
|
102
|
+
attributes: {
|
|
103
|
+
'fastify.type': 'route_handler',
|
|
104
|
+
'http.route': currentRoute,
|
|
105
|
+
url: request?.url
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
options,
|
|
110
|
+
{
|
|
111
|
+
callbackCompletesSpan: true,
|
|
112
|
+
responseEndsSpan: true
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const wrapMaybeArray = (
|
|
118
|
+
value: any,
|
|
119
|
+
wrap: (handler: any) => any
|
|
120
|
+
) => {
|
|
121
|
+
if (Array.isArray(value)) return value.map(wrap);
|
|
122
|
+
return wrap(value);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const patchFastifyInstance = (
|
|
126
|
+
instance: any,
|
|
127
|
+
options?: SenzorOptions
|
|
128
|
+
) => {
|
|
129
|
+
if (!instance || instance[INSTANCE_PATCHED]) return instance;
|
|
130
|
+
|
|
131
|
+
Object.defineProperty(instance, INSTANCE_PATCHED, {
|
|
132
|
+
value: true,
|
|
133
|
+
enumerable: false
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
patchMethod(
|
|
137
|
+
instance,
|
|
138
|
+
'addHook',
|
|
139
|
+
'senzor.fastify.addHook',
|
|
140
|
+
(original) =>
|
|
141
|
+
function patchedFastifyAddHook(this: any, hookName: string, handler: any) {
|
|
142
|
+
if (lifecycleHookNames.has(hookName)) {
|
|
143
|
+
return original.call(this, hookName, wrapHook(hookName, handler, options));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return original.apply(this, arguments as any);
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
patchMethod(
|
|
151
|
+
instance,
|
|
152
|
+
'route',
|
|
153
|
+
'senzor.fastify.route',
|
|
154
|
+
(original) =>
|
|
155
|
+
function patchedFastifyRoute(this: any, routeOptions: any) {
|
|
156
|
+
if (!routeOptions || typeof routeOptions !== 'object') {
|
|
157
|
+
return original.apply(this, arguments as any);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const nextRouteOptions = { ...routeOptions };
|
|
161
|
+
const route =
|
|
162
|
+
nextRouteOptions.url ||
|
|
163
|
+
nextRouteOptions.path ||
|
|
164
|
+
nextRouteOptions.routePath;
|
|
165
|
+
|
|
166
|
+
if (nextRouteOptions.handler) {
|
|
167
|
+
nextRouteOptions.handler = wrapRouteHandler(
|
|
168
|
+
nextRouteOptions.handler,
|
|
169
|
+
options,
|
|
170
|
+
route
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for (const key of routeLifecycleKeys) {
|
|
175
|
+
if (nextRouteOptions[key]) {
|
|
176
|
+
nextRouteOptions[key] = wrapMaybeArray(
|
|
177
|
+
nextRouteOptions[key],
|
|
178
|
+
(handler) => wrapHook(key, handler, options, route)
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return original.call(this, nextRouteOptions);
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
patchMethod(
|
|
188
|
+
instance,
|
|
189
|
+
'setErrorHandler',
|
|
190
|
+
'senzor.fastify.setErrorHandler',
|
|
191
|
+
(original) =>
|
|
192
|
+
function patchedFastifySetErrorHandler(this: any, handler: any) {
|
|
193
|
+
return original.call(
|
|
194
|
+
this,
|
|
195
|
+
wrapFrameworkHandlerWithArity(
|
|
196
|
+
handler,
|
|
197
|
+
(_thisArg, args) => {
|
|
198
|
+
const request = args[1];
|
|
199
|
+
const reply = args[2];
|
|
200
|
+
const route = getRoute(request);
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
framework: 'fastify',
|
|
204
|
+
type: 'error_handler',
|
|
205
|
+
name: `fastify.error_handler ${route || request?.url || ''}`.trim(),
|
|
206
|
+
route,
|
|
207
|
+
method: request?.method,
|
|
208
|
+
handlerName: handler?.name || 'errorHandler',
|
|
209
|
+
request,
|
|
210
|
+
response: reply?.raw || reply,
|
|
211
|
+
attributes: {
|
|
212
|
+
'fastify.type': 'error_handler',
|
|
213
|
+
error: args[0]?.message,
|
|
214
|
+
'error.type': args[0]?.name || typeof args[0]
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
},
|
|
218
|
+
options,
|
|
219
|
+
{
|
|
220
|
+
callbackCompletesSpan: true,
|
|
221
|
+
responseEndsSpan: true
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
return instance;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const copyFactoryProperties = (
|
|
232
|
+
source: any,
|
|
233
|
+
target: any
|
|
234
|
+
) => {
|
|
235
|
+
for (const key of Reflect.ownKeys(source)) {
|
|
236
|
+
if (['length', 'name', 'prototype'].includes(String(key))) continue;
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)!);
|
|
240
|
+
} catch { }
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const wrapFastifyFactory = (
|
|
245
|
+
factory: any,
|
|
246
|
+
options?: SenzorOptions
|
|
247
|
+
) => {
|
|
248
|
+
if (typeof factory !== 'function' || factory[FACTORY_PATCHED]) {
|
|
249
|
+
return factory;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const wrapped = function senzorFastifyFactory(this: unknown, ...args: any[]) {
|
|
253
|
+
const instance = factory.apply(this, args);
|
|
254
|
+
return patchFastifyInstance(instance, options);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
copyFactoryProperties(factory, wrapped);
|
|
258
|
+
|
|
259
|
+
Object.defineProperty(wrapped, FACTORY_PATCHED, {
|
|
260
|
+
value: true,
|
|
261
|
+
enumerable: false
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const mutableWrapped = wrapped as any;
|
|
265
|
+
|
|
266
|
+
if (mutableWrapped.fastify === factory) {
|
|
267
|
+
mutableWrapped.fastify = mutableWrapped;
|
|
268
|
+
}
|
|
269
|
+
if (mutableWrapped.default === factory) {
|
|
270
|
+
mutableWrapped.default = mutableWrapped;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return mutableWrapped;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
export const instrumentFastify = (options?: SenzorOptions) => {
|
|
277
|
+
hookRequire('fastify', (exports: any) => {
|
|
278
|
+
if (typeof exports === 'function') {
|
|
279
|
+
return wrapFastifyFactory(exports, options);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (exports?.fastify) {
|
|
283
|
+
exports.fastify = wrapFastifyFactory(exports.fastify, options);
|
|
284
|
+
}
|
|
285
|
+
if (exports?.default) {
|
|
286
|
+
exports.default = wrapFastifyFactory(exports.default, options);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return exports;
|
|
290
|
+
});
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export const instrumentFastifyInstance = (
|
|
294
|
+
instance: any,
|
|
295
|
+
options?: SenzorOptions
|
|
296
|
+
) => patchFastifyInstance(instance, options);
|