apitally 0.9.5 → 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.
Files changed (85) hide show
  1. package/README.md +1 -2
  2. package/dist/common/client.cjs +398 -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 +394 -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 +419 -0
  13. package/dist/common/requestLogger.cjs.map +1 -0
  14. package/dist/common/requestLogger.d.cts +63 -0
  15. package/dist/common/requestLogger.d.ts +63 -0
  16. package/dist/common/requestLogger.js +395 -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 +500 -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 +496 -30
  36. package/dist/express/index.js.map +1 -1
  37. package/dist/express/middleware.cjs +500 -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 +496 -30
  42. package/dist/express/middleware.js.map +1 -1
  43. package/dist/fastify/index.cjs +491 -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 +487 -23
  48. package/dist/fastify/index.js.map +1 -1
  49. package/dist/fastify/plugin.cjs +491 -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 +487 -23
  54. package/dist/fastify/plugin.js.map +1 -1
  55. package/dist/hono/index.cjs +460 -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 +456 -11
  60. package/dist/hono/index.js.map +1 -1
  61. package/dist/hono/middleware.cjs +460 -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 +456 -11
  66. package/dist/hono/middleware.js.map +1 -1
  67. package/dist/koa/index.cjs +475 -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 +471 -11
  72. package/dist/koa/index.js.map +1 -1
  73. package/dist/koa/middleware.cjs +475 -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 +471 -11
  78. package/dist/koa/middleware.js.map +1 -1
  79. package/dist/nestjs/index.cjs +536 -41
  80. package/dist/nestjs/index.cjs.map +1 -1
  81. package/dist/nestjs/index.d.cts +10 -2
  82. package/dist/nestjs/index.d.ts +10 -2
  83. package/dist/nestjs/index.js +534 -37
  84. package/dist/nestjs/index.js.map +1 -1
  85. package/package.json +8 -2
@@ -42,7 +42,7 @@ var import_handler = require("hono/utils/handler");
42
42
  var import_perf_hooks = require("perf_hooks");
43
43
 
44
44
  // src/common/client.ts
45
- var import_crypto3 = require("crypto");
45
+ var import_crypto5 = require("crypto");
46
46
  var import_fetch_retry = __toESM(require("fetch-retry"), 1);
47
47
 
48
48
  // src/common/consumerRegistry.ts
