@senzops/apm-node 1.2.3 → 1.2.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.
@@ -57,18 +57,24 @@ const getRequestRoute = (
57
57
  layer: any,
58
58
  layerPath?: string
59
59
  ): string | undefined => {
60
- const routePath = stringifyPath(layer?.route?.path);
61
- if (routePath) {
60
+ if (layer?.route?.path) {
61
+ const routePath = stringifyPath(layer.route.path);
62
62
  const baseUrl = req?.baseUrl || '';
63
+ // If baseUrl already ends with or contains the routePath, be careful
63
64
  return `${baseUrl}${routePath}` || routePath;
64
65
  }
65
66
 
66
67
  if (req?.route?.path) {
67
- return `${req.baseUrl || ''}${req.route.path}`;
68
+ const baseUrl = req?.baseUrl || '';
69
+ return `${baseUrl}${req.route.path}`;
68
70
  }
69
71
 
70
72
  if (layerPath) {
71
73
  const baseUrl = req?.baseUrl || '';
74
+ // If baseUrl already contains layerPath, don't double it
75
+ if (baseUrl === layerPath || baseUrl.endsWith(layerPath)) {
76
+ return baseUrl;
77
+ }
72
78
  return `${baseUrl}${layerPath}` || layerPath;
73
79
  }
74
80
 
@@ -84,9 +90,14 @@ const getLayerType = (
84
90
  if (forcedType) return forcedType;
85
91
  if (original.length === 4) return 'error_handler' as const;
86
92
  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
- }
93
+
94
+ const isRouter =
95
+ layer?.name === 'router' ||
96
+ layer?.handle?.name === 'router' ||
97
+ typeof layer?.handle?.stack !== 'undefined' ||
98
+ typeof layer?.handle?.route === 'function';
99
+
100
+ if (isRouter) return 'router' as const;
90
101
  return 'middleware' as const;
91
102
  };
92
103
 
@@ -105,6 +116,7 @@ const copyEnumerableProperties = (
105
116
  source: Function,
106
117
  target: Function
107
118
  ) => {
119
+ // Copy enumerable properties
108
120
  for (const key in source as any) {
109
121
  try {
110
122
  Object.defineProperty(target, key, {
@@ -119,6 +131,13 @@ const copyEnumerableProperties = (
119
131
  });
120
132
  } catch { }
121
133
  }
134
+
135
+ // Ensure 'stack' is copied even if not enumerable (though it usually is)
136
+ if ((source as any).stack && !(target as any).stack) {
137
+ try {
138
+ (target as any).stack = (source as any).stack;
139
+ } catch { }
140
+ }
122
141
  };
123
142
 
124
143
  const patchLayer = (
@@ -145,7 +164,7 @@ const patchLayer = (
145
164
  const handlerName =
146
165
  original.name ||
147
166
  layer.name ||
148
- layerType;
167
+ (layerType === 'request_handler' ? 'handler' : layerType);
149
168
 
150
169
  if (original.length === 4) {
151
170
  const wrapped = function senzorExpressErrorHandler(
@@ -264,7 +283,7 @@ const patchRouteMethodHandlers = (
264
283
  const stack = this?.stack || [];
265
284
 
266
285
  for (const layer of stack) {
267
- patchLayer(layer, routePath, options, 'request_handler');
286
+ patchLayer(layer, routePath, options, 'request_handler');
268
287
  }
269
288
 
270
289
  return result;
@@ -277,10 +296,13 @@ const getSafeRouter = (app: any) => {
277
296
  if (!app) return undefined;
278
297
  if (app._router) return app._router;
279
298
  try {
280
- return app.router;
281
- } catch {
282
- return undefined;
283
- }
299
+ // In Express 4, app.router is a getter that throws.
300
+ // We only want it if it's not a throwing getter (Express 3)
301
+ // or if it actually has a stack.
302
+ const r = app.router;
303
+ if (r && (r.stack || typeof r === 'function')) return r;
304
+ } catch { }
305
+ return undefined;
284
306
  };
285
307
 
286
308
  const patchExpress = (
@@ -290,7 +312,7 @@ const patchExpress = (
290
312
  if (!expressModule) return;
291
313
 
292
314
  const routerProto =
293
- typeof expressModule?.Router?.prototype?.route === 'function'
315
+ typeof expressModule?.Router?.prototype?.use === 'function'
294
316
  ? expressModule.Router.prototype
295
317
  : expressModule.Router;
296
318
 
@@ -302,8 +324,9 @@ const patchExpress = (
302
324
  function patchedExpressRoute(this: any, ...args: any[]) {
303
325
  const route = original.apply(this, args);
304
326
  const routePath = getLayerPath(args);
305
- const layer = this?.stack?.[this.stack.length - 1];
327
+ const stack = this?.stack || [];
306
328
 
329
+ const layer = stack[stack.length - 1];
307
330
  patchLayer(layer, routePath, options, 'router');
308
331
  patchRouteMethodHandlers(route, routePath, options);
309
332
 
@@ -317,9 +340,13 @@ const patchExpress = (
317
340
  'senzor.express.router.use',
318
341
  (original) =>
319
342
  function patchedExpressRouterUse(this: any, ...args: any[]) {
343
+ const prevStackLength = this?.stack?.length || 0;
320
344
  const result = original.apply(this, args);
321
- const layer = this?.stack?.[this.stack.length - 1];
322
- patchLayer(layer, getLayerPath(args), options);
345
+ const stack = this?.stack || [];
346
+
347
+ for (let i = prevStackLength; i < stack.length; i++) {
348
+ patchLayer(stack[i], getLayerPath(args), options);
349
+ }
323
350
  return result;
324
351
  }
325
352
  );
@@ -330,11 +357,17 @@ const patchExpress = (
330
357
  'senzor.express.application.use',
331
358
  (original) =>
332
359
  function patchedExpressApplicationUse(this: any, ...args: any[]) {
333
- const router = getSafeRouter(this);
360
+ const routerBefore = getSafeRouter(this);
361
+ const prevStackLength = routerBefore?.stack?.length || 0;
362
+
334
363
  const result = original.apply(this, args);
335
- const activeRouter = getSafeRouter(this) || router;
336
- const layer = activeRouter?.stack?.[activeRouter.stack.length - 1];
337
- patchLayer(layer, getLayerPath(args), options);
364
+
365
+ const routerAfter = getSafeRouter(this);
366
+ const stack = routerAfter?.stack || [];
367
+
368
+ for (let i = prevStackLength; i < stack.length; i++) {
369
+ patchLayer(stack[i], getLayerPath(args), options);
370
+ }
338
371
  return result;
339
372
  }
340
373
  );
@@ -259,7 +259,7 @@ const patchIncomingServer = (
259
259
  finalized = true;
260
260
 
261
261
  setImmediate(() => {
262
- if (trace.ended) return;
262
+ if (trace.state.ended) return;
263
263
 
264
264
  Context.run(trace, () => {
265
265
  client.endTrace(res.statusCode || 0, {
@@ -3,7 +3,7 @@ import { getClientIp } from '../utils/getClientIp';
3
3
 
4
4
  // 1. Request Handler (Place before routes)
5
5
  export const expressMiddleware = () => {
6
- return (req: any, res: any, next: () => void) => {
6
+ return function senzorMiddleware(req: any, res: any, next: () => void) {
7
7
  client.startTrace({
8
8
  method: req.method,
9
9
  path: req.originalUrl || req.url,
@@ -37,7 +37,7 @@ export const expressMiddleware = () => {
37
37
  // 2. Error Handler (Place after routes)
38
38
  // This is required in Express to capture the actual Error Object (Stack Trace)
39
39
  export const expressErrorHandler = () => {
40
- return (err: any, req: any, res: any, next: (err?: any) => void) => {
40
+ return function senzorErrorHandler(err: any, req: any, res: any, next: (err?: any) => void) {
41
41
 
42
42
  // 1. Capture the exception context
43
43
  client.captureError(err);
@@ -1,16 +1,16 @@
1
- import { client } from '../core/client';
2
- import { SenzorOptions } from '../core/types';
3
- import { instrumentFastifyInstance } from '../instrumentation/fastify';
4
- import { getClientIp } from '../utils/getClientIp';
1
+ import { client } from '../core/client';
2
+ import { SenzorOptions } from '../core/types';
3
+ import { instrumentFastifyInstance } from '../instrumentation/fastify';
4
+ import { getClientIp } from '../utils/getClientIp';
5
5
 
6
6
  export const senzorPlugin = (fastify: any, options: SenzorOptions, done: Function) => {
7
- if (options && options.apiKey) {
8
- client.init(options);
9
- }
10
-
11
- instrumentFastifyInstance(fastify, options);
7
+ if (options && options.apiKey) {
8
+ client.init(options);
9
+ }
12
10
 
13
- fastify.addHook('onRequest', (request: any, reply: any, next: Function) => {
11
+ instrumentFastifyInstance(fastify, options);
12
+
13
+ fastify.addHook('onRequest', function senzorOnRequest(request: any, reply: any, next: Function) {
14
14
  client.startTrace({
15
15
  method: request.method,
16
16
  path: request.raw.url || request.url,
@@ -20,16 +20,16 @@ export const senzorPlugin = (fastify: any, options: SenzorOptions, done: Functio
20
20
  }, () => next());
21
21
  });
22
22
 
23
- fastify.addHook('onError', (request: any, reply: any, error: any, next: Function) => {
23
+ fastify.addHook('onError', function senzorOnError(request: any, reply: any, error: any, next: Function) {
24
24
  client.captureError(error);
25
25
  next();
26
26
  });
27
27
 
28
- fastify.addHook('onResponse', (request: any, reply: any, next: Function) => {
28
+ fastify.addHook('onResponse', function senzorOnResponse(request: any, reply: any, next: Function) {
29
29
  const route = request.routeOptions?.url || request.routerPath || 'UNKNOWN';
30
30
  client.endTrace(reply.statusCode, { route });
31
31
  next();
32
32
  });
33
33
 
34
34
  done();
35
- };
35
+ };
@@ -1,7 +1,7 @@
1
- import { client } from '../core/client';
2
- import { getRoute } from '../core/normalizer';
3
- import { invokeWithFrameworkSpan } from '../instrumentation/framework';
4
- import { getClientIp } from '../utils/getClientIp';
1
+ import { client } from '../core/client';
2
+ import { getRoute } from '../core/normalizer';
3
+ import { invokeWithFrameworkSpan } from '../instrumentation/framework';
4
+ import { getClientIp } from '../utils/getClientIp';
5
5
 
6
6
  type EventHandler = (event: any) => any;
7
7
 
@@ -16,44 +16,45 @@ export const wrapH3 = (handler: EventHandler) => {
16
16
  ip: getClientIp(req),
17
17
  userAgent: req.headers['user-agent'],
18
18
  headers: req.headers // Pass headers
19
- }, async () => {
20
- try {
21
- const route = getRoute(event, path);
22
- const response = await invokeWithFrameworkSpan(
23
- handler,
24
- undefined,
25
- [event],
26
- {
27
- framework: 'h3',
28
- type: 'event_handler',
29
- name: `h3.event_handler ${req.method || 'GET'} ${route}`,
30
- route,
31
- method: req.method || 'GET',
32
- request: req,
33
- response: event.node.res,
34
- attributes: {
35
- 'h3.type': 'event_handler',
36
- 'http.route': route
37
- }
38
- },
39
- undefined,
40
- {
41
- callbackCompletesSpan: false,
42
- responseEndsSpan: false
43
- }
44
- );
45
- let status = 200;
46
- if (event.node.res.statusCode) status = event.node.res.statusCode;
47
- if (response && response.statusCode) status = response.statusCode;
48
-
49
- client.endTrace(status, { route });
50
- return response;
51
- } catch (err: any) {
52
- client.captureError(err);
53
- const status = err.statusCode || err.status || 500;
19
+ }, async () => {
20
+ try {
21
+ const route = getRoute(event, path);
22
+ const response = await invokeWithFrameworkSpan(
23
+ handler,
24
+ undefined,
25
+ [event],
26
+ {
27
+ framework: 'h3',
28
+ type: 'event_handler',
29
+ name: `h3.event_handler ${req.method || 'GET'} ${route}`,
30
+ route,
31
+ method: req.method || 'GET',
32
+ handlerName: (handler as any).name || 'handler',
33
+ request: req,
34
+ response: event.node.res,
35
+ attributes: {
36
+ 'h3.type': 'event_handler',
37
+ 'http.route': route
38
+ }
39
+ },
40
+ undefined,
41
+ {
42
+ callbackCompletesSpan: false,
43
+ responseEndsSpan: false
44
+ }
45
+ );
46
+ let status = 200;
47
+ if (event.node.res.statusCode) status = event.node.res.statusCode;
48
+ if (response && response.statusCode) status = response.statusCode;
49
+
50
+ client.endTrace(status, { route });
51
+ return response;
52
+ } catch (err: any) {
53
+ client.captureError(err);
54
+ const status = err.statusCode || err.status || 500;
54
55
  client.endTrace(status, { route: getRoute(event, path) });
55
56
  throw err;
56
57
  }
57
58
  });
58
59
  };
59
- };
60
+ };
@@ -1,7 +1,7 @@
1
- import { client } from '../core/client';
2
- import { normalizePath } from '../core/normalizer';
3
- import { invokeWithFrameworkSpan } from '../instrumentation/framework';
4
- import { getClientIp } from '../utils/getClientIp';
1
+ import { client } from '../core/client';
2
+ import { normalizePath } from '../core/normalizer';
3
+ import { invokeWithFrameworkSpan } from '../instrumentation/framework';
4
+ import { getClientIp } from '../utils/getClientIp';
5
5
 
6
6
  // --- App Router Wrapper ---
7
7
  export const wrapNextRoute = (handler: Function) => {
@@ -38,39 +38,40 @@ export const wrapNextRoute = (handler: Function) => {
38
38
  userAgent: ua,
39
39
  ip: ip || getClientIp(req),
40
40
  headers: headers // Pass extracted headers
41
- }, async () => {
42
- try {
43
- const route = normalizePath(url.pathname);
44
- const response = await invokeWithFrameworkSpan(
45
- handler,
46
- undefined,
47
- [req, context],
48
- {
49
- framework: 'next',
50
- type: 'route_handler',
51
- name: `next.app_route_handler ${method} ${route}`,
52
- route,
53
- method,
54
- request: req,
55
- attributes: {
56
- 'next.router': 'app',
57
- 'http.route': route,
58
- 'url.path': url.pathname
59
- }
60
- },
61
- undefined,
62
- {
63
- callbackCompletesSpan: false,
64
- responseEndsSpan: false
65
- }
66
- );
67
- const status = response?.status || 200;
68
-
69
- client.endTrace(status, { route });
70
- return response;
71
- } catch (err: any) {
72
- client.captureError(err);
73
- client.endTrace(500, { route: normalizePath(url.pathname) });
41
+ }, async () => {
42
+ try {
43
+ const route = normalizePath(url.pathname);
44
+ const response = await invokeWithFrameworkSpan(
45
+ handler,
46
+ undefined,
47
+ [req, context],
48
+ {
49
+ framework: 'next',
50
+ type: 'route_handler',
51
+ name: `next.app_route_handler ${method} ${route}`,
52
+ route,
53
+ method,
54
+ handlerName: (handler as any).name || 'handler',
55
+ request: req,
56
+ attributes: {
57
+ 'next.router': 'app',
58
+ 'http.route': route,
59
+ 'url.path': url.pathname
60
+ }
61
+ },
62
+ undefined,
63
+ {
64
+ callbackCompletesSpan: false,
65
+ responseEndsSpan: false
66
+ }
67
+ );
68
+ const status = response?.status || 200;
69
+
70
+ client.endTrace(status, { route });
71
+ return response;
72
+ } catch (err: any) {
73
+ client.captureError(err);
74
+ client.endTrace(500, { route: normalizePath(url.pathname) });
74
75
  throw err;
75
76
  }
76
77
  });
@@ -95,37 +96,38 @@ export const wrapNextPages = (handler: Function) => {
95
96
  };
96
97
 
97
98
  res.once('finish', done);
98
- res.once('close', done);
99
-
100
- try {
101
- const route = normalizePath(path);
102
- return await invokeWithFrameworkSpan(
103
- handler,
104
- undefined,
105
- [req, res],
106
- {
107
- framework: 'next',
108
- type: 'route_handler',
109
- name: `next.pages_api_handler ${req.method || 'GET'} ${route}`,
110
- route,
111
- method: req.method || 'GET',
112
- request: req,
113
- response: res,
114
- attributes: {
115
- 'next.router': 'pages',
116
- 'http.route': route
117
- }
118
- },
119
- undefined,
120
- {
121
- callbackCompletesSpan: false,
122
- responseEndsSpan: true
123
- }
124
- );
125
- } catch (e: any) {
126
- client.captureError(e);
127
- throw e;
99
+ res.once('close', done);
100
+
101
+ try {
102
+ const route = normalizePath(path);
103
+ return await invokeWithFrameworkSpan(
104
+ handler,
105
+ undefined,
106
+ [req, res],
107
+ {
108
+ framework: 'next',
109
+ type: 'route_handler',
110
+ name: `next.pages_api_handler ${req.method || 'GET'} ${route}`,
111
+ route,
112
+ method: req.method || 'GET',
113
+ handlerName: (handler as any).name || 'handler',
114
+ request: req,
115
+ response: res,
116
+ attributes: {
117
+ 'next.router': 'pages',
118
+ 'http.route': route
119
+ }
120
+ },
121
+ undefined,
122
+ {
123
+ callbackCompletesSpan: false,
124
+ responseEndsSpan: true
125
+ }
126
+ );
127
+ } catch (e: any) {
128
+ client.captureError(e);
129
+ throw e;
128
130
  }
129
131
  });
130
132
  };
131
- };
133
+ };