@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
@@ -0,0 +1,173 @@
1
+ import { normalizePath } from '../core/normalizer';
2
+ import { SenzorOptions } from '../core/types';
3
+ import { hookRequire } from './hook';
4
+ import { patchMethod } from './patch';
5
+ import { wrapFrameworkHandlerWithArity } from './framework';
6
+
7
+ const routerMethods = [
8
+ 'all',
9
+ 'del',
10
+ 'delete',
11
+ 'get',
12
+ 'head',
13
+ 'options',
14
+ 'patch',
15
+ 'post',
16
+ 'put'
17
+ ];
18
+
19
+ const stringifyPath = (value: unknown): string | undefined => {
20
+ if (typeof value === 'string') return value;
21
+ if (value instanceof RegExp) return value.toString();
22
+ if (Array.isArray(value)) {
23
+ return value.map(stringifyPath).filter(Boolean).join(',');
24
+ }
25
+ return undefined;
26
+ };
27
+
28
+ const getPathFromArgs = (args: any[]): string | undefined => {
29
+ for (const arg of args) {
30
+ if (typeof arg === 'function') return undefined;
31
+ const path = stringifyPath(arg);
32
+ if (path) return path;
33
+ }
34
+
35
+ return undefined;
36
+ };
37
+
38
+ const wrapKoaMiddleware = (
39
+ middleware: any,
40
+ options?: SenzorOptions,
41
+ layerPath?: string,
42
+ layerType: 'middleware' | 'router' | 'route_handler' = 'middleware',
43
+ method?: string
44
+ ) => {
45
+ if (typeof middleware !== 'function') return middleware;
46
+
47
+ return wrapFrameworkHandlerWithArity(
48
+ middleware,
49
+ (_thisArg, args) => {
50
+ const ctx = args[0];
51
+ const route =
52
+ ctx?._matchedRoute ||
53
+ ctx?.matched?.[0]?.path ||
54
+ layerPath ||
55
+ normalizePath(ctx?.path || ctx?.request?.path || '/');
56
+ const actualMethod = method || ctx?.method || ctx?.request?.method;
57
+ const handlerName = middleware.name || layerType;
58
+
59
+ return {
60
+ framework: 'koa',
61
+ type: layerType,
62
+ name:
63
+ layerType === 'route_handler'
64
+ ? `koa.request_handler ${actualMethod || ''} ${route}`.trim()
65
+ : `koa.${layerType} ${route || handlerName}`,
66
+ route,
67
+ method: actualMethod,
68
+ layerPath,
69
+ handlerName,
70
+ request: ctx?.req || ctx?.request,
71
+ response: ctx?.res || ctx?.response,
72
+ attributes: {
73
+ 'koa.type': layerType,
74
+ 'http.route': route,
75
+ path: ctx?.path || ctx?.request?.path
76
+ }
77
+ };
78
+ },
79
+ options,
80
+ {
81
+ callbackCompletesSpan: false,
82
+ responseEndsSpan: false
83
+ }
84
+ );
85
+ };
86
+
87
+ const patchKoaApplication = (
88
+ koa: any,
89
+ options?: SenzorOptions
90
+ ) => {
91
+ const proto = koa?.prototype || koa?.default?.prototype;
92
+ if (!proto) return;
93
+
94
+ patchMethod(
95
+ proto,
96
+ 'use',
97
+ 'senzor.koa.application.use',
98
+ (original) =>
99
+ function patchedKoaUse(this: any, middleware: any) {
100
+ return original.call(
101
+ this,
102
+ wrapKoaMiddleware(middleware, options, undefined, 'middleware')
103
+ );
104
+ }
105
+ );
106
+ };
107
+
108
+ const patchKoaRouter = (
109
+ routerModule: any,
110
+ options?: SenzorOptions
111
+ ) => {
112
+ const Router =
113
+ routerModule?.Router ||
114
+ routerModule?.default ||
115
+ routerModule;
116
+ const proto = Router?.prototype;
117
+ if (!proto) return;
118
+
119
+ patchMethod(
120
+ proto,
121
+ 'use',
122
+ 'senzor.koa.router.use',
123
+ (original) =>
124
+ function patchedKoaRouterUse(this: any, ...args: any[]) {
125
+ const layerPath = getPathFromArgs(args);
126
+ const nextArgs = args.map((arg) =>
127
+ typeof arg === 'function'
128
+ ? wrapKoaMiddleware(arg, options, layerPath, 'router')
129
+ : arg
130
+ );
131
+ return original.apply(this, nextArgs);
132
+ }
133
+ );
134
+
135
+ for (const method of routerMethods) {
136
+ patchMethod(
137
+ proto,
138
+ method,
139
+ `senzor.koa.router.${method}`,
140
+ (original) =>
141
+ function patchedKoaRouterMethod(this: any, ...args: any[]) {
142
+ const layerPath = getPathFromArgs(args);
143
+ const nextArgs = args.map((arg) =>
144
+ typeof arg === 'function'
145
+ ? wrapKoaMiddleware(
146
+ arg,
147
+ options,
148
+ layerPath,
149
+ 'route_handler',
150
+ method.toUpperCase()
151
+ )
152
+ : arg
153
+ );
154
+
155
+ return original.apply(this, nextArgs);
156
+ }
157
+ );
158
+ }
159
+ };
160
+
161
+ export const instrumentKoa = (options?: SenzorOptions) => {
162
+ hookRequire('koa', (exports: any) => {
163
+ patchKoaApplication(exports, options);
164
+ });
165
+
166
+ hookRequire('@koa/router', (exports: any) => {
167
+ patchKoaRouter(exports, options);
168
+ });
169
+
170
+ hookRequire('koa-router', (exports: any) => {
171
+ patchKoaRouter(exports, options);
172
+ });
173
+ };
package/src/register.ts CHANGED
@@ -31,6 +31,22 @@ const options = {
31
31
  captureHeaders: truthy(process.env.SENZOR_CAPTURE_HEADERS),
32
32
  captureDbStatement:
33
33
  process.env.SENZOR_CAPTURE_DB_STATEMENT === 'false'
34
+ ? false
35
+ : undefined,
36
+ frameworkSpans:
37
+ process.env.SENZOR_FRAMEWORK_SPANS === 'false'
38
+ ? false
39
+ : undefined,
40
+ captureMiddlewareSpans:
41
+ process.env.SENZOR_CAPTURE_MIDDLEWARE_SPANS === 'false'
42
+ ? false
43
+ : undefined,
44
+ captureRouterSpans:
45
+ process.env.SENZOR_CAPTURE_ROUTER_SPANS === 'false'
46
+ ? false
47
+ : undefined,
48
+ captureLifecycleHookSpans:
49
+ process.env.SENZOR_CAPTURE_LIFECYCLE_HOOK_SPANS === 'false'
34
50
  ? false
35
51
  : undefined
36
52
  };
