sysmonitoringexpress 1.0.0
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.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +129 -0
- package/dist/index.js.map +1 -0
- package/package.json +29 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { NextFunction, Request, Response } from "express";
|
|
2
|
+
type MonitorOptions = {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
endpoint?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function initializeSysMonitoring(options?: MonitorOptions): void;
|
|
7
|
+
export declare function createMonitorMiddleware(options: MonitorOptions): typeof monitorMiddleware;
|
|
8
|
+
export declare function monitorMiddleware(req: Request, res: Response, next: NextFunction): void;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAO/D,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAmCF,wBAAgB,uBAAuB,CAAC,OAAO,CAAC,EAAE,cAAc,QAsF/D;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,cAAc,4BAG9D;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,QAoCnB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { TDigest } from "tdigest";
|
|
2
|
+
let initialized = false;
|
|
3
|
+
let started = false;
|
|
4
|
+
const digests = new Map();
|
|
5
|
+
let config = null;
|
|
6
|
+
function ensureConfig(options) {
|
|
7
|
+
if (config)
|
|
8
|
+
return config;
|
|
9
|
+
const endpoint = options?.endpoint ?? process.env.SYS_MONITOR_ENDPOINT;
|
|
10
|
+
const apiKey = options?.apiKey ?? process.env.SYS_MONITOR_API_KEY;
|
|
11
|
+
if (!endpoint || !apiKey) {
|
|
12
|
+
throw new Error("SYS_MONITOR_ENDPOINT and apiKey or SYS_MONITOR_API_KEY must be set");
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
new URL(endpoint);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw new Error("SYS_MONITOR_ENDPOINT must be a valid absolute URL, for example http://localhost:3000/api/ping");
|
|
19
|
+
}
|
|
20
|
+
config = { endpoint, apiKey, flushInterval: 5 * 60 * 1000 };
|
|
21
|
+
return config;
|
|
22
|
+
}
|
|
23
|
+
export function initializeSysMonitoring(options) {
|
|
24
|
+
if (initialized)
|
|
25
|
+
return;
|
|
26
|
+
const cfg = ensureConfig(options);
|
|
27
|
+
if (started) {
|
|
28
|
+
initialized = true;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
started = true;
|
|
32
|
+
const interval = setInterval(() => {
|
|
33
|
+
if (digests.size === 0)
|
|
34
|
+
return;
|
|
35
|
+
const fiveWindow = (ts) => {
|
|
36
|
+
const d = new Date(ts);
|
|
37
|
+
d.setUTCMinutes(Math.floor(d.getUTCMinutes() / 5) * 5, 0, 0);
|
|
38
|
+
return d.toISOString().replace(".000Z", "Z");
|
|
39
|
+
};
|
|
40
|
+
const currentWindowKey = fiveWindow(Date.now());
|
|
41
|
+
const flushable = Array.from(digests.values()).filter((b) => b.windowKey < currentWindowKey);
|
|
42
|
+
if (flushable.length === 0)
|
|
43
|
+
return;
|
|
44
|
+
const fetchFn = globalThis.fetch;
|
|
45
|
+
if (!fetchFn)
|
|
46
|
+
return;
|
|
47
|
+
for (const bucket of flushable) {
|
|
48
|
+
// remove from map; we'll re-add if needed
|
|
49
|
+
const key = `${bucket.requestUrl}\n${bucket.windowKey}`;
|
|
50
|
+
digests.delete(key);
|
|
51
|
+
bucket.digest.compress();
|
|
52
|
+
const arr = bucket.digest.toArray();
|
|
53
|
+
const centroids = arr.map((c) => ({ mean: c.mean, count: c.n }));
|
|
54
|
+
const n = centroids.reduce((t, c) => t + c.count, 0);
|
|
55
|
+
if (centroids.length === 0)
|
|
56
|
+
continue;
|
|
57
|
+
void fetchFn(cfg.endpoint, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: {
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
"x-api-key": cfg.apiKey,
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
requestUrl: bucket.requestUrl,
|
|
65
|
+
windowKey: bucket.windowKey,
|
|
66
|
+
centroids,
|
|
67
|
+
n,
|
|
68
|
+
}),
|
|
69
|
+
})
|
|
70
|
+
.then((response) => {
|
|
71
|
+
const data = async () => {
|
|
72
|
+
const data = await response.json();
|
|
73
|
+
console.log("Response from monitoring endpoint:", data);
|
|
74
|
+
};
|
|
75
|
+
data();
|
|
76
|
+
const ok = response && typeof response === "object"
|
|
77
|
+
? response.ok
|
|
78
|
+
: undefined;
|
|
79
|
+
const status = response && typeof response === "object"
|
|
80
|
+
? response.status
|
|
81
|
+
: undefined;
|
|
82
|
+
if (ok === false && status !== 409) {
|
|
83
|
+
console.error(`Failed to flush digest for ${bucket.requestUrl} (${bucket.windowKey}): backend returned ${status ?? "non-ok response"}`);
|
|
84
|
+
digests.set(key, bucket);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
.catch((err) => {
|
|
89
|
+
console.error(`Failed to flush digest for ${bucket.requestUrl} (${bucket.windowKey}):`, err);
|
|
90
|
+
digests.set(key, bucket);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}, cfg.flushInterval);
|
|
94
|
+
interval.unref?.();
|
|
95
|
+
initialized = true;
|
|
96
|
+
}
|
|
97
|
+
export function createMonitorMiddleware(options) {
|
|
98
|
+
initializeSysMonitoring(options);
|
|
99
|
+
return monitorMiddleware;
|
|
100
|
+
}
|
|
101
|
+
export function monitorMiddleware(req, res, next) {
|
|
102
|
+
initializeSysMonitoring();
|
|
103
|
+
const start = Date.now();
|
|
104
|
+
const forwardedProtocol = req.get("x-forwarded-proto")?.split(",")[0]?.trim();
|
|
105
|
+
const forwardedHost = req.get("x-forwarded-host")?.split(",")[0]?.trim();
|
|
106
|
+
const protocol = forwardedProtocol || req.protocol || "http";
|
|
107
|
+
const host = forwardedHost || req.get("host");
|
|
108
|
+
const path = req.originalUrl || req.url;
|
|
109
|
+
if (!host) {
|
|
110
|
+
throw new Error("Unable to determine request host for monitoring payload");
|
|
111
|
+
}
|
|
112
|
+
const requestUrl = `${protocol}://${host}${path}`;
|
|
113
|
+
res.once("finish", () => {
|
|
114
|
+
const duration = Date.now() - start;
|
|
115
|
+
const timestamp = Date.now();
|
|
116
|
+
const d = new Date(timestamp);
|
|
117
|
+
d.setUTCMinutes(Math.floor(d.getUTCMinutes() / 5) * 5, 0, 0);
|
|
118
|
+
const windowKey = d.toISOString().replace(".000Z", "Z");
|
|
119
|
+
const digestKey = `${requestUrl}\n${windowKey}`;
|
|
120
|
+
let bucket = digests.get(digestKey);
|
|
121
|
+
if (!bucket) {
|
|
122
|
+
bucket = { requestUrl, windowKey, digest: new TDigest() };
|
|
123
|
+
digests.set(digestKey, bucket);
|
|
124
|
+
}
|
|
125
|
+
bucket.digest.push(duration);
|
|
126
|
+
});
|
|
127
|
+
next();
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sysmonitoringexpress",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"dev": "npx tsx src/index.ts"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [],
|
|
16
|
+
"author": "",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"type": "module",
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/express": "^5.0.6",
|
|
21
|
+
"@types/node": "^22.19.15",
|
|
22
|
+
"typescript": "^5.9.2"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"dotenv": "^16.0.3",
|
|
26
|
+
"express": "^4.18.2",
|
|
27
|
+
"tdigest": "^0.1.2"
|
|
28
|
+
}
|
|
29
|
+
}
|