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