@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,301 @@
1
+ import { Context } from '../core/context';
2
+ import { SenzorOptions } from '../core/types';
3
+ import { CapturedSpan, runWithCapturedSpan, startCapturedSpan } from './span';
4
+
5
+ export type FrameworkSpanType =
6
+ | 'middleware'
7
+ | 'router'
8
+ | 'request_handler'
9
+ | 'route_handler'
10
+ | 'controller_handler'
11
+ | 'lifecycle_hook'
12
+ | 'error_handler'
13
+ | 'event_handler';
14
+
15
+ export interface FrameworkSpanInfo {
16
+ framework: string;
17
+ type: FrameworkSpanType;
18
+ name: string;
19
+ route?: string;
20
+ method?: string;
21
+ layerPath?: string;
22
+ handlerName?: string;
23
+ request?: any;
24
+ response?: any;
25
+ attributes?: Record<string, unknown>;
26
+ }
27
+
28
+ interface InvokeOptions {
29
+ callbackIndex?: number;
30
+ callbackCompletesSpan?: boolean;
31
+ responseEndsSpan?: boolean;
32
+ }
33
+
34
+ const ignoredNextValues = new Set([undefined, null, 'route', 'router']);
35
+
36
+ export const shouldCaptureFrameworkSpan = (
37
+ type: FrameworkSpanType,
38
+ options?: SenzorOptions
39
+ ): boolean => {
40
+ if (options?.frameworkSpans === false) return false;
41
+ if (type === 'middleware' && options?.captureMiddlewareSpans === false) return false;
42
+ if (type === 'router' && options?.captureRouterSpans === false) return false;
43
+ if (type === 'lifecycle_hook' && options?.captureLifecycleHookSpans === false) return false;
44
+ if (options?.ignoreFrameworkSpanTypes?.includes(type)) return false;
45
+ return true;
46
+ };
47
+
48
+ const statusFrom = (
49
+ info: FrameworkSpanInfo,
50
+ fallback = 0
51
+ ): number => {
52
+ const res = info.response;
53
+ return (
54
+ res?.statusCode ||
55
+ res?.status ||
56
+ res?.raw?.statusCode ||
57
+ res?.status_code ||
58
+ fallback
59
+ );
60
+ };
61
+
62
+ const isPromiseLike = (value: unknown): value is Promise<unknown> =>
63
+ Boolean(value && typeof (value as any).then === 'function');
64
+
65
+ const runWithParentOf = <T>(
66
+ span: CapturedSpan,
67
+ fn: () => T
68
+ ): T => {
69
+ if (!span.parentSpanId) return fn();
70
+ return Context.withActiveSpan(span.parentSpanId, fn);
71
+ };
72
+
73
+ const copyFunctionProperties = (
74
+ source: Function,
75
+ target: Function
76
+ ) => {
77
+ for (const key in source as any) {
78
+ try {
79
+ Object.defineProperty(target, key, {
80
+ configurable: true,
81
+ enumerable: true,
82
+ get() {
83
+ return (source as any)[key];
84
+ },
85
+ set(value) {
86
+ (source as any)[key] = value;
87
+ }
88
+ });
89
+ } catch { }
90
+ }
91
+ };
92
+
93
+ export const invokeWithFrameworkSpan = (
94
+ handler: Function,
95
+ thisArg: unknown,
96
+ args: any[],
97
+ info: FrameworkSpanInfo,
98
+ options?: SenzorOptions,
99
+ invokeOptions: InvokeOptions = {}
100
+ ) => {
101
+ if (!shouldCaptureFrameworkSpan(info.type, options) || !Context.current()) {
102
+ return handler.apply(thisArg, args);
103
+ }
104
+
105
+ const span = startCapturedSpan(
106
+ info.name,
107
+ 'function',
108
+ {
109
+ framework: info.framework,
110
+ 'senzor.framework': info.framework,
111
+ 'senzor.framework.type': info.type,
112
+ 'http.route': info.route,
113
+ route: info.route,
114
+ method: info.method,
115
+ layerPath: info.layerPath,
116
+ handlerName: info.handlerName,
117
+ ...info.attributes
118
+ },
119
+ options
120
+ );
121
+
122
+ if (!span) return handler.apply(thisArg, args);
123
+
124
+ let ended = false;
125
+ const cleanup: Array<() => void> = [];
126
+
127
+ const endSpan = (
128
+ status = statusFrom(info),
129
+ meta: Record<string, unknown> = {}
130
+ ) => {
131
+ if (ended) return;
132
+ ended = true;
133
+
134
+ for (const clean of cleanup) {
135
+ try { clean(); } catch { }
136
+ }
137
+
138
+ span.end(status, meta);
139
+ };
140
+
141
+ const res = info.response;
142
+ if (invokeOptions.responseEndsSpan !== false && res?.once) {
143
+ const onFinish = () =>
144
+ endSpan(statusFrom(info), { completion: 'response.finish' });
145
+ const onClose = () =>
146
+ endSpan(statusFrom(info), { completion: 'response.close' });
147
+
148
+ res.once('finish', onFinish);
149
+ res.once('close', onClose);
150
+
151
+ cleanup.push(() => {
152
+ try { res.removeListener?.('finish', onFinish); } catch { }
153
+ try { res.removeListener?.('close', onClose); } catch { }
154
+ });
155
+ }
156
+
157
+ const callbackIndex =
158
+ invokeOptions.callbackIndex ??
159
+ args.findIndex((arg) => typeof arg === 'function');
160
+
161
+ if (
162
+ invokeOptions.callbackCompletesSpan !== false &&
163
+ callbackIndex >= 0 &&
164
+ typeof args[callbackIndex] === 'function'
165
+ ) {
166
+ const originalCallback = args[callbackIndex];
167
+ args[callbackIndex] = function wrappedFrameworkCallback(
168
+ this: unknown,
169
+ ...callbackArgs: any[]
170
+ ) {
171
+ const maybeError = callbackArgs[0];
172
+ const hasError = !ignoredNextValues.has(maybeError);
173
+
174
+ endSpan(hasError ? 500 : statusFrom(info), {
175
+ completion: 'callback',
176
+ error: hasError ? String(maybeError?.message || maybeError) : undefined,
177
+ 'error.type': hasError
178
+ ? maybeError?.name || typeof maybeError
179
+ : undefined
180
+ });
181
+
182
+ return runWithParentOf(span, () =>
183
+ originalCallback.apply(this, callbackArgs)
184
+ );
185
+ };
186
+ }
187
+
188
+ return runWithCapturedSpan(span, () => {
189
+ try {
190
+ const result = handler.apply(thisArg, args);
191
+
192
+ if (isPromiseLike(result)) {
193
+ return result.then(
194
+ (value) => {
195
+ endSpan(statusFrom(info), { completion: 'promise.resolve' });
196
+ return value;
197
+ },
198
+ (error) => {
199
+ endSpan(500, {
200
+ completion: 'promise.reject',
201
+ error: error?.message,
202
+ 'error.type': error?.name || 'Error'
203
+ });
204
+ throw error;
205
+ }
206
+ );
207
+ }
208
+
209
+ if (callbackIndex < 0 && invokeOptions.responseEndsSpan === false) {
210
+ endSpan(statusFrom(info), { completion: 'sync.return' });
211
+ }
212
+
213
+ return result;
214
+ } catch (error: any) {
215
+ endSpan(500, {
216
+ completion: 'throw',
217
+ error: error?.message,
218
+ 'error.type': error?.name || 'Error'
219
+ });
220
+ throw error;
221
+ }
222
+ });
223
+ };
224
+
225
+ export const wrapFrameworkHandler = <T extends Function>(
226
+ handler: T,
227
+ getInfo: (thisArg: unknown, args: any[]) => FrameworkSpanInfo,
228
+ options?: SenzorOptions,
229
+ invokeOptions: InvokeOptions = {}
230
+ ): T => {
231
+ if (typeof handler !== 'function') return handler;
232
+
233
+ const wrapped = function wrappedFrameworkHandler(
234
+ this: unknown,
235
+ ...args: any[]
236
+ ) {
237
+ return invokeWithFrameworkSpan(
238
+ handler,
239
+ this,
240
+ args,
241
+ getInfo(this, args),
242
+ options,
243
+ invokeOptions
244
+ );
245
+ };
246
+
247
+ copyFunctionProperties(handler, wrapped);
248
+ return wrapped as unknown as T;
249
+ };
250
+
251
+ export const wrapFrameworkHandlerWithArity = <T extends Function>(
252
+ handler: T,
253
+ getInfo: (thisArg: unknown, args: any[]) => FrameworkSpanInfo,
254
+ options?: SenzorOptions,
255
+ invokeOptions: InvokeOptions = {}
256
+ ): T => {
257
+ if (typeof handler !== 'function') return handler;
258
+
259
+ const invoke = (thisArg: unknown, args: any[]) =>
260
+ invokeWithFrameworkSpan(
261
+ handler,
262
+ thisArg,
263
+ args,
264
+ getInfo(thisArg, args),
265
+ options,
266
+ invokeOptions
267
+ );
268
+
269
+ let wrapped: Function;
270
+
271
+ switch (handler.length) {
272
+ case 4:
273
+ wrapped = function wrapped4(this: unknown, a: any, b: any, c: any, d: any) {
274
+ return invoke(this, [a, b, c, d]);
275
+ };
276
+ break;
277
+ case 3:
278
+ wrapped = function wrapped3(this: unknown, a: any, b: any, c: any) {
279
+ return invoke(this, [a, b, c]);
280
+ };
281
+ break;
282
+ case 2:
283
+ wrapped = function wrapped2(this: unknown, a: any, b: any) {
284
+ return invoke(this, [a, b]);
285
+ };
286
+ break;
287
+ case 1:
288
+ wrapped = function wrapped1(this: unknown, a: any) {
289
+ return invoke(this, [a]);
290
+ };
291
+ break;
292
+ default:
293
+ wrapped = function wrapped0(this: unknown) {
294
+ return invoke(this, Array.from(arguments));
295
+ };
296
+ break;
297
+ }
298
+
299
+ copyFunctionProperties(handler, wrapped);
300
+ return wrapped as unknown as T;
301
+ };
@@ -1,247 +1,134 @@
1
1
  import Module from 'module';
