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