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.
Files changed (85) hide show
  1. package/README.md +1 -2
  2. package/dist/common/client.cjs +408 -12
  3. package/dist/common/client.cjs.map +1 -1
  4. package/dist/common/client.d.cts +8 -1
  5. package/dist/common/client.d.ts +8 -1
  6. package/dist/common/client.js +404 -8
  7. package/dist/common/client.js.map +1 -1
  8. package/dist/common/consumerRegistry.d.cts +4 -0
  9. package/dist/common/consumerRegistry.d.ts +4 -0
  10. package/dist/common/requestCounter.d.cts +4 -0
  11. package/dist/common/requestCounter.d.ts +4 -0
  12. package/dist/common/requestLogger.cjs +429 -0
  13. package/dist/common/requestLogger.cjs.map +1 -0
  14. package/dist/common/requestLogger.d.cts +64 -0
  15. package/dist/common/requestLogger.d.ts +64 -0
  16. package/dist/common/requestLogger.js +405 -0
  17. package/dist/common/requestLogger.js.map +1 -0
  18. package/dist/common/serverErrorCounter.d.cts +4 -0
  19. package/dist/common/serverErrorCounter.d.ts +4 -0
  20. package/dist/common/tempGzipFile.cjs +93 -0
  21. package/dist/common/tempGzipFile.cjs.map +1 -0
  22. package/dist/common/tempGzipFile.d.cts +17 -0
  23. package/dist/common/tempGzipFile.d.ts +17 -0
  24. package/dist/common/tempGzipFile.js +74 -0
  25. package/dist/common/tempGzipFile.js.map +1 -0
  26. package/dist/common/types.cjs.map +1 -1
  27. package/dist/common/types.d.cts +5 -0
  28. package/dist/common/types.d.ts +5 -0
  29. package/dist/common/validationErrorCounter.d.cts +4 -0
  30. package/dist/common/validationErrorCounter.d.ts +4 -0
  31. package/dist/express/index.cjs +510 -34
  32. package/dist/express/index.cjs.map +1 -1
  33. package/dist/express/index.d.cts +4 -0
  34. package/dist/express/index.d.ts +4 -0
  35. package/dist/express/index.js +506 -30
  36. package/dist/express/index.js.map +1 -1
  37. package/dist/express/middleware.cjs +510 -34
  38. package/dist/express/middleware.cjs.map +1 -1
  39. package/dist/express/middleware.d.cts +4 -0
  40. package/dist/express/middleware.d.ts +4 -0
  41. package/dist/express/middleware.js +506 -30
  42. package/dist/express/middleware.js.map +1 -1
  43. package/dist/fastify/index.cjs +501 -27
  44. package/dist/fastify/index.cjs.map +1 -1
  45. package/dist/fastify/index.d.cts +4 -0
  46. package/dist/fastify/index.d.ts +4 -0
  47. package/dist/fastify/index.js +497 -23
  48. package/dist/fastify/index.js.map +1 -1
  49. package/dist/fastify/plugin.cjs +501 -27
  50. package/dist/fastify/plugin.cjs.map +1 -1
  51. package/dist/fastify/plugin.d.cts +4 -0
  52. package/dist/fastify/plugin.d.ts +4 -0
  53. package/dist/fastify/plugin.js +497 -23
  54. package/dist/fastify/plugin.js.map +1 -1
  55. package/dist/hono/index.cjs +470 -15
  56. package/dist/hono/index.cjs.map +1 -1
  57. package/dist/hono/index.d.cts +4 -0
  58. package/dist/hono/index.d.ts +4 -0
  59. package/dist/hono/index.js +466 -11
  60. package/dist/hono/index.js.map +1 -1
  61. package/dist/hono/middleware.cjs +470 -15
  62. package/dist/hono/middleware.cjs.map +1 -1
  63. package/dist/hono/middleware.d.cts +4 -0
  64. package/dist/hono/middleware.d.ts +4 -0
  65. package/dist/hono/middleware.js +466 -11
  66. package/dist/hono/middleware.js.map +1 -1
  67. package/dist/koa/index.cjs +485 -15
  68. package/dist/koa/index.cjs.map +1 -1
  69. package/dist/koa/index.d.cts +4 -0
  70. package/dist/koa/index.d.ts +4 -0
  71. package/dist/koa/index.js +481 -11
  72. package/dist/koa/index.js.map +1 -1
  73. package/dist/koa/middleware.cjs +485 -15
  74. package/dist/koa/middleware.cjs.map +1 -1
  75. package/dist/koa/middleware.d.cts +4 -0
  76. package/dist/koa/middleware.d.ts +4 -0
  77. package/dist/koa/middleware.js +481 -11
  78. package/dist/koa/middleware.js.map +1 -1
  79. package/dist/nestjs/index.cjs +510 -34
  80. package/dist/nestjs/index.cjs.map +1 -1
  81. package/dist/nestjs/index.d.cts +4 -0
  82. package/dist/nestjs/index.d.ts +4 -0
  83. package/dist/nestjs/index.js +506 -30
  84. package/dist/nestjs/index.js.map +1 -1
  85. package/package.json +8 -2