@@ -211,8 +211,363 @@ var _RequestCounter = class _RequestCounter {
211
211
  __name(_RequestCounter, "RequestCounter");
212
212
  var RequestCounter = _RequestCounter;
213
213
 
214
- // src/common/serverErrorCounter.ts
214
+ // src/common/requestLogger.ts
215
+ var import_buffer2 = require("buffer");
216
+ var import_crypto2 = require("crypto");
217
+ var import_fs2 = require("fs");
218
+ var import_os2 = require("os");
219
+ var import_path2 = require("path");
220
+
221
+ // src/common/tempGzipFile.ts
222
+ var import_buffer = require("buffer");
215
223
  var import_crypto = require("crypto");
224
+ var import_fs = require("fs");
225
+ var import_os = require("os");
226
+ var import_path = require("path");
227
+ var import_zlib = require("zlib");
228
+ var _TempGzipFile = class _TempGzipFile {
229
+ uuid;
230
+ filePath;
231
+ gzip;
232
+ writeStream;
233
+ readyPromise;
234
+ constructor() {
235
+ this.uuid = (0, import_crypto.randomUUID)();
236
+ this.filePath = (0, import_path.join)((0, import_os.tmpdir)(), `apitally-${this.uuid}.gz`);
237
+ this.writeStream = (0, import_fs.createWriteStream)(this.filePath);
238
+ this.readyPromise = new Promise((resolve, reject) => {
239
+ this.writeStream.once("ready", resolve);
240
+ this.writeStream.once("error", reject);
241
+ });
242
+ this.gzip = (0, import_zlib.createGzip)();
243
+ this.gzip.pipe(this.writeStream);
244
+ }
245
+ get size() {
246
+ return this.writeStream.bytesWritten;
247
+ }
248
+ async writeLine(data) {
249
+ await this.readyPromise;
250
+ return new Promise((resolve, reject) => {
251
+ this.gzip.write(import_buffer.Buffer.concat([
252
+ data,
253
+ import_buffer.Buffer.from("\n")
254
+ ]), (error) => {
255
+ if (error) {
256
+ reject(error);
257
+ } else {
258
+ resolve();
259
+ }
260
+ });
261
+ });
262
+ }
263
+ async getContent() {
264
+ return new Promise((resolve, reject) => {
265
+ (0, import_fs.readFile)(this.filePath, (error, data) => {
266
+ if (error) {
267
+ reject(error);
268
+ } else {
269
+ resolve(data);
270
+ }
271
+ });
272
+ });
273
+ }
274
+ async close() {
275
+ return new Promise((resolve) => {
276
+ this.gzip.end(() => {
277
+ resolve();
278
+ });
279
+ });
280
+ }
281
+ async delete() {
282
+ await this.close();
283
+ (0, import_fs.unlinkSync)(this.filePath);
284
+ }
285
+ };
286
+ __name(_TempGzipFile, "TempGzipFile");
287
+ var TempGzipFile = _TempGzipFile;
288
+
289
+ // src/common/requestLogger.ts
290
+ var MAX_BODY_SIZE = 5e4;
291
+ var MAX_FILE_SIZE = 1e6;
292
+ var MAX_FILES = 50;
293
+ var MAX_PENDING_WRITES = 100;
294
+ var BODY_TOO_LARGE = import_buffer2.Buffer.from("<body too large>");
295
+ var BODY_MASKED = import_buffer2.Buffer.from("<masked>");
296
+ var MASKED = "******";
297
+ var ALLOWED_CONTENT_TYPES = [
298
+ "application/json",
299
+ "text/plain"
300
+ ];
301
+ var EXCLUDE_PATH_PATTERNS = [
302
+ /\/_?healthz?$/,
303
+ /\/_?health[_-]?checks?$/,
304
+ /\/_?heart[_-]?beats?$/,
305
+ /\/ping$/,
306
+ /\/ready$/,
307
+ /\/live$/
308
+ ];
309
+ var MASK_QUERY_PARAM_PATTERNS = [
310
+ /auth/i,
311
+ /api-?key/i,
312
+ /secret/i,
313
+ /token/i,
314
+ /password/i,
315
+ /pwd/i
316
+ ];
317
+ var MASK_HEADER_PATTERNS = [
318
+ /auth/i,
319
+ /api-?key/i,
320
+ /secret/i,
321
+ /token/i,
322
+ /cookie/i
323
+ ];
324
+ var DEFAULT_CONFIG = {
325
+ enabled: false,
326
+ logQueryParams: true,
327
+ logRequestHeaders: false,
328
+ logRequestBody: false,
329
+ logResponseHeaders: true,
330
+ logResponseBody: false,
331
+ maskQueryParams: [],
332
+ maskHeaders: [],
333
+ excludePaths: []
334
+ };
335
+ var _RequestLogger = class _RequestLogger {
336
+ config;
337
+ enabled;
338
+ suspendUntil = null;
339
+ pendingWrites = [];
340
+ currentFile = null;
341
+ files = [];
342
+ maintainIntervalId;
343
+ constructor(config) {
344
+ this.config = {
345
+ ...DEFAULT_CONFIG,
346
+ ...config
347
+ };
348
+ this.enabled = this.config.enabled && checkWritableFs();
349
+ if (this.enabled) {
350
+ this.maintainIntervalId = setInterval(() => {
351
+ this.maintain();
352
+ }, 1e3);
353
+ }
354
+ }
355
+ shouldExcludePath(urlPath) {
356
+ const patterns = [
357
+ ...this.config.excludePaths,
358
+ ...EXCLUDE_PATH_PATTERNS
359
+ ];
360
+ return matchPatterns(urlPath, patterns);
361
+ }
362
+ shouldMaskQueryParam(name) {
363
+ const patterns = [
364
+ ...this.config.maskQueryParams,
365
+ ...MASK_QUERY_PARAM_PATTERNS
366
+ ];
367
+ return matchPatterns(name, patterns);
368
+ }
369
+ shouldMaskHeader(name) {
370
+ const patterns = [
371
+ ...this.config.maskHeaders,
372
+ ...MASK_HEADER_PATTERNS
373
+ ];
374
+ return matchPatterns(name, patterns);
375
+ }
376
+ hasSupportedContentType(headers) {
377
+ var _a2;
378
+ const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
379
+ return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
380
+ }
381
+ maskQueryParams(search) {
382
+ const params = new URLSearchParams(search);
383
+ for (const [key] of params) {
384
+ if (this.shouldMaskQueryParam(key)) {
385
+ params.set(key, MASKED);
386
+ }
387
+ }
388
+ return params.toString();
389
+ }
390
+ maskHeaders(headers) {
391
+ return headers.map(([k, v]) => [
392
+ k,
393
+ this.shouldMaskHeader(k) ? MASKED : v
394
+ ]);
395
+ }
396
+ logRequest(request, response) {
397
+ var _a2, _b;
398
+ if (!this.enabled || this.suspendUntil !== null) return;
399
+ const url = new URL(request.url);
400
+ const path = request.path ?? url.pathname;
401
+ if (this.shouldExcludePath(path) || (((_b = (_a2 = this.config).excludeCallback) == null ? void 0 : _b.call(_a2, request, response)) ?? false)) {
402
+ return;
403
+ }
404
+ url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
405
+ request.url = url.toString();
406
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
407
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
408
+ if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
409
+ request.body = void 0;
410
+ } else if (request.body) {
411
+ if (request.body.length > MAX_BODY_SIZE) {
412
+ request.body = BODY_TOO_LARGE;
413
+ } else if (this.config.maskRequestBodyCallback) {
414
+ try {
415
+ request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
416
+ if (request.body.length > MAX_BODY_SIZE) {
417
+ request.body = BODY_TOO_LARGE;
418
+ }
419
+ } catch {
420
+ request.body = void 0;
421
+ }
422
+ }
423
+ }
424
+ if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
425
+ response.body = void 0;
426
+ } else if (response.body) {
427
+ if (response.body.length > MAX_BODY_SIZE) {
428
+ response.body = BODY_TOO_LARGE;
429
+ } else if (this.config.maskResponseBodyCallback) {
430
+ try {
431
+ response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
432
+ if (response.body.length > MAX_BODY_SIZE) {
433
+ response.body = BODY_TOO_LARGE;
434
+ }
435
+ } catch {
436
+ response.body = void 0;
437
+ }
438
+ }
439
+ }
440
+ const item = {
441
+ uuid: (0, import_crypto2.randomUUID)(),
442
+ request: skipEmptyValues(request),
443
+ response: skipEmptyValues(response)
444
+ };
445
+ [
446
+ item.request.body,
447
+ item.response.body
448
+ ].forEach((body) => {
449
+ if (body) {
450
+ body.toJSON = function() {
451
+ return this.toString("base64");
452
+ };
453
+ }
454
+ });
455
+ this.pendingWrites.push(JSON.stringify(item));
456
+ if (this.pendingWrites.length > MAX_PENDING_WRITES) {
457
+ this.pendingWrites.shift();
458
+ }
459
+ }
460
+ async writeToFile() {
461
+ if (!this.enabled || this.pendingWrites.length === 0) {
462
+ return;
463
+ }
464
+ if (!this.currentFile) {
465
+ this.currentFile = new TempGzipFile();
466
+ }
467
+ while (this.pendingWrites.length > 0) {
468
+ const item = this.pendingWrites.shift();
469
+ if (item) {
470
+ await this.currentFile.writeLine(import_buffer2.Buffer.from(item));
471
+ }
472
+ }
473
+ }
474
+ getFile() {
475
+ return this.files.shift();
476
+ }
477
+ retryFileLater(file) {
478
+ this.files.unshift(file);
479
+ }
480
+ async rotateFile() {
481
+ if (this.currentFile) {
482
+ await this.currentFile.close();
483
+ this.files.push(this.currentFile);
484
+ this.currentFile = null;
485
+ }
486
+ }
487
+ async maintain() {
488
+ await this.writeToFile();
489
+ if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
490
+ await this.rotateFile();
491
+ }
492
+ while (this.files.length > MAX_FILES) {
493
+ const file = this.files.shift();
494
+ file == null ? void 0 : file.delete();
495
+ }
496
+ if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
497
+ this.suspendUntil = null;
498
+ }
499
+ }
500
+ async clear() {
501
+ this.pendingWrites = [];
502
+ await this.rotateFile();
503
+ this.files.forEach((file) => {
504
+ file.delete();
505
+ });
506
+ this.files = [];
507
+ }
508
+ async close() {
509
+ this.enabled = false;
510
+ await this.clear();
511
+ if (this.maintainIntervalId) {
512
+ clearInterval(this.maintainIntervalId);
513
+ }
514
+ }
515
+ };
516
+ __name(_RequestLogger, "RequestLogger");
517
+ var RequestLogger = _RequestLogger;
518
+ function convertHeaders(headers) {
519
+ if (headers instanceof Headers) {
520
+ return Array.from(headers.entries());
521
+ }
522
+ return Object.entries(headers).flatMap(([key, value]) => {
523
+ if (value === void 0) {
524
+ return [];
525
+ }
526
+ if (Array.isArray(value)) {
527
+ return value.map((v) => [
528
+ key,
529
+ v
530
+ ]);
531
+ }
532
+ return [
533
+ [
534
+ key,
535
+ value.toString()
536
+ ]
537
+ ];
538
+ });
539
+ }
540
+ __name(convertHeaders, "convertHeaders");
541
+ function matchPatterns(value, patterns) {
542
+ return patterns.some((pattern) => {
543
+ return pattern.test(value);
544
+ });
545
+ }
546
+ __name(matchPatterns, "matchPatterns");
547
+ function skipEmptyValues(data) {
548
+ return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
549
+ if (v == null || Number.isNaN(v)) return false;
550
+ if (Array.isArray(v) || import_buffer2.Buffer.isBuffer(v) || typeof v === "string") {
551
+ return v.length > 0;
552
+ }
553
+ return true;
554
+ }));
555
+ }
556
+ __name(skipEmptyValues, "skipEmptyValues");
557
+ function checkWritableFs() {
558
+ try {
559
+ const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto2.randomUUID)()}`);
560
+ (0, import_fs2.writeFileSync)(testPath, "test");
561
+ (0, import_fs2.unlinkSync)(testPath);
562
+ return true;
563
+ } catch (error) {
564
+ return false;
565
+ }
566
+ }
567
+ __name(checkWritableFs, "checkWritableFs");
568
+
569
+ // src/common/serverErrorCounter.ts
570
+ var import_crypto3 = require("crypto");
216
571
  var MAX_MSG_LENGTH = 2048;
217
572
  var MAX_STACKTRACE_LENGTH = 65536;
218
573
  var _ServerErrorCounter = class _ServerErrorCounter {
@@ -264,7 +619,7 @@ var _ServerErrorCounter = class _ServerErrorCounter {
264
619
  serverError.msg.trim(),
265
620
  serverError.traceback.trim()
266
621
  ].join("|");
267
- return (0, import_crypto.createHash)("md5").update(hashInput).digest("hex");
622
+ return (0, import_crypto3.createHash)("md5").update(hashInput).digest("hex");
268
623
  }
269
624
  getTruncatedMessage(msg) {
270
625
  msg = msg.trim();
@@ -310,7 +665,7 @@ __name(_ServerErrorCounter, "ServerErrorCounter");
310
665
  var ServerErrorCounter = _ServerErrorCounter;
311
666
 
312
667
  // src/common/validationErrorCounter.ts
313
- var import_crypto2 = require("crypto");
668
+ var import_crypto4 = require("crypto");
314
669
  var _ValidationErrorCounter = class _ValidationErrorCounter {
315
670
  errorCounts;
316
671
  errorDetails;
@@ -354,7 +709,7 @@ var _ValidationErrorCounter = class _ValidationErrorCounter {
354
709
  validationError.msg.trim(),
355
710
  validationError.type
356
711
  ].join("|");
357
- return (0, import_crypto2.createHash)("md5").update(hashInput).digest("hex");
712
+ return (0, import_crypto4.createHash)("md5").update(hashInput).digest("hex");
358
713
  }
359
714
  };
360
715
  __name(_ValidationErrorCounter, "ValidationErrorCounter");
@@ -383,11 +738,12 @@ var _ApitallyClient = class _ApitallyClient {
383
738
  startupData;
384
739
  startupDataSent = false;
385
740
  requestCounter;
741
+ requestLogger;
386
742
  validationErrorCounter;
387
743
  serverErrorCounter;
388
744
  consumerRegistry;
389
745
  logger;
390
- constructor({ clientId, env = "dev", logger }) {
746
+ constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
391
747
  if (_ApitallyClient.instance) {
392
748
  throw new Error("Apitally client is already initialized");
393
749
  }
@@ -400,9 +756,10 @@ var _ApitallyClient = class _ApitallyClient {
400
756
  _ApitallyClient.instance = this;
401
757
  this.clientId = clientId;
402
758
  this.env = env;
403
- this.instanceUuid = (0, import_crypto3.randomUUID)();
759
+ this.instanceUuid = (0, import_crypto5.randomUUID)();
404
760
  this.syncDataQueue = [];
405
761
  this.requestCounter = new RequestCounter();
762
+ this.requestLogger = new RequestLogger(requestLoggingConfig);
406
763
  this.validationErrorCounter = new ValidationErrorCounter();
407
764
  this.serverErrorCounter = new ServerErrorCounter();
408
765
  this.consumerRegistry = new ConsumerRegistry();
@@ -424,6 +781,8 @@ var _ApitallyClient = class _ApitallyClient {
424
781
  async handleShutdown() {
425
782
  this.stopSync();
426
783
  await this.sendSyncData();
784
+ await this.sendLogData();
785
+ await this.requestLogger.close();
427
786
  _ApitallyClient.instance = void 0;
428
787
  }
429
788
  getHubUrlPrefix() {
@@ -470,7 +829,8 @@ var _ApitallyClient = class _ApitallyClient {
470
829
  async sync() {
471
830
  try {
472
831
  const promises = [
473
- this.sendSyncData()
832
+ this.sendSyncData(),
833
+ this.sendLogData()
474
834
  ];
475
835
  if (!this.startupDataSent) {
476
836
  promises.push(this.sendStartupData());
@@ -498,7 +858,7 @@ var _ApitallyClient = class _ApitallyClient {
498
858
  this.logger.debug("Sending startup data to Apitally Hub");
499
859
  const payload = {
500
860
  instance_uuid: this.instanceUuid,
501
- message_uuid: (0, import_crypto3.randomUUID)(),
861
+ message_uuid: (0, import_crypto5.randomUUID)(),
502
862
  ...this.startupData
503
863
  };
504
864
  try {
@@ -520,7 +880,7 @@ var _ApitallyClient = class _ApitallyClient {
520
880
  const newPayload = {
521
881
  time_offset: 0,
522
882
  instance_uuid: this.instanceUuid,
523
- message_uuid: (0, import_crypto3.randomUUID)(),
883
+ message_uuid: (0, import_crypto5.randomUUID)(),
524
884
  requests: this.requestCounter.getAndResetRequests(),
525
885
  validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
526
886
  server_errors: this.serverErrorCounter.getAndResetServerErrors(),
@@ -539,8 +899,7 @@ var _ApitallyClient = class _ApitallyClient {
539
899
  const timeOffset = Date.now() - time;
540
900
  if (timeOffset <= MAX_QUEUE_TIME) {
541
901
  if (i > 0) {
542
- const waitMs = 100 + Math.random() * 200;
543
- await new Promise((resolve) => setTimeout(resolve, waitMs));
902
+ await this.randomDelay();
544
903
  }
545
904
  payload.time_offset = timeOffset / 1e3;
546
905
  await this.sendData("sync", payload);
@@ -559,6 +918,52 @@ var _ApitallyClient = class _ApitallyClient {
559
918
  }
560
919
  }
561
920
  }
921
+ async sendLogData() {
922
+ this.logger.debug("Sending request log data to Apitally Hub");
923
+ await this.requestLogger.rotateFile();
924
+ const fetchWithRetry = (0, import_fetch_retry.default)(fetch, {
925
+ retries: 3,
926
+ retryDelay: 1e3,
927
+ retryOn: [
928
+ 408,
929
+ 429,
930
+ 500,
931
+ 502,
932
+ 503,
933
+ 504
934
+ ]
935
+ });
936
+ let i = 0;
937
+ let logFile;
938
+ while (logFile = this.requestLogger.getFile()) {
939
+ if (i > 0) {
940
+ await this.randomDelay();
941
+ }
942
+ try {
943
+ const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
944
+ method: "POST",
945
+ body: await logFile.getContent()
946
+ });
947
+ if (response.status === 402 && response.headers.has("Retry-After")) {
948
+ const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
949
+ if (retryAfter > 0) {
950
+ this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
951
+ this.requestLogger.clear();
952
+ return;
953
+ }
954
+ }
955
+ if (!response.ok) {
956
+ throw new HTTPError(response);
957
+ }
958
+ logFile.delete();
959
+ } catch (error) {
960
+ this.requestLogger.retryFileLater(logFile);
961
+ break;
962
+ }
963
+ i++;
964
+ if (i >= 10) break;
965
+ }
966
+ }
562
967
  handleHubError(error) {
563
968
  if (error instanceof HTTPError) {
564
969
  if (error.response.status === 404) {
@@ -573,6 +978,10 @@ var _ApitallyClient = class _ApitallyClient {
573
978
  }
574
979
  return false;
575
980
  }
981
+ async randomDelay() {
982
+ const delay = 100 + Math.random() * 200;
983
+ await new Promise((resolve) => setTimeout(resolve, delay));
984
+ }
576
985
  };
577
986
  __name(_ApitallyClient, "ApitallyClient");
578
987
  __publicField(_ApitallyClient, "instance");
@@ -606,13 +1015,13 @@ var useApitally = /* @__PURE__ */ __name((app, config) => {
606
1015
  }, 1e3);
607
1016
  }, "useApitally");
608
1017
  var getMiddleware = /* @__PURE__ */ __name((client) => {
609
- const zodInstalled = getPackageVersion("zod") !== null;
610
1018
  return async (c, next) => {
611
1019
  const startTime = import_perf_hooks.performance.now();
612
1020
  await next();
613
1021
  let response;
614
1022
  const responseTime = import_perf_hooks.performance.now() - startTime;
615
1023
  const [responseSize, newResponse] = await measureResponseSize(c.res);
1024
+ const requestSize = c.req.header("Content-Length");
616
1025
  const consumer = getConsumer(c);
617
1026
  client.consumerRegistry.addOrUpdateConsumer(consumer);
618
1027
  client.requestCounter.addRequest({
@@ -621,11 +1030,11 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
621
1030
  path: c.req.routePath,
622
1031
  statusCode: c.res.status,
623
1032
  responseTime,
624
- requestSize: c.req.header("Content-Length"),
1033
+ requestSize,
625
1034
  responseSize
626
1035
  });
627
1036
  response = newResponse;
628
- if (c.res.status === 400 && zodInstalled) {
1037
+ if (c.res.status === 400) {
629
1038
  const [responseJson, newResponse2] = await getResponseJson(response);
630
1039
  const validationErrors = extractZodErrors(responseJson);
631
1040
  validationErrors.forEach((error) => {
@@ -648,6 +1057,34 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
648
1057
  traceback: c.error.stack || ""
649
1058
  });
650
1059
  }
1060
+ if (client.requestLogger.enabled) {
1061
+ let requestBody;
1062
+ let responseBody;
1063
+ let newResponse2 = response;
1064
+ if (client.requestLogger.config.logRequestBody) {
1065
+ requestBody = Buffer.from(await c.req.arrayBuffer());
1066
+ }
1067
+ if (client.requestLogger.config.logResponseBody) {
1068
+ [responseBody, newResponse2] = await getResponseBody(response);
1069
+ response = newResponse2;
1070
+ }
1071
+ client.requestLogger.logRequest({
1072
+ timestamp: Date.now() / 1e3,
1073
+ method: c.req.method,
1074
+ path: c.req.routePath,
1075
+ url: c.req.url,
1076
+ headers: convertHeaders(c.req.header()),
1077
+ size: Number(requestSize),
1078
+ consumer: consumer == null ? void 0 : consumer.identifier,
1079
+ body: requestBody
1080
+ }, {
1081
+ statusCode: c.res.status,
1082
+ responseTime: responseTime / 1e3,
1083
+ headers: convertHeaders(c.res.headers),
1084
+ size: responseSize,
1085
+ body: responseBody
1086
+ });
1087
+ }
651
1088
  c.res = response;
652
1089
  };
653
1090
  }, "getMiddleware");
@@ -677,6 +1114,14 @@ var measureResponseSize = /* @__PURE__ */ __name(async (response) => {
677
1114
  newResponse1
678
1115
  ];
679
1116
  }, "measureResponseSize");
1117
+ var getResponseBody = /* @__PURE__ */ __name(async (response) => {
1118
+ const [newResponse1, newResponse2] = await teeResponse(response);
1119
+ const responseBuffer = Buffer.from(await newResponse2.arrayBuffer());
1120
+ return [
1121
+ responseBuffer,
1122
+ newResponse1
1123
+ ];
1124
+ }, "getResponseBody");
680
1125
  var getResponseJson = /* @__PURE__ */ __name(async (response) => {
681
1126
  const contentType = response.headers.get("content-type");
682
1127
  if (contentType && contentType.includes("application/json")) {