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
@@ -38,7 +38,7 @@ __export(koa_exports, {
38
38
  module.exports = __toCommonJS(koa_exports);
39
39
 
40
40
  // src/common/client.ts
41
- var import_crypto3 = require("crypto");
41
+ var import_crypto5 = require("crypto");
42
42
  var import_fetch_retry = __toESM(require("fetch-retry"), 1);
43
43
 
44
44
  // src/common/consumerRegistry.ts
@@ -207,8 +207,405 @@ var _RequestCounter = class _RequestCounter {
207
207
  __name(_RequestCounter, "RequestCounter");
208
208
  var RequestCounter = _RequestCounter;
209
209
 
210
- // src/common/serverErrorCounter.ts
210
+ // src/common/requestLogger.ts
211
+ var import_buffer2 = require("buffer");
212
+ var import_crypto2 = require("crypto");
213
+ var import_fs2 = require("fs");
214
+ var import_os2 = require("os");
215
+ var import_path2 = require("path");
216
+
217
+ // src/common/tempGzipFile.ts
218
+ var import_buffer = require("buffer");
211
219
  var import_crypto = require("crypto");
220
+ var import_fs = require("fs");
221
+ var import_os = require("os");
222
+ var import_path = require("path");
223
+ var import_zlib = require("zlib");
224
+ var _TempGzipFile = class _TempGzipFile {
225
+ uuid;
226
+ filePath;
227
+ gzip;
228
+ writeStream;
229
+ readyPromise;
230
+ constructor() {
231
+ this.uuid = (0, import_crypto.randomUUID)();
232
+ this.filePath = (0, import_path.join)((0, import_os.tmpdir)(), `apitally-${this.uuid}.gz`);
233
+ this.writeStream = (0, import_fs.createWriteStream)(this.filePath);
234
+ this.readyPromise = new Promise((resolve, reject) => {
235
+ this.writeStream.once("ready", resolve);
236
+ this.writeStream.once("error", reject);
237
+ });
238
+ this.gzip = (0, import_zlib.createGzip)();
239
+ this.gzip.pipe(this.writeStream);
240
+ }
241
+ get size() {
242
+ return this.writeStream.bytesWritten;
243
+ }
244
+ async writeLine(data) {
245
+ await this.readyPromise;
246
+ return new Promise((resolve, reject) => {
247
+ this.gzip.write(import_buffer.Buffer.concat([
248
+ data,
249
+ import_buffer.Buffer.from("\n")
250
+ ]), (error) => {
251
+ if (error) {
252
+ reject(error);
253
+ } else {
254
+ resolve();
255
+ }
256
+ });
257
+ });
258
+ }
259
+ async getContent() {
260
+ return new Promise((resolve, reject) => {
261
+ (0, import_fs.readFile)(this.filePath, (error, data) => {
262
+ if (error) {
263
+ reject(error);
264
+ } else {
265
+ resolve(data);
266
+ }
267
+ });
268
+ });
269
+ }
270
+ async close() {
271
+ return new Promise((resolve) => {
272
+ this.gzip.end(() => {
273
+ resolve();
274
+ });
275
+ });
276
+ }
277
+ async delete() {
278
+ await this.close();
279
+ (0, import_fs.unlinkSync)(this.filePath);
280
+ }
281
+ };
282
+ __name(_TempGzipFile, "TempGzipFile");
283
+ var TempGzipFile = _TempGzipFile;
284
+
285
+ // src/common/requestLogger.ts
286
+ var MAX_BODY_SIZE = 5e4;
287
+ var MAX_FILE_SIZE = 1e6;
288
+ var MAX_FILES = 50;
289
+ var MAX_PENDING_WRITES = 100;
290
+ var BODY_TOO_LARGE = import_buffer2.Buffer.from("<body too large>");
291
+ var BODY_MASKED = import_buffer2.Buffer.from("<masked>");
292
+ var MASKED = "******";
293
+ var ALLOWED_CONTENT_TYPES = [
294
+ "application/json",
295
+ "text/plain"
296
+ ];
297
+ var EXCLUDE_PATH_PATTERNS = [
298
+ /\/_?healthz?$/i,
299
+ /\/_?health[_-]?checks?$/i,
300
+ /\/_?heart[_-]?beats?$/i,
301
+ /\/ping$/i,
302
+ /\/ready$/i,
303
+ /\/live$/i
304
+ ];
305
+ var EXCLUDE_USER_AGENT_PATTERNS = [
306
+ /health[-_ ]?check/i,
307
+ /microsoft-azure-application-lb/i,
308
+ /googlehc/i,
309
+ /kube-probe/i
310
+ ];
311
+ var MASK_QUERY_PARAM_PATTERNS = [
312
+ /auth/i,
313
+ /api-?key/i,
314
+ /secret/i,
315
+ /token/i,
316
+ /password/i,
317
+ /pwd/i
318
+ ];
319
+ var MASK_HEADER_PATTERNS = [
320
+ /auth/i,
321
+ /api-?key/i,
322
+ /secret/i,
323
+ /token/i,
324
+ /cookie/i
325
+ ];
326
+ var DEFAULT_CONFIG = {
327
+ enabled: false,
328
+ logQueryParams: true,
329
+ logRequestHeaders: false,
330
+ logRequestBody: false,
331
+ logResponseHeaders: true,
332
+ logResponseBody: false,
333
+ maskQueryParams: [],
334
+ maskHeaders: [],
335
+ excludePaths: []
336
+ };
337
+ var _RequestLogger = class _RequestLogger {
338
+ config;
339
+ enabled;
340
+ suspendUntil = null;
341
+ pendingWrites = [];
342
+ currentFile = null;
343
+ files = [];
344
+ maintainIntervalId;
345
+ constructor(config) {
346
+ this.config = {
347
+ ...DEFAULT_CONFIG,
348
+ ...config
349
+ };
350
+ this.enabled = this.config.enabled && checkWritableFs();
351
+ if (this.enabled) {
352
+ this.maintainIntervalId = setInterval(() => {
353
+ this.maintain();
354
+ }, 1e3);
355
+ }
356
+ }
357
+ shouldExcludePath(urlPath) {
358
+ const patterns = [
359
+ ...this.config.excludePaths,
360
+ ...EXCLUDE_PATH_PATTERNS
361
+ ];
362
+ return matchPatterns(urlPath, patterns);
363
+ }
364
+ shouldExcludeUserAgent(userAgent) {
365
+ return userAgent ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS) : false;
366
+ }
367
+ shouldMaskQueryParam(name) {
368
+ const patterns = [
369
+ ...this.config.maskQueryParams,
370
+ ...MASK_QUERY_PARAM_PATTERNS
371
+ ];
372
+ return matchPatterns(name, patterns);
373
+ }
374
+ shouldMaskHeader(name) {
375
+ const patterns = [
376
+ ...this.config.maskHeaders,
377
+ ...MASK_HEADER_PATTERNS
378
+ ];
379
+ return matchPatterns(name, patterns);
380
+ }
381
+ hasSupportedContentType(headers) {
382
+ var _a2;
383
+ const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
384
+ return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
385
+ }
386
+ maskQueryParams(search) {
387
+ const params = new URLSearchParams(search);
388
+ for (const [key] of params) {
389
+ if (this.shouldMaskQueryParam(key)) {
390
+ params.set(key, MASKED);
391
+ }
392
+ }
393
+ return params.toString();
394
+ }
395
+ maskHeaders(headers) {
396
+ return headers.map(([k, v]) => [
397
+ k,
398
+ this.shouldMaskHeader(k) ? MASKED : v
399
+ ]);
400
+ }
401
+ logRequest(request, response) {
402
+ var _a2, _b, _c;
403
+ if (!this.enabled || this.suspendUntil !== null) return;
404
+ const url = new URL(request.url);
405
+ const path = request.path ?? url.pathname;
406
+ const userAgent = (_a2 = request.headers.find(([k]) => k.toLowerCase() === "user-agent")) == null ? void 0 : _a2[1];
407
+ if (this.shouldExcludePath(path) || this.shouldExcludeUserAgent(userAgent) || (((_c = (_b = this.config).excludeCallback) == null ? void 0 : _c.call(_b, request, response)) ?? false)) {
408
+ return;
409
+ }
410
+ url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
411
+ request.url = url.toString();
412
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
413
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
414
+ if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
415
+ request.body = void 0;
416
+ } else if (request.body) {
417
+ if (request.body.length > MAX_BODY_SIZE) {
418
+ request.body = BODY_TOO_LARGE;
419
+ } else if (this.config.maskRequestBodyCallback) {
420
+ try {
421
+ request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
422
+ if (request.body.length > MAX_BODY_SIZE) {
423
+ request.body = BODY_TOO_LARGE;
424
+ }
425
+ } catch {
426
+ request.body = void 0;
427
+ }
428
+ }
429
+ }
430
+ if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
431
+ response.body = void 0;
432
+ } else if (response.body) {
433
+ if (response.body.length > MAX_BODY_SIZE) {
434
+ response.body = BODY_TOO_LARGE;
435
+ } else if (this.config.maskResponseBodyCallback) {
436
+ try {
437
+ response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
438
+ if (response.body.length > MAX_BODY_SIZE) {
439
+ response.body = BODY_TOO_LARGE;
440
+ }
441
+ } catch {
442
+ response.body = void 0;
443
+ }
444
+ }
445
+ }
446
+ const item = {
447
+ uuid: (0, import_crypto2.randomUUID)(),
448
+ request: skipEmptyValues(request),
449
+ response: skipEmptyValues(response)
450
+ };
451
+ [
452
+ item.request.body,
453
+ item.response.body
454
+ ].forEach((body) => {
455
+ if (body) {
456
+ body.toJSON = function() {
457
+ return this.toString("base64");
458
+ };
459
+ }
460
+ });
461
+ this.pendingWrites.push(JSON.stringify(item));
462
+ if (this.pendingWrites.length > MAX_PENDING_WRITES) {
463
+ this.pendingWrites.shift();
464
+ }
465
+ }
466
+ async writeToFile() {
467
+ if (!this.enabled || this.pendingWrites.length === 0) {
468
+ return;
469
+ }
470
+ if (!this.currentFile) {
471
+ this.currentFile = new TempGzipFile();
472
+ }
473
+ while (this.pendingWrites.length > 0) {
474
+ const item = this.pendingWrites.shift();
475
+ if (item) {
476
+ await this.currentFile.writeLine(import_buffer2.Buffer.from(item));
477
+ }
478
+ }
479
+ }
480
+ getFile() {
481
+ return this.files.shift();
482
+ }
483
+ retryFileLater(file) {
484
+ this.files.unshift(file);
485
+ }
486
+ async rotateFile() {
487
+ if (this.currentFile) {
488
+ await this.currentFile.close();
489
+ this.files.push(this.currentFile);
490
+ this.currentFile = null;
491
+ }
492
+ }
493
+ async maintain() {
494
+ await this.writeToFile();
495
+ if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
496
+ await this.rotateFile();
497
+ }
498
+ while (this.files.length > MAX_FILES) {
499
+ const file = this.files.shift();
500
+ file == null ? void 0 : file.delete();
501
+ }
502
+ if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
503
+ this.suspendUntil = null;
504
+ }
505
+ }
506
+ async clear() {
507
+ this.pendingWrites = [];
508
+ await this.rotateFile();
509
+ this.files.forEach((file) => {
510
+ file.delete();
511
+ });
512
+ this.files = [];
513
+ }
514
+ async close() {
515
+ this.enabled = false;
516
+ await this.clear();
517
+ if (this.maintainIntervalId) {
518
+ clearInterval(this.maintainIntervalId);
519
+ }
520
+ }
521
+ };
522
+ __name(_RequestLogger, "RequestLogger");
523
+ var RequestLogger = _RequestLogger;
524
+ function convertHeaders(headers) {
525
+ if (headers instanceof Headers) {
526
+ return Array.from(headers.entries());
527
+ }
528
+ return Object.entries(headers).flatMap(([key, value]) => {
529
+ if (value === void 0) {
530
+ return [];
531
+ }
532
+ if (Array.isArray(value)) {
533
+ return value.map((v) => [
534
+ key,
535
+ v
536
+ ]);
537
+ }
538
+ return [
539
+ [
540
+ key,
541
+ value.toString()
542
+ ]
543
+ ];
544
+ });
545
+ }
546
+ __name(convertHeaders, "convertHeaders");
547
+ function convertBody(body, contentType) {
548
+ if (!body || !contentType) {
549
+ return;
550
+ }
551
+ try {
552
+ if (contentType.startsWith("application/json")) {
553
+ if (isValidJsonString(body)) {
554
+ return import_buffer2.Buffer.from(body);
555
+ } else {
556
+ return import_buffer2.Buffer.from(JSON.stringify(body));
557
+ }
558
+ }
559
+ if (contentType.startsWith("text/") && typeof body === "string") {
560
+ return import_buffer2.Buffer.from(body);
561
+ }
562
+ } catch (error) {
563
+ return;
564
+ }
565
+ }
566
+ __name(convertBody, "convertBody");
567
+ function isValidJsonString(body) {
568
+ if (typeof body !== "string") {
569
+ return false;
570
+ }
571
+ try {
572
+ JSON.parse(body);
573
+ return true;
574
+ } catch {
575
+ return false;
576
+ }
577
+ }
578
+ __name(isValidJsonString, "isValidJsonString");
579
+ function matchPatterns(value, patterns) {
580
+ return patterns.some((pattern) => {
581
+ return pattern.test(value);
582
+ });
583
+ }
584
+ __name(matchPatterns, "matchPatterns");
585
+ function skipEmptyValues(data) {
586
+ return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
587
+ if (v == null || Number.isNaN(v)) return false;
588
+ if (Array.isArray(v) || import_buffer2.Buffer.isBuffer(v) || typeof v === "string") {
589
+ return v.length > 0;
590
+ }
591
+ return true;
592
+ }));
593
+ }
594
+ __name(skipEmptyValues, "skipEmptyValues");
595
+ function checkWritableFs() {
596
+ try {
597
+ const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto2.randomUUID)()}`);
598
+ (0, import_fs2.writeFileSync)(testPath, "test");
599
+ (0, import_fs2.unlinkSync)(testPath);
600
+ return true;
601
+ } catch (error) {
602
+ return false;
603
+ }
604
+ }
605
+ __name(checkWritableFs, "checkWritableFs");
606
+
607
+ // src/common/serverErrorCounter.ts
608
+ var import_crypto3 = require("crypto");
212
609
  var MAX_MSG_LENGTH = 2048;
213
610
  var MAX_STACKTRACE_LENGTH = 65536;
214
611
  var _ServerErrorCounter = class _ServerErrorCounter {
@@ -260,7 +657,7 @@ var _ServerErrorCounter = class _ServerErrorCounter {
260
657
  serverError.msg.trim(),
261
658
  serverError.traceback.trim()
262
659
  ].join("|");
263
- return (0, import_crypto.createHash)("md5").update(hashInput).digest("hex");
660
+ return (0, import_crypto3.createHash)("md5").update(hashInput).digest("hex");
264
661
  }
265
662
  getTruncatedMessage(msg) {
266
663
  msg = msg.trim();
@@ -306,7 +703,7 @@ __name(_ServerErrorCounter, "ServerErrorCounter");
306
703
  var ServerErrorCounter = _ServerErrorCounter;
307
704
 
308
705
  // src/common/validationErrorCounter.ts
309
- var import_crypto2 = require("crypto");
706
+ var import_crypto4 = require("crypto");
310
707
  var _ValidationErrorCounter = class _ValidationErrorCounter {
311
708
  errorCounts;
312
709
  errorDetails;
@@ -350,7 +747,7 @@ var _ValidationErrorCounter = class _ValidationErrorCounter {
350
747
  validationError.msg.trim(),
351
748
  validationError.type
352
749
  ].join("|");
353
- return (0, import_crypto2.createHash)("md5").update(hashInput).digest("hex");
750
+ return (0, import_crypto4.createHash)("md5").update(hashInput).digest("hex");
354
751
  }
355
752
  };
356
753
  __name(_ValidationErrorCounter, "ValidationErrorCounter");
@@ -379,11 +776,12 @@ var _ApitallyClient = class _ApitallyClient {
379
776
  startupData;
380
777
  startupDataSent = false;
381
778
  requestCounter;
779
+ requestLogger;
382
780
  validationErrorCounter;
383
781
  serverErrorCounter;
384
782
  consumerRegistry;
385
783
  logger;
386
- constructor({ clientId, env = "dev", logger }) {
784
+ constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
387
785
  if (_ApitallyClient.instance) {
388
786
  throw new Error("Apitally client is already initialized");
389
787
  }
@@ -396,9 +794,10 @@ var _ApitallyClient = class _ApitallyClient {
396
794
  _ApitallyClient.instance = this;
397
795
  this.clientId = clientId;
398
796
  this.env = env;
399
- this.instanceUuid = (0, import_crypto3.randomUUID)();
797
+ this.instanceUuid = (0, import_crypto5.randomUUID)();
400
798
  this.syncDataQueue = [];
401
799
  this.requestCounter = new RequestCounter();
800
+ this.requestLogger = new RequestLogger(requestLoggingConfig);
402
801
  this.validationErrorCounter = new ValidationErrorCounter();
403
802
  this.serverErrorCounter = new ServerErrorCounter();
404
803
  this.consumerRegistry = new ConsumerRegistry();
@@ -420,6 +819,8 @@ var _ApitallyClient = class _ApitallyClient {
420
819
  async handleShutdown() {
421
820
  this.stopSync();
422
821
  await this.sendSyncData();
822
+ await this.sendLogData();
823
+ await this.requestLogger.close();
423
824
  _ApitallyClient.instance = void 0;
424
825
  }
425
826
  getHubUrlPrefix() {
@@ -466,7 +867,8 @@ var _ApitallyClient = class _ApitallyClient {
466
867
  async sync() {
467
868
  try {
468
869
  const promises = [
469
- this.sendSyncData()
870
+ this.sendSyncData(),
871
+ this.sendLogData()
470
872
  ];
471
873
  if (!this.startupDataSent) {
472
874
  promises.push(this.sendStartupData());
@@ -494,7 +896,7 @@ var _ApitallyClient = class _ApitallyClient {
494
896
  this.logger.debug("Sending startup data to Apitally Hub");
495
897
  const payload = {
496
898
  instance_uuid: this.instanceUuid,
497
- message_uuid: (0, import_crypto3.randomUUID)(),
899
+ message_uuid: (0, import_crypto5.randomUUID)(),
498
900
  ...this.startupData
499
901
  };
500
902
  try {
@@ -516,7 +918,7 @@ var _ApitallyClient = class _ApitallyClient {
516
918
  const newPayload = {
517
919
  time_offset: 0,
518
920
  instance_uuid: this.instanceUuid,
519
- message_uuid: (0, import_crypto3.randomUUID)(),
921
+ message_uuid: (0, import_crypto5.randomUUID)(),
520
922
  requests: this.requestCounter.getAndResetRequests(),
521
923
  validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
522
924
  server_errors: this.serverErrorCounter.getAndResetServerErrors(),
@@ -535,8 +937,7 @@ var _ApitallyClient = class _ApitallyClient {
535
937
  const timeOffset = Date.now() - time;
536
938
  if (timeOffset <= MAX_QUEUE_TIME) {
537
939
  if (i > 0) {
538
- const waitMs = 100 + Math.random() * 200;
539
- await new Promise((resolve) => setTimeout(resolve, waitMs));
940
+ await this.randomDelay();
540
941
  }
541
942
  payload.time_offset = timeOffset / 1e3;
542
943
  await this.sendData("sync", payload);
@@ -555,6 +956,52 @@ var _ApitallyClient = class _ApitallyClient {
555
956
  }
556
957
  }
557
958
  }
959
+ async sendLogData() {
960
+ this.logger.debug("Sending request log data to Apitally Hub");
961
+ await this.requestLogger.rotateFile();
962
+ const fetchWithRetry = (0, import_fetch_retry.default)(fetch, {
963
+ retries: 3,
964
+ retryDelay: 1e3,
965
+ retryOn: [
966
+ 408,
967
+ 429,
968
+ 500,
969
+ 502,
970
+ 503,
971
+ 504
972
+ ]
973
+ });
974
+ let i = 0;
975
+ let logFile;
976
+ while (logFile = this.requestLogger.getFile()) {
977
+ if (i > 0) {
978
+ await this.randomDelay();
979
+ }
980
+ try {
981
+ const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
982
+ method: "POST",
983
+ body: await logFile.getContent()
984
+ });
985
+ if (response.status === 402 && response.headers.has("Retry-After")) {
986
+ const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
987
+ if (retryAfter > 0) {
988
+ this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
989
+ this.requestLogger.clear();
990
+ return;
991
+ }
992
+ }
993
+ if (!response.ok) {
994
+ throw new HTTPError(response);
995
+ }
996
+ logFile.delete();
997
+ } catch (error) {
998
+ this.requestLogger.retryFileLater(logFile);
999
+ break;
1000
+ }
1001
+ i++;
1002
+ if (i >= 10) break;
1003
+ }
1004
+ }
558
1005
  handleHubError(error) {
559
1006
  if (error instanceof HTTPError) {
560
1007
  if (error.response.status === 404) {
@@ -569,6 +1016,10 @@ var _ApitallyClient = class _ApitallyClient {
569
1016
  }
570
1017
  return false;
571
1018
  }
1019
+ async randomDelay() {
1020
+ const delay = 100 + Math.random() * 200;
1021
+ await new Promise((resolve) => setTimeout(resolve, delay));
1022
+ }
572
1023
  };
573
1024
  __name(_ApitallyClient, "ApitallyClient");
574
1025
  __publicField(_ApitallyClient, "instance");
@@ -624,19 +1075,20 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
624
1075
  }
625
1076
  throw error;
626
1077
  } finally {
1078
+ const responseTime = performance.now() - startTime;
1079
+ const consumer = getConsumer(ctx);
1080
+ client.consumerRegistry.addOrUpdateConsumer(consumer);
627
1081
  if (!path) {
628
1082
  path = getPath(ctx);
629
1083
  }
630
1084
  if (path) {
631
1085
  try {
632
- const consumer = getConsumer(ctx);
633
- client.consumerRegistry.addOrUpdateConsumer(consumer);
634
1086
  client.requestCounter.addRequest({
635
1087
  consumer: consumer == null ? void 0 : consumer.identifier,
636
1088
  method: ctx.request.method,
637
1089
  path,
638
1090
  statusCode: statusCode || ctx.response.status,
639
- responseTime: performance.now() - startTime,
1091
+ responseTime,
640
1092
  requestSize: ctx.request.length,
641
1093
  responseSize: ctx.response.length
642
1094
  });
@@ -647,6 +1099,24 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
647
1099
  });
648
1100
  }
649
1101
  }
1102
+ if (client.requestLogger.enabled) {
1103
+ client.requestLogger.logRequest({
1104
+ timestamp: Date.now() / 1e3,
1105
+ method: ctx.request.method,
1106
+ path,
1107
+ url: ctx.request.href,
1108
+ headers: convertHeaders(ctx.request.headers),
1109
+ size: Number(ctx.request.headers["content-length"]),
1110
+ consumer: consumer == null ? void 0 : consumer.identifier,
1111
+ body: convertBody(ctx.request.body, ctx.request.get("content-type"))
1112
+ }, {
1113
+ statusCode: statusCode || ctx.response.status,
1114
+ responseTime: responseTime / 1e3,
1115
+ headers: convertHeaders(ctx.response.headers),
1116
+ size: Number(ctx.response.headers["content-length"]),
1117
+ body: convertBody(ctx.response.body, ctx.response.get("content-type"))
1118
+ });
1119
+ }
650
1120
  }
651
1121
  };
652
1122
  }, "getMiddleware");