@@ -39,7 +39,7 @@ module.exports = __toCommonJS(middleware_exports);
39
39
  var import_perf_hooks = require("perf_hooks");
40
40
 
41
41
  // src/common/client.ts
42
- var import_crypto3 = require("crypto");
42
+ var import_crypto5 = require("crypto");
43
43
  var import_fetch_retry = __toESM(require("fetch-retry"), 1);
44
44
 
45
45
  // src/common/consumerRegistry.ts
@@ -208,8 +208,405 @@ var _RequestCounter = class _RequestCounter {
208
208
  __name(_RequestCounter, "RequestCounter");
209
209
  var RequestCounter = _RequestCounter;
210
210
 
211
- // src/common/serverErrorCounter.ts
211
+ // src/common/requestLogger.ts
212
+ var import_buffer2 = require("buffer");
213
+ var import_crypto2 = require("crypto");
214
+ var import_fs2 = require("fs");
215
+ var import_os2 = require("os");
216
+ var import_path2 = require("path");
217
+
218
+ // src/common/tempGzipFile.ts
219
+ var import_buffer = require("buffer");
212
220
  var import_crypto = require("crypto");
221
+ var import_fs = require("fs");
222
+ var import_os = require("os");
223
+ var import_path = require("path");
224
+ var import_zlib = require("zlib");
225
+ var _TempGzipFile = class _TempGzipFile {
226
+ uuid;
227
+ filePath;
228
+ gzip;
229
+ writeStream;
230
+ readyPromise;
231
+ constructor() {
232
+ this.uuid = (0, import_crypto.randomUUID)();
233
+ this.filePath = (0, import_path.join)((0, import_os.tmpdir)(), `apitally-${this.uuid}.gz`);
234
+ this.writeStream = (0, import_fs.createWriteStream)(this.filePath);
235
+ this.readyPromise = new Promise((resolve, reject) => {
236
+ this.writeStream.once("ready", resolve);
237
+ this.writeStream.once("error", reject);
238
+ });
239
+ this.gzip = (0, import_zlib.createGzip)();
240
+ this.gzip.pipe(this.writeStream);
241
+ }
242
+ get size() {
243
+ return this.writeStream.bytesWritten;
244
+ }
245
+ async writeLine(data) {
246
+ await this.readyPromise;
247
+ return new Promise((resolve, reject) => {
248
+ this.gzip.write(import_buffer.Buffer.concat([
249
+ data,
250
+ import_buffer.Buffer.from("\n")
251
+ ]), (error) => {
252
+ if (error) {
253
+ reject(error);
254
+ } else {
255
+ resolve();
256
+ }
257
+ });
258
+ });
259
+ }
260
+ async getContent() {
261
+ return new Promise((resolve, reject) => {
262
+ (0, import_fs.readFile)(this.filePath, (error, data) => {
263
+ if (error) {
264
+ reject(error);
265
+ } else {
266
+ resolve(data);
267
+ }
268
+ });
269
+ });
270
+ }
271
+ async close() {
272
+ return new Promise((resolve) => {
273
+ this.gzip.end(() => {
274
+ resolve();
275
+ });
276
+ });
277
+ }
278
+ async delete() {
279
+ await this.close();
280
+ (0, import_fs.unlinkSync)(this.filePath);
281
+ }
282
+ };
283
+ __name(_TempGzipFile, "TempGzipFile");
284
+ var TempGzipFile = _TempGzipFile;
285
+
286
+ // src/common/requestLogger.ts
287
+ var MAX_BODY_SIZE = 5e4;
288
+ var MAX_FILE_SIZE = 1e6;
289
+ var MAX_FILES = 50;
290
+ var MAX_PENDING_WRITES = 100;
291
+ var BODY_TOO_LARGE = import_buffer2.Buffer.from("<body too large>");
292
+ var BODY_MASKED = import_buffer2.Buffer.from("<masked>");
293
+ var MASKED = "******";
294
+ var ALLOWED_CONTENT_TYPES = [
295
+ "application/json",
296
+ "text/plain"
297
+ ];
298
+ var EXCLUDE_PATH_PATTERNS = [
299
+ /\/_?healthz?$/i,
300
+ /\/_?health[_-]?checks?$/i,
301
+ /\/_?heart[_-]?beats?$/i,
302
+ /\/ping$/i,
303
+ /\/ready$/i,
304
+ /\/live$/i
305
+ ];
306
+ var EXCLUDE_USER_AGENT_PATTERNS = [
307
+ /health[-_ ]?check/i,
308
+ /microsoft-azure-application-lb/i,
309
+ /googlehc/i,
310
+ /kube-probe/i
311
+ ];
312
+ var MASK_QUERY_PARAM_PATTERNS = [
313
+ /auth/i,
314
+ /api-?key/i,
315
+ /secret/i,
316
+ /token/i,
317
+ /password/i,
318
+ /pwd/i
319
+ ];
320
+ var MASK_HEADER_PATTERNS = [
321
+ /auth/i,
322
+ /api-?key/i,
323
+ /secret/i,
324
+ /token/i,
325
+ /cookie/i
326
+ ];
327
+ var DEFAULT_CONFIG = {
328
+ enabled: false,
329
+ logQueryParams: true,
330
+ logRequestHeaders: false,
331
+ logRequestBody: false,
332
+ logResponseHeaders: true,
333
+ logResponseBody: false,
334
+ maskQueryParams: [],
335
+ maskHeaders: [],
336
+ excludePaths: []
337
+ };
338
+ var _RequestLogger = class _RequestLogger {
339
+ config;
340
+ enabled;
341
+ suspendUntil = null;
342
+ pendingWrites = [];
343
+ currentFile = null;
344
+ files = [];
345
+ maintainIntervalId;
346
+ constructor(config) {
347
+ this.config = {
348
+ ...DEFAULT_CONFIG,
349
+ ...config
350
+ };
351
+ this.enabled = this.config.enabled && checkWritableFs();
352
+ if (this.enabled) {
353
+ this.maintainIntervalId = setInterval(() => {
354
+ this.maintain();
355
+ }, 1e3);
356
+ }
357
+ }
358
+ shouldExcludePath(urlPath) {
359
+ const patterns = [
360
+ ...this.config.excludePaths,
361
+ ...EXCLUDE_PATH_PATTERNS
362
+ ];
363
+ return matchPatterns(urlPath, patterns);
364
+ }
365
+ shouldExcludeUserAgent(userAgent) {
366
+ return userAgent ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS) : false;
367
+ }
368
+ shouldMaskQueryParam(name) {
369
+ const patterns = [
370
+ ...this.config.maskQueryParams,
371
+ ...MASK_QUERY_PARAM_PATTERNS
372
+ ];
373
+ return matchPatterns(name, patterns);
374
+ }
375
+ shouldMaskHeader(name) {
376
+ const patterns = [
377
+ ...this.config.maskHeaders,
378
+ ...MASK_HEADER_PATTERNS
379
+ ];
380
+ return matchPatterns(name, patterns);
381
+ }
382
+ hasSupportedContentType(headers) {
383
+ var _a2;
384
+ const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
385
+ return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
386
+ }
387
+ maskQueryParams(search) {
388
+ const params = new URLSearchParams(search);
389
+ for (const [key] of params) {
390
+ if (this.shouldMaskQueryParam(key)) {
391
+ params.set(key, MASKED);
392
+ }
393
+ }
394
+ return params.toString();
395
+ }
396
+ maskHeaders(headers) {
397
+ return headers.map(([k, v]) => [
398
+ k,
399
+ this.shouldMaskHeader(k) ? MASKED : v
400
+ ]);
401
+ }
402
+ logRequest(request, response) {
403
+ var _a2, _b, _c;
404
+ if (!this.enabled || this.suspendUntil !== null) return;
405
+ const url = new URL(request.url);
406
+ const path = request.path ?? url.pathname;
407
+ const userAgent = (_a2 = request.headers.find(([k]) => k.toLowerCase() === "user-agent")) == null ? void 0 : _a2[1];
408
+ if (this.shouldExcludePath(path) || this.shouldExcludeUserAgent(userAgent) || (((_c = (_b = this.config).excludeCallback) == null ? void 0 : _c.call(_b, request, response)) ?? false)) {
409
+ return;
410
+ }
411
+ url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
412
+ request.url = url.toString();
413
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
414
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
415
+ if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
416
+ request.body = void 0;
417
+ } else if (request.body) {
418
+ if (request.body.length > MAX_BODY_SIZE) {
419
+ request.body = BODY_TOO_LARGE;
420
+ } else if (this.config.maskRequestBodyCallback) {
421
+ try {
422
+ request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
423
+ if (request.body.length > MAX_BODY_SIZE) {
424
+ request.body = BODY_TOO_LARGE;
425
+ }
426
+ } catch {
427
+ request.body = void 0;
428
+ }
429
+ }
430
+ }
431
+ if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
432
+ response.body = void 0;
433
+ } else if (response.body) {
434
+ if (response.body.length > MAX_BODY_SIZE) {
435
+ response.body = BODY_TOO_LARGE;
436
+ } else if (this.config.maskResponseBodyCallback) {
437
+ try {
438
+ response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
439
+ if (response.body.length > MAX_BODY_SIZE) {
440
+ response.body = BODY_TOO_LARGE;
441
+ }
442
+ } catch {
443
+ response.body = void 0;
444
+ }
445
+ }
446
+ }
447
+ const item = {
448
+ uuid: (0, import_crypto2.randomUUID)(),
449
+ request: skipEmptyValues(request),
450
+ response: skipEmptyValues(response)
451
+ };
452
+ [
453
+ item.request.body,
454
+ item.response.body
455
+ ].forEach((body) => {
456
+ if (body) {
457
+ body.toJSON = function() {
458
+ return this.toString("base64");
459
+ };
460
+ }
461
+ });
462
+ this.pendingWrites.push(JSON.stringify(item));
463
+ if (this.pendingWrites.length > MAX_PENDING_WRITES) {
464
+ this.pendingWrites.shift();
465
+ }
466
+ }
467
+ async writeToFile() {
468
+ if (!this.enabled || this.pendingWrites.length === 0) {
469
+ return;
470
+ }
471
+ if (!this.currentFile) {
472
+ this.currentFile = new TempGzipFile();
473
+ }
474
+ while (this.pendingWrites.length > 0) {
475
+ const item = this.pendingWrites.shift();
476
+ if (item) {
477
+ await this.currentFile.writeLine(import_buffer2.Buffer.from(item));
478
+ }
479
+ }
480
+ }
481
+ getFile() {
482
+ return this.files.shift();
483
+ }
484
+ retryFileLater(file) {
485
+ this.files.unshift(file);
486
+ }
487
+ async rotateFile() {
488
+ if (this.currentFile) {
489
+ await this.currentFile.close();
490
+ this.files.push(this.currentFile);
491
+ this.currentFile = null;
492
+ }
493
+ }
494
+ async maintain() {
495
+ await this.writeToFile();
496
+ if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
497
+ await this.rotateFile();
498
+ }
499
+ while (this.files.length > MAX_FILES) {
500
+ const file = this.files.shift();
501
+ file == null ? void 0 : file.delete();
502
+ }
503
+ if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
504
+ this.suspendUntil = null;
505
+ }
506
+ }
507
+ async clear() {
508
+ this.pendingWrites = [];
509
+ await this.rotateFile();
510
+ this.files.forEach((file) => {
511
+ file.delete();
512
+ });
513
+ this.files = [];
514
+ }
515
+ async close() {
516
+ this.enabled = false;
517
+ await this.clear();
518
+ if (this.maintainIntervalId) {
519
+ clearInterval(this.maintainIntervalId);
520
+ }
521
+ }
522
+ };
523
+ __name(_RequestLogger, "RequestLogger");
524
+ var RequestLogger = _RequestLogger;
525
+ function convertHeaders(headers) {
526
+ if (headers instanceof Headers) {
527
+ return Array.from(headers.entries());
528
+ }
529
+ return Object.entries(headers).flatMap(([key, value]) => {
530
+ if (value === void 0) {
531
+ return [];
532
+ }
533
+ if (Array.isArray(value)) {
534
+ return value.map((v) => [
535
+ key,
536
+ v
537
+ ]);
538
+ }
539
+ return [
540
+ [
541
+ key,
542
+ value.toString()
543
+ ]
544
+ ];
545
+ });
546
+ }
547
+ __name(convertHeaders, "convertHeaders");
548
+ function convertBody(body, contentType) {
549
+ if (!body || !contentType) {
550
+ return;
551
+ }
552
+ try {
553
+ if (contentType.startsWith("application/json")) {
554
+ if (isValidJsonString(body)) {
555
+ return import_buffer2.Buffer.from(body);
556
+ } else {
557
+ return import_buffer2.Buffer.from(JSON.stringify(body));
558
+ }
559
+ }
560
+ if (contentType.startsWith("text/") && typeof body === "string") {
561
+ return import_buffer2.Buffer.from(body);
562
+ }
563
+ } catch (error) {
564
+ return;
565
+ }
566
+ }
567
+ __name(convertBody, "convertBody");
568
+ function isValidJsonString(body) {
569
+ if (typeof body !== "string") {
570
+ return false;
571
+ }
572
+ try {
573
+ JSON.parse(body);
574
+ return true;
575
+ } catch {
576
+ return false;
577
+ }
578
+ }
579
+ __name(isValidJsonString, "isValidJsonString");
580
+ function matchPatterns(value, patterns) {
581
+ return patterns.some((pattern) => {
582
+ return pattern.test(value);
583
+ });
584
+ }
585
+ __name(matchPatterns, "matchPatterns");
586
+ function skipEmptyValues(data) {
587
+ return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
588
+ if (v == null || Number.isNaN(v)) return false;
589
+ if (Array.isArray(v) || import_buffer2.Buffer.isBuffer(v) || typeof v === "string") {
590
+ return v.length > 0;
591
+ }
592
+ return true;
593
+ }));
594
+ }
595
+ __name(skipEmptyValues, "skipEmptyValues");
596
+ function checkWritableFs() {
597
+ try {
598
+ const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto2.randomUUID)()}`);
599
+ (0, import_fs2.writeFileSync)(testPath, "test");
600
+ (0, import_fs2.unlinkSync)(testPath);
601
+ return true;
602
+ } catch (error) {
603
+ return false;
604
+ }
605
+ }
606
+ __name(checkWritableFs, "checkWritableFs");
607
+
608
+ // src/common/serverErrorCounter.ts
609
+ var import_crypto3 = require("crypto");
213
610
  var MAX_MSG_LENGTH = 2048;
214
611
  var MAX_STACKTRACE_LENGTH = 65536;
215
612
  var _ServerErrorCounter = class _ServerErrorCounter {
@@ -261,7 +658,7 @@ var _ServerErrorCounter = class _ServerErrorCounter {
261
658
  serverError.msg.trim(),
262
659
  serverError.traceback.trim()
263
660
  ].join("|");
264
- return (0, import_crypto.createHash)("md5").update(hashInput).digest("hex");
661
+ return (0, import_crypto3.createHash)("md5").update(hashInput).digest("hex");
265
662
  }
266
663
  getTruncatedMessage(msg) {
267
664
  msg = msg.trim();
@@ -307,7 +704,7 @@ __name(_ServerErrorCounter, "ServerErrorCounter");
307
704
  var ServerErrorCounter = _ServerErrorCounter;
308
705
 
309
706
  // src/common/validationErrorCounter.ts
310
- var import_crypto2 = require("crypto");
707
+ var import_crypto4 = require("crypto");
311
708
  var _ValidationErrorCounter = class _ValidationErrorCounter {
312
709
  errorCounts;
313
710
  errorDetails;
@@ -351,7 +748,7 @@ var _ValidationErrorCounter = class _ValidationErrorCounter {
351
748
  validationError.msg.trim(),
352
749
  validationError.type
353
750
  ].join("|");
354
- return (0, import_crypto2.createHash)("md5").update(hashInput).digest("hex");
751
+ return (0, import_crypto4.createHash)("md5").update(hashInput).digest("hex");
355
752
  }
356
753
  };
357
754
  __name(_ValidationErrorCounter, "ValidationErrorCounter");
@@ -380,11 +777,12 @@ var _ApitallyClient = class _ApitallyClient {
380
777
  startupData;
381
778
  startupDataSent = false;
382
779
  requestCounter;
780
+ requestLogger;
383
781
  validationErrorCounter;
384
782
  serverErrorCounter;
385
783
  consumerRegistry;
386
784
  logger;
387
- constructor({ clientId, env = "dev", logger }) {
785
+ constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
388
786
  if (_ApitallyClient.instance) {
389
787
  throw new Error("Apitally client is already initialized");
390
788
  }
@@ -397,9 +795,10 @@ var _ApitallyClient = class _ApitallyClient {
397
795
  _ApitallyClient.instance = this;
398
796
  this.clientId = clientId;
399
797
  this.env = env;
400
- this.instanceUuid = (0, import_crypto3.randomUUID)();
798
+ this.instanceUuid = (0, import_crypto5.randomUUID)();
401
799
  this.syncDataQueue = [];
402
800
  this.requestCounter = new RequestCounter();
801
+ this.requestLogger = new RequestLogger(requestLoggingConfig);
403
802
  this.validationErrorCounter = new ValidationErrorCounter();
404
803
  this.serverErrorCounter = new ServerErrorCounter();
405
804
  this.consumerRegistry = new ConsumerRegistry();
@@ -421,6 +820,8 @@ var _ApitallyClient = class _ApitallyClient {
421
820
  async handleShutdown() {
422
821
  this.stopSync();
423
822
  await this.sendSyncData();
823
+ await this.sendLogData();
824
+ await this.requestLogger.close();
424
825
  _ApitallyClient.instance = void 0;
425
826
  }
426
827
  getHubUrlPrefix() {
@@ -467,7 +868,8 @@ var _ApitallyClient = class _ApitallyClient {
467
868
  async sync() {
468
869
  try {
469
870
  const promises = [
470
- this.sendSyncData()
871
+ this.sendSyncData(),
872
+ this.sendLogData()
471
873
  ];
472
874
  if (!this.startupDataSent) {
473
875
  promises.push(this.sendStartupData());
@@ -495,7 +897,7 @@ var _ApitallyClient = class _ApitallyClient {
495
897
  this.logger.debug("Sending startup data to Apitally Hub");
496
898
  const payload = {
497
899
  instance_uuid: this.instanceUuid,
498
- message_uuid: (0, import_crypto3.randomUUID)(),
900
+ message_uuid: (0, import_crypto5.randomUUID)(),
499
901
  ...this.startupData
500
902
  };
501
903
  try {
@@ -517,7 +919,7 @@ var _ApitallyClient = class _ApitallyClient {
517
919
  const newPayload = {
518
920
  time_offset: 0,
519
921
  instance_uuid: this.instanceUuid,
520
- message_uuid: (0, import_crypto3.randomUUID)(),
922
+ message_uuid: (0, import_crypto5.randomUUID)(),
521
923
  requests: this.requestCounter.getAndResetRequests(),
522
924
  validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
523
925
  server_errors: this.serverErrorCounter.getAndResetServerErrors(),
@@ -536,8 +938,7 @@ var _ApitallyClient = class _ApitallyClient {
536
938
  const timeOffset = Date.now() - time;
537
939
  if (timeOffset <= MAX_QUEUE_TIME) {
538
940
  if (i > 0) {
539
- const waitMs = 100 + Math.random() * 200;
540
- await new Promise((resolve) => setTimeout(resolve, waitMs));
941
+ await this.randomDelay();
541
942
  }
542
943
  payload.time_offset = timeOffset / 1e3;
543
944
  await this.sendData("sync", payload);
@@ -556,6 +957,52 @@ var _ApitallyClient = class _ApitallyClient {
556
957
  }
557
958
  }
558
959
  }
960
+ async sendLogData() {
961
+ this.logger.debug("Sending request log data to Apitally Hub");
962
+ await this.requestLogger.rotateFile();
963
+ const fetchWithRetry = (0, import_fetch_retry.default)(fetch, {
964
+ retries: 3,
965
+ retryDelay: 1e3,
966
+ retryOn: [
967
+ 408,
968
+ 429,
969
+ 500,
970
+ 502,
971
+ 503,
972
+ 504
973
+ ]
974
+ });
975
+ let i = 0;
976
+ let logFile;
977
+ while (logFile = this.requestLogger.getFile()) {
978
+ if (i > 0) {
979
+ await this.randomDelay();
980
+ }
981
+ try {
982
+ const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
983
+ method: "POST",
984
+ body: await logFile.getContent()
985
+ });
986
+ if (response.status === 402 && response.headers.has("Retry-After")) {
987
+ const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
988
+ if (retryAfter > 0) {
989
+ this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
990
+ this.requestLogger.clear();
991
+ return;
992
+ }
993
+ }
994
+ if (!response.ok) {
995
+ throw new HTTPError(response);
996
+ }
997
+ logFile.delete();
998
+ } catch (error) {
999
+ this.requestLogger.retryFileLater(logFile);
1000
+ break;
1001
+ }
1002
+ i++;
1003
+ if (i >= 10) break;
1004
+ }
1005
+ }
559
1006
  handleHubError(error) {
560
1007
  if (error instanceof HTTPError) {
561
1008
  if (error.response.status === 404) {
@@ -570,6 +1017,10 @@ var _ApitallyClient = class _ApitallyClient {
570
1017
  }
571
1018
  return false;
572
1019
  }
1020
+ async randomDelay() {
1021
+ const delay = 100 + Math.random() * 200;
1022
+ await new Promise((resolve) => setTimeout(resolve, delay));
1023
+ }
573
1024
  };
574
1025
  __name(_ApitallyClient, "ApitallyClient");
575
1026
  __publicField(_ApitallyClient, "instance");
@@ -740,18 +1191,18 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
740
1191
  }
741
1192
  try {
742
1193
  const startTime = import_perf_hooks.performance.now();
743
- const originalJson = res.json;
744
- res.json = (body) => {
1194
+ const originalSend = res.send;
1195
+ res.send = (body) => {
745
1196
  res.locals.body = body;
746
- return originalJson.call(res, body);
1197
+ return originalSend.call(res, body);
747
1198
  };
748
1199
  res.on("finish", () => {
749
1200
  try {
1201
+ const responseTime = import_perf_hooks.performance.now() - startTime;
750
1202
  const path = getRoutePath(req);
1203
+ const consumer = getConsumer(req);
1204
+ client.consumerRegistry.addOrUpdateConsumer(consumer);
751
1205
  if (path) {
752
- const responseTime = import_perf_hooks.performance.now() - startTime;
753
- const consumer = getConsumer(req);
754
- client.consumerRegistry.addOrUpdateConsumer(consumer);
755
1206
  client.requestCounter.addRequest({
756
1207
  consumer: consumer == null ? void 0 : consumer.identifier,
757
1208
  method: req.method,
@@ -762,24 +1213,31 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
762
1213
  responseSize: res.get("content-length")
763
1214
  });
764
1215
  if ((res.statusCode === 400 || res.statusCode === 422) && res.locals.body) {
765
- const validationErrors = [];
766
- if (validationErrors.length === 0) {
767
- validationErrors.push(...extractExpressValidatorErrors(res.locals.body));
768
- }
769
- if (validationErrors.length === 0) {
770
- validationErrors.push(...extractCelebrateErrors(res.locals.body));
1216
+ let jsonBody;
1217
+ try {
1218
+ jsonBody = JSON.parse(res.locals.body);
1219
+ } catch {
771
1220
  }
772
- if (validationErrors.length === 0) {
773
- validationErrors.push(...extractNestValidationErrors(res.locals.body));
774
- }
775
- validationErrors.forEach((error) => {
776
- client.validationErrorCounter.addValidationError({
777
- consumer: consumer == null ? void 0 : consumer.identifier,
778
- method: req.method,
779
- path: req.route.path,
780
- ...error
1221
+ if (jsonBody) {
1222
+ const validationErrors = [];
1223
+ if (validationErrors.length === 0) {
1224
+ validationErrors.push(...extractExpressValidatorErrors(jsonBody));
1225
+ }
1226
+ if (validationErrors.length === 0) {
1227
+ validationErrors.push(...extractCelebrateErrors(jsonBody));
1228
+ }
1229
+ if (validationErrors.length === 0) {
1230
+ validationErrors.push(...extractNestValidationErrors(jsonBody));
1231
+ }
1232
+ validationErrors.forEach((error) => {
1233
+ client.validationErrorCounter.addValidationError({
1234
+ consumer: consumer == null ? void 0 : consumer.identifier,
1235
+ method: req.method,
1236
+ path: req.route.path,
1237
+ ...error
1238
+ });
781
1239
  });
782
- });
1240
+ }
783
1241
  }
784
1242
  if (res.statusCode === 500 && res.locals.serverError) {
785
1243
  const serverError = res.locals.serverError;
@@ -793,6 +1251,24 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
793
1251
  });
794
1252
  }
795
1253
  }
1254
+ if (client.requestLogger.enabled) {
1255
+ client.requestLogger.logRequest({
1256
+ timestamp: Date.now() / 1e3,
1257
+ method: req.method,
1258
+ path,
1259
+ url: `${req.protocol}://${req.host}${req.originalUrl}`,
1260
+ headers: convertHeaders(req.headers),
1261
+ size: Number(req.get("content-length")),
1262
+ consumer: consumer == null ? void 0 : consumer.identifier,
1263
+ body: convertBody(req.body, req.get("content-type"))
1264
+ }, {
1265
+ statusCode: res.statusCode,
1266
+ responseTime: responseTime / 1e3,
1267
+ headers: convertHeaders(res.getHeaders()),
1268
+ size: Number(res.get("content-length")),
1269
+ body: convertBody(res.locals.body, res.get("content-type"))
1270
+ });
1271
+ }
796
1272
  } catch (error) {
797
1273
  client.logger.error("Error while logging request in Apitally middleware.", {
798
1274
  request: req,