@@ -1,11 +1,14 @@
1
- import { client } from '../core/client';
2
- import { SenzorOptions } from '../core/types';
3
- 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';
4
5
 
5
6
  export const senzorPlugin = (fastify: any, options: SenzorOptions, done: Function) => {
6
- if (options && options.apiKey) {
7
- client.init(options);
8
- }
7
+ if (options && options.apiKey) {
8
+ client.init(options);
9
+ }
10
+
11
+ instrumentFastifyInstance(fastify, options);
9
12
 
10
13
  fastify.addHook('onRequest', (request: any, reply: any, next: Function) => {
11
14
  client.startTrace({
@@ -29,4 +32,4 @@ export const senzorPlugin = (fastify: any, options: SenzorOptions, done: Functio
29
32
  });
30
33
 
31
34
  done();
32
- };
35
+ };
@@ -1,6 +1,7 @@
1
- import { client } from '../core/client';
2
- import { getRoute } from '../core/normalizer';
3
- 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';
4
5
 
5
6
  type EventHandler = (event: any) => any;
6
7
 
@@ -15,21 +16,44 @@ export const wrapH3 = (handler: EventHandler) => {
15
16
  ip: getClientIp(req),
16
17
  userAgent: req.headers['user-agent'],
17
18
  headers: req.headers // Pass headers
18
- }, async () => {
19
- try {
20
- const response = await handler(event);
21
- let status = 200;
22
- if (event.node.res.statusCode) status = event.node.res.statusCode;
23
- if (response && response.statusCode) status = response.statusCode;
24
-
25
- client.endTrace(status, { route: getRoute(event, path) });
26
- return response;
27
- } catch (err: any) {
28
- client.captureError(err);
29
- 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
+ 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;
30
54
  client.endTrace(status, { route: getRoute(event, path) });
31
55
  throw err;
32
56
  }
33
57
  });
34
58
  };
35
- };
59
+ };
@@ -1,6 +1,7 @@
1
- import { client } from '../core/client';
2
- import { normalizePath } from '../core/normalizer';
3
- 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';
4
5
 
5
6
  // --- App Router Wrapper ---
6
7
  export const wrapNextRoute = (handler: Function) => {
@@ -37,16 +38,39 @@ export const wrapNextRoute = (handler: Function) => {
37
38
  userAgent: ua,
38
39
  ip: ip || getClientIp(req),
39
40
  headers: headers // Pass extracted headers
40
- }, async () => {
41
- try {
42
- const response = await handler(req, context);
43
- const status = response?.status || 200;
44
-
45
- client.endTrace(status, { route: normalizePath(url.pathname) });
46
- return response;
47
- } catch (err: any) {
48
- client.captureError(err);
49
- 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
+ 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) });
50
74
  throw err;
51
75
  }
52
76
  });
@@ -71,14 +95,37 @@ export const wrapNextPages = (handler: Function) => {
71
95
  };
72
96
 
73
97
  res.once('finish', done);
74
- res.once('close', done);
75
-
76
- try {
77
- return await handler(req, res);
78
- } catch (e: any) {
79
- client.captureError(e);
80
- throw e;
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;
81
128
  }
82
129
  });
83
130
  };
84
- };
131
+ };
package/wiki.md CHANGED
@@ -82,6 +82,10 @@ Senzor.init({
82
82
  maxAttributes: 64,
83
83
  captureHeaders: false,
84
84
  captureDbStatement: true,
85
+ frameworkSpans: true,
86
+ captureMiddlewareSpans: true,
87
+ captureRouterSpans: true,
88
+ captureLifecycleHookSpans: true,
85
89
  autoLogs: true,
86
90
  debug: false
87
91
  });
@@ -133,6 +137,10 @@ SENZOR_MAX_QUEUE_SIZE=10000
133
137
  SENZOR_MAX_SPANS_PER_TRACE=500
134
138
  SENZOR_CAPTURE_HEADERS=false
135
139
  SENZOR_CAPTURE_DB_STATEMENT=true
140
+ SENZOR_FRAMEWORK_SPANS=true
141
+ SENZOR_CAPTURE_MIDDLEWARE_SPANS=true
142
+ SENZOR_CAPTURE_ROUTER_SPANS=true
143
+ SENZOR_CAPTURE_LIFECYCLE_HOOK_SPANS=true
136
144
  SENZOR_AUTO_LOGS=true
137
145
  SENZOR_DEBUG=false
138
146
  ```