@sentienguard/apm 1.0.4 → 1.0.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/package.json +1 -1
- package/src/aggregator.js +463 -463
- package/src/browser/aggregator.js +38 -50
- package/src/browser/instrumentation.js +16 -19
- package/src/browser/transport.js +22 -21
- package/src/browser.js +3 -2
- package/src/circuitBreaker.js +264 -254
- package/src/dependencies.js +231 -236
- package/src/index.js +209 -209
- package/src/instrumentation.js +208 -208
- package/src/normalizer.js +147 -147
- package/src/transport.js +215 -214
package/src/instrumentation.js
CHANGED
|
@@ -1,208 +1,208 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTTP Request Auto-Instrumentation
|
|
3
|
-
* Automatically instruments incoming HTTP requests for Express, Fastify, and plain Node.js http.
|
|
4
|
-
*
|
|
5
|
-
* Captures:
|
|
6
|
-
* - Method
|
|
7
|
-
* - Normalized route
|
|
8
|
-
* - Status code
|
|
9
|
-
* - Response time
|
|
10
|
-
* - Error flag
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import http from 'http';
|
|
14
|
-
import https from 'https';
|
|
15
|
-
import { extractRoute
|
|
16
|
-
import { getAggregator } from './aggregator.js';
|
|
17
|
-
import { debug } from './config.js';
|
|
18
|
-
|
|
19
|
-
let isInstrumented = false;
|
|
20
|
-
let originalHttpCreateServer = null;
|
|
21
|
-
let originalHttpsCreateServer = null;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Wrap a request handler to capture metrics
|
|
25
|
-
*/
|
|
26
|
-
function wrapRequestHandler(handler) {
|
|
27
|
-
return function wrappedHandler(req, res) {
|
|
28
|
-
const startTime = process.hrtime.bigint();
|
|
29
|
-
|
|
30
|
-
// Store original end method
|
|
31
|
-
const originalEnd = res.end;
|
|
32
|
-
|
|
33
|
-
// Override res.end to capture timing
|
|
34
|
-
res.end = function (...args) {
|
|
35
|
-
const endTime = process.hrtime.bigint();
|
|
36
|
-
const latencyMs = Number(endTime - startTime) / 1e6; // Convert nanoseconds to milliseconds
|
|
37
|
-
|
|
38
|
-
// Extract route (after response, frameworks may have populated route info)
|
|
39
|
-
const route = extractRoute(req);
|
|
40
|
-
const method = req.method || 'GET';
|
|
41
|
-
const statusCode = res.statusCode || 200;
|
|
42
|
-
const isError = statusCode >= 400;
|
|
43
|
-
|
|
44
|
-
// Record the metric
|
|
45
|
-
const aggregator = getAggregator();
|
|
46
|
-
aggregator.recordRequest(method, route, latencyMs, isError);
|
|
47
|
-
|
|
48
|
-
debug(`Request: ${method} ${route} ${statusCode} ${latencyMs.toFixed(2)}ms`);
|
|
49
|
-
|
|
50
|
-
// Call original end
|
|
51
|
-
return originalEnd.apply(this, args);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// Call original handler
|
|
55
|
-
return handler.call(this, req, res);
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Wrap a server's request listener
|
|
61
|
-
*/
|
|
62
|
-
function wrapServer(server) {
|
|
63
|
-
const listeners = server.listeners('request');
|
|
64
|
-
|
|
65
|
-
// Remove existing listeners
|
|
66
|
-
server.removeAllListeners('request');
|
|
67
|
-
|
|
68
|
-
// Re-add wrapped listeners
|
|
69
|
-
for (const listener of listeners) {
|
|
70
|
-
server.on('request', wrapRequestHandler(listener));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Also wrap future listeners
|
|
74
|
-
const originalOn = server.on.bind(server);
|
|
75
|
-
server.on = function (event, listener) {
|
|
76
|
-
if (event === 'request') {
|
|
77
|
-
return originalOn(event, wrapRequestHandler(listener));
|
|
78
|
-
}
|
|
79
|
-
return originalOn(event, listener);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
return server;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Create instrumented http.createServer
|
|
87
|
-
*/
|
|
88
|
-
function createInstrumentedCreateServer(original, protocol) {
|
|
89
|
-
return function instrumentedCreateServer(...args) {
|
|
90
|
-
const server = original.apply(this, args);
|
|
91
|
-
|
|
92
|
-
// If a request handler was passed, it's already attached
|
|
93
|
-
// We need to wrap the server after creation
|
|
94
|
-
setImmediate(() => {
|
|
95
|
-
wrapServer(server);
|
|
96
|
-
debug(`${protocol} server instrumented`);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
return server;
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Instrument Node.js HTTP module
|
|
105
|
-
* This is the core instrumentation that works with any framework
|
|
106
|
-
*/
|
|
107
|
-
export function instrumentHttp() {
|
|
108
|
-
if (isInstrumented) {
|
|
109
|
-
debug('HTTP already instrumented, skipping');
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Store originals for cleanup
|
|
114
|
-
originalHttpCreateServer = http.createServer;
|
|
115
|
-
originalHttpsCreateServer = https.createServer;
|
|
116
|
-
|
|
117
|
-
// Patch http.createServer
|
|
118
|
-
http.createServer = createInstrumentedCreateServer(originalHttpCreateServer, 'HTTP');
|
|
119
|
-
|
|
120
|
-
// Patch https.createServer
|
|
121
|
-
https.createServer = createInstrumentedCreateServer(originalHttpsCreateServer, 'HTTPS');
|
|
122
|
-
|
|
123
|
-
isInstrumented = true;
|
|
124
|
-
debug('HTTP instrumentation enabled');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Create Express middleware for more accurate route extraction
|
|
129
|
-
* This is optional but provides better route patterns
|
|
130
|
-
*/
|
|
131
|
-
export function expressMiddleware() {
|
|
132
|
-
return function sentienguardMiddleware(req, res, next) {
|
|
133
|
-
const startTime = process.hrtime.bigint();
|
|
134
|
-
|
|
135
|
-
// Use res.on('finish') for Express
|
|
136
|
-
res.on('finish', () => {
|
|
137
|
-
const endTime = process.hrtime.bigint();
|
|
138
|
-
const latencyMs = Number(endTime - startTime) / 1e6;
|
|
139
|
-
|
|
140
|
-
// Express populates req.route after routing
|
|
141
|
-
const route = extractRoute(req);
|
|
142
|
-
const method = req.method || 'GET';
|
|
143
|
-
const statusCode = res.statusCode || 200;
|
|
144
|
-
const isError = statusCode >= 400;
|
|
145
|
-
|
|
146
|
-
const aggregator = getAggregator();
|
|
147
|
-
aggregator.recordRequest(method, route, latencyMs, isError);
|
|
148
|
-
|
|
149
|
-
debug(`Express: ${method} ${route} ${statusCode} ${latencyMs.toFixed(2)}ms`);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
next();
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Create Fastify plugin for instrumentation
|
|
158
|
-
*/
|
|
159
|
-
export function fastifyPlugin(fastify, options, done) {
|
|
160
|
-
fastify.addHook('onRequest', async (request, reply) => {
|
|
161
|
-
request.sentienguardStart = process.hrtime.bigint();
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
fastify.addHook('onResponse', async (request, reply) => {
|
|
165
|
-
const endTime = process.hrtime.bigint();
|
|
166
|
-
const startTime = request.sentienguardStart || endTime;
|
|
167
|
-
const latencyMs = Number(endTime - startTime) / 1e6;
|
|
168
|
-
|
|
169
|
-
const route = extractRoute(request);
|
|
170
|
-
const method = request.method || 'GET';
|
|
171
|
-
const statusCode = reply.statusCode || 200;
|
|
172
|
-
const isError = statusCode >= 400;
|
|
173
|
-
|
|
174
|
-
const aggregator = getAggregator();
|
|
175
|
-
aggregator.recordRequest(method, route, latencyMs, isError);
|
|
176
|
-
|
|
177
|
-
debug(`Fastify: ${method} ${route} ${statusCode} ${latencyMs.toFixed(2)}ms`);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
done();
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Mark as fastify plugin
|
|
184
|
-
fastifyPlugin[Symbol.for('skip-override')] = true;
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Remove instrumentation (for testing/cleanup)
|
|
188
|
-
*/
|
|
189
|
-
export function uninstrumentHttp() {
|
|
190
|
-
if (!isInstrumented) return;
|
|
191
|
-
|
|
192
|
-
if (originalHttpCreateServer) {
|
|
193
|
-
http.createServer = originalHttpCreateServer;
|
|
194
|
-
}
|
|
195
|
-
if (originalHttpsCreateServer) {
|
|
196
|
-
https.createServer = originalHttpsCreateServer;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
isInstrumented = false;
|
|
200
|
-
debug('HTTP instrumentation disabled');
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export default {
|
|
204
|
-
instrumentHttp,
|
|
205
|
-
expressMiddleware,
|
|
206
|
-
fastifyPlugin,
|
|
207
|
-
uninstrumentHttp
|
|
208
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Request Auto-Instrumentation
|
|
3
|
+
* Automatically instruments incoming HTTP requests for Express, Fastify, and plain Node.js http.
|
|
4
|
+
*
|
|
5
|
+
* Captures:
|
|
6
|
+
* - Method
|
|
7
|
+
* - Normalized route
|
|
8
|
+
* - Status code
|
|
9
|
+
* - Response time
|
|
10
|
+
* - Error flag
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import http from 'http';
|
|
14
|
+
import https from 'https';
|
|
15
|
+
import { extractRoute } from './normalizer.js';
|
|
16
|
+
import { getAggregator } from './aggregator.js';
|
|
17
|
+
import { debug } from './config.js';
|
|
18
|
+
|
|
19
|
+
let isInstrumented = false;
|
|
20
|
+
let originalHttpCreateServer = null;
|
|
21
|
+
let originalHttpsCreateServer = null;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Wrap a request handler to capture metrics
|
|
25
|
+
*/
|
|
26
|
+
function wrapRequestHandler(handler) {
|
|
27
|
+
return function wrappedHandler(req, res) {
|
|
28
|
+
const startTime = process.hrtime.bigint();
|
|
29
|
+
|
|
30
|
+
// Store original end method
|
|
31
|
+
const originalEnd = res.end;
|
|
32
|
+
|
|
33
|
+
// Override res.end to capture timing
|
|
34
|
+
res.end = function (...args) {
|
|
35
|
+
const endTime = process.hrtime.bigint();
|
|
36
|
+
const latencyMs = Number(endTime - startTime) / 1e6; // Convert nanoseconds to milliseconds
|
|
37
|
+
|
|
38
|
+
// Extract route (after response, frameworks may have populated route info)
|
|
39
|
+
const route = extractRoute(req);
|
|
40
|
+
const method = req.method || 'GET';
|
|
41
|
+
const statusCode = res.statusCode || 200;
|
|
42
|
+
const isError = statusCode >= 400;
|
|
43
|
+
|
|
44
|
+
// Record the metric
|
|
45
|
+
const aggregator = getAggregator();
|
|
46
|
+
aggregator.recordRequest(method, route, latencyMs, isError);
|
|
47
|
+
|
|
48
|
+
debug(`Request: ${method} ${route} ${statusCode} ${latencyMs.toFixed(2)}ms`);
|
|
49
|
+
|
|
50
|
+
// Call original end
|
|
51
|
+
return originalEnd.apply(this, args);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Call original handler
|
|
55
|
+
return handler.call(this, req, res);
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Wrap a server's request listener
|
|
61
|
+
*/
|
|
62
|
+
function wrapServer(server) {
|
|
63
|
+
const listeners = server.listeners('request');
|
|
64
|
+
|
|
65
|
+
// Remove existing listeners
|
|
66
|
+
server.removeAllListeners('request');
|
|
67
|
+
|
|
68
|
+
// Re-add wrapped listeners
|
|
69
|
+
for (const listener of listeners) {
|
|
70
|
+
server.on('request', wrapRequestHandler(listener));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Also wrap future listeners
|
|
74
|
+
const originalOn = server.on.bind(server);
|
|
75
|
+
server.on = function (event, listener) {
|
|
76
|
+
if (event === 'request') {
|
|
77
|
+
return originalOn(event, wrapRequestHandler(listener));
|
|
78
|
+
}
|
|
79
|
+
return originalOn(event, listener);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return server;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create instrumented http.createServer
|
|
87
|
+
*/
|
|
88
|
+
function createInstrumentedCreateServer(original, protocol) {
|
|
89
|
+
return function instrumentedCreateServer(...args) {
|
|
90
|
+
const server = original.apply(this, args);
|
|
91
|
+
|
|
92
|
+
// If a request handler was passed, it's already attached
|
|
93
|
+
// We need to wrap the server after creation
|
|
94
|
+
setImmediate(() => {
|
|
95
|
+
wrapServer(server);
|
|
96
|
+
debug(`${protocol} server instrumented`);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return server;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Instrument Node.js HTTP module
|
|
105
|
+
* This is the core instrumentation that works with any framework
|
|
106
|
+
*/
|
|
107
|
+
export function instrumentHttp() {
|
|
108
|
+
if (isInstrumented) {
|
|
109
|
+
debug('HTTP already instrumented, skipping');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Store originals for cleanup
|
|
114
|
+
originalHttpCreateServer = http.createServer;
|
|
115
|
+
originalHttpsCreateServer = https.createServer;
|
|
116
|
+
|
|
117
|
+
// Patch http.createServer
|
|
118
|
+
http.createServer = createInstrumentedCreateServer(originalHttpCreateServer, 'HTTP');
|
|
119
|
+
|
|
120
|
+
// Patch https.createServer
|
|
121
|
+
https.createServer = createInstrumentedCreateServer(originalHttpsCreateServer, 'HTTPS');
|
|
122
|
+
|
|
123
|
+
isInstrumented = true;
|
|
124
|
+
debug('HTTP instrumentation enabled');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Create Express middleware for more accurate route extraction
|
|
129
|
+
* This is optional but provides better route patterns
|
|
130
|
+
*/
|
|
131
|
+
export function expressMiddleware() {
|
|
132
|
+
return function sentienguardMiddleware(req, res, next) {
|
|
133
|
+
const startTime = process.hrtime.bigint();
|
|
134
|
+
|
|
135
|
+
// Use res.on('finish') for Express
|
|
136
|
+
res.on('finish', () => {
|
|
137
|
+
const endTime = process.hrtime.bigint();
|
|
138
|
+
const latencyMs = Number(endTime - startTime) / 1e6;
|
|
139
|
+
|
|
140
|
+
// Express populates req.route after routing
|
|
141
|
+
const route = extractRoute(req);
|
|
142
|
+
const method = req.method || 'GET';
|
|
143
|
+
const statusCode = res.statusCode || 200;
|
|
144
|
+
const isError = statusCode >= 400;
|
|
145
|
+
|
|
146
|
+
const aggregator = getAggregator();
|
|
147
|
+
aggregator.recordRequest(method, route, latencyMs, isError);
|
|
148
|
+
|
|
149
|
+
debug(`Express: ${method} ${route} ${statusCode} ${latencyMs.toFixed(2)}ms`);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
next();
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Create Fastify plugin for instrumentation
|
|
158
|
+
*/
|
|
159
|
+
export function fastifyPlugin(fastify, options, done) {
|
|
160
|
+
fastify.addHook('onRequest', async (request, reply) => {
|
|
161
|
+
request.sentienguardStart = process.hrtime.bigint();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
fastify.addHook('onResponse', async (request, reply) => {
|
|
165
|
+
const endTime = process.hrtime.bigint();
|
|
166
|
+
const startTime = request.sentienguardStart || endTime;
|
|
167
|
+
const latencyMs = Number(endTime - startTime) / 1e6;
|
|
168
|
+
|
|
169
|
+
const route = extractRoute(request);
|
|
170
|
+
const method = request.method || 'GET';
|
|
171
|
+
const statusCode = reply.statusCode || 200;
|
|
172
|
+
const isError = statusCode >= 400;
|
|
173
|
+
|
|
174
|
+
const aggregator = getAggregator();
|
|
175
|
+
aggregator.recordRequest(method, route, latencyMs, isError);
|
|
176
|
+
|
|
177
|
+
debug(`Fastify: ${method} ${route} ${statusCode} ${latencyMs.toFixed(2)}ms`);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
done();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Mark as fastify plugin
|
|
184
|
+
fastifyPlugin[Symbol.for('skip-override')] = true;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Remove instrumentation (for testing/cleanup)
|
|
188
|
+
*/
|
|
189
|
+
export function uninstrumentHttp() {
|
|
190
|
+
if (!isInstrumented) return;
|
|
191
|
+
|
|
192
|
+
if (originalHttpCreateServer) {
|
|
193
|
+
http.createServer = originalHttpCreateServer;
|
|
194
|
+
}
|
|
195
|
+
if (originalHttpsCreateServer) {
|
|
196
|
+
https.createServer = originalHttpsCreateServer;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
isInstrumented = false;
|
|
200
|
+
debug('HTTP instrumentation disabled');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export default {
|
|
204
|
+
instrumentHttp,
|
|
205
|
+
expressMiddleware,
|
|
206
|
+
fastifyPlugin,
|
|
207
|
+
uninstrumentHttp
|
|
208
|
+
};
|