apitally 0.10.0 → 0.11.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/README.md +1 -2
- package/dist/common/client.cjs +398 -12
- package/dist/common/client.cjs.map +1 -1
- package/dist/common/client.d.cts +8 -1
- package/dist/common/client.d.ts +8 -1
- package/dist/common/client.js +394 -8
- package/dist/common/client.js.map +1 -1
- package/dist/common/consumerRegistry.d.cts +4 -0
- package/dist/common/consumerRegistry.d.ts +4 -0
- package/dist/common/requestCounter.d.cts +4 -0
- package/dist/common/requestCounter.d.ts +4 -0
- package/dist/common/requestLogger.cjs +419 -0
- package/dist/common/requestLogger.cjs.map +1 -0
- package/dist/common/requestLogger.d.cts +63 -0
- package/dist/common/requestLogger.d.ts +63 -0
- package/dist/common/requestLogger.js +395 -0
- package/dist/common/requestLogger.js.map +1 -0
- package/dist/common/serverErrorCounter.d.cts +4 -0
- package/dist/common/serverErrorCounter.d.ts +4 -0
- package/dist/common/tempGzipFile.cjs +93 -0
- package/dist/common/tempGzipFile.cjs.map +1 -0
- package/dist/common/tempGzipFile.d.cts +17 -0
- package/dist/common/tempGzipFile.d.ts +17 -0
- package/dist/common/tempGzipFile.js +74 -0
- package/dist/common/tempGzipFile.js.map +1 -0
- package/dist/common/types.cjs.map +1 -1
- package/dist/common/types.d.cts +5 -0
- package/dist/common/types.d.ts +5 -0
- package/dist/common/validationErrorCounter.d.cts +4 -0
- package/dist/common/validationErrorCounter.d.ts +4 -0
- package/dist/express/index.cjs +500 -34
- package/dist/express/index.cjs.map +1 -1
- package/dist/express/index.d.cts +4 -0
- package/dist/express/index.d.ts +4 -0
- package/dist/express/index.js +496 -30
- package/dist/express/index.js.map +1 -1
- package/dist/express/middleware.cjs +500 -34
- package/dist/express/middleware.cjs.map +1 -1
- package/dist/express/middleware.d.cts +4 -0
- package/dist/express/middleware.d.ts +4 -0
- package/dist/express/middleware.js +496 -30
- package/dist/express/middleware.js.map +1 -1
- package/dist/fastify/index.cjs +491 -27
- package/dist/fastify/index.cjs.map +1 -1
- package/dist/fastify/index.d.cts +4 -0
- package/dist/fastify/index.d.ts +4 -0
- package/dist/fastify/index.js +487 -23
- package/dist/fastify/index.js.map +1 -1
- package/dist/fastify/plugin.cjs +491 -27
- package/dist/fastify/plugin.cjs.map +1 -1
- package/dist/fastify/plugin.d.cts +4 -0
- package/dist/fastify/plugin.d.ts +4 -0
- package/dist/fastify/plugin.js +487 -23
- package/dist/fastify/plugin.js.map +1 -1
- package/dist/hono/index.cjs +460 -15
- package/dist/hono/index.cjs.map +1 -1
- package/dist/hono/index.d.cts +4 -0
- package/dist/hono/index.d.ts +4 -0
- package/dist/hono/index.js +456 -11
- package/dist/hono/index.js.map +1 -1
- package/dist/hono/middleware.cjs +460 -15
- package/dist/hono/middleware.cjs.map +1 -1
- package/dist/hono/middleware.d.cts +4 -0
- package/dist/hono/middleware.d.ts +4 -0
- package/dist/hono/middleware.js +456 -11
- package/dist/hono/middleware.js.map +1 -1
- package/dist/koa/index.cjs +475 -15
- package/dist/koa/index.cjs.map +1 -1
- package/dist/koa/index.d.cts +4 -0
- package/dist/koa/index.d.ts +4 -0
- package/dist/koa/index.js +471 -11
- package/dist/koa/index.js.map +1 -1
- package/dist/koa/middleware.cjs +475 -15
- package/dist/koa/middleware.cjs.map +1 -1
- package/dist/koa/middleware.d.cts +4 -0
- package/dist/koa/middleware.d.ts +4 -0
- package/dist/koa/middleware.js +471 -11
- package/dist/koa/middleware.js.map +1 -1
- package/dist/nestjs/index.cjs +500 -34
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.d.cts +4 -0
- package/dist/nestjs/index.d.ts +4 -0
- package/dist/nestjs/index.js +496 -30
- package/dist/nestjs/index.js.map +1 -1
- package/package.json +8 -2
package/dist/nestjs/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import { BaseExceptionFilter } from "@nestjs/core";
|
|
|
17
17
|
import { performance } from "perf_hooks";
|
|
18
18
|
|
|
19
19
|
// src/common/client.ts
|
|
20
|
-
import { randomUUID } from "crypto";
|
|
20
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
21
21
|
import fetchRetry from "fetch-retry";
|
|
22
22
|
|
|
23
23
|
// src/common/consumerRegistry.ts
|
|
@@ -186,6 +186,393 @@ var _RequestCounter = class _RequestCounter {
|
|
|
186
186
|
__name(_RequestCounter, "RequestCounter");
|
|
187
187
|
var RequestCounter = _RequestCounter;
|
|
188
188
|
|
|
189
|
+
// src/common/requestLogger.ts
|
|
190
|
+
import { Buffer as Buffer3 } from "buffer";
|
|
191
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
192
|
+
import { unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
193
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
194
|
+
import { join as join2 } from "path";
|
|
195
|
+
|
|
196
|
+
// src/common/tempGzipFile.ts
|
|
197
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
198
|
+
import { randomUUID } from "crypto";
|
|
199
|
+
import { createWriteStream, readFile, unlinkSync } from "fs";
|
|
200
|
+
import { tmpdir } from "os";
|
|
201
|
+
import { join } from "path";
|
|
202
|
+
import { createGzip } from "zlib";
|
|
203
|
+
var _TempGzipFile = class _TempGzipFile {
|
|
204
|
+
uuid;
|
|
205
|
+
filePath;
|
|
206
|
+
gzip;
|
|
207
|
+
writeStream;
|
|
208
|
+
readyPromise;
|
|
209
|
+
constructor() {
|
|
210
|
+
this.uuid = randomUUID();
|
|
211
|
+
this.filePath = join(tmpdir(), `apitally-${this.uuid}.gz`);
|
|
212
|
+
this.writeStream = createWriteStream(this.filePath);
|
|
213
|
+
this.readyPromise = new Promise((resolve, reject) => {
|
|
214
|
+
this.writeStream.once("ready", resolve);
|
|
215
|
+
this.writeStream.once("error", reject);
|
|
216
|
+
});
|
|
217
|
+
this.gzip = createGzip();
|
|
218
|
+
this.gzip.pipe(this.writeStream);
|
|
219
|
+
}
|
|
220
|
+
get size() {
|
|
221
|
+
return this.writeStream.bytesWritten;
|
|
222
|
+
}
|
|
223
|
+
async writeLine(data) {
|
|
224
|
+
await this.readyPromise;
|
|
225
|
+
return new Promise((resolve, reject) => {
|
|
226
|
+
this.gzip.write(Buffer2.concat([
|
|
227
|
+
data,
|
|
228
|
+
Buffer2.from("\n")
|
|
229
|
+
]), (error) => {
|
|
230
|
+
if (error) {
|
|
231
|
+
reject(error);
|
|
232
|
+
} else {
|
|
233
|
+
resolve();
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
async getContent() {
|
|
239
|
+
return new Promise((resolve, reject) => {
|
|
240
|
+
readFile(this.filePath, (error, data) => {
|
|
241
|
+
if (error) {
|
|
242
|
+
reject(error);
|
|
243
|
+
} else {
|
|
244
|
+
resolve(data);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
async close() {
|
|
250
|
+
return new Promise((resolve) => {
|
|
251
|
+
this.gzip.end(() => {
|
|
252
|
+
resolve();
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
async delete() {
|
|
257
|
+
await this.close();
|
|
258
|
+
unlinkSync(this.filePath);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
__name(_TempGzipFile, "TempGzipFile");
|
|
262
|
+
var TempGzipFile = _TempGzipFile;
|
|
263
|
+
|
|
264
|
+
// src/common/requestLogger.ts
|
|
265
|
+
var MAX_BODY_SIZE = 5e4;
|
|
266
|
+
var MAX_FILE_SIZE = 1e6;
|
|
267
|
+
var MAX_FILES = 50;
|
|
268
|
+
var MAX_PENDING_WRITES = 100;
|
|
269
|
+
var BODY_TOO_LARGE = Buffer3.from("<body too large>");
|
|
270
|
+
var BODY_MASKED = Buffer3.from("<masked>");
|
|
271
|
+
var MASKED = "******";
|
|
272
|
+
var ALLOWED_CONTENT_TYPES = [
|
|
273
|
+
"application/json",
|
|
274
|
+
"text/plain"
|
|
275
|
+
];
|
|
276
|
+
var EXCLUDE_PATH_PATTERNS = [
|
|
277
|
+
/\/_?healthz?$/,
|
|
278
|
+
/\/_?health[_-]?checks?$/,
|
|
279
|
+
/\/_?heart[_-]?beats?$/,
|
|
280
|
+
/\/ping$/,
|
|
281
|
+
/\/ready$/,
|
|
282
|
+
/\/live$/
|
|
283
|
+
];
|
|
284
|
+
var MASK_QUERY_PARAM_PATTERNS = [
|
|
285
|
+
/auth/i,
|
|
286
|
+
/api-?key/i,
|
|
287
|
+
/secret/i,
|
|
288
|
+
/token/i,
|
|
289
|
+
/password/i,
|
|
290
|
+
/pwd/i
|
|
291
|
+
];
|
|
292
|
+
var MASK_HEADER_PATTERNS = [
|
|
293
|
+
/auth/i,
|
|
294
|
+
/api-?key/i,
|
|
295
|
+
/secret/i,
|
|
296
|
+
/token/i,
|
|
297
|
+
/cookie/i
|
|
298
|
+
];
|
|
299
|
+
var DEFAULT_CONFIG = {
|
|
300
|
+
enabled: false,
|
|
301
|
+
logQueryParams: true,
|
|
302
|
+
logRequestHeaders: false,
|
|
303
|
+
logRequestBody: false,
|
|
304
|
+
logResponseHeaders: true,
|
|
305
|
+
logResponseBody: false,
|
|
306
|
+
maskQueryParams: [],
|
|
307
|
+
maskHeaders: [],
|
|
308
|
+
excludePaths: []
|
|
309
|
+
};
|
|
310
|
+
var _RequestLogger = class _RequestLogger {
|
|
311
|
+
config;
|
|
312
|
+
enabled;
|
|
313
|
+
suspendUntil = null;
|
|
314
|
+
pendingWrites = [];
|
|
315
|
+
currentFile = null;
|
|
316
|
+
files = [];
|
|
317
|
+
maintainIntervalId;
|
|
318
|
+
constructor(config) {
|
|
319
|
+
this.config = {
|
|
320
|
+
...DEFAULT_CONFIG,
|
|
321
|
+
...config
|
|
322
|
+
};
|
|
323
|
+
this.enabled = this.config.enabled && checkWritableFs();
|
|
324
|
+
if (this.enabled) {
|
|
325
|
+
this.maintainIntervalId = setInterval(() => {
|
|
326
|
+
this.maintain();
|
|
327
|
+
}, 1e3);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
shouldExcludePath(urlPath) {
|
|
331
|
+
const patterns = [
|
|
332
|
+
...this.config.excludePaths,
|
|
333
|
+
...EXCLUDE_PATH_PATTERNS
|
|
334
|
+
];
|
|
335
|
+
return matchPatterns(urlPath, patterns);
|
|
336
|
+
}
|
|
337
|
+
shouldMaskQueryParam(name) {
|
|
338
|
+
const patterns = [
|
|
339
|
+
...this.config.maskQueryParams,
|
|
340
|
+
...MASK_QUERY_PARAM_PATTERNS
|
|
341
|
+
];
|
|
342
|
+
return matchPatterns(name, patterns);
|
|
343
|
+
}
|
|
344
|
+
shouldMaskHeader(name) {
|
|
345
|
+
const patterns = [
|
|
346
|
+
...this.config.maskHeaders,
|
|
347
|
+
...MASK_HEADER_PATTERNS
|
|
348
|
+
];
|
|
349
|
+
return matchPatterns(name, patterns);
|
|
350
|
+
}
|
|
351
|
+
hasSupportedContentType(headers) {
|
|
352
|
+
var _a3;
|
|
353
|
+
const contentType = (_a3 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a3[1];
|
|
354
|
+
return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
|
|
355
|
+
}
|
|
356
|
+
maskQueryParams(search) {
|
|
357
|
+
const params = new URLSearchParams(search);
|
|
358
|
+
for (const [key] of params) {
|
|
359
|
+
if (this.shouldMaskQueryParam(key)) {
|
|
360
|
+
params.set(key, MASKED);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return params.toString();
|
|
364
|
+
}
|
|
365
|
+
maskHeaders(headers) {
|
|
366
|
+
return headers.map(([k, v]) => [
|
|
367
|
+
k,
|
|
368
|
+
this.shouldMaskHeader(k) ? MASKED : v
|
|
369
|
+
]);
|
|
370
|
+
}
|
|
371
|
+
logRequest(request, response) {
|
|
372
|
+
var _a3, _b;
|
|
373
|
+
if (!this.enabled || this.suspendUntil !== null) return;
|
|
374
|
+
const url = new URL(request.url);
|
|
375
|
+
const path = request.path ?? url.pathname;
|
|
376
|
+
if (this.shouldExcludePath(path) || (((_b = (_a3 = this.config).excludeCallback) == null ? void 0 : _b.call(_a3, request, response)) ?? false)) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
|
|
380
|
+
request.url = url.toString();
|
|
381
|
+
request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
|
|
382
|
+
response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
|
|
383
|
+
if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
|
|
384
|
+
request.body = void 0;
|
|
385
|
+
} else if (request.body) {
|
|
386
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
387
|
+
request.body = BODY_TOO_LARGE;
|
|
388
|
+
} else if (this.config.maskRequestBodyCallback) {
|
|
389
|
+
try {
|
|
390
|
+
request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
|
|
391
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
392
|
+
request.body = BODY_TOO_LARGE;
|
|
393
|
+
}
|
|
394
|
+
} catch {
|
|
395
|
+
request.body = void 0;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
|
|
400
|
+
response.body = void 0;
|
|
401
|
+
} else if (response.body) {
|
|
402
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
403
|
+
response.body = BODY_TOO_LARGE;
|
|
404
|
+
} else if (this.config.maskResponseBodyCallback) {
|
|
405
|
+
try {
|
|
406
|
+
response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
|
|
407
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
408
|
+
response.body = BODY_TOO_LARGE;
|
|
409
|
+
}
|
|
410
|
+
} catch {
|
|
411
|
+
response.body = void 0;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const item = {
|
|
416
|
+
uuid: randomUUID2(),
|
|
417
|
+
request: skipEmptyValues(request),
|
|
418
|
+
response: skipEmptyValues(response)
|
|
419
|
+
};
|
|
420
|
+
[
|
|
421
|
+
item.request.body,
|
|
422
|
+
item.response.body
|
|
423
|
+
].forEach((body) => {
|
|
424
|
+
if (body) {
|
|
425
|
+
body.toJSON = function() {
|
|
426
|
+
return this.toString("base64");
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
this.pendingWrites.push(JSON.stringify(item));
|
|
431
|
+
if (this.pendingWrites.length > MAX_PENDING_WRITES) {
|
|
432
|
+
this.pendingWrites.shift();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
async writeToFile() {
|
|
436
|
+
if (!this.enabled || this.pendingWrites.length === 0) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (!this.currentFile) {
|
|
440
|
+
this.currentFile = new TempGzipFile();
|
|
441
|
+
}
|
|
442
|
+
while (this.pendingWrites.length > 0) {
|
|
443
|
+
const item = this.pendingWrites.shift();
|
|
444
|
+
if (item) {
|
|
445
|
+
await this.currentFile.writeLine(Buffer3.from(item));
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
getFile() {
|
|
450
|
+
return this.files.shift();
|
|
451
|
+
}
|
|
452
|
+
retryFileLater(file) {
|
|
453
|
+
this.files.unshift(file);
|
|
454
|
+
}
|
|
455
|
+
async rotateFile() {
|
|
456
|
+
if (this.currentFile) {
|
|
457
|
+
await this.currentFile.close();
|
|
458
|
+
this.files.push(this.currentFile);
|
|
459
|
+
this.currentFile = null;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
async maintain() {
|
|
463
|
+
await this.writeToFile();
|
|
464
|
+
if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
|
|
465
|
+
await this.rotateFile();
|
|
466
|
+
}
|
|
467
|
+
while (this.files.length > MAX_FILES) {
|
|
468
|
+
const file = this.files.shift();
|
|
469
|
+
file == null ? void 0 : file.delete();
|
|
470
|
+
}
|
|
471
|
+
if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
|
|
472
|
+
this.suspendUntil = null;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
async clear() {
|
|
476
|
+
this.pendingWrites = [];
|
|
477
|
+
await this.rotateFile();
|
|
478
|
+
this.files.forEach((file) => {
|
|
479
|
+
file.delete();
|
|
480
|
+
});
|
|
481
|
+
this.files = [];
|
|
482
|
+
}
|
|
483
|
+
async close() {
|
|
484
|
+
this.enabled = false;
|
|
485
|
+
await this.clear();
|
|
486
|
+
if (this.maintainIntervalId) {
|
|
487
|
+
clearInterval(this.maintainIntervalId);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
__name(_RequestLogger, "RequestLogger");
|
|
492
|
+
var RequestLogger = _RequestLogger;
|
|
493
|
+
function convertHeaders(headers) {
|
|
494
|
+
if (headers instanceof Headers) {
|
|
495
|
+
return Array.from(headers.entries());
|
|
496
|
+
}
|
|
497
|
+
return Object.entries(headers).flatMap(([key, value]) => {
|
|
498
|
+
if (value === void 0) {
|
|
499
|
+
return [];
|
|
500
|
+
}
|
|
501
|
+
if (Array.isArray(value)) {
|
|
502
|
+
return value.map((v) => [
|
|
503
|
+
key,
|
|
504
|
+
v
|
|
505
|
+
]);
|
|
506
|
+
}
|
|
507
|
+
return [
|
|
508
|
+
[
|
|
509
|
+
key,
|
|
510
|
+
value.toString()
|
|
511
|
+
]
|
|
512
|
+
];
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
__name(convertHeaders, "convertHeaders");
|
|
516
|
+
function convertBody(body, contentType) {
|
|
517
|
+
if (!body || !contentType) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
try {
|
|
521
|
+
if (contentType.startsWith("application/json")) {
|
|
522
|
+
if (isValidJsonString(body)) {
|
|
523
|
+
return Buffer3.from(body);
|
|
524
|
+
} else {
|
|
525
|
+
return Buffer3.from(JSON.stringify(body));
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (contentType.startsWith("text/") && typeof body === "string") {
|
|
529
|
+
return Buffer3.from(body);
|
|
530
|
+
}
|
|
531
|
+
} catch (error) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
__name(convertBody, "convertBody");
|
|
536
|
+
function isValidJsonString(body) {
|
|
537
|
+
if (typeof body !== "string") {
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
try {
|
|
541
|
+
JSON.parse(body);
|
|
542
|
+
return true;
|
|
543
|
+
} catch {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
__name(isValidJsonString, "isValidJsonString");
|
|
548
|
+
function matchPatterns(value, patterns) {
|
|
549
|
+
return patterns.some((pattern) => {
|
|
550
|
+
return pattern.test(value);
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
__name(matchPatterns, "matchPatterns");
|
|
554
|
+
function skipEmptyValues(data) {
|
|
555
|
+
return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
|
|
556
|
+
if (v == null || Number.isNaN(v)) return false;
|
|
557
|
+
if (Array.isArray(v) || Buffer3.isBuffer(v) || typeof v === "string") {
|
|
558
|
+
return v.length > 0;
|
|
559
|
+
}
|
|
560
|
+
return true;
|
|
561
|
+
}));
|
|
562
|
+
}
|
|
563
|
+
__name(skipEmptyValues, "skipEmptyValues");
|
|
564
|
+
function checkWritableFs() {
|
|
565
|
+
try {
|
|
566
|
+
const testPath = join2(tmpdir2(), `apitally-${randomUUID2()}`);
|
|
567
|
+
writeFileSync(testPath, "test");
|
|
568
|
+
unlinkSync2(testPath);
|
|
569
|
+
return true;
|
|
570
|
+
} catch (error) {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
__name(checkWritableFs, "checkWritableFs");
|
|
575
|
+
|
|
189
576
|
// src/common/serverErrorCounter.ts
|
|
190
577
|
import { createHash } from "crypto";
|
|
191
578
|
var MAX_MSG_LENGTH = 2048;
|
|
@@ -358,11 +745,12 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
358
745
|
startupData;
|
|
359
746
|
startupDataSent = false;
|
|
360
747
|
requestCounter;
|
|
748
|
+
requestLogger;
|
|
361
749
|
validationErrorCounter;
|
|
362
750
|
serverErrorCounter;
|
|
363
751
|
consumerRegistry;
|
|
364
752
|
logger;
|
|
365
|
-
constructor({ clientId, env = "dev", logger }) {
|
|
753
|
+
constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
|
|
366
754
|
if (_ApitallyClient.instance) {
|
|
367
755
|
throw new Error("Apitally client is already initialized");
|
|
368
756
|
}
|
|
@@ -375,9 +763,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
375
763
|
_ApitallyClient.instance = this;
|
|
376
764
|
this.clientId = clientId;
|
|
377
765
|
this.env = env;
|
|
378
|
-
this.instanceUuid =
|
|
766
|
+
this.instanceUuid = randomUUID3();
|
|
379
767
|
this.syncDataQueue = [];
|
|
380
768
|
this.requestCounter = new RequestCounter();
|
|
769
|
+
this.requestLogger = new RequestLogger(requestLoggingConfig);
|
|
381
770
|
this.validationErrorCounter = new ValidationErrorCounter();
|
|
382
771
|
this.serverErrorCounter = new ServerErrorCounter();
|
|
383
772
|
this.consumerRegistry = new ConsumerRegistry();
|
|
@@ -399,6 +788,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
399
788
|
async handleShutdown() {
|
|
400
789
|
this.stopSync();
|
|
401
790
|
await this.sendSyncData();
|
|
791
|
+
await this.sendLogData();
|
|
792
|
+
await this.requestLogger.close();
|
|
402
793
|
_ApitallyClient.instance = void 0;
|
|
403
794
|
}
|
|
404
795
|
getHubUrlPrefix() {
|
|
@@ -445,7 +836,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
445
836
|
async sync() {
|
|
446
837
|
try {
|
|
447
838
|
const promises = [
|
|
448
|
-
this.sendSyncData()
|
|
839
|
+
this.sendSyncData(),
|
|
840
|
+
this.sendLogData()
|
|
449
841
|
];
|
|
450
842
|
if (!this.startupDataSent) {
|
|
451
843
|
promises.push(this.sendStartupData());
|
|
@@ -473,7 +865,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
473
865
|
this.logger.debug("Sending startup data to Apitally Hub");
|
|
474
866
|
const payload = {
|
|
475
867
|
instance_uuid: this.instanceUuid,
|
|
476
|
-
message_uuid:
|
|
868
|
+
message_uuid: randomUUID3(),
|
|
477
869
|
...this.startupData
|
|
478
870
|
};
|
|
479
871
|
try {
|
|
@@ -495,7 +887,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
495
887
|
const newPayload = {
|
|
496
888
|
time_offset: 0,
|
|
497
889
|
instance_uuid: this.instanceUuid,
|
|
498
|
-
message_uuid:
|
|
890
|
+
message_uuid: randomUUID3(),
|
|
499
891
|
requests: this.requestCounter.getAndResetRequests(),
|
|
500
892
|
validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
|
|
501
893
|
server_errors: this.serverErrorCounter.getAndResetServerErrors(),
|
|
@@ -514,8 +906,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
514
906
|
const timeOffset = Date.now() - time;
|
|
515
907
|
if (timeOffset <= MAX_QUEUE_TIME) {
|
|
516
908
|
if (i > 0) {
|
|
517
|
-
|
|
518
|
-
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
909
|
+
await this.randomDelay();
|
|
519
910
|
}
|
|
520
911
|
payload.time_offset = timeOffset / 1e3;
|
|
521
912
|
await this.sendData("sync", payload);
|
|
@@ -534,6 +925,52 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
534
925
|
}
|
|
535
926
|
}
|
|
536
927
|
}
|
|
928
|
+
async sendLogData() {
|
|
929
|
+
this.logger.debug("Sending request log data to Apitally Hub");
|
|
930
|
+
await this.requestLogger.rotateFile();
|
|
931
|
+
const fetchWithRetry = fetchRetry(fetch, {
|
|
932
|
+
retries: 3,
|
|
933
|
+
retryDelay: 1e3,
|
|
934
|
+
retryOn: [
|
|
935
|
+
408,
|
|
936
|
+
429,
|
|
937
|
+
500,
|
|
938
|
+
502,
|
|
939
|
+
503,
|
|
940
|
+
504
|
|
941
|
+
]
|
|
942
|
+
});
|
|
943
|
+
let i = 0;
|
|
944
|
+
let logFile;
|
|
945
|
+
while (logFile = this.requestLogger.getFile()) {
|
|
946
|
+
if (i > 0) {
|
|
947
|
+
await this.randomDelay();
|
|
948
|
+
}
|
|
949
|
+
try {
|
|
950
|
+
const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
|
|
951
|
+
method: "POST",
|
|
952
|
+
body: await logFile.getContent()
|
|
953
|
+
});
|
|
954
|
+
if (response.status === 402 && response.headers.has("Retry-After")) {
|
|
955
|
+
const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
|
|
956
|
+
if (retryAfter > 0) {
|
|
957
|
+
this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
|
|
958
|
+
this.requestLogger.clear();
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (!response.ok) {
|
|
963
|
+
throw new HTTPError(response);
|
|
964
|
+
}
|
|
965
|
+
logFile.delete();
|
|
966
|
+
} catch (error) {
|
|
967
|
+
this.requestLogger.retryFileLater(logFile);
|
|
968
|
+
break;
|
|
969
|
+
}
|
|
970
|
+
i++;
|
|
971
|
+
if (i >= 10) break;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
537
974
|
handleHubError(error) {
|
|
538
975
|
if (error instanceof HTTPError) {
|
|
539
976
|
if (error.response.status === 404) {
|
|
@@ -548,6 +985,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
548
985
|
}
|
|
549
986
|
return false;
|
|
550
987
|
}
|
|
988
|
+
async randomDelay() {
|
|
989
|
+
const delay = 100 + Math.random() * 200;
|
|
990
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
991
|
+
}
|
|
551
992
|
};
|
|
552
993
|
__name(_ApitallyClient, "ApitallyClient");
|
|
553
994
|
__publicField(_ApitallyClient, "instance");
|
|
@@ -717,18 +1158,18 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
717
1158
|
}
|
|
718
1159
|
try {
|
|
719
1160
|
const startTime = performance.now();
|
|
720
|
-
const
|
|
721
|
-
res.
|
|
1161
|
+
const originalSend = res.send;
|
|
1162
|
+
res.send = (body) => {
|
|
722
1163
|
res.locals.body = body;
|
|
723
|
-
return
|
|
1164
|
+
return originalSend.call(res, body);
|
|
724
1165
|
};
|
|
725
1166
|
res.on("finish", () => {
|
|
726
1167
|
try {
|
|
1168
|
+
const responseTime = performance.now() - startTime;
|
|
727
1169
|
const path = getRoutePath(req);
|
|
1170
|
+
const consumer = getConsumer(req);
|
|
1171
|
+
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
728
1172
|
if (path) {
|
|
729
|
-
const responseTime = performance.now() - startTime;
|
|
730
|
-
const consumer = getConsumer(req);
|
|
731
|
-
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
732
1173
|
client.requestCounter.addRequest({
|
|
733
1174
|
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
734
1175
|
method: req.method,
|
|
@@ -739,24 +1180,31 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
739
1180
|
responseSize: res.get("content-length")
|
|
740
1181
|
});
|
|
741
1182
|
if ((res.statusCode === 400 || res.statusCode === 422) && res.locals.body) {
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
}
|
|
746
|
-
if (validationErrors.length === 0) {
|
|
747
|
-
validationErrors.push(...extractCelebrateErrors(res.locals.body));
|
|
1183
|
+
let jsonBody;
|
|
1184
|
+
try {
|
|
1185
|
+
jsonBody = JSON.parse(res.locals.body);
|
|
1186
|
+
} catch {
|
|
748
1187
|
}
|
|
749
|
-
if (
|
|
750
|
-
validationErrors
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
1188
|
+
if (jsonBody) {
|
|
1189
|
+
const validationErrors = [];
|
|
1190
|
+
if (validationErrors.length === 0) {
|
|
1191
|
+
validationErrors.push(...extractExpressValidatorErrors(jsonBody));
|
|
1192
|
+
}
|
|
1193
|
+
if (validationErrors.length === 0) {
|
|
1194
|
+
validationErrors.push(...extractCelebrateErrors(jsonBody));
|
|
1195
|
+
}
|
|
1196
|
+
if (validationErrors.length === 0) {
|
|
1197
|
+
validationErrors.push(...extractNestValidationErrors(jsonBody));
|
|
1198
|
+
}
|
|
1199
|
+
validationErrors.forEach((error) => {
|
|
1200
|
+
client.validationErrorCounter.addValidationError({
|
|
1201
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1202
|
+
method: req.method,
|
|
1203
|
+
path: req.route.path,
|
|
1204
|
+
...error
|
|
1205
|
+
});
|
|
758
1206
|
});
|
|
759
|
-
}
|
|
1207
|
+
}
|
|
760
1208
|
}
|
|
761
1209
|
if (res.statusCode === 500 && res.locals.serverError) {
|
|
762
1210
|
const serverError = res.locals.serverError;
|
|
@@ -770,6 +1218,24 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
770
1218
|
});
|
|
771
1219
|
}
|
|
772
1220
|
}
|
|
1221
|
+
if (client.requestLogger.enabled) {
|
|
1222
|
+
client.requestLogger.logRequest({
|
|
1223
|
+
timestamp: Date.now() / 1e3,
|
|
1224
|
+
method: req.method,
|
|
1225
|
+
path,
|
|
1226
|
+
url: `${req.protocol}://${req.host}${req.originalUrl}`,
|
|
1227
|
+
headers: convertHeaders(req.headers),
|
|
1228
|
+
size: Number(req.get("content-length")),
|
|
1229
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1230
|
+
body: convertBody(req.body, req.get("content-type"))
|
|
1231
|
+
}, {
|
|
1232
|
+
statusCode: res.statusCode,
|
|
1233
|
+
responseTime: responseTime / 1e3,
|
|
1234
|
+
headers: convertHeaders(res.getHeaders()),
|
|
1235
|
+
size: Number(res.get("content-length")),
|
|
1236
|
+
body: convertBody(res.locals.body, res.get("content-type"))
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
773
1239
|
} catch (error) {
|
|
774
1240
|
client.logger.error("Error while logging request in Apitally middleware.", {
|
|
775
1241
|
request: req,
|