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.cjs
CHANGED
|
@@ -43,7 +43,7 @@ var import_core = require("@nestjs/core");
|
|
|
43
43
|
var import_perf_hooks = require("perf_hooks");
|
|
44
44
|
|
|
45
45
|
// src/common/client.ts
|
|
46
|
-
var
|
|
46
|
+
var import_crypto5 = require("crypto");
|
|
47
47
|
var import_fetch_retry = __toESM(require("fetch-retry"), 1);
|
|
48
48
|
|
|
49
49
|
// src/common/consumerRegistry.ts
|
|
@@ -212,8 +212,395 @@ var _RequestCounter = class _RequestCounter {
|
|
|
212
212
|
__name(_RequestCounter, "RequestCounter");
|
|
213
213
|
var RequestCounter = _RequestCounter;
|
|
214
214
|
|
|
215
|
-
// src/common/
|
|
215
|
+
// src/common/requestLogger.ts
|
|
216
|
+
var import_buffer2 = require("buffer");
|
|
217
|
+
var import_crypto2 = require("crypto");
|
|
218
|
+
var import_fs2 = require("fs");
|
|
219
|
+
var import_os2 = require("os");
|
|
220
|
+
var import_path2 = require("path");
|
|
221
|
+
|
|
222
|
+
// src/common/tempGzipFile.ts
|
|
223
|
+
var import_buffer = require("buffer");
|
|
216
224
|
var import_crypto = require("crypto");
|
|
225
|
+
var import_fs = require("fs");
|
|
226
|
+
var import_os = require("os");
|
|
227
|
+
var import_path = require("path");
|
|
228
|
+
var import_zlib = require("zlib");
|
|
229
|
+
var _TempGzipFile = class _TempGzipFile {
|
|
230
|
+
uuid;
|
|
231
|
+
filePath;
|
|
232
|
+
gzip;
|
|
233
|
+
writeStream;
|
|
234
|
+
readyPromise;
|
|
235
|
+
constructor() {
|
|
236
|
+
this.uuid = (0, import_crypto.randomUUID)();
|
|
237
|
+
this.filePath = (0, import_path.join)((0, import_os.tmpdir)(), `apitally-${this.uuid}.gz`);
|
|
238
|
+
this.writeStream = (0, import_fs.createWriteStream)(this.filePath);
|
|
239
|
+
this.readyPromise = new Promise((resolve, reject) => {
|
|
240
|
+
this.writeStream.once("ready", resolve);
|
|
241
|
+
this.writeStream.once("error", reject);
|
|
242
|
+
});
|
|
243
|
+
this.gzip = (0, import_zlib.createGzip)();
|
|
244
|
+
this.gzip.pipe(this.writeStream);
|
|
245
|
+
}
|
|
246
|
+
get size() {
|
|
247
|
+
return this.writeStream.bytesWritten;
|
|
248
|
+
}
|
|
249
|
+
async writeLine(data) {
|
|
250
|
+
await this.readyPromise;
|
|
251
|
+
return new Promise((resolve, reject) => {
|
|
252
|
+
this.gzip.write(import_buffer.Buffer.concat([
|
|
253
|
+
data,
|
|
254
|
+
import_buffer.Buffer.from("\n")
|
|
255
|
+
]), (error) => {
|
|
256
|
+
if (error) {
|
|
257
|
+
reject(error);
|
|
258
|
+
} else {
|
|
259
|
+
resolve();
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
async getContent() {
|
|
265
|
+
return new Promise((resolve, reject) => {
|
|
266
|
+
(0, import_fs.readFile)(this.filePath, (error, data) => {
|
|
267
|
+
if (error) {
|
|
268
|
+
reject(error);
|
|
269
|
+
} else {
|
|
270
|
+
resolve(data);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
async close() {
|
|
276
|
+
return new Promise((resolve) => {
|
|
277
|
+
this.gzip.end(() => {
|
|
278
|
+
resolve();
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
async delete() {
|
|
283
|
+
await this.close();
|
|
284
|
+
(0, import_fs.unlinkSync)(this.filePath);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
__name(_TempGzipFile, "TempGzipFile");
|
|
288
|
+
var TempGzipFile = _TempGzipFile;
|
|
289
|
+
|
|
290
|
+
// src/common/requestLogger.ts
|
|
291
|
+
var MAX_BODY_SIZE = 5e4;
|
|
292
|
+
var MAX_FILE_SIZE = 1e6;
|
|
293
|
+
var MAX_FILES = 50;
|
|
294
|
+
var MAX_PENDING_WRITES = 100;
|
|
295
|
+
var BODY_TOO_LARGE = import_buffer2.Buffer.from("<body too large>");
|
|
296
|
+
var BODY_MASKED = import_buffer2.Buffer.from("<masked>");
|
|
297
|
+
var MASKED = "******";
|
|
298
|
+
var ALLOWED_CONTENT_TYPES = [
|
|
299
|
+
"application/json",
|
|
300
|
+
"text/plain"
|
|
301
|
+
];
|
|
302
|
+
var EXCLUDE_PATH_PATTERNS = [
|
|
303
|
+
/\/_?healthz?$/,
|
|
304
|
+
/\/_?health[_-]?checks?$/,
|
|
305
|
+
/\/_?heart[_-]?beats?$/,
|
|
306
|
+
/\/ping$/,
|
|
307
|
+
/\/ready$/,
|
|
308
|
+
/\/live$/
|
|
309
|
+
];
|
|
310
|
+
var MASK_QUERY_PARAM_PATTERNS = [
|
|
311
|
+
/auth/i,
|
|
312
|
+
/api-?key/i,
|
|
313
|
+
/secret/i,
|
|
314
|
+
/token/i,
|
|
315
|
+
/password/i,
|
|
316
|
+
/pwd/i
|
|
317
|
+
];
|
|
318
|
+
var MASK_HEADER_PATTERNS = [
|
|
319
|
+
/auth/i,
|
|
320
|
+
/api-?key/i,
|
|
321
|
+
/secret/i,
|
|
322
|
+
/token/i,
|
|
323
|
+
/cookie/i
|
|
324
|
+
];
|
|
325
|
+
var DEFAULT_CONFIG = {
|
|
326
|
+
enabled: false,
|
|
327
|
+
logQueryParams: true,
|
|
328
|
+
logRequestHeaders: false,
|
|
329
|
+
logRequestBody: false,
|
|
330
|
+
logResponseHeaders: true,
|
|
331
|
+
logResponseBody: false,
|
|
332
|
+
maskQueryParams: [],
|
|
333
|
+
maskHeaders: [],
|
|
334
|
+
excludePaths: []
|
|
335
|
+
};
|
|
336
|
+
var _RequestLogger = class _RequestLogger {
|
|
337
|
+
config;
|
|
338
|
+
enabled;
|
|
339
|
+
suspendUntil = null;
|
|
340
|
+
pendingWrites = [];
|
|
341
|
+
currentFile = null;
|
|
342
|
+
files = [];
|
|
343
|
+
maintainIntervalId;
|
|
344
|
+
constructor(config) {
|
|
345
|
+
this.config = {
|
|
346
|
+
...DEFAULT_CONFIG,
|
|
347
|
+
...config
|
|
348
|
+
};
|
|
349
|
+
this.enabled = this.config.enabled && checkWritableFs();
|
|
350
|
+
if (this.enabled) {
|
|
351
|
+
this.maintainIntervalId = setInterval(() => {
|
|
352
|
+
this.maintain();
|
|
353
|
+
}, 1e3);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
shouldExcludePath(urlPath) {
|
|
357
|
+
const patterns = [
|
|
358
|
+
...this.config.excludePaths,
|
|
359
|
+
...EXCLUDE_PATH_PATTERNS
|
|
360
|
+
];
|
|
361
|
+
return matchPatterns(urlPath, patterns);
|
|
362
|
+
}
|
|
363
|
+
shouldMaskQueryParam(name) {
|
|
364
|
+
const patterns = [
|
|
365
|
+
...this.config.maskQueryParams,
|
|
366
|
+
...MASK_QUERY_PARAM_PATTERNS
|
|
367
|
+
];
|
|
368
|
+
return matchPatterns(name, patterns);
|
|
369
|
+
}
|
|
370
|
+
shouldMaskHeader(name) {
|
|
371
|
+
const patterns = [
|
|
372
|
+
...this.config.maskHeaders,
|
|
373
|
+
...MASK_HEADER_PATTERNS
|
|
374
|
+
];
|
|
375
|
+
return matchPatterns(name, patterns);
|
|
376
|
+
}
|
|
377
|
+
hasSupportedContentType(headers) {
|
|
378
|
+
var _a3;
|
|
379
|
+
const contentType = (_a3 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a3[1];
|
|
380
|
+
return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
|
|
381
|
+
}
|
|
382
|
+
maskQueryParams(search) {
|
|
383
|
+
const params = new URLSearchParams(search);
|
|
384
|
+
for (const [key] of params) {
|
|
385
|
+
if (this.shouldMaskQueryParam(key)) {
|
|
386
|
+
params.set(key, MASKED);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return params.toString();
|
|
390
|
+
}
|
|
391
|
+
maskHeaders(headers) {
|
|
392
|
+
return headers.map(([k, v]) => [
|
|
393
|
+
k,
|
|
394
|
+
this.shouldMaskHeader(k) ? MASKED : v
|
|
395
|
+
]);
|
|
396
|
+
}
|
|
397
|
+
logRequest(request, response) {
|
|
398
|
+
var _a3, _b;
|
|
399
|
+
if (!this.enabled || this.suspendUntil !== null) return;
|
|
400
|
+
const url = new URL(request.url);
|
|
401
|
+
const path = request.path ?? url.pathname;
|
|
402
|
+
if (this.shouldExcludePath(path) || (((_b = (_a3 = this.config).excludeCallback) == null ? void 0 : _b.call(_a3, request, response)) ?? false)) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
|
|
406
|
+
request.url = url.toString();
|
|
407
|
+
request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
|
|
408
|
+
response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
|
|
409
|
+
if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
|
|
410
|
+
request.body = void 0;
|
|
411
|
+
} else if (request.body) {
|
|
412
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
413
|
+
request.body = BODY_TOO_LARGE;
|
|
414
|
+
} else if (this.config.maskRequestBodyCallback) {
|
|
415
|
+
try {
|
|
416
|
+
request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
|
|
417
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
418
|
+
request.body = BODY_TOO_LARGE;
|
|
419
|
+
}
|
|
420
|
+
} catch {
|
|
421
|
+
request.body = void 0;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
|
|
426
|
+
response.body = void 0;
|
|
427
|
+
} else if (response.body) {
|
|
428
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
429
|
+
response.body = BODY_TOO_LARGE;
|
|
430
|
+
} else if (this.config.maskResponseBodyCallback) {
|
|
431
|
+
try {
|
|
432
|
+
response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
|
|
433
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
434
|
+
response.body = BODY_TOO_LARGE;
|
|
435
|
+
}
|
|
436
|
+
} catch {
|
|
437
|
+
response.body = void 0;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
const item = {
|
|
442
|
+
uuid: (0, import_crypto2.randomUUID)(),
|
|
443
|
+
request: skipEmptyValues(request),
|
|
444
|
+
response: skipEmptyValues(response)
|
|
445
|
+
};
|
|
446
|
+
[
|
|
447
|
+
item.request.body,
|
|
448
|
+
item.response.body
|
|
449
|
+
].forEach((body) => {
|
|
450
|
+
if (body) {
|
|
451
|
+
body.toJSON = function() {
|
|
452
|
+
return this.toString("base64");
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
this.pendingWrites.push(JSON.stringify(item));
|
|
457
|
+
if (this.pendingWrites.length > MAX_PENDING_WRITES) {
|
|
458
|
+
this.pendingWrites.shift();
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
async writeToFile() {
|
|
462
|
+
if (!this.enabled || this.pendingWrites.length === 0) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (!this.currentFile) {
|
|
466
|
+
this.currentFile = new TempGzipFile();
|
|
467
|
+
}
|
|
468
|
+
while (this.pendingWrites.length > 0) {
|
|
469
|
+
const item = this.pendingWrites.shift();
|
|
470
|
+
if (item) {
|
|
471
|
+
await this.currentFile.writeLine(import_buffer2.Buffer.from(item));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
getFile() {
|
|
476
|
+
return this.files.shift();
|
|
477
|
+
}
|
|
478
|
+
retryFileLater(file) {
|
|
479
|
+
this.files.unshift(file);
|
|
480
|
+
}
|
|
481
|
+
async rotateFile() {
|
|
482
|
+
if (this.currentFile) {
|
|
483
|
+
await this.currentFile.close();
|
|
484
|
+
this.files.push(this.currentFile);
|
|
485
|
+
this.currentFile = null;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
async maintain() {
|
|
489
|
+
await this.writeToFile();
|
|
490
|
+
if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
|
|
491
|
+
await this.rotateFile();
|
|
492
|
+
}
|
|
493
|
+
while (this.files.length > MAX_FILES) {
|
|
494
|
+
const file = this.files.shift();
|
|
495
|
+
file == null ? void 0 : file.delete();
|
|
496
|
+
}
|
|
497
|
+
if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
|
|
498
|
+
this.suspendUntil = null;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
async clear() {
|
|
502
|
+
this.pendingWrites = [];
|
|
503
|
+
await this.rotateFile();
|
|
504
|
+
this.files.forEach((file) => {
|
|
505
|
+
file.delete();
|
|
506
|
+
});
|
|
507
|
+
this.files = [];
|
|
508
|
+
}
|
|
509
|
+
async close() {
|
|
510
|
+
this.enabled = false;
|
|
511
|
+
await this.clear();
|
|
512
|
+
if (this.maintainIntervalId) {
|
|
513
|
+
clearInterval(this.maintainIntervalId);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
__name(_RequestLogger, "RequestLogger");
|
|
518
|
+
var RequestLogger = _RequestLogger;
|
|
519
|
+
function convertHeaders(headers) {
|
|
520
|
+
if (headers instanceof Headers) {
|
|
521
|
+
return Array.from(headers.entries());
|
|
522
|
+
}
|
|
523
|
+
return Object.entries(headers).flatMap(([key, value]) => {
|
|
524
|
+
if (value === void 0) {
|
|
525
|
+
return [];
|
|
526
|
+
}
|
|
527
|
+
if (Array.isArray(value)) {
|
|
528
|
+
return value.map((v) => [
|
|
529
|
+
key,
|
|
530
|
+
v
|
|
531
|
+
]);
|
|
532
|
+
}
|
|
533
|
+
return [
|
|
534
|
+
[
|
|
535
|
+
key,
|
|
536
|
+
value.toString()
|
|
537
|
+
]
|
|
538
|
+
];
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
__name(convertHeaders, "convertHeaders");
|
|
542
|
+
function convertBody(body, contentType) {
|
|
543
|
+
if (!body || !contentType) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
try {
|
|
547
|
+
if (contentType.startsWith("application/json")) {
|
|
548
|
+
if (isValidJsonString(body)) {
|
|
549
|
+
return import_buffer2.Buffer.from(body);
|
|
550
|
+
} else {
|
|
551
|
+
return import_buffer2.Buffer.from(JSON.stringify(body));
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (contentType.startsWith("text/") && typeof body === "string") {
|
|
555
|
+
return import_buffer2.Buffer.from(body);
|
|
556
|
+
}
|
|
557
|
+
} catch (error) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
__name(convertBody, "convertBody");
|
|
562
|
+
function isValidJsonString(body) {
|
|
563
|
+
if (typeof body !== "string") {
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
try {
|
|
567
|
+
JSON.parse(body);
|
|
568
|
+
return true;
|
|
569
|
+
} catch {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
__name(isValidJsonString, "isValidJsonString");
|
|
574
|
+
function matchPatterns(value, patterns) {
|
|
575
|
+
return patterns.some((pattern) => {
|
|
576
|
+
return pattern.test(value);
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
__name(matchPatterns, "matchPatterns");
|
|
580
|
+
function skipEmptyValues(data) {
|
|
581
|
+
return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
|
|
582
|
+
if (v == null || Number.isNaN(v)) return false;
|
|
583
|
+
if (Array.isArray(v) || import_buffer2.Buffer.isBuffer(v) || typeof v === "string") {
|
|
584
|
+
return v.length > 0;
|
|
585
|
+
}
|
|
586
|
+
return true;
|
|
587
|
+
}));
|
|
588
|
+
}
|
|
589
|
+
__name(skipEmptyValues, "skipEmptyValues");
|
|
590
|
+
function checkWritableFs() {
|
|
591
|
+
try {
|
|
592
|
+
const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto2.randomUUID)()}`);
|
|
593
|
+
(0, import_fs2.writeFileSync)(testPath, "test");
|
|
594
|
+
(0, import_fs2.unlinkSync)(testPath);
|
|
595
|
+
return true;
|
|
596
|
+
} catch (error) {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
__name(checkWritableFs, "checkWritableFs");
|
|
601
|
+
|
|
602
|
+
// src/common/serverErrorCounter.ts
|
|
603
|
+
var import_crypto3 = require("crypto");
|
|
217
604
|
var MAX_MSG_LENGTH = 2048;
|
|
218
605
|
var MAX_STACKTRACE_LENGTH = 65536;
|
|
219
606
|
var _ServerErrorCounter = class _ServerErrorCounter {
|
|
@@ -265,7 +652,7 @@ var _ServerErrorCounter = class _ServerErrorCounter {
|
|
|
265
652
|
serverError.msg.trim(),
|
|
266
653
|
serverError.traceback.trim()
|
|
267
654
|
].join("|");
|
|
268
|
-
return (0,
|
|
655
|
+
return (0, import_crypto3.createHash)("md5").update(hashInput).digest("hex");
|
|
269
656
|
}
|
|
270
657
|
getTruncatedMessage(msg) {
|
|
271
658
|
msg = msg.trim();
|
|
@@ -311,7 +698,7 @@ __name(_ServerErrorCounter, "ServerErrorCounter");
|
|
|
311
698
|
var ServerErrorCounter = _ServerErrorCounter;
|
|
312
699
|
|
|
313
700
|
// src/common/validationErrorCounter.ts
|
|
314
|
-
var
|
|
701
|
+
var import_crypto4 = require("crypto");
|
|
315
702
|
var _ValidationErrorCounter = class _ValidationErrorCounter {
|
|
316
703
|
errorCounts;
|
|
317
704
|
errorDetails;
|
|
@@ -355,7 +742,7 @@ var _ValidationErrorCounter = class _ValidationErrorCounter {
|
|
|
355
742
|
validationError.msg.trim(),
|
|
356
743
|
validationError.type
|
|
357
744
|
].join("|");
|
|
358
|
-
return (0,
|
|
745
|
+
return (0, import_crypto4.createHash)("md5").update(hashInput).digest("hex");
|
|
359
746
|
}
|
|
360
747
|
};
|
|
361
748
|
__name(_ValidationErrorCounter, "ValidationErrorCounter");
|
|
@@ -384,11 +771,12 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
384
771
|
startupData;
|
|
385
772
|
startupDataSent = false;
|
|
386
773
|
requestCounter;
|
|
774
|
+
requestLogger;
|
|
387
775
|
validationErrorCounter;
|
|
388
776
|
serverErrorCounter;
|
|
389
777
|
consumerRegistry;
|
|
390
778
|
logger;
|
|
391
|
-
constructor({ clientId, env = "dev", logger }) {
|
|
779
|
+
constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
|
|
392
780
|
if (_ApitallyClient.instance) {
|
|
393
781
|
throw new Error("Apitally client is already initialized");
|
|
394
782
|
}
|
|
@@ -401,9 +789,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
401
789
|
_ApitallyClient.instance = this;
|
|
402
790
|
this.clientId = clientId;
|
|
403
791
|
this.env = env;
|
|
404
|
-
this.instanceUuid = (0,
|
|
792
|
+
this.instanceUuid = (0, import_crypto5.randomUUID)();
|
|
405
793
|
this.syncDataQueue = [];
|
|
406
794
|
this.requestCounter = new RequestCounter();
|
|
795
|
+
this.requestLogger = new RequestLogger(requestLoggingConfig);
|
|
407
796
|
this.validationErrorCounter = new ValidationErrorCounter();
|
|
408
797
|
this.serverErrorCounter = new ServerErrorCounter();
|
|
409
798
|
this.consumerRegistry = new ConsumerRegistry();
|
|
@@ -425,6 +814,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
425
814
|
async handleShutdown() {
|
|
426
815
|
this.stopSync();
|
|
427
816
|
await this.sendSyncData();
|
|
817
|
+
await this.sendLogData();
|
|
818
|
+
await this.requestLogger.close();
|
|
428
819
|
_ApitallyClient.instance = void 0;
|
|
429
820
|
}
|
|
430
821
|
getHubUrlPrefix() {
|
|
@@ -471,7 +862,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
471
862
|
async sync() {
|
|
472
863
|
try {
|
|
473
864
|
const promises = [
|
|
474
|
-
this.sendSyncData()
|
|
865
|
+
this.sendSyncData(),
|
|
866
|
+
this.sendLogData()
|
|
475
867
|
];
|
|
476
868
|
if (!this.startupDataSent) {
|
|
477
869
|
promises.push(this.sendStartupData());
|
|
@@ -499,7 +891,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
499
891
|
this.logger.debug("Sending startup data to Apitally Hub");
|
|
500
892
|
const payload = {
|
|
501
893
|
instance_uuid: this.instanceUuid,
|
|
502
|
-
message_uuid: (0,
|
|
894
|
+
message_uuid: (0, import_crypto5.randomUUID)(),
|
|
503
895
|
...this.startupData
|
|
504
896
|
};
|
|
505
897
|
try {
|
|
@@ -521,7 +913,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
521
913
|
const newPayload = {
|
|
522
914
|
time_offset: 0,
|
|
523
915
|
instance_uuid: this.instanceUuid,
|
|
524
|
-
message_uuid: (0,
|
|
916
|
+
message_uuid: (0, import_crypto5.randomUUID)(),
|
|
525
917
|
requests: this.requestCounter.getAndResetRequests(),
|
|
526
918
|
validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
|
|
527
919
|
server_errors: this.serverErrorCounter.getAndResetServerErrors(),
|
|
@@ -540,8 +932,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
540
932
|
const timeOffset = Date.now() - time;
|
|
541
933
|
if (timeOffset <= MAX_QUEUE_TIME) {
|
|
542
934
|
if (i > 0) {
|
|
543
|
-
|
|
544
|
-
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
935
|
+
await this.randomDelay();
|
|
545
936
|
}
|
|
546
937
|
payload.time_offset = timeOffset / 1e3;
|
|
547
938
|
await this.sendData("sync", payload);
|
|
@@ -560,6 +951,52 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
560
951
|
}
|
|
561
952
|
}
|
|
562
953
|
}
|
|
954
|
+
async sendLogData() {
|
|
955
|
+
this.logger.debug("Sending request log data to Apitally Hub");
|
|
956
|
+
await this.requestLogger.rotateFile();
|
|
957
|
+
const fetchWithRetry = (0, import_fetch_retry.default)(fetch, {
|
|
958
|
+
retries: 3,
|
|
959
|
+
retryDelay: 1e3,
|
|
960
|
+
retryOn: [
|
|
961
|
+
408,
|
|
962
|
+
429,
|
|
963
|
+
500,
|
|
964
|
+
502,
|
|
965
|
+
503,
|
|
966
|
+
504
|
|
967
|
+
]
|
|
968
|
+
});
|
|
969
|
+
let i = 0;
|
|
970
|
+
let logFile;
|
|
971
|
+
while (logFile = this.requestLogger.getFile()) {
|
|
972
|
+
if (i > 0) {
|
|
973
|
+
await this.randomDelay();
|
|
974
|
+
}
|
|
975
|
+
try {
|
|
976
|
+
const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
|
|
977
|
+
method: "POST",
|
|
978
|
+
body: await logFile.getContent()
|
|
979
|
+
});
|
|
980
|
+
if (response.status === 402 && response.headers.has("Retry-After")) {
|
|
981
|
+
const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
|
|
982
|
+
if (retryAfter > 0) {
|
|
983
|
+
this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
|
|
984
|
+
this.requestLogger.clear();
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
if (!response.ok) {
|
|
989
|
+
throw new HTTPError(response);
|
|
990
|
+
}
|
|
991
|
+
logFile.delete();
|
|
992
|
+
} catch (error) {
|
|
993
|
+
this.requestLogger.retryFileLater(logFile);
|
|
994
|
+
break;
|
|
995
|
+
}
|
|
996
|
+
i++;
|
|
997
|
+
if (i >= 10) break;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
563
1000
|
handleHubError(error) {
|
|
564
1001
|
if (error instanceof HTTPError) {
|
|
565
1002
|
if (error.response.status === 404) {
|
|
@@ -574,6 +1011,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
574
1011
|
}
|
|
575
1012
|
return false;
|
|
576
1013
|
}
|
|
1014
|
+
async randomDelay() {
|
|
1015
|
+
const delay = 100 + Math.random() * 200;
|
|
1016
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1017
|
+
}
|
|
577
1018
|
};
|
|
578
1019
|
__name(_ApitallyClient, "ApitallyClient");
|
|
579
1020
|
__publicField(_ApitallyClient, "instance");
|
|
@@ -744,18 +1185,18 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
744
1185
|
}
|
|
745
1186
|
try {
|
|
746
1187
|
const startTime = import_perf_hooks.performance.now();
|
|
747
|
-
const
|
|
748
|
-
res.
|
|
1188
|
+
const originalSend = res.send;
|
|
1189
|
+
res.send = (body) => {
|
|
749
1190
|
res.locals.body = body;
|
|
750
|
-
return
|
|
1191
|
+
return originalSend.call(res, body);
|
|
751
1192
|
};
|
|
752
1193
|
res.on("finish", () => {
|
|
753
1194
|
try {
|
|
1195
|
+
const responseTime = import_perf_hooks.performance.now() - startTime;
|
|
754
1196
|
const path = getRoutePath(req);
|
|
1197
|
+
const consumer = getConsumer(req);
|
|
1198
|
+
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
755
1199
|
if (path) {
|
|
756
|
-
const responseTime = import_perf_hooks.performance.now() - startTime;
|
|
757
|
-
const consumer = getConsumer(req);
|
|
758
|
-
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
759
1200
|
client.requestCounter.addRequest({
|
|
760
1201
|
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
761
1202
|
method: req.method,
|
|
@@ -766,24 +1207,31 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
766
1207
|
responseSize: res.get("content-length")
|
|
767
1208
|
});
|
|
768
1209
|
if ((res.statusCode === 400 || res.statusCode === 422) && res.locals.body) {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
}
|
|
773
|
-
if (validationErrors.length === 0) {
|
|
774
|
-
validationErrors.push(...extractCelebrateErrors(res.locals.body));
|
|
1210
|
+
let jsonBody;
|
|
1211
|
+
try {
|
|
1212
|
+
jsonBody = JSON.parse(res.locals.body);
|
|
1213
|
+
} catch {
|
|
775
1214
|
}
|
|
776
|
-
if (
|
|
777
|
-
validationErrors
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
1215
|
+
if (jsonBody) {
|
|
1216
|
+
const validationErrors = [];
|
|
1217
|
+
if (validationErrors.length === 0) {
|
|
1218
|
+
validationErrors.push(...extractExpressValidatorErrors(jsonBody));
|
|
1219
|
+
}
|
|
1220
|
+
if (validationErrors.length === 0) {
|
|
1221
|
+
validationErrors.push(...extractCelebrateErrors(jsonBody));
|
|
1222
|
+
}
|
|
1223
|
+
if (validationErrors.length === 0) {
|
|
1224
|
+
validationErrors.push(...extractNestValidationErrors(jsonBody));
|
|
1225
|
+
}
|
|
1226
|
+
validationErrors.forEach((error) => {
|
|
1227
|
+
client.validationErrorCounter.addValidationError({
|
|
1228
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1229
|
+
method: req.method,
|
|
1230
|
+
path: req.route.path,
|
|
1231
|
+
...error
|
|
1232
|
+
});
|
|
785
1233
|
});
|
|
786
|
-
}
|
|
1234
|
+
}
|
|
787
1235
|
}
|
|
788
1236
|
if (res.statusCode === 500 && res.locals.serverError) {
|
|
789
1237
|
const serverError = res.locals.serverError;
|
|
@@ -797,6 +1245,24 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
797
1245
|
});
|
|
798
1246
|
}
|
|
799
1247
|
}
|
|
1248
|
+
if (client.requestLogger.enabled) {
|
|
1249
|
+
client.requestLogger.logRequest({
|
|
1250
|
+
timestamp: Date.now() / 1e3,
|
|
1251
|
+
method: req.method,
|
|
1252
|
+
path,
|
|
1253
|
+
url: `${req.protocol}://${req.host}${req.originalUrl}`,
|
|
1254
|
+
headers: convertHeaders(req.headers),
|
|
1255
|
+
size: Number(req.get("content-length")),
|
|
1256
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1257
|
+
body: convertBody(req.body, req.get("content-type"))
|
|
1258
|
+
}, {
|
|
1259
|
+
statusCode: res.statusCode,
|
|
1260
|
+
responseTime: responseTime / 1e3,
|
|
1261
|
+
headers: convertHeaders(res.getHeaders()),
|
|
1262
|
+
size: Number(res.get("content-length")),
|
|
1263
|
+
body: convertBody(res.locals.body, res.get("content-type"))
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
800
1266
|
} catch (error) {
|
|
801
1267
|
client.logger.error("Error while logging request in Apitally middleware.", {
|
|
802
1268
|
request: req,
|