apitally 0.10.0 → 0.11.1
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 +408 -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 +404 -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 +429 -0
- package/dist/common/requestLogger.cjs.map +1 -0
- package/dist/common/requestLogger.d.cts +64 -0
- package/dist/common/requestLogger.d.ts +64 -0
- package/dist/common/requestLogger.js +405 -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 +510 -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 +506 -30
- package/dist/express/index.js.map +1 -1
- package/dist/express/middleware.cjs +510 -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 +506 -30
- package/dist/express/middleware.js.map +1 -1
- package/dist/fastify/index.cjs +501 -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 +497 -23
- package/dist/fastify/index.js.map +1 -1
- package/dist/fastify/plugin.cjs +501 -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 +497 -23
- package/dist/fastify/plugin.js.map +1 -1
- package/dist/hono/index.cjs +470 -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 +466 -11
- package/dist/hono/index.js.map +1 -1
- package/dist/hono/middleware.cjs +470 -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 +466 -11
- package/dist/hono/middleware.js.map +1 -1
- package/dist/koa/index.cjs +485 -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 +481 -11
- package/dist/koa/index.js.map +1 -1
- package/dist/koa/middleware.cjs +485 -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 +481 -11
- package/dist/koa/middleware.js.map +1 -1
- package/dist/nestjs/index.cjs +510 -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 +506 -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,405 @@ 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?$/i,
|
|
304
|
+
/\/_?health[_-]?checks?$/i,
|
|
305
|
+
/\/_?heart[_-]?beats?$/i,
|
|
306
|
+
/\/ping$/i,
|
|
307
|
+
/\/ready$/i,
|
|
308
|
+
/\/live$/i
|
|
309
|
+
];
|
|
310
|
+
var EXCLUDE_USER_AGENT_PATTERNS = [
|
|
311
|
+
/health[-_ ]?check/i,
|
|
312
|
+
/microsoft-azure-application-lb/i,
|
|
313
|
+
/googlehc/i,
|
|
314
|
+
/kube-probe/i
|
|
315
|
+
];
|
|
316
|
+
var MASK_QUERY_PARAM_PATTERNS = [
|
|
317
|
+
/auth/i,
|
|
318
|
+
/api-?key/i,
|
|
319
|
+
/secret/i,
|
|
320
|
+
/token/i,
|
|
321
|
+
/password/i,
|
|
322
|
+
/pwd/i
|
|
323
|
+
];
|
|
324
|
+
var MASK_HEADER_PATTERNS = [
|
|
325
|
+
/auth/i,
|
|
326
|
+
/api-?key/i,
|
|
327
|
+
/secret/i,
|
|
328
|
+
/token/i,
|
|
329
|
+
/cookie/i
|
|
330
|
+
];
|
|
331
|
+
var DEFAULT_CONFIG = {
|
|
332
|
+
enabled: false,
|
|
333
|
+
logQueryParams: true,
|
|
334
|
+
logRequestHeaders: false,
|
|
335
|
+
logRequestBody: false,
|
|
336
|
+
logResponseHeaders: true,
|
|
337
|
+
logResponseBody: false,
|
|
338
|
+
maskQueryParams: [],
|
|
339
|
+
maskHeaders: [],
|
|
340
|
+
excludePaths: []
|
|
341
|
+
};
|
|
342
|
+
var _RequestLogger = class _RequestLogger {
|
|
343
|
+
config;
|
|
344
|
+
enabled;
|
|
345
|
+
suspendUntil = null;
|
|
346
|
+
pendingWrites = [];
|
|
347
|
+
currentFile = null;
|
|
348
|
+
files = [];
|
|
349
|
+
maintainIntervalId;
|
|
350
|
+
constructor(config) {
|
|
351
|
+
this.config = {
|
|
352
|
+
...DEFAULT_CONFIG,
|
|
353
|
+
...config
|
|
354
|
+
};
|
|
355
|
+
this.enabled = this.config.enabled && checkWritableFs();
|
|
356
|
+
if (this.enabled) {
|
|
357
|
+
this.maintainIntervalId = setInterval(() => {
|
|
358
|
+
this.maintain();
|
|
359
|
+
}, 1e3);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
shouldExcludePath(urlPath) {
|
|
363
|
+
const patterns = [
|
|
364
|
+
...this.config.excludePaths,
|
|
365
|
+
...EXCLUDE_PATH_PATTERNS
|
|
366
|
+
];
|
|
367
|
+
return matchPatterns(urlPath, patterns);
|
|
368
|
+
}
|
|
369
|
+
shouldExcludeUserAgent(userAgent) {
|
|
370
|
+
return userAgent ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS) : false;
|
|
371
|
+
}
|
|
372
|
+
shouldMaskQueryParam(name) {
|
|
373
|
+
const patterns = [
|
|
374
|
+
...this.config.maskQueryParams,
|
|
375
|
+
...MASK_QUERY_PARAM_PATTERNS
|
|
376
|
+
];
|
|
377
|
+
return matchPatterns(name, patterns);
|
|
378
|
+
}
|
|
379
|
+
shouldMaskHeader(name) {
|
|
380
|
+
const patterns = [
|
|
381
|
+
...this.config.maskHeaders,
|
|
382
|
+
...MASK_HEADER_PATTERNS
|
|
383
|
+
];
|
|
384
|
+
return matchPatterns(name, patterns);
|
|
385
|
+
}
|
|
386
|
+
hasSupportedContentType(headers) {
|
|
387
|
+
var _a3;
|
|
388
|
+
const contentType = (_a3 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a3[1];
|
|
389
|
+
return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
|
|
390
|
+
}
|
|
391
|
+
maskQueryParams(search) {
|
|
392
|
+
const params = new URLSearchParams(search);
|
|
393
|
+
for (const [key] of params) {
|
|
394
|
+
if (this.shouldMaskQueryParam(key)) {
|
|
395
|
+
params.set(key, MASKED);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return params.toString();
|
|
399
|
+
}
|
|
400
|
+
maskHeaders(headers) {
|
|
401
|
+
return headers.map(([k, v]) => [
|
|
402
|
+
k,
|
|
403
|
+
this.shouldMaskHeader(k) ? MASKED : v
|
|
404
|
+
]);
|
|
405
|
+
}
|
|
406
|
+
logRequest(request, response) {
|
|
407
|
+
var _a3, _b, _c;
|
|
408
|
+
if (!this.enabled || this.suspendUntil !== null) return;
|
|
409
|
+
const url = new URL(request.url);
|
|
410
|
+
const path = request.path ?? url.pathname;
|
|
411
|
+
const userAgent = (_a3 = request.headers.find(([k]) => k.toLowerCase() === "user-agent")) == null ? void 0 : _a3[1];
|
|
412
|
+
if (this.shouldExcludePath(path) || this.shouldExcludeUserAgent(userAgent) || (((_c = (_b = this.config).excludeCallback) == null ? void 0 : _c.call(_b, request, response)) ?? false)) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
|
|
416
|
+
request.url = url.toString();
|
|
417
|
+
request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
|
|
418
|
+
response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
|
|
419
|
+
if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
|
|
420
|
+
request.body = void 0;
|
|
421
|
+
} else if (request.body) {
|
|
422
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
423
|
+
request.body = BODY_TOO_LARGE;
|
|
424
|
+
} else if (this.config.maskRequestBodyCallback) {
|
|
425
|
+
try {
|
|
426
|
+
request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
|
|
427
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
428
|
+
request.body = BODY_TOO_LARGE;
|
|
429
|
+
}
|
|
430
|
+
} catch {
|
|
431
|
+
request.body = void 0;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
|
|
436
|
+
response.body = void 0;
|
|
437
|
+
} else if (response.body) {
|
|
438
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
439
|
+
response.body = BODY_TOO_LARGE;
|
|
440
|
+
} else if (this.config.maskResponseBodyCallback) {
|
|
441
|
+
try {
|
|
442
|
+
response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
|
|
443
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
444
|
+
response.body = BODY_TOO_LARGE;
|
|
445
|
+
}
|
|
446
|
+
} catch {
|
|
447
|
+
response.body = void 0;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const item = {
|
|
452
|
+
uuid: (0, import_crypto2.randomUUID)(),
|
|
453
|
+
request: skipEmptyValues(request),
|
|
454
|
+
response: skipEmptyValues(response)
|
|
455
|
+
};
|
|
456
|
+
[
|
|
457
|
+
item.request.body,
|
|
458
|
+
item.response.body
|
|
459
|
+
].forEach((body) => {
|
|
460
|
+
if (body) {
|
|
461
|
+
body.toJSON = function() {
|
|
462
|
+
return this.toString("base64");
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
this.pendingWrites.push(JSON.stringify(item));
|
|
467
|
+
if (this.pendingWrites.length > MAX_PENDING_WRITES) {
|
|
468
|
+
this.pendingWrites.shift();
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
async writeToFile() {
|
|
472
|
+
if (!this.enabled || this.pendingWrites.length === 0) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (!this.currentFile) {
|
|
476
|
+
this.currentFile = new TempGzipFile();
|
|
477
|
+
}
|
|
478
|
+
while (this.pendingWrites.length > 0) {
|
|
479
|
+
const item = this.pendingWrites.shift();
|
|
480
|
+
if (item) {
|
|
481
|
+
await this.currentFile.writeLine(import_buffer2.Buffer.from(item));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
getFile() {
|
|
486
|
+
return this.files.shift();
|
|
487
|
+
}
|
|
488
|
+
retryFileLater(file) {
|
|
489
|
+
this.files.unshift(file);
|
|
490
|
+
}
|
|
491
|
+
async rotateFile() {
|
|
492
|
+
if (this.currentFile) {
|
|
493
|
+
await this.currentFile.close();
|
|
494
|
+
this.files.push(this.currentFile);
|
|
495
|
+
this.currentFile = null;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
async maintain() {
|
|
499
|
+
await this.writeToFile();
|
|
500
|
+
if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
|
|
501
|
+
await this.rotateFile();
|
|
502
|
+
}
|
|
503
|
+
while (this.files.length > MAX_FILES) {
|
|
504
|
+
const file = this.files.shift();
|
|
505
|
+
file == null ? void 0 : file.delete();
|
|
506
|
+
}
|
|
507
|
+
if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
|
|
508
|
+
this.suspendUntil = null;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
async clear() {
|
|
512
|
+
this.pendingWrites = [];
|
|
513
|
+
await this.rotateFile();
|
|
514
|
+
this.files.forEach((file) => {
|
|
515
|
+
file.delete();
|
|
516
|
+
});
|
|
517
|
+
this.files = [];
|
|
518
|
+
}
|
|
519
|
+
async close() {
|
|
520
|
+
this.enabled = false;
|
|
521
|
+
await this.clear();
|
|
522
|
+
if (this.maintainIntervalId) {
|
|
523
|
+
clearInterval(this.maintainIntervalId);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
__name(_RequestLogger, "RequestLogger");
|
|
528
|
+
var RequestLogger = _RequestLogger;
|
|
529
|
+
function convertHeaders(headers) {
|
|
530
|
+
if (headers instanceof Headers) {
|
|
531
|
+
return Array.from(headers.entries());
|
|
532
|
+
}
|
|
533
|
+
return Object.entries(headers).flatMap(([key, value]) => {
|
|
534
|
+
if (value === void 0) {
|
|
535
|
+
return [];
|
|
536
|
+
}
|
|
537
|
+
if (Array.isArray(value)) {
|
|
538
|
+
return value.map((v) => [
|
|
539
|
+
key,
|
|
540
|
+
v
|
|
541
|
+
]);
|
|
542
|
+
}
|
|
543
|
+
return [
|
|
544
|
+
[
|
|
545
|
+
key,
|
|
546
|
+
value.toString()
|
|
547
|
+
]
|
|
548
|
+
];
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
__name(convertHeaders, "convertHeaders");
|
|
552
|
+
function convertBody(body, contentType) {
|
|
553
|
+
if (!body || !contentType) {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
try {
|
|
557
|
+
if (contentType.startsWith("application/json")) {
|
|
558
|
+
if (isValidJsonString(body)) {
|
|
559
|
+
return import_buffer2.Buffer.from(body);
|
|
560
|
+
} else {
|
|
561
|
+
return import_buffer2.Buffer.from(JSON.stringify(body));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (contentType.startsWith("text/") && typeof body === "string") {
|
|
565
|
+
return import_buffer2.Buffer.from(body);
|
|
566
|
+
}
|
|
567
|
+
} catch (error) {
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
__name(convertBody, "convertBody");
|
|
572
|
+
function isValidJsonString(body) {
|
|
573
|
+
if (typeof body !== "string") {
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
try {
|
|
577
|
+
JSON.parse(body);
|
|
578
|
+
return true;
|
|
579
|
+
} catch {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
__name(isValidJsonString, "isValidJsonString");
|
|
584
|
+
function matchPatterns(value, patterns) {
|
|
585
|
+
return patterns.some((pattern) => {
|
|
586
|
+
return pattern.test(value);
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
__name(matchPatterns, "matchPatterns");
|
|
590
|
+
function skipEmptyValues(data) {
|
|
591
|
+
return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
|
|
592
|
+
if (v == null || Number.isNaN(v)) return false;
|
|
593
|
+
if (Array.isArray(v) || import_buffer2.Buffer.isBuffer(v) || typeof v === "string") {
|
|
594
|
+
return v.length > 0;
|
|
595
|
+
}
|
|
596
|
+
return true;
|
|
597
|
+
}));
|
|
598
|
+
}
|
|
599
|
+
__name(skipEmptyValues, "skipEmptyValues");
|
|
600
|
+
function checkWritableFs() {
|
|
601
|
+
try {
|
|
602
|
+
const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto2.randomUUID)()}`);
|
|
603
|
+
(0, import_fs2.writeFileSync)(testPath, "test");
|
|
604
|
+
(0, import_fs2.unlinkSync)(testPath);
|
|
605
|
+
return true;
|
|
606
|
+
} catch (error) {
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
__name(checkWritableFs, "checkWritableFs");
|
|
611
|
+
|
|
612
|
+
// src/common/serverErrorCounter.ts
|
|
613
|
+
var import_crypto3 = require("crypto");
|
|
217
614
|
var MAX_MSG_LENGTH = 2048;
|
|
218
615
|
var MAX_STACKTRACE_LENGTH = 65536;
|
|
219
616
|
var _ServerErrorCounter = class _ServerErrorCounter {
|
|
@@ -265,7 +662,7 @@ var _ServerErrorCounter = class _ServerErrorCounter {
|
|
|
265
662
|
serverError.msg.trim(),
|
|
266
663
|
serverError.traceback.trim()
|
|
267
664
|
].join("|");
|
|
268
|
-
return (0,
|
|
665
|
+
return (0, import_crypto3.createHash)("md5").update(hashInput).digest("hex");
|
|
269
666
|
}
|
|
270
667
|
getTruncatedMessage(msg) {
|
|
271
668
|
msg = msg.trim();
|
|
@@ -311,7 +708,7 @@ __name(_ServerErrorCounter, "ServerErrorCounter");
|
|
|
311
708
|
var ServerErrorCounter = _ServerErrorCounter;
|
|
312
709
|
|
|
313
710
|
// src/common/validationErrorCounter.ts
|
|
314
|
-
var
|
|
711
|
+
var import_crypto4 = require("crypto");
|
|
315
712
|
var _ValidationErrorCounter = class _ValidationErrorCounter {
|
|
316
713
|
errorCounts;
|
|
317
714
|
errorDetails;
|
|
@@ -355,7 +752,7 @@ var _ValidationErrorCounter = class _ValidationErrorCounter {
|
|
|
355
752
|
validationError.msg.trim(),
|
|
356
753
|
validationError.type
|
|
357
754
|
].join("|");
|
|
358
|
-
return (0,
|
|
755
|
+
return (0, import_crypto4.createHash)("md5").update(hashInput).digest("hex");
|
|
359
756
|
}
|
|
360
757
|
};
|
|
361
758
|
__name(_ValidationErrorCounter, "ValidationErrorCounter");
|
|
@@ -384,11 +781,12 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
384
781
|
startupData;
|
|
385
782
|
startupDataSent = false;
|
|
386
783
|
requestCounter;
|
|
784
|
+
requestLogger;
|
|
387
785
|
validationErrorCounter;
|
|
388
786
|
serverErrorCounter;
|
|
389
787
|
consumerRegistry;
|
|
390
788
|
logger;
|
|
391
|
-
constructor({ clientId, env = "dev", logger }) {
|
|
789
|
+
constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
|
|
392
790
|
if (_ApitallyClient.instance) {
|
|
393
791
|
throw new Error("Apitally client is already initialized");
|
|
394
792
|
}
|
|
@@ -401,9 +799,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
401
799
|
_ApitallyClient.instance = this;
|
|
402
800
|
this.clientId = clientId;
|
|
403
801
|
this.env = env;
|
|
404
|
-
this.instanceUuid = (0,
|
|
802
|
+
this.instanceUuid = (0, import_crypto5.randomUUID)();
|
|
405
803
|
this.syncDataQueue = [];
|
|
406
804
|
this.requestCounter = new RequestCounter();
|
|
805
|
+
this.requestLogger = new RequestLogger(requestLoggingConfig);
|
|
407
806
|
this.validationErrorCounter = new ValidationErrorCounter();
|
|
408
807
|
this.serverErrorCounter = new ServerErrorCounter();
|
|
409
808
|
this.consumerRegistry = new ConsumerRegistry();
|
|
@@ -425,6 +824,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
425
824
|
async handleShutdown() {
|
|
426
825
|
this.stopSync();
|
|
427
826
|
await this.sendSyncData();
|
|
827
|
+
await this.sendLogData();
|
|
828
|
+
await this.requestLogger.close();
|
|
428
829
|
_ApitallyClient.instance = void 0;
|
|
429
830
|
}
|
|
430
831
|
getHubUrlPrefix() {
|
|
@@ -471,7 +872,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
471
872
|
async sync() {
|
|
472
873
|
try {
|
|
473
874
|
const promises = [
|
|
474
|
-
this.sendSyncData()
|
|
875
|
+
this.sendSyncData(),
|
|
876
|
+
this.sendLogData()
|
|
475
877
|
];
|
|
476
878
|
if (!this.startupDataSent) {
|
|
477
879
|
promises.push(this.sendStartupData());
|
|
@@ -499,7 +901,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
499
901
|
this.logger.debug("Sending startup data to Apitally Hub");
|
|
500
902
|
const payload = {
|
|
501
903
|
instance_uuid: this.instanceUuid,
|
|
502
|
-
message_uuid: (0,
|
|
904
|
+
message_uuid: (0, import_crypto5.randomUUID)(),
|
|
503
905
|
...this.startupData
|
|
504
906
|
};
|
|
505
907
|
try {
|
|
@@ -521,7 +923,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
521
923
|
const newPayload = {
|
|
522
924
|
time_offset: 0,
|
|
523
925
|
instance_uuid: this.instanceUuid,
|
|
524
|
-
message_uuid: (0,
|
|
926
|
+
message_uuid: (0, import_crypto5.randomUUID)(),
|
|
525
927
|
requests: this.requestCounter.getAndResetRequests(),
|
|
526
928
|
validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
|
|
527
929
|
server_errors: this.serverErrorCounter.getAndResetServerErrors(),
|
|
@@ -540,8 +942,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
540
942
|
const timeOffset = Date.now() - time;
|
|
541
943
|
if (timeOffset <= MAX_QUEUE_TIME) {
|
|
542
944
|
if (i > 0) {
|
|
543
|
-
|
|
544
|
-
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
945
|
+
await this.randomDelay();
|
|
545
946
|
}
|
|
546
947
|
payload.time_offset = timeOffset / 1e3;
|
|
547
948
|
await this.sendData("sync", payload);
|
|
@@ -560,6 +961,52 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
560
961
|
}
|
|
561
962
|
}
|
|
562
963
|
}
|
|
964
|
+
async sendLogData() {
|
|
965
|
+
this.logger.debug("Sending request log data to Apitally Hub");
|
|
966
|
+
await this.requestLogger.rotateFile();
|
|
967
|
+
const fetchWithRetry = (0, import_fetch_retry.default)(fetch, {
|
|
968
|
+
retries: 3,
|
|
969
|
+
retryDelay: 1e3,
|
|
970
|
+
retryOn: [
|
|
971
|
+
408,
|
|
972
|
+
429,
|
|
973
|
+
500,
|
|
974
|
+
502,
|
|
975
|
+
503,
|
|
976
|
+
504
|
|
977
|
+
]
|
|
978
|
+
});
|
|
979
|
+
let i = 0;
|
|
980
|
+
let logFile;
|
|
981
|
+
while (logFile = this.requestLogger.getFile()) {
|
|
982
|
+
if (i > 0) {
|
|
983
|
+
await this.randomDelay();
|
|
984
|
+
}
|
|
985
|
+
try {
|
|
986
|
+
const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
|
|
987
|
+
method: "POST",
|
|
988
|
+
body: await logFile.getContent()
|
|
989
|
+
});
|
|
990
|
+
if (response.status === 402 && response.headers.has("Retry-After")) {
|
|
991
|
+
const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
|
|
992
|
+
if (retryAfter > 0) {
|
|
993
|
+
this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
|
|
994
|
+
this.requestLogger.clear();
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
if (!response.ok) {
|
|
999
|
+
throw new HTTPError(response);
|
|
1000
|
+
}
|
|
1001
|
+
logFile.delete();
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
this.requestLogger.retryFileLater(logFile);
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
1006
|
+
i++;
|
|
1007
|
+
if (i >= 10) break;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
563
1010
|
handleHubError(error) {
|
|
564
1011
|
if (error instanceof HTTPError) {
|
|
565
1012
|
if (error.response.status === 404) {
|
|
@@ -574,6 +1021,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
574
1021
|
}
|
|
575
1022
|
return false;
|
|
576
1023
|
}
|
|
1024
|
+
async randomDelay() {
|
|
1025
|
+
const delay = 100 + Math.random() * 200;
|
|
1026
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1027
|
+
}
|
|
577
1028
|
};
|
|
578
1029
|
__name(_ApitallyClient, "ApitallyClient");
|
|
579
1030
|
__publicField(_ApitallyClient, "instance");
|
|
@@ -744,18 +1195,18 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
744
1195
|
}
|
|
745
1196
|
try {
|
|
746
1197
|
const startTime = import_perf_hooks.performance.now();
|
|
747
|
-
const
|
|
748
|
-
res.
|
|
1198
|
+
const originalSend = res.send;
|
|
1199
|
+
res.send = (body) => {
|
|
749
1200
|
res.locals.body = body;
|
|
750
|
-
return
|
|
1201
|
+
return originalSend.call(res, body);
|
|
751
1202
|
};
|
|
752
1203
|
res.on("finish", () => {
|
|
753
1204
|
try {
|
|
1205
|
+
const responseTime = import_perf_hooks.performance.now() - startTime;
|
|
754
1206
|
const path = getRoutePath(req);
|
|
1207
|
+
const consumer = getConsumer(req);
|
|
1208
|
+
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
755
1209
|
if (path) {
|
|
756
|
-
const responseTime = import_perf_hooks.performance.now() - startTime;
|
|
757
|
-
const consumer = getConsumer(req);
|
|
758
|
-
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
759
1210
|
client.requestCounter.addRequest({
|
|
760
1211
|
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
761
1212
|
method: req.method,
|
|
@@ -766,24 +1217,31 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
766
1217
|
responseSize: res.get("content-length")
|
|
767
1218
|
});
|
|
768
1219
|
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));
|
|
1220
|
+
let jsonBody;
|
|
1221
|
+
try {
|
|
1222
|
+
jsonBody = JSON.parse(res.locals.body);
|
|
1223
|
+
} catch {
|
|
775
1224
|
}
|
|
776
|
-
if (
|
|
777
|
-
validationErrors
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
1225
|
+
if (jsonBody) {
|
|
1226
|
+
const validationErrors = [];
|
|
1227
|
+
if (validationErrors.length === 0) {
|
|
1228
|
+
validationErrors.push(...extractExpressValidatorErrors(jsonBody));
|
|
1229
|
+
}
|
|
1230
|
+
if (validationErrors.length === 0) {
|
|
1231
|
+
validationErrors.push(...extractCelebrateErrors(jsonBody));
|
|
1232
|
+
}
|
|
1233
|
+
if (validationErrors.length === 0) {
|
|
1234
|
+
validationErrors.push(...extractNestValidationErrors(jsonBody));
|
|
1235
|
+
}
|
|
1236
|
+
validationErrors.forEach((error) => {
|
|
1237
|
+
client.validationErrorCounter.addValidationError({
|
|
1238
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1239
|
+
method: req.method,
|
|
1240
|
+
path: req.route.path,
|
|
1241
|
+
...error
|
|
1242
|
+
});
|
|
785
1243
|
});
|
|
786
|
-
}
|
|
1244
|
+
}
|
|
787
1245
|
}
|
|
788
1246
|
if (res.statusCode === 500 && res.locals.serverError) {
|
|
789
1247
|
const serverError = res.locals.serverError;
|
|
@@ -797,6 +1255,24 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
797
1255
|
});
|
|
798
1256
|
}
|
|
799
1257
|
}
|
|
1258
|
+
if (client.requestLogger.enabled) {
|
|
1259
|
+
client.requestLogger.logRequest({
|
|
1260
|
+
timestamp: Date.now() / 1e3,
|
|
1261
|
+
method: req.method,
|
|
1262
|
+
path,
|
|
1263
|
+
url: `${req.protocol}://${req.host}${req.originalUrl}`,
|
|
1264
|
+
headers: convertHeaders(req.headers),
|
|
1265
|
+
size: Number(req.get("content-length")),
|
|
1266
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1267
|
+
body: convertBody(req.body, req.get("content-type"))
|
|
1268
|
+
}, {
|
|
1269
|
+
statusCode: res.statusCode,
|
|
1270
|
+
responseTime: responseTime / 1e3,
|
|
1271
|
+
headers: convertHeaders(res.getHeaders()),
|
|
1272
|
+
size: Number(res.get("content-length")),
|
|
1273
|
+
body: convertBody(res.locals.body, res.get("content-type"))
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
800
1276
|
} catch (error) {
|
|
801
1277
|
client.logger.error("Error while logging request in Apitally middleware.", {
|
|
802
1278
|
request: req,
|