badmfck-api-server 4.1.26 → 4.1.28
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/apiServer/BaseEndpoint.d.ts +26 -0
- package/dist/apiServer/BaseEndpoint.js +58 -0
- package/dist/apiServer/BaseService.d.ts +4 -0
- package/dist/apiServer/BaseService.js +9 -1
- package/dist/apiServer/LogService.d.ts +5 -0
- package/dist/apiServer/LogService.js +47 -0
- package/dist/apiServer/external/MicroserviceClient.js +4 -4
- package/dist/apiServer/http/Http.d.ts +56 -0
- package/dist/apiServer/http/Http.js +146 -1
- package/dist/apiServer/monitor/Monitor.d.ts +2 -0
- package/dist/apiServer/monitor/Monitor.js +33 -0
- package/dist/apiServer/monitor/index.html +113 -72
- package/package.json +1 -1
|
@@ -37,6 +37,7 @@ export interface IEndpointHandler {
|
|
|
37
37
|
resultModel?: any;
|
|
38
38
|
}
|
|
39
39
|
export declare class BaseEndpoint implements IBaseEndpoint {
|
|
40
|
+
static allInstances: BaseEndpoint[];
|
|
40
41
|
private inializedEndpointNames;
|
|
41
42
|
description: string;
|
|
42
43
|
endpoints?: IEndpointHandler[];
|
|
@@ -44,6 +45,8 @@ export declare class BaseEndpoint implements IBaseEndpoint {
|
|
|
44
45
|
ignoreInDocumentation: boolean;
|
|
45
46
|
private static entrypoint;
|
|
46
47
|
endpoint: string;
|
|
48
|
+
createdAt: number;
|
|
49
|
+
initializedAt: number | null;
|
|
47
50
|
protected streamOptions: Record<string, any>;
|
|
48
51
|
static setEntryPoint: (ep: string) => void;
|
|
49
52
|
static getEntryPoint: () => string;
|
|
@@ -51,6 +54,29 @@ export declare class BaseEndpoint implements IBaseEndpoint {
|
|
|
51
54
|
registerEndpoints(endpoints: IEndpointHandler[]): void;
|
|
52
55
|
init(): Promise<void>;
|
|
53
56
|
getInitalizedEndpointNames(): String[];
|
|
57
|
+
getInventory(): {
|
|
58
|
+
className: string;
|
|
59
|
+
basePath: string;
|
|
60
|
+
ignoreHttpLogging: boolean;
|
|
61
|
+
ignoreInDocumentation: boolean;
|
|
62
|
+
description: string;
|
|
63
|
+
createdAt: number;
|
|
64
|
+
initializedAt: number | null;
|
|
65
|
+
handlerCount: number;
|
|
66
|
+
handlers: {
|
|
67
|
+
path: string;
|
|
68
|
+
methods: string[];
|
|
69
|
+
ignoreInterceptor: boolean;
|
|
70
|
+
ignoreInDocumentation: boolean;
|
|
71
|
+
ignoreHttpLogging: boolean;
|
|
72
|
+
independed: boolean;
|
|
73
|
+
asStream: boolean;
|
|
74
|
+
hasValidationModel: boolean;
|
|
75
|
+
hasResultModel: boolean;
|
|
76
|
+
description?: string | undefined;
|
|
77
|
+
title?: string | undefined;
|
|
78
|
+
}[];
|
|
79
|
+
};
|
|
54
80
|
__precheck(req: HTTPRequestVO): Promise<TransferPacketVO<any> | null>;
|
|
55
81
|
__execute(req: HTTPRequestVO): Promise<TransferPacketVO<any>>;
|
|
56
82
|
protected validateStructure(structure: any, req: HTTPRequestVO): Promise<void>;
|
|
@@ -10,6 +10,7 @@ const DefaultErrors_1 = __importDefault(require("./structures/DefaultErrors"));
|
|
|
10
10
|
const stream_1 = require("stream");
|
|
11
11
|
const httpMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "TRACE", "CONNECT"];
|
|
12
12
|
class BaseEndpoint {
|
|
13
|
+
static allInstances = [];
|
|
13
14
|
inializedEndpointNames = [];
|
|
14
15
|
description = "no description";
|
|
15
16
|
endpoints;
|
|
@@ -17,6 +18,8 @@ class BaseEndpoint {
|
|
|
17
18
|
ignoreInDocumentation = false;
|
|
18
19
|
static entrypoint = "/";
|
|
19
20
|
endpoint = "";
|
|
21
|
+
createdAt = Date.now();
|
|
22
|
+
initializedAt = null;
|
|
20
23
|
streamOptions = {};
|
|
21
24
|
static setEntryPoint = (ep) => {
|
|
22
25
|
this.entrypoint = ep;
|
|
@@ -35,6 +38,7 @@ class BaseEndpoint {
|
|
|
35
38
|
if (!endpoint.endsWith("/"))
|
|
36
39
|
endpoint = endpoint + "/";
|
|
37
40
|
this.endpoint = endpoint;
|
|
41
|
+
BaseEndpoint.allInstances.push(this);
|
|
38
42
|
}
|
|
39
43
|
registerEndpoints(endpoints) {
|
|
40
44
|
for (let i of endpoints) {
|
|
@@ -63,11 +67,65 @@ class BaseEndpoint {
|
|
|
63
67
|
(0, LogService_1.logInfo)("${BaseEndpoint.js}", "endpoint: " + i.endpoint + " initalized, ignoreInterceptor: " + (i.ignoreInterceptor ?? "false"));
|
|
64
68
|
this.inializedEndpointNames.push(i.endpoint + " ignore interceptor: " + (i.ignoreInterceptor ?? "false"));
|
|
65
69
|
}
|
|
70
|
+
this.initializedAt = Date.now();
|
|
66
71
|
}
|
|
67
72
|
;
|
|
68
73
|
getInitalizedEndpointNames() {
|
|
69
74
|
return this.inializedEndpointNames;
|
|
70
75
|
}
|
|
76
|
+
getInventory() {
|
|
77
|
+
const handlers = [];
|
|
78
|
+
for (const i of this.endpoints ?? []) {
|
|
79
|
+
let methods = [];
|
|
80
|
+
let asStream = i.asStream === true;
|
|
81
|
+
let hasVm = i.validationModel !== undefined;
|
|
82
|
+
let hasRm = i.resultModel !== undefined;
|
|
83
|
+
if (typeof i.handler === "function") {
|
|
84
|
+
methods = ["ANY"];
|
|
85
|
+
}
|
|
86
|
+
else if (i.handler && typeof i.handler === "object") {
|
|
87
|
+
for (const key of Object.keys(i.handler)) {
|
|
88
|
+
const h = i.handler[key];
|
|
89
|
+
if (typeof h === "function") {
|
|
90
|
+
methods.push(key);
|
|
91
|
+
}
|
|
92
|
+
else if (h && typeof h === "object" && typeof h.controller === "function") {
|
|
93
|
+
methods.push(key);
|
|
94
|
+
if (h.asStream)
|
|
95
|
+
asStream = true;
|
|
96
|
+
if (h.validationModel !== undefined)
|
|
97
|
+
hasVm = true;
|
|
98
|
+
if (h.resultModel !== undefined)
|
|
99
|
+
hasRm = true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
handlers.push({
|
|
104
|
+
path: (BaseEndpoint.entrypoint + i.endpoint).replaceAll("//", "/"),
|
|
105
|
+
methods,
|
|
106
|
+
ignoreInterceptor: i.ignoreInterceptor === true,
|
|
107
|
+
ignoreInDocumentation: i.ignoreInDocumentation === true,
|
|
108
|
+
ignoreHttpLogging: i.ignoreHttpLogging === true,
|
|
109
|
+
independed: i.independed === true,
|
|
110
|
+
asStream,
|
|
111
|
+
hasValidationModel: hasVm,
|
|
112
|
+
hasResultModel: hasRm,
|
|
113
|
+
description: i.description,
|
|
114
|
+
title: i.title,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
className: this.constructor.name,
|
|
119
|
+
basePath: this.endpoint,
|
|
120
|
+
ignoreHttpLogging: this.ignoreHttpLogging,
|
|
121
|
+
ignoreInDocumentation: this.ignoreInDocumentation,
|
|
122
|
+
description: this.description,
|
|
123
|
+
createdAt: this.createdAt,
|
|
124
|
+
initializedAt: this.initializedAt,
|
|
125
|
+
handlerCount: handlers.length,
|
|
126
|
+
handlers,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
71
129
|
async __precheck(req) { return null; }
|
|
72
130
|
async __execute(req) {
|
|
73
131
|
if (this.endpoints && this.endpoints.length > 0) {
|
|
@@ -4,7 +4,11 @@ export interface IBaseService {
|
|
|
4
4
|
getName: () => string;
|
|
5
5
|
}
|
|
6
6
|
export declare class BaseService implements IBaseService {
|
|
7
|
+
static allInstances: BaseService[];
|
|
7
8
|
protected name: string;
|
|
9
|
+
createdAt: number;
|
|
10
|
+
initializedAt: number | null;
|
|
11
|
+
applicationReadyAt: number | null;
|
|
8
12
|
constructor(name: string);
|
|
9
13
|
init(): Promise<void>;
|
|
10
14
|
finishService(): Promise<void>;
|
|
@@ -11,14 +11,20 @@ process.on('SIGTERM', async () => {
|
|
|
11
11
|
process.exit(0);
|
|
12
12
|
});
|
|
13
13
|
class BaseService {
|
|
14
|
+
static allInstances = [];
|
|
14
15
|
name = "BaseService";
|
|
16
|
+
createdAt = Date.now();
|
|
17
|
+
initializedAt = null;
|
|
18
|
+
applicationReadyAt = null;
|
|
15
19
|
constructor(name) {
|
|
16
20
|
this.name = name;
|
|
17
21
|
this.finishService = this.finishService.bind(this);
|
|
22
|
+
BaseService.allInstances.push(this);
|
|
18
23
|
}
|
|
19
24
|
async init() {
|
|
20
25
|
console.log("Service: " + this.name + " initialized");
|
|
21
26
|
finalize.push(this.finishService);
|
|
27
|
+
this.initializedAt = Date.now();
|
|
22
28
|
}
|
|
23
29
|
async finishService() {
|
|
24
30
|
console.log("finishService -> " + (this.name ?? "no-name"));
|
|
@@ -26,6 +32,8 @@ class BaseService {
|
|
|
26
32
|
getName() {
|
|
27
33
|
return this.name;
|
|
28
34
|
}
|
|
29
|
-
applicationReady() {
|
|
35
|
+
applicationReady() {
|
|
36
|
+
this.applicationReadyAt = Date.now();
|
|
37
|
+
}
|
|
30
38
|
}
|
|
31
39
|
exports.BaseService = BaseService;
|
|
@@ -62,4 +62,9 @@ export declare class LogService extends BaseService {
|
|
|
62
62
|
createDate(d?: Date): string;
|
|
63
63
|
leadZero(num: number): string;
|
|
64
64
|
createText(data: any, level: LOG_LEVEL): string;
|
|
65
|
+
private static readonly SANITIZERS;
|
|
66
|
+
static sanitize(text: string): string;
|
|
67
|
+
private static readonly PAN_CANDIDATE_REGEX;
|
|
68
|
+
private static isLuhnValid;
|
|
69
|
+
static maskPAN(text: string): string;
|
|
65
70
|
}
|
|
@@ -244,7 +244,54 @@ class LogService extends BaseService_1.BaseService {
|
|
|
244
244
|
}
|
|
245
245
|
if (res.length > this.options.textLimit && level === LOG_LEVEL.INFO)
|
|
246
246
|
res = res.substring(0, this.options.textLimit) + "... (total: " + res.length + ")";
|
|
247
|
+
res = LogService.sanitize(res);
|
|
247
248
|
return res;
|
|
248
249
|
}
|
|
250
|
+
static SANITIZERS = [
|
|
251
|
+
[/-----BEGIN [A-Z0-9 ]+-----[\s\S]+?-----END [A-Z0-9 ]+-----/g, "<REDACTED PEM>"],
|
|
252
|
+
[/eyJ[A-Za-z0-9_-]{5,}\.eyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]+/g, "<REDACTED JWT>"],
|
|
253
|
+
[/(Bearer|Basic|Digest)\s+[A-Za-z0-9\-._~+\/=]+/gi, "$1 <REDACTED>"],
|
|
254
|
+
[/%B\d{13,19}\^[^?]{2,50}\^\d{4,}[^?]*\?/g, "<REDACTED TRACK1>"],
|
|
255
|
+
[/;\d{13,19}=\d{4,}[^?]*\?/g, "<REDACTED TRACK2>"],
|
|
256
|
+
[/("(?:password|passwd|pwd|secret|api[_-]?key|api[_-]?secret|client[_-]?secret|access[_-]?key|private[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token|token|authorization|cookie|set[_-]?cookie|session[_-]?id|sess[_-]?id|sid|cvv2?|cvc2?|cvn|cav2?|cid|security[_-]?code|card[_-]?verification[_-]?(?:value|code)?|pin|pin[_-]?block|encrypted[_-]?pin|iso[_-]?9564|exp|expiry|expiration|expiration[_-]?date|exp[_-]?date|exp[_-]?month|exp[_-]?year|cardholder[_-]?name|holder[_-]?name|card[_-]?holder)"\s*:\s*)"[^"]*"/gi, '$1"<REDACTED>"'],
|
|
257
|
+
[/("(?:cvv2?|cvc2?|pin|exp[_-]?month|exp[_-]?year|service[_-]?code)"\s*:\s*)(-?\d+)/gi, '$1"<REDACTED>"'],
|
|
258
|
+
];
|
|
259
|
+
static sanitize(text) {
|
|
260
|
+
if (!text || text.length < 3)
|
|
261
|
+
return text;
|
|
262
|
+
let out = text;
|
|
263
|
+
for (const [re, repl] of LogService.SANITIZERS)
|
|
264
|
+
out = out.replace(re, repl);
|
|
265
|
+
return LogService.maskPAN(out);
|
|
266
|
+
}
|
|
267
|
+
static PAN_CANDIDATE_REGEX = /(?<!\d)(?:\d[ -]?){12,18}\d(?!\d)/g;
|
|
268
|
+
static isLuhnValid(digits) {
|
|
269
|
+
if (digits.length < 13 || digits.length > 19)
|
|
270
|
+
return false;
|
|
271
|
+
let sum = 0;
|
|
272
|
+
let alt = false;
|
|
273
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
274
|
+
const d = digits.charCodeAt(i) - 48;
|
|
275
|
+
if (d < 0 || d > 9)
|
|
276
|
+
return false;
|
|
277
|
+
let x = d;
|
|
278
|
+
if (alt) {
|
|
279
|
+
x *= 2;
|
|
280
|
+
if (x > 9)
|
|
281
|
+
x -= 9;
|
|
282
|
+
}
|
|
283
|
+
sum += x;
|
|
284
|
+
alt = !alt;
|
|
285
|
+
}
|
|
286
|
+
return sum % 10 === 0;
|
|
287
|
+
}
|
|
288
|
+
static maskPAN(text) {
|
|
289
|
+
if (!text || text.length < 13)
|
|
290
|
+
return text;
|
|
291
|
+
return text.replace(LogService.PAN_CANDIDATE_REGEX, (match) => {
|
|
292
|
+
const digits = match.replace(/\D/g, "");
|
|
293
|
+
return LogService.isLuhnValid(digits) ? "xxxx...xxxx" : match;
|
|
294
|
+
});
|
|
295
|
+
}
|
|
249
296
|
}
|
|
250
297
|
exports.LogService = LogService;
|
|
@@ -90,14 +90,14 @@ class MicroserviceClient extends BaseService_1.BaseService {
|
|
|
90
90
|
await TimeframeService_1.TimeframeService.wait(attemptTimeout * attempt);
|
|
91
91
|
continue;
|
|
92
92
|
}
|
|
93
|
-
return { ...DefaultErrors_1.default.EXTERNAL_SERVICE_ERROR, details: "Network connection lost after multiple retries, attempts: " + attempt };
|
|
93
|
+
return { ...DefaultErrors_1.default.EXTERNAL_SERVICE_ERROR, stack: [this.options.host, req], details: "Network connection lost after multiple retries, attempts: " + attempt };
|
|
94
94
|
}
|
|
95
95
|
if (!resp.ok) {
|
|
96
96
|
console.log(`Microservice call error response:`, resp);
|
|
97
97
|
const remoteError = resp.error?.error;
|
|
98
98
|
if (DefaultErrors_1.ErrorUtils.isError(remoteError))
|
|
99
99
|
return remoteError;
|
|
100
|
-
return { ...DefaultErrors_1.default.BAD_REQUEST, stack: [resp.error], details: resp.details };
|
|
100
|
+
return { ...DefaultErrors_1.default.BAD_REQUEST, stack: [this.options.host, req, resp.error], details: resp.details };
|
|
101
101
|
}
|
|
102
102
|
const body = resp.data;
|
|
103
103
|
const isAccepted = body?.error?.code === DefaultErrors_1.default.ACCEPTED.code || body?.code === DefaultErrors_1.default.ACCEPTED.code;
|
|
@@ -106,14 +106,14 @@ class MicroserviceClient extends BaseService_1.BaseService {
|
|
|
106
106
|
await TimeframeService_1.TimeframeService.wait(attemptTimeout * attempt);
|
|
107
107
|
continue;
|
|
108
108
|
}
|
|
109
|
-
return { ...DefaultErrors_1.default.EXTERNAL_SERVICE_ERROR, details: "Microservice processing timeout, attempts: " + attempt };
|
|
109
|
+
return { ...DefaultErrors_1.default.EXTERNAL_SERVICE_ERROR, stack: [this.options.host, req], details: "Microservice processing timeout, attempts: " + attempt };
|
|
110
110
|
}
|
|
111
111
|
if (body && typeof body === "object") {
|
|
112
112
|
return "error" in body ? body.error : (body.data ?? null);
|
|
113
113
|
}
|
|
114
114
|
return body ?? null;
|
|
115
115
|
}
|
|
116
|
-
return { ...DefaultErrors_1.default.EXTERNAL_SERVICE_ERROR, details: "Unknown polling error" };
|
|
116
|
+
return { ...DefaultErrors_1.default.EXTERNAL_SERVICE_ERROR, stack: [this.options.host, req], details: "Unknown polling error" };
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
exports.MicroserviceClient = MicroserviceClient;
|
|
@@ -26,6 +26,47 @@ export type THttpOutgoingLogItem = {
|
|
|
26
26
|
response: any;
|
|
27
27
|
error?: string;
|
|
28
28
|
};
|
|
29
|
+
export type THttpHostStats = {
|
|
30
|
+
host: string;
|
|
31
|
+
count: number;
|
|
32
|
+
errors: number;
|
|
33
|
+
timeouts: number;
|
|
34
|
+
retries: number;
|
|
35
|
+
avgMs: number;
|
|
36
|
+
minMs: number;
|
|
37
|
+
maxMs: number;
|
|
38
|
+
totalTimeMs: number;
|
|
39
|
+
inFlight: number;
|
|
40
|
+
byStatus: {
|
|
41
|
+
s2xx: number;
|
|
42
|
+
s3xx: number;
|
|
43
|
+
s4xx: number;
|
|
44
|
+
s5xx: number;
|
|
45
|
+
other: number;
|
|
46
|
+
networkErrors: number;
|
|
47
|
+
};
|
|
48
|
+
lastRequestAt: number;
|
|
49
|
+
};
|
|
50
|
+
export type THttpStatusDistribution = {
|
|
51
|
+
s2xx: number;
|
|
52
|
+
s3xx: number;
|
|
53
|
+
s4xx: number;
|
|
54
|
+
s5xx: number;
|
|
55
|
+
other: number;
|
|
56
|
+
networkErrors: number;
|
|
57
|
+
timeouts: number;
|
|
58
|
+
};
|
|
59
|
+
export type THttpStatsSnapshot = {
|
|
60
|
+
totalRequests: number;
|
|
61
|
+
inFlight: {
|
|
62
|
+
current: number;
|
|
63
|
+
peak: number;
|
|
64
|
+
byHost: Record<string, number>;
|
|
65
|
+
};
|
|
66
|
+
statusDistribution: THttpStatusDistribution;
|
|
67
|
+
perHost: THttpHostStats[];
|
|
68
|
+
topSlow: THttpOutgoingLogItem[];
|
|
69
|
+
};
|
|
29
70
|
export type TRetryPolicy = {
|
|
30
71
|
enabled?: boolean;
|
|
31
72
|
maxAttempts?: number;
|
|
@@ -90,6 +131,21 @@ export declare class Http {
|
|
|
90
131
|
private static _truncate;
|
|
91
132
|
private static _pushOutgoingLog;
|
|
92
133
|
static getOutgoingLogSnapshot(): THttpOutgoingLogItem[];
|
|
134
|
+
private static _totalRequests;
|
|
135
|
+
private static _inFlight;
|
|
136
|
+
private static _peakInFlight;
|
|
137
|
+
private static _inFlightByHost;
|
|
138
|
+
private static _statusDist;
|
|
139
|
+
private static _perHost;
|
|
140
|
+
private static _topSlow;
|
|
141
|
+
private static _topSlowLimit;
|
|
142
|
+
private static _hostFromUrl;
|
|
143
|
+
private static _isTimeoutError;
|
|
144
|
+
private static _incInFlight;
|
|
145
|
+
private static _decInFlight;
|
|
146
|
+
private static _updateStats;
|
|
147
|
+
private static _pushToTopSlow;
|
|
148
|
+
static getHttpStats(): THttpStatsSnapshot;
|
|
93
149
|
private static ensureInit;
|
|
94
150
|
static configure(partial: Partial<THttpConfig>): void;
|
|
95
151
|
static createAgent(opts?: ConstructorParameters<typeof Agent>[0]): Agent;
|
|
@@ -118,10 +118,149 @@ class Http {
|
|
|
118
118
|
this._outgoingLog.shift();
|
|
119
119
|
if (this._nextOutgoingLogID > Number.MAX_SAFE_INTEGER - 1000)
|
|
120
120
|
this._nextOutgoingLogID = 0;
|
|
121
|
+
return entry;
|
|
121
122
|
}
|
|
122
123
|
static getOutgoingLogSnapshot() {
|
|
123
124
|
return this._outgoingLog.slice();
|
|
124
125
|
}
|
|
126
|
+
static _totalRequests = 0;
|
|
127
|
+
static _inFlight = 0;
|
|
128
|
+
static _peakInFlight = 0;
|
|
129
|
+
static _inFlightByHost = new Map();
|
|
130
|
+
static _statusDist = {
|
|
131
|
+
s2xx: 0, s3xx: 0, s4xx: 0, s5xx: 0, other: 0, networkErrors: 0, timeouts: 0,
|
|
132
|
+
};
|
|
133
|
+
static _perHost = new Map();
|
|
134
|
+
static _topSlow = [];
|
|
135
|
+
static _topSlowLimit = 20;
|
|
136
|
+
static _hostFromUrl(url) {
|
|
137
|
+
try {
|
|
138
|
+
return new URL(url).host || "unknown";
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return "unknown";
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
static _isTimeoutError(err) {
|
|
145
|
+
if (!err || typeof err !== "object")
|
|
146
|
+
return false;
|
|
147
|
+
const e = err;
|
|
148
|
+
if (e.name === "AbortError")
|
|
149
|
+
return true;
|
|
150
|
+
if (typeof e.code === "string" && e.code.includes("TIMEOUT"))
|
|
151
|
+
return true;
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
static _incInFlight(host) {
|
|
155
|
+
this._inFlight++;
|
|
156
|
+
if (this._inFlight > this._peakInFlight)
|
|
157
|
+
this._peakInFlight = this._inFlight;
|
|
158
|
+
this._inFlightByHost.set(host, (this._inFlightByHost.get(host) ?? 0) + 1);
|
|
159
|
+
}
|
|
160
|
+
static _decInFlight(host) {
|
|
161
|
+
this._inFlight = Math.max(0, this._inFlight - 1);
|
|
162
|
+
const prev = this._inFlightByHost.get(host) ?? 0;
|
|
163
|
+
const next = Math.max(0, prev - 1);
|
|
164
|
+
if (next === 0)
|
|
165
|
+
this._inFlightByHost.delete(host);
|
|
166
|
+
else
|
|
167
|
+
this._inFlightByHost.set(host, next);
|
|
168
|
+
}
|
|
169
|
+
static _updateStats(host, entry, isTimeout) {
|
|
170
|
+
this._totalRequests++;
|
|
171
|
+
if (typeof entry.status === "number") {
|
|
172
|
+
const s = entry.status;
|
|
173
|
+
if (s >= 200 && s < 300)
|
|
174
|
+
this._statusDist.s2xx++;
|
|
175
|
+
else if (s >= 300 && s < 400)
|
|
176
|
+
this._statusDist.s3xx++;
|
|
177
|
+
else if (s >= 400 && s < 500)
|
|
178
|
+
this._statusDist.s4xx++;
|
|
179
|
+
else if (s >= 500 && s < 600)
|
|
180
|
+
this._statusDist.s5xx++;
|
|
181
|
+
else
|
|
182
|
+
this._statusDist.other++;
|
|
183
|
+
}
|
|
184
|
+
else if (entry.error) {
|
|
185
|
+
this._statusDist.networkErrors++;
|
|
186
|
+
if (isTimeout)
|
|
187
|
+
this._statusDist.timeouts++;
|
|
188
|
+
}
|
|
189
|
+
let h = this._perHost.get(host);
|
|
190
|
+
if (!h) {
|
|
191
|
+
h = {
|
|
192
|
+
host,
|
|
193
|
+
count: 0, errors: 0, timeouts: 0, retries: 0,
|
|
194
|
+
avgMs: 0, minMs: Infinity, maxMs: 0, totalTimeMs: 0,
|
|
195
|
+
inFlight: 0,
|
|
196
|
+
byStatus: { s2xx: 0, s3xx: 0, s4xx: 0, s5xx: 0, other: 0, networkErrors: 0 },
|
|
197
|
+
lastRequestAt: 0,
|
|
198
|
+
};
|
|
199
|
+
this._perHost.set(host, h);
|
|
200
|
+
}
|
|
201
|
+
h.count++;
|
|
202
|
+
h.totalTimeMs += entry.time;
|
|
203
|
+
h.avgMs = Math.round((h.totalTimeMs / h.count) * 10) / 10;
|
|
204
|
+
if (entry.time < h.minMs)
|
|
205
|
+
h.minMs = entry.time;
|
|
206
|
+
if (entry.time > h.maxMs)
|
|
207
|
+
h.maxMs = entry.time;
|
|
208
|
+
if (!entry.ok)
|
|
209
|
+
h.errors++;
|
|
210
|
+
if (isTimeout)
|
|
211
|
+
h.timeouts++;
|
|
212
|
+
h.retries += Math.max(0, entry.attempts - 1);
|
|
213
|
+
h.lastRequestAt = Date.now();
|
|
214
|
+
if (typeof entry.status === "number") {
|
|
215
|
+
const s = entry.status;
|
|
216
|
+
if (s >= 200 && s < 300)
|
|
217
|
+
h.byStatus.s2xx++;
|
|
218
|
+
else if (s >= 300 && s < 400)
|
|
219
|
+
h.byStatus.s3xx++;
|
|
220
|
+
else if (s >= 400 && s < 500)
|
|
221
|
+
h.byStatus.s4xx++;
|
|
222
|
+
else if (s >= 500 && s < 600)
|
|
223
|
+
h.byStatus.s5xx++;
|
|
224
|
+
else
|
|
225
|
+
h.byStatus.other++;
|
|
226
|
+
}
|
|
227
|
+
else if (entry.error) {
|
|
228
|
+
h.byStatus.networkErrors++;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
static _pushToTopSlow(entry) {
|
|
232
|
+
if (this._topSlow.length < this._topSlowLimit) {
|
|
233
|
+
this._topSlow.push(entry);
|
|
234
|
+
this._topSlow.sort((a, b) => b.time - a.time);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const slowest_of_top = this._topSlow[this._topSlow.length - 1];
|
|
238
|
+
if (entry.time > slowest_of_top.time) {
|
|
239
|
+
this._topSlow.push(entry);
|
|
240
|
+
this._topSlow.sort((a, b) => b.time - a.time);
|
|
241
|
+
this._topSlow.pop();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
static getHttpStats() {
|
|
245
|
+
const perHost = Array.from(this._perHost.values())
|
|
246
|
+
.map(h => ({
|
|
247
|
+
...h,
|
|
248
|
+
minMs: Number.isFinite(h.minMs) ? h.minMs : 0,
|
|
249
|
+
inFlight: this._inFlightByHost.get(h.host) ?? 0,
|
|
250
|
+
}))
|
|
251
|
+
.sort((a, b) => b.count - a.count);
|
|
252
|
+
return {
|
|
253
|
+
totalRequests: this._totalRequests,
|
|
254
|
+
inFlight: {
|
|
255
|
+
current: this._inFlight,
|
|
256
|
+
peak: this._peakInFlight,
|
|
257
|
+
byHost: Object.fromEntries(this._inFlightByHost),
|
|
258
|
+
},
|
|
259
|
+
statusDistribution: { ...this._statusDist },
|
|
260
|
+
perHost,
|
|
261
|
+
topSlow: this._topSlow.slice(),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
125
264
|
static ensureInit() {
|
|
126
265
|
if (this._inited)
|
|
127
266
|
return;
|
|
@@ -299,8 +438,11 @@ class Http {
|
|
|
299
438
|
const responseType = opts.responseType ?? "json";
|
|
300
439
|
const throwOnJsonParseError = opts.throwOnJsonParseError ?? false;
|
|
301
440
|
let attemptsMade = 0;
|
|
441
|
+
const host = Http._hostFromUrl(url);
|
|
442
|
+
Http._incInFlight(host);
|
|
302
443
|
const finish = (result, networkErr) => {
|
|
303
|
-
Http.
|
|
444
|
+
Http._decInFlight(host);
|
|
445
|
+
const entry = Http._pushOutgoingLog({
|
|
304
446
|
created: overallStarted,
|
|
305
447
|
time: Date.now() - overallStarted,
|
|
306
448
|
method: opts.method,
|
|
@@ -312,6 +454,9 @@ class Http {
|
|
|
312
454
|
response: Http._truncate(result.ok ? result.data : result.error),
|
|
313
455
|
error: networkErr ? String(networkErr?.message ?? networkErr) : undefined,
|
|
314
456
|
});
|
|
457
|
+
const isTimeout = Http._isTimeoutError(networkErr);
|
|
458
|
+
Http._updateStats(host, entry, isTimeout);
|
|
459
|
+
Http._pushToTopSlow(entry);
|
|
315
460
|
return result;
|
|
316
461
|
};
|
|
317
462
|
for (let attempt = 1; attempt <= retryCfg.maxAttempts; attempt++) {
|
|
@@ -23,6 +23,8 @@ export declare class Monitor extends BaseEndpoint {
|
|
|
23
23
|
outgoingNetlog(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
24
24
|
sysinfo(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
25
25
|
procinfo(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
26
|
+
httpstats(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
27
|
+
inventory(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
26
28
|
metrics(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
27
29
|
connections(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
28
30
|
db(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
@@ -7,6 +7,7 @@ exports.Monitor = exports.S_MONITOR_REGISTRATE_ACTION = void 0;
|
|
|
7
7
|
const badmfck_signal_1 = require("badmfck-signal");
|
|
8
8
|
const APIService_1 = require("../APIService");
|
|
9
9
|
const BaseEndpoint_1 = require("../BaseEndpoint");
|
|
10
|
+
const BaseService_1 = require("../BaseService");
|
|
10
11
|
const LogService_1 = require("../LogService");
|
|
11
12
|
const crypto_1 = __importDefault(require("crypto"));
|
|
12
13
|
const DefaultErrors_1 = __importDefault(require("../structures/DefaultErrors"));
|
|
@@ -44,6 +45,8 @@ class Monitor extends BaseEndpoint_1.BaseEndpoint {
|
|
|
44
45
|
{ ignoreInterceptor: true, endpoint: "outgoingnetlog", handler: this.outgoingNetlog },
|
|
45
46
|
{ ignoreInterceptor: true, endpoint: "sysinfo", handler: this.sysinfo },
|
|
46
47
|
{ ignoreInterceptor: true, endpoint: "procinfo", handler: this.procinfo },
|
|
48
|
+
{ ignoreInterceptor: true, endpoint: "inventory", handler: this.inventory },
|
|
49
|
+
{ ignoreInterceptor: true, endpoint: "httpstats", handler: this.httpstats },
|
|
47
50
|
{ ignoreInterceptor: true, endpoint: "metrics", handler: this.metrics, validationModel: {
|
|
48
51
|
range: "",
|
|
49
52
|
$__range_values: ["week", "day", "hour"]
|
|
@@ -137,6 +140,36 @@ class Monitor extends BaseEndpoint_1.BaseEndpoint {
|
|
|
137
140
|
await this.checkAuthentication(req);
|
|
138
141
|
return { data: await ProcessMonitor_1.ProcessMonitor.getSnapshot() };
|
|
139
142
|
}
|
|
143
|
+
async httpstats(req) {
|
|
144
|
+
await this.checkAuthentication(req);
|
|
145
|
+
return { data: Http_1.Http.getHttpStats() };
|
|
146
|
+
}
|
|
147
|
+
async inventory(req) {
|
|
148
|
+
await this.checkAuthentication(req);
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
const services = BaseService_1.BaseService.allInstances.map(s => ({
|
|
151
|
+
className: s.constructor.name,
|
|
152
|
+
name: s.getName(),
|
|
153
|
+
initialized: s.initializedAt !== null,
|
|
154
|
+
createdAt: s.createdAt,
|
|
155
|
+
initializedAt: s.initializedAt,
|
|
156
|
+
applicationReadyAt: s.applicationReadyAt,
|
|
157
|
+
uptimeSec: s.initializedAt ? Math.round((now - s.initializedAt) / 1000) : null,
|
|
158
|
+
}));
|
|
159
|
+
const endpoints = BaseEndpoint_1.BaseEndpoint.allInstances.map(e => e.getInventory());
|
|
160
|
+
return {
|
|
161
|
+
data: {
|
|
162
|
+
services,
|
|
163
|
+
endpoints,
|
|
164
|
+
counts: {
|
|
165
|
+
services: services.length,
|
|
166
|
+
endpoints: endpoints.length,
|
|
167
|
+
totalHandlers: endpoints.reduce((sum, e) => sum + e.handlerCount, 0),
|
|
168
|
+
},
|
|
169
|
+
entrypoint: BaseEndpoint_1.BaseEndpoint.getEntryPoint(),
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
140
173
|
async metrics(req) {
|
|
141
174
|
await this.checkAuthentication(req);
|
|
142
175
|
const result = await MonitorService_1.REQ_STAT.request(req.data);
|