2
2
 
3
- const SENZOR_PATCHED =
4
- Symbol.for('senzor.require.patched');
3
+ const SENZOR_PATCHED = Symbol.for('senzor.require.patched');
4
+ const SENZOR_HOOKS = Symbol.for('senzor.require.hooks');
5
5
 
6
- const SENZOR_HOOKS =
7
- Symbol.for('senzor.require.hooks');
6
+ type HookFn = (exports: unknown) => unknown | void;
7
+ type HookMap = Map<string, HookFn[]>;
8
8
 
9
- type HookFn =
10
- (exports: unknown) => void;
11
-
12
- type HookMap =
13
- Map<string, HookFn[]>;
9
+ // Module.createRequire works in both CJS and ESM contexts,
10
+ // unlike bare `require` which is unavailable in ESM builds.
11
+ const safeRequire: NodeRequire = Module.createRequire(
12
+ typeof __filename !== 'undefined'
13
+ ? __filename
14
+ : process.cwd() + '/'
15
+ );
14
16
 
15
17
  function getHookRegistry(): HookMap {
16
-
17
- const mod =
18
- Module as unknown as Record<
19
- symbol,
20
- HookMap
21
- >;
18
+ const mod = Module as unknown as Record<symbol, HookMap>;
22
19
 
23
20
  if (!mod[SENZOR_HOOKS]) {
24
-
25
- Object.defineProperty(
26
- mod,
27
- SENZOR_HOOKS,
28
- {
29
- value: new Map(),
30
- enumerable: false
31
- }
32
- );
33
-
21
+ Object.defineProperty(mod, SENZOR_HOOKS, {
22
+ value: new Map(),
23
+ enumerable: false
24
+ });
34
25
  }
35
26
 
36
27
  return mod[SENZOR_HOOKS];
37
-
38
28
  }
39
29
 
40
- function runHooks(
41
- moduleName: string,
42
- exports: unknown
43
- ) {
44
-
45
- const registry =
46
- (Module as unknown as Record<
47
- symbol,
48
- HookMap
49
- >)[SENZOR_HOOKS];
30
+ function runHooks(moduleName: string, exports: unknown) {
31
+ const registry = (Module as unknown as Record<symbol, HookMap>)[SENZOR_HOOKS];
32
+ if (!registry) return exports;
50
33
 
51
- if (!registry) return;
34
+ const hooks = registry.get(moduleName);
35
+ if (!hooks?.length) return exports;
52
36
 
53
- const hooks =
54
- registry.get(moduleName);
55
-
56
- if (!hooks?.length) return;
37
+ let currentExports = exports;
57
38
 
58
39
  for (const hook of hooks) {
59
-
60
40
  try {
61
- hook(exports);
62
- }
63
- catch (err) {
64
-
65
- console.error(
66
- `[Senzor] instrumentation failed for ${moduleName}`,
67
- err
68
- );
69
-
41
+ const nextExports = hook(currentExports);
42
+ if (nextExports !== undefined) {
43
+ currentExports = nextExports;
44
+ }
45
+ } catch (err) {
46
+ console.error(`[Senzor] instrumentation failed for ${moduleName}`, err);
70
47
  }
71
-
72
48
  }
73
49
 
50
+ return currentExports;
74
51
  }
75
52
 
76
53
  function patchLoaderOnce() {
54
+ const mod = Module as unknown as any;
77
55
 
78
- const mod =
79
- Module as unknown as any;
56
+ if (mod[SENZOR_PATCHED]) return;
80
57
 
81
- if (mod[SENZOR_PATCHED]) {
82
- return;
83
- }
58
+ const previousLoad = mod._load;
84
59
 
85
- const previousLoad =
86
- mod._load;
87
-
88
- mod._load =
89
- function patchedLoad(
90
- request: string,
91
- parent: unknown,
92
- isMain: boolean
93
- ) {
94
-
95
- const exports =
96
- previousLoad.apply(
97
- this,
98
- arguments
99
- );
100
-
101
- runHooks(
102
- request,
103
- exports
104
- );
105
-
106
- return exports;
107
-
108
- };
109
-
110
- Object.defineProperty(
111
- mod,
112
- SENZOR_PATCHED,
113
- {
114
- value: true,
115
- enumerable: false
116
- }
117
- );
60
+ mod._load = function patchedLoad(
61
+ request: string,
62
+ parent: unknown,
63
+ isMain: boolean
64
+ ) {
65
+ const exports = previousLoad.apply(this, arguments);
66
+ return runHooks(request, exports);
67
+ };
118
68
 
69
+ Object.defineProperty(mod, SENZOR_PATCHED, {
70
+ value: true,
71
+ enumerable: false
72
+ });
119
73
  }
120
74
 
121
- function patchCached(
122
- moduleName: string,
123
- hook: HookFn
124
- ) {
125
-
75
+ function patchCached(moduleName: string, hook: HookFn) {
126
76
  try {
127
-
128
- const resolved =
129
- require.resolve(
130
- moduleName
131
- );
132
-
133
- const cached =
134
- require.cache?.[
135
- resolved
136
- ];
77
+ const resolved = safeRequire.resolve(moduleName);
78
+ const cached = safeRequire.cache?.[resolved];
137
79
 
138
80
  if (cached?.exports) {
139
-
140
- hook(
141
- cached.exports
142
- );
143
-
81
+ const replacement = hook(cached.exports);
82
+ if (replacement !== undefined) {
83
+ cached.exports = replacement;
84
+ }
144
85
  }
145
-
146
- }
147
- catch { }
148
-
86
+ } catch { }
149
87
  }
150
88
 
151
- function tryRequire(
152
- moduleName: string,
153
- hook: HookFn
154
- ) {
155
-
89
+ function tryRequire(moduleName: string, hook: HookFn) {
156
90
  try {
157
-
158
- const mod =
159
- require(moduleName);
160
-
91
+ const mod = safeRequire(moduleName);
161
92
  if (mod) {
162
93
  hook(mod);
163
94
  }
164
-
165
- }
166
- catch { }
167
-
95
+ } catch { }
168
96
  }
169
97
 
170
- function retryPatch(
171
- moduleName: string,
172
- hook: HookFn
173
- ) {
174
-
98
+ function retryPatch(moduleName: string, hook: HookFn) {
175
99
  let attempts = 0;
176
-
177
100
  const max = 5;
178
101
 
179
- const timer =
180
- setInterval(() => {
181
-
182
- attempts++;
183
-
184
- try {
185
-
186
- const mod =
187
- require(moduleName);
188
-
189
- if (mod) {
190
-
191
- hook(mod);
102
+ const timer = setInterval(() => {
103
+ attempts++;
192
104
 
193
- clearInterval(timer);
194
-
195
- }
196
-
197
- }
198
- catch { }
199
-
200
- if (attempts >= max) {
105
+ try {
106
+ const mod = safeRequire(moduleName);
107
+ if (mod) {
108
+ hook(mod);
201
109
  clearInterval(timer);
202
110
  }
111
+ } catch { }
203
112
 
204
- }, 200);
205
-
206
- }
207
-
208
- export const hookRequire =
209
- (
210
- moduleName: string,
211
- onRequire: HookFn
212
- ) => {
213
-
214
- const registry =
215
- getHookRegistry();
216
-
217
- if (!registry.has(moduleName)) {
218
-
219
- registry.set(
220
- moduleName,
221
- []
222
- );
223
-
113
+ if (attempts >= max) {
114
+ clearInterval(timer);
224
115
  }
116
+ }, 200);
225
117
 
226
- registry
227
- .get(moduleName)!
228
- .push(onRequire);
229
-
230
- patchLoaderOnce();
118
+ if (typeof timer.unref === 'function') timer.unref();
119
+ }
231
120
 
232
- patchCached(
233
- moduleName,
234
- onRequire
235
- );
121
+ export const hookRequire = (moduleName: string, onRequire: HookFn) => {
122
+ const registry = getHookRegistry();
236
123
 
237
- tryRequire(
238
- moduleName,
239
- onRequire
240
- );
124
+ if (!registry.has(moduleName)) {
125
+ registry.set(moduleName, []);
126
+ }
241
127
 
242
- retryPatch(
243
- moduleName,
244
- onRequire
245
- );
128
+ registry.get(moduleName)!.push(onRequire);
246
129
 
247
- };
130
+ patchLoaderOnce();
131
+ patchCached(moduleName, onRequire);
132
+ tryRequire(moduleName, onRequire);
133
+ retryPatch(moduleName, onRequire);
134
+ };