@sentienguard/apm 1.0.5 → 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/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/normalizer.js
CHANGED
|
@@ -1,147 +1,147 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Route Normalizer
|
|
3
|
-
* Normalizes dynamic routes before aggregation to prevent cardinality explosion.
|
|
4
|
-
*
|
|
5
|
-
* Examples:
|
|
6
|
-
* /api/users/123 → /api/users/:id
|
|
7
|
-
* /api/orders/ab23f9 → /api/orders/:id
|
|
8
|
-
* /api/users/123/posts/456 → /api/users/:id/posts/:id
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// Patterns for ID detection
|
|
12
|
-
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
13
|
-
const MONGO_ID_PATTERN = /^[0-9a-f]{24}$/i;
|
|
14
|
-
const NUMERIC_PATTERN = /^\d+$/;
|
|
15
|
-
const SHORT_HEX_PATTERN = /^[0-9a-f]{6,}$/i;
|
|
16
|
-
|
|
17
|
-
// Max route length to prevent abuse
|
|
18
|
-
const MAX_ROUTE_LENGTH = 200;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Check if a path segment looks like a dynamic ID
|
|
22
|
-
*/
|
|
23
|
-
function isIdSegment(segment) {
|
|
24
|
-
if (!segment) return false;
|
|
25
|
-
|
|
26
|
-
// Check for common ID patterns
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Normalize a route path by replacing dynamic segments with :id
|
|
37
|
-
*
|
|
38
|
-
* @param {string} path - The URL path to normalize
|
|
39
|
-
* @returns {string} Normalized route path
|
|
40
|
-
*/
|
|
41
|
-
export function normalizeRoute(path) {
|
|
42
|
-
if (!path || typeof path !== 'string') {
|
|
43
|
-
return '/unknown';
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Remove query string and hash
|
|
47
|
-
let cleanPath = path.split('?')[0].split('#')[0];
|
|
48
|
-
|
|
49
|
-
// Ensure path starts with /
|
|
50
|
-
if (!cleanPath.startsWith('/')) {
|
|
51
|
-
cleanPath = '/' + cleanPath;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Truncate overly long paths
|
|
55
|
-
if (cleanPath.length > MAX_ROUTE_LENGTH) {
|
|
56
|
-
return '/unknown';
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Split into segments and normalize
|
|
60
|
-
const segments = cleanPath.split('/');
|
|
61
|
-
const normalizedSegments = segments.map(segment => {
|
|
62
|
-
if (isIdSegment(segment)) {
|
|
63
|
-
return ':id';
|
|
64
|
-
}
|
|
65
|
-
return segment;
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const normalized = normalizedSegments.join('/');
|
|
69
|
-
|
|
70
|
-
// Clean up multiple slashes
|
|
71
|
-
return normalized.replace(/\/+/g, '/') || '/';
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Extract route pattern from Express/Fastify request
|
|
76
|
-
* Prefers the framework's route pattern if available
|
|
77
|
-
*
|
|
78
|
-
* @param {object} req - HTTP request object
|
|
79
|
-
* @returns {string} Route pattern
|
|
80
|
-
*/
|
|
81
|
-
export function extractRoute(req) {
|
|
82
|
-
// Express stores the matched route pattern
|
|
83
|
-
if (req.route
|
|
84
|
-
return req.baseUrl ? req.baseUrl + req.route.path : req.route.path;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Fastify stores route info differently
|
|
88
|
-
if (req.routerPath) {
|
|
89
|
-
return req.routerPath;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Fastify alternative
|
|
93
|
-
if (req.routeOptions
|
|
94
|
-
return req.routeOptions.url;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Fall back to normalizing the URL
|
|
98
|
-
return normalizeRoute(req.url || req.originalUrl || '/');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Route registry to track and limit unique routes
|
|
103
|
-
*/
|
|
104
|
-
export class RouteRegistry {
|
|
105
|
-
constructor(maxRoutes = 100) {
|
|
106
|
-
this.maxRoutes = maxRoutes;
|
|
107
|
-
this.routes = new Set();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Register a route and check if it should be tracked
|
|
112
|
-
* Returns the route if under limit, '/unknown' if limit exceeded
|
|
113
|
-
*/
|
|
114
|
-
register(route) {
|
|
115
|
-
if (this.routes.has(route)) {
|
|
116
|
-
return route;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (this.routes.size >= this.maxRoutes) {
|
|
120
|
-
// Bucket overflow routes as /unknown
|
|
121
|
-
return '/unknown';
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
this.routes.add(route);
|
|
125
|
-
return route;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Get count of unique routes
|
|
130
|
-
*/
|
|
131
|
-
get size() {
|
|
132
|
-
return this.routes.size;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Reset the registry
|
|
137
|
-
*/
|
|
138
|
-
clear() {
|
|
139
|
-
this.routes.clear();
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export default {
|
|
144
|
-
normalizeRoute,
|
|
145
|
-
extractRoute,
|
|
146
|
-
RouteRegistry
|
|
147
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Route Normalizer
|
|
3
|
+
* Normalizes dynamic routes before aggregation to prevent cardinality explosion.
|
|
4
|
+
*
|
|
5
|
+
* Examples:
|
|
6
|
+
* /api/users/123 → /api/users/:id
|
|
7
|
+
* /api/orders/ab23f9 → /api/orders/:id
|
|
8
|
+
* /api/users/123/posts/456 → /api/users/:id/posts/:id
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Patterns for ID detection
|
|
12
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
13
|
+
const MONGO_ID_PATTERN = /^[0-9a-f]{24}$/i;
|
|
14
|
+
const NUMERIC_PATTERN = /^\d+$/;
|
|
15
|
+
const SHORT_HEX_PATTERN = /^[0-9a-f]{6,}$/i;
|
|
16
|
+
|
|
17
|
+
// Max route length to prevent abuse
|
|
18
|
+
const MAX_ROUTE_LENGTH = 200;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if a path segment looks like a dynamic ID
|
|
22
|
+
*/
|
|
23
|
+
function isIdSegment(segment) {
|
|
24
|
+
if (!segment) return false;
|
|
25
|
+
|
|
26
|
+
// Check for common ID patterns
|
|
27
|
+
return (
|
|
28
|
+
NUMERIC_PATTERN.test(segment) ||
|
|
29
|
+
UUID_PATTERN.test(segment) ||
|
|
30
|
+
MONGO_ID_PATTERN.test(segment) ||
|
|
31
|
+
SHORT_HEX_PATTERN.test(segment)
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Normalize a route path by replacing dynamic segments with :id
|
|
37
|
+
*
|
|
38
|
+
* @param {string} path - The URL path to normalize
|
|
39
|
+
* @returns {string} Normalized route path
|
|
40
|
+
*/
|
|
41
|
+
export function normalizeRoute(path) {
|
|
42
|
+
if (!path || typeof path !== 'string') {
|
|
43
|
+
return '/unknown';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Remove query string and hash
|
|
47
|
+
let cleanPath = path.split('?')[0].split('#')[0];
|
|
48
|
+
|
|
49
|
+
// Ensure path starts with /
|
|
50
|
+
if (!cleanPath.startsWith('/')) {
|
|
51
|
+
cleanPath = '/' + cleanPath;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Truncate overly long paths
|
|
55
|
+
if (cleanPath.length > MAX_ROUTE_LENGTH) {
|
|
56
|
+
return '/unknown';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Split into segments and normalize
|
|
60
|
+
const segments = cleanPath.split('/');
|
|
61
|
+
const normalizedSegments = segments.map(segment => {
|
|
62
|
+
if (isIdSegment(segment)) {
|
|
63
|
+
return ':id';
|
|
64
|
+
}
|
|
65
|
+
return segment;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const normalized = normalizedSegments.join('/');
|
|
69
|
+
|
|
70
|
+
// Clean up multiple slashes
|
|
71
|
+
return normalized.replace(/\/+/g, '/') || '/';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Extract route pattern from Express/Fastify request
|
|
76
|
+
* Prefers the framework's route pattern if available
|
|
77
|
+
*
|
|
78
|
+
* @param {object} req - HTTP request object
|
|
79
|
+
* @returns {string} Route pattern
|
|
80
|
+
*/
|
|
81
|
+
export function extractRoute(req) {
|
|
82
|
+
// Express stores the matched route pattern
|
|
83
|
+
if (req.route?.path) {
|
|
84
|
+
return req.baseUrl ? req.baseUrl + req.route.path : req.route.path;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Fastify stores route info differently
|
|
88
|
+
if (req.routerPath) {
|
|
89
|
+
return req.routerPath;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Fastify alternative
|
|
93
|
+
if (req.routeOptions?.url) {
|
|
94
|
+
return req.routeOptions.url;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Fall back to normalizing the URL
|
|
98
|
+
return normalizeRoute(req.url || req.originalUrl || '/');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Route registry to track and limit unique routes
|
|
103
|
+
*/
|
|
104
|
+
export class RouteRegistry {
|
|
105
|
+
constructor(maxRoutes = 100) {
|
|
106
|
+
this.maxRoutes = maxRoutes;
|
|
107
|
+
this.routes = new Set();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Register a route and check if it should be tracked
|
|
112
|
+
* Returns the route if under limit, '/unknown' if limit exceeded
|
|
113
|
+
*/
|
|
114
|
+
register(route) {
|
|
115
|
+
if (this.routes.has(route)) {
|
|
116
|
+
return route;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (this.routes.size >= this.maxRoutes) {
|
|
120
|
+
// Bucket overflow routes as /unknown
|
|
121
|
+
return '/unknown';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
this.routes.add(route);
|
|
125
|
+
return route;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get count of unique routes
|
|
130
|
+
*/
|
|
131
|
+
get size() {
|
|
132
|
+
return this.routes.size;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Reset the registry
|
|
137
|
+
*/
|
|
138
|
+
clear() {
|
|
139
|
+
this.routes.clear();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export default {
|
|
144
|
+
normalizeRoute,
|
|
145
|
+
extractRoute,
|
|
146
|
+
RouteRegistry
|
|
147
|
+
};
|