@senzops/apm-node 1.2.4 → 1.2.6
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/dist/index.d.mts +11 -0
- package/dist/index.d.ts +11 -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 +8 -3
- package/src/core/client.ts +37 -39
- package/src/core/context.ts +46 -2
- package/src/core/runtime.ts +15 -0
- package/src/core/transport.ts +2 -0
- package/src/index.ts +15 -7
- package/src/instrumentation/express.ts +53 -20
- package/src/instrumentation/hook.ts +16 -12
- package/src/instrumentation/http.ts +15 -8
- package/src/middleware/express.ts +2 -2
- package/src/register.ts +19 -18
- package/src/utils/getClientIp.ts +190 -175
- package/src/utils/ids.ts +20 -3
- package/src/wrappers/fastify.ts +13 -13
- package/src/wrappers/h3.ts +41 -40
- package/src/wrappers/next.ts +70 -68
- package/src/wrappers/nitro.ts +60 -0
- package/src/wrappers/worker.ts +45 -0
- package/tsup.config.ts +9 -0
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@senzops/apm-node",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "Universal APM SDK for Senzor",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
|
+
"worker": "./dist/index.mjs",
|
|
10
11
|
"require": "./dist/index.js",
|
|
11
12
|
"import": "./dist/index.mjs"
|
|
12
13
|
},
|
|
@@ -26,7 +27,8 @@
|
|
|
26
27
|
"typescript": "^5.0.0"
|
|
27
28
|
},
|
|
28
29
|
"engines": {
|
|
29
|
-
"node": ">=18.0.0"
|
|
30
|
+
"node": ">=18.0.0",
|
|
31
|
+
"bun": ">=1.0.0"
|
|
30
32
|
},
|
|
31
33
|
"keywords": [
|
|
32
34
|
"apm",
|
|
@@ -35,7 +37,10 @@
|
|
|
35
37
|
"node",
|
|
36
38
|
"javascript",
|
|
37
39
|
"api",
|
|
38
|
-
"observability"
|
|
40
|
+
"observability",
|
|
41
|
+
"cloudflare-workers",
|
|
42
|
+
"edge",
|
|
43
|
+
"universal"
|
|
39
44
|
],
|
|
40
45
|
"author": "Senzops",
|
|
41
46
|
"license": "MIT"
|
package/src/core/client.ts
CHANGED
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
import { Transport } from './transport';
|
|
2
2
|
import { Context } from './context';
|
|
3
3
|
import { SenzorOptions, ActiveTrace, TaskRun, SenzorLog } from './types';
|
|
4
|
-
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';
|
|
10
|
-
import { instrumentPg } from '../instrumentation/pg';
|
|
11
|
-
import { instrumentUndici } from '../instrumentation/undici';
|
|
12
|
-
import { instrumentRedis } from '../instrumentation/redis';
|
|
13
|
-
import { instrumentMysql } from '../instrumentation/mysql';
|
|
14
|
-
import { instrumentMongoose } from '../instrumentation/mongoose';
|
|
15
|
-
import { instrumentBullMQ } from '../instrumentation/bullmq';
|
|
16
|
-
import { instrumentNodeCron } from '../instrumentation/cron';
|
|
4
|
+
import { isNode } from './runtime';
|
|
17
5
|
import { SDK_META } from '../utils/sdkMeta';
|
|
18
6
|
import { parseTraceparent } from '../utils/traceContext';
|
|
19
7
|
import { generateSpanId, generateTraceId } from '../utils/ids';
|
|
@@ -72,29 +60,37 @@ export class SenzorClient {
|
|
|
72
60
|
}
|
|
73
61
|
|
|
74
62
|
private installNativeInstrumentations(endpoint: string, debug: boolean) {
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
try { if (this.isInstrumentationEnabled('
|
|
93
|
-
try { if (this.isInstrumentationEnabled('
|
|
94
|
-
|
|
95
|
-
this.
|
|
96
|
-
if (
|
|
63
|
+
if (this.isInstrumented) return;
|
|
64
|
+
|
|
65
|
+
this.setupGlobalErrorHandlers();
|
|
66
|
+
this.setupLogInterception();
|
|
67
|
+
|
|
68
|
+
// Fetch instrumentation works on all runtimes (Workers, Node, Bun, Deno)
|
|
69
|
+
try {
|
|
70
|
+
if (this.isInstrumentationEnabled('fetch')) {
|
|
71
|
+
const { instrumentFetch } = require('../instrumentation/http');
|
|
72
|
+
instrumentFetch(endpoint, this.options || undefined);
|
|
73
|
+
}
|
|
74
|
+
} catch {}
|
|
75
|
+
|
|
76
|
+
// Node-only instrumentations: http, module hooking, db drivers, etc.
|
|
77
|
+
if (isNode()) {
|
|
78
|
+
try { if (this.isInstrumentationEnabled('http')) { const { instrumentHttp } = require('../instrumentation/http'); instrumentHttp(this, endpoint, this.options || undefined); } } catch {}
|
|
79
|
+
try { if (this.isInstrumentationEnabled('express')) { const { instrumentExpress } = require('../instrumentation/express'); instrumentExpress(this.options || undefined); } } catch {}
|
|
80
|
+
try { if (this.isInstrumentationEnabled('fastify')) { const { instrumentFastify } = require('../instrumentation/fastify'); instrumentFastify(this.options || undefined); } } catch {}
|
|
81
|
+
try { if (this.isInstrumentationEnabled('koa')) { const { instrumentKoa } = require('../instrumentation/koa'); instrumentKoa(this.options || undefined); } } catch {}
|
|
82
|
+
try { if (this.isInstrumentationEnabled('undici')) { const { instrumentUndici } = require('../instrumentation/undici'); instrumentUndici(this.options || undefined); } } catch {}
|
|
83
|
+
try { if (this.isInstrumentationEnabled('mongo')) { const { instrumentMongo } = require('../instrumentation/mongo'); instrumentMongo(this.options || undefined); } } catch {}
|
|
84
|
+
try { if (this.isInstrumentationEnabled('mongoose')) { const { instrumentMongoose } = require('../instrumentation/mongoose'); instrumentMongoose(this.options || undefined); } } catch {}
|
|
85
|
+
try { if (this.isInstrumentationEnabled('pg')) { const { instrumentPg } = require('../instrumentation/pg'); instrumentPg(this.options || undefined); } } catch {}
|
|
86
|
+
try { if (this.isInstrumentationEnabled('mysql')) { const { instrumentMysql } = require('../instrumentation/mysql'); instrumentMysql(this.options || undefined); } } catch {}
|
|
87
|
+
try { if (this.isInstrumentationEnabled('redis')) { const { instrumentRedis } = require('../instrumentation/redis'); instrumentRedis(this.options || undefined); } } catch {}
|
|
88
|
+
try { if (this.isInstrumentationEnabled('bullmq')) { const { instrumentBullMQ } = require('../instrumentation/bullmq'); instrumentBullMQ(this, debug); } } catch {}
|
|
89
|
+
try { if (this.isInstrumentationEnabled('cron')) { const { instrumentNodeCron } = require('../instrumentation/cron'); instrumentNodeCron(this, debug); } } catch {}
|
|
97
90
|
}
|
|
91
|
+
|
|
92
|
+
this.isInstrumented = true;
|
|
93
|
+
if (debug) console.log('[Senzor] Auto-instrumentation enabled');
|
|
98
94
|
}
|
|
99
95
|
|
|
100
96
|
// --- Enterprise Auto-Log Interception ---
|
|
@@ -177,6 +173,8 @@ export class SenzorClient {
|
|
|
177
173
|
}
|
|
178
174
|
|
|
179
175
|
private setupGlobalErrorHandlers() {
|
|
176
|
+
if (!isNode()) return;
|
|
177
|
+
|
|
180
178
|
if ((process as any).__senzorGlobalHandlersInstalled) {
|
|
181
179
|
return;
|
|
182
180
|
}
|
|
@@ -338,11 +336,11 @@ export class SenzorClient {
|
|
|
338
336
|
const currentContext = Context.current();
|
|
339
337
|
const triggerTraceId = currentContext?.contextType === 'apm' ? currentContext.id : undefined;
|
|
340
338
|
|
|
341
|
-
const startMemory = process.memoryUsage ? process.memoryUsage().heapUsed : 0;
|
|
342
|
-
const startCpu = process.cpuUsage ? process.cpuUsage() : undefined;
|
|
339
|
+
const startMemory = isNode() && process.memoryUsage ? process.memoryUsage().heapUsed : 0;
|
|
340
|
+
const startCpu = isNode() && process.cpuUsage ? process.cpuUsage() : undefined;
|
|
343
341
|
|
|
344
342
|
const task: ActiveTrace = {
|
|
345
|
-
id:
|
|
343
|
+
id: generateTraceId(),
|
|
346
344
|
contextType: 'task',
|
|
347
345
|
startTime: performance.now(),
|
|
348
346
|
rootSpanId: generateSpanId(),
|
|
@@ -367,7 +365,7 @@ export class SenzorClient {
|
|
|
367
365
|
task.state.ended = true;
|
|
368
366
|
|
|
369
367
|
let resourceMetrics;
|
|
370
|
-
if (process.memoryUsage && task.startMemory !== undefined && process.cpuUsage && task.startCpu) {
|
|
368
|
+
if (isNode() && process.memoryUsage && task.startMemory !== undefined && process.cpuUsage && task.startCpu) {
|
|
371
369
|
const endMemory = process.memoryUsage().heapUsed;
|
|
372
370
|
const cpuDelta = process.cpuUsage(task.startCpu);
|
|
373
371
|
|
package/src/core/context.ts
CHANGED
|
@@ -1,7 +1,51 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
1
|
import { ActiveTrace, Span } from './types';
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
interface IStorage<T> {
|
|
4
|
+
run<R>(store: T, callback: (...args: any[]) => R, ...args: any[]): R;
|
|
5
|
+
getStore(): T | undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
class NaiveStorage<T> implements IStorage<T> {
|
|
9
|
+
private store: T | undefined;
|
|
10
|
+
|
|
11
|
+
run<R>(store: T, callback: (...args: any[]) => R, ...args: any[]): R {
|
|
12
|
+
const prev = this.store;
|
|
13
|
+
this.store = store;
|
|
14
|
+
try {
|
|
15
|
+
return callback(...args);
|
|
16
|
+
} finally {
|
|
17
|
+
this.store = prev;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getStore(): T | undefined {
|
|
22
|
+
return this.store;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const resolveStorage = <T>(): IStorage<T> => {
|
|
27
|
+
if (typeof globalThis !== 'undefined' && (globalThis as any).AsyncLocalStorage) {
|
|
28
|
+
return new (globalThis as any).AsyncLocalStorage();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
if (typeof require !== 'undefined') {
|
|
33
|
+
const { AsyncLocalStorage } = require('node:async_hooks');
|
|
34
|
+
if (AsyncLocalStorage) return new AsyncLocalStorage();
|
|
35
|
+
}
|
|
36
|
+
} catch {}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
if (typeof require !== 'undefined') {
|
|
40
|
+
const { AsyncLocalStorage } = require('async_hooks');
|
|
41
|
+
if (AsyncLocalStorage) return new AsyncLocalStorage();
|
|
42
|
+
}
|
|
43
|
+
} catch {}
|
|
44
|
+
|
|
45
|
+
return new NaiveStorage<T>();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const storage = resolveStorage<ActiveTrace>();
|
|
5
49
|
|
|
6
50
|
export const Context = {
|
|
7
51
|
run: <T>(trace: ActiveTrace, fn: () => T): T => {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const _isNode = typeof process !== 'undefined' &&
|
|
2
|
+
typeof process.versions !== 'undefined' &&
|
|
3
|
+
typeof process.versions.node !== 'undefined';
|
|
4
|
+
|
|
5
|
+
export const isNode = (): boolean => _isNode;
|
|
6
|
+
|
|
7
|
+
export const isEdgeRuntime = (): boolean =>
|
|
8
|
+
typeof navigator !== 'undefined' &&
|
|
9
|
+
typeof navigator.userAgent === 'string' &&
|
|
10
|
+
navigator.userAgent.includes('Cloudflare');
|
|
11
|
+
|
|
12
|
+
export const getEnv = (key: string): string | undefined => {
|
|
13
|
+
if (_isNode) return process.env[key];
|
|
14
|
+
return undefined;
|
|
15
|
+
};
|
package/src/core/transport.ts
CHANGED
|
@@ -255,6 +255,8 @@ export class Transport {
|
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
private installShutdownFlush() {
|
|
258
|
+
if (typeof process === 'undefined' || typeof process.once !== 'function') return;
|
|
259
|
+
|
|
258
260
|
const key = Symbol.for('senzor.transport.shutdownFlushInstalled');
|
|
259
261
|
const proc = process as unknown as Record<symbol, boolean>;
|
|
260
262
|
if (proc[key]) return;
|
package/src/index.ts
CHANGED
|
@@ -2,18 +2,20 @@ import { client } from './core/client';
|
|
|
2
2
|
import { expressMiddleware, expressErrorHandler } from './middleware/express';
|
|
3
3
|
import { wrapH3 } from './wrappers/h3';
|
|
4
4
|
import { wrapNextRoute, wrapNextPages } from './wrappers/next';
|
|
5
|
+
import { wrapWorker } from './wrappers/worker';
|
|
6
|
+
import { nitroPlugin } from './wrappers/nitro';
|
|
5
7
|
import { senzorPlugin } from './wrappers/fastify';
|
|
6
8
|
import { SenzorOptions } from './core/types';
|
|
7
9
|
|
|
8
|
-
const Senzor = {
|
|
9
|
-
preload: (options: Partial<SenzorOptions> = {}) => client.preload(options),
|
|
10
|
-
init: (options: SenzorOptions) => client.init(options),
|
|
11
|
-
flush: () => client.flush(),
|
|
10
|
+
const Senzor = {
|
|
11
|
+
preload: (options: Partial<SenzorOptions> = {}) => client.preload(options),
|
|
12
|
+
init: (options: SenzorOptions) => client.init(options),
|
|
13
|
+
flush: () => client.flush(),
|
|
12
14
|
track: client.track.bind(client),
|
|
13
15
|
startSpan: client.startSpan.bind(client),
|
|
14
16
|
captureException: client.captureError.bind(client),
|
|
15
17
|
|
|
16
|
-
// Task Monitoring
|
|
18
|
+
// Task Monitoring
|
|
17
19
|
wrapTask: client.wrapTask.bind(client),
|
|
18
20
|
startTask: client.startTask.bind(client),
|
|
19
21
|
|
|
@@ -29,8 +31,14 @@ const Senzor = {
|
|
|
29
31
|
wrapH3,
|
|
30
32
|
|
|
31
33
|
// Fastify
|
|
32
|
-
fastifyPlugin: senzorPlugin
|
|
34
|
+
fastifyPlugin: senzorPlugin,
|
|
35
|
+
|
|
36
|
+
// Cloudflare Workers
|
|
37
|
+
worker: wrapWorker,
|
|
38
|
+
|
|
39
|
+
// Nitro / Nuxt
|
|
40
|
+
nitroPlugin
|
|
33
41
|
};
|
|
34
42
|
|
|
35
43
|
export default Senzor;
|
|
36
|
-
export { Senzor };
|
|
44
|
+
export { Senzor };
|
|
@@ -57,18 +57,24 @@ const getRequestRoute = (
|
|
|
57
57
|
layer: any,
|
|
58
58
|
layerPath?: string
|
|
59
59
|
): string | undefined => {
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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?.
|
|
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
|
|
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
|
|
322
|
-
|
|
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
|
|
360
|
+
const routerBefore = getSafeRouter(this);
|
|
361
|
+
const prevStackLength = routerBefore?.stack?.length || 0;
|
|
362
|
+
|
|
334
363
|
const result = original.apply(this, args);
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
|
|
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
|
);
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import Module from 'module';
|
|
2
|
-
|
|
3
|
-
const SENZOR_PATCHED = Symbol.for('senzor.require.patched');
|
|
4
|
-
const SENZOR_HOOKS = Symbol.for('senzor.require.hooks');
|
|
5
|
-
|
|
6
1
|
type HookFn = (exports: unknown) => unknown | void;
|
|
7
2
|
type HookMap = Map<string, HookFn[]>;
|
|
8
3
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
let Module: any;
|
|
5
|
+
let safeRequire: NodeRequire;
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
Module = require('module');
|
|
9
|
+
safeRequire = Module.createRequire(
|
|
10
|
+
typeof __filename !== 'undefined'
|
|
11
|
+
? __filename
|
|
12
|
+
: process.cwd() + '/'
|
|
13
|
+
);
|
|
14
|
+
} catch {}
|
|
15
|
+
|
|
16
|
+
const SENZOR_PATCHED = Symbol.for('senzor.require.patched');
|
|
17
|
+
const SENZOR_HOOKS = Symbol.for('senzor.require.hooks');
|
|
16
18
|
|
|
17
19
|
function getHookRegistry(): HookMap {
|
|
18
20
|
const mod = Module as unknown as Record<symbol, HookMap>;
|
|
@@ -119,6 +121,8 @@ function retryPatch(moduleName: string, hook: HookFn) {
|
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
export const hookRequire = (moduleName: string, onRequire: HookFn) => {
|
|
124
|
+
if (!Module || !safeRequire) return;
|
|
125
|
+
|
|
122
126
|
const registry = getHookRegistry();
|
|
123
127
|
|
|
124
128
|
if (!registry.has(moduleName)) {
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import http from 'http';
|
|
2
|
-
import https from 'https';
|
|
3
|
-
import { URL } from 'url';
|
|
4
1
|
import type { SenzorClient } from '../core/client';
|
|
5
2
|
import { Context } from '../core/context';
|
|
6
3
|
import { getRoute, normalizePath } from '../core/normalizer';
|
|
@@ -299,7 +296,7 @@ const patchIncomingServer = (
|
|
|
299
296
|
};
|
|
300
297
|
|
|
301
298
|
const patchOutgoing = (
|
|
302
|
-
moduleRef:
|
|
299
|
+
moduleRef: any,
|
|
303
300
|
protocol: 'http:' | 'https:',
|
|
304
301
|
ingestUrl: string,
|
|
305
302
|
options?: SenzorOptions
|
|
@@ -522,9 +519,19 @@ export const instrumentHttp = (
|
|
|
522
519
|
ingestUrl: string,
|
|
523
520
|
options?: SenzorOptions
|
|
524
521
|
) => {
|
|
525
|
-
|
|
526
|
-
|
|
522
|
+
let httpMod: any;
|
|
523
|
+
let httpsMod: any;
|
|
527
524
|
|
|
528
|
-
|
|
529
|
-
|
|
525
|
+
try { httpMod = require('http'); } catch { return; }
|
|
526
|
+
try { httpsMod = require('https'); } catch {}
|
|
527
|
+
|
|
528
|
+
if (httpMod?.Server?.prototype) {
|
|
529
|
+
patchIncomingServer(httpMod.Server.prototype, 'http', client, options);
|
|
530
|
+
}
|
|
531
|
+
if (httpsMod?.Server?.prototype) {
|
|
532
|
+
patchIncomingServer(httpsMod.Server.prototype, 'https', client, options);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
patchOutgoing(httpMod, 'http:', ingestUrl, options);
|
|
536
|
+
if (httpsMod) patchOutgoing(httpsMod, 'https:', ingestUrl, options);
|
|
530
537
|
};
|
|
@@ -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);
|
package/src/register.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { client } from './core/client';
|
|
2
|
+
import { getEnv } from './core/runtime';
|
|
2
3
|
|
|
3
4
|
const truthy = (value: string | undefined): boolean =>
|
|
4
5
|
value === '1' || value === 'true' || value === 'yes';
|
|
@@ -10,43 +11,43 @@ const numberFromEnv = (value: string | undefined): number | undefined => {
|
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
const apiKey =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
getEnv('SENZOR_API_KEY') ||
|
|
15
|
+
getEnv('SENZOR_APM_API_KEY') ||
|
|
16
|
+
getEnv('SENZOR_SERVICE_API_KEY');
|
|
16
17
|
|
|
17
18
|
const endpoint =
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
getEnv('SENZOR_ENDPOINT') ||
|
|
20
|
+
getEnv('SENZOR_APM_ENDPOINT');
|
|
20
21
|
|
|
21
22
|
const options = {
|
|
22
23
|
apiKey: apiKey || '',
|
|
23
24
|
endpoint,
|
|
24
|
-
debug: truthy(
|
|
25
|
-
autoLogs:
|
|
26
|
-
batchSize: numberFromEnv(
|
|
27
|
-
flushInterval: numberFromEnv(
|
|
28
|
-
flushTimeoutMs: numberFromEnv(
|
|
29
|
-
maxQueueSize: numberFromEnv(
|
|
30
|
-
maxSpansPerTrace: numberFromEnv(
|
|
31
|
-
captureHeaders: truthy(
|
|
25
|
+
debug: truthy(getEnv('SENZOR_DEBUG')),
|
|
26
|
+
autoLogs: getEnv('SENZOR_AUTO_LOGS') === 'false' ? false : undefined,
|
|
27
|
+
batchSize: numberFromEnv(getEnv('SENZOR_BATCH_SIZE')),
|
|
28
|
+
flushInterval: numberFromEnv(getEnv('SENZOR_FLUSH_INTERVAL')),
|
|
29
|
+
flushTimeoutMs: numberFromEnv(getEnv('SENZOR_FLUSH_TIMEOUT_MS')),
|
|
30
|
+
maxQueueSize: numberFromEnv(getEnv('SENZOR_MAX_QUEUE_SIZE')),
|
|
31
|
+
maxSpansPerTrace: numberFromEnv(getEnv('SENZOR_MAX_SPANS_PER_TRACE')),
|
|
32
|
+
captureHeaders: truthy(getEnv('SENZOR_CAPTURE_HEADERS')),
|
|
32
33
|
captureDbStatement:
|
|
33
|
-
|
|
34
|
+
getEnv('SENZOR_CAPTURE_DB_STATEMENT') === 'false'
|
|
34
35
|
? false
|
|
35
36
|
: undefined,
|
|
36
37
|
frameworkSpans:
|
|
37
|
-
|
|
38
|
+
getEnv('SENZOR_FRAMEWORK_SPANS') === 'false'
|
|
38
39
|
? false
|
|
39
40
|
: undefined,
|
|
40
41
|
captureMiddlewareSpans:
|
|
41
|
-
|
|
42
|
+
getEnv('SENZOR_CAPTURE_MIDDLEWARE_SPANS') === 'false'
|
|
42
43
|
? false
|
|
43
44
|
: undefined,
|
|
44
45
|
captureRouterSpans:
|
|
45
|
-
|
|
46
|
+
getEnv('SENZOR_CAPTURE_ROUTER_SPANS') === 'false'
|
|
46
47
|
? false
|
|
47
48
|
: undefined,
|
|
48
49
|
captureLifecycleHookSpans:
|
|
49
|
-
|
|
50
|
+
getEnv('SENZOR_CAPTURE_LIFECYCLE_HOOK_SPANS') === 'false'
|
|
50
51
|
? false
|
|
51
52
|
: undefined
|
|
52
53
|
};
|