@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.
Files changed (68) hide show
  1. package/.claude/worktrees/infallible-chatelet-f3fb36/.claude/settings.local.json +9 -0
  2. package/.claude/worktrees/infallible-chatelet-f3fb36/CHANGELOG.md +49 -0
  3. package/.claude/worktrees/infallible-chatelet-f3fb36/README.md +398 -0
  4. package/.claude/worktrees/infallible-chatelet-f3fb36/package-lock.json +1494 -0
  5. package/.claude/worktrees/infallible-chatelet-f3fb36/package.json +42 -0
  6. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/client.ts +451 -0
  7. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/context.ts +48 -0
  8. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/normalizer.ts +44 -0
  9. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/sanitizer.ts +203 -0
  10. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/transport.ts +273 -0
  11. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/types.ts +106 -0
  12. package/.claude/worktrees/infallible-chatelet-f3fb36/src/index.ts +36 -0
  13. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/bullmq.ts +195 -0
  14. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/cron.ts +204 -0
  15. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/express.ts +338 -0
  16. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/fastify.ts +296 -0
  17. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/framework.ts +301 -0
  18. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/hook.ts +134 -0
  19. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/http.ts +530 -0
  20. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/koa.ts +173 -0
  21. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mongo.ts +202 -0
  22. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mongoose.ts +156 -0
  23. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mysql.ts +169 -0
  24. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/patch.ts +56 -0
  25. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/pg.ts +131 -0
  26. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/redis.ts +109 -0
  27. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/span.ts +73 -0
  28. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/undici.ts +189 -0
  29. package/.claude/worktrees/infallible-chatelet-f3fb36/src/middleware/express.ts +48 -0
  30. package/.claude/worktrees/infallible-chatelet-f3fb36/src/register.ts +58 -0
  31. package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/getClientIp.ts +175 -0
  32. package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/ids.ts +7 -0
  33. package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/internal.ts +1 -0
  34. package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/sdkMeta.ts +6 -0
  35. package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/traceContext.ts +44 -0
  36. package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/fastify.ts +35 -0
  37. package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/h3.ts +59 -0
  38. package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/next.ts +131 -0
  39. package/.claude/worktrees/infallible-chatelet-f3fb36/tsconfig.json +15 -0
  40. package/.claude/worktrees/infallible-chatelet-f3fb36/tsup.config.ts +21 -0
  41. package/.claude/worktrees/infallible-chatelet-f3fb36/wiki.md +852 -0
  42. package/CHANGELOG.md +8 -0
  43. package/README.md +12 -0
  44. package/dist/index.d.mts +5 -0
  45. package/dist/index.d.ts +5 -0
  46. package/dist/index.global.js +1 -1
  47. package/dist/index.global.js.map +1 -1
  48. package/dist/index.js +1 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/index.mjs +1 -1
  51. package/dist/index.mjs.map +1 -1
  52. package/dist/register.js +1 -1
  53. package/dist/register.js.map +1 -1
  54. package/dist/register.mjs +1 -1
  55. package/dist/register.mjs.map +1 -1
  56. package/package.json +1 -1
  57. package/src/core/client.ts +8 -2
  58. package/src/core/types.ts +5 -0
  59. package/src/instrumentation/express.ts +338 -0
  60. package/src/instrumentation/fastify.ts +296 -0
  61. package/src/instrumentation/framework.ts +301 -0
  62. package/src/instrumentation/hook.ts +79 -192
  63. package/src/instrumentation/koa.ts +173 -0
  64. package/src/register.ts +16 -0
  65. package/src/wrappers/fastify.ts +10 -7
  66. package/src/wrappers/h3.ts +40 -16
  67. package/src/wrappers/next.ts +68 -21
  68. package/wiki.md +8 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@senzops/apm-node",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Universal APM SDK for Senzor",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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 { instrumentMongo } from '../instrumentation/mongo';
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);