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
@@ -14,7 +14,7 @@ import { isMiddleware } from "hono/utils/handler";
14
14
  import { performance } from "perf_hooks";
15
15
 
16
16
  // src/common/client.ts
17
- import { randomUUID } from "crypto";
17
+ import { randomUUID as randomUUID3 } from "crypto";
18
18
  import fetchRetry from "fetch-retry";
19
19
 
20
20
  // src/common/consumerRegistry.ts
@@ -183,6 +183,371 @@ var _RequestCounter = class _RequestCounter {
183
183
  __name(_RequestCounter, "RequestCounter");
184
184
  var RequestCounter = _RequestCounter;
185
185
 
186
+ // src/common/requestLogger.ts
187
+ import { Buffer as Buffer3 } from "buffer";
188
+ import { randomUUID as randomUUID2 } from "crypto";
189
+ import { unlinkSync as unlinkSync2, writeFileSync } from "fs";
190
+ import { tmpdir as tmpdir2 } from "os";
191
+ import { join as join2 } from "path";
192
+
193
+ // src/common/tempGzipFile.ts
194
+ import { Buffer as Buffer2 } from "buffer";
195
+ import { randomUUID } from "crypto";
196
+ import { createWriteStream, readFile, unlinkSync } from "fs";
197
+ import { tmpdir } from "os";
198
+ import { join } from "path";
199
+ import { createGzip } from "zlib";
200
+ var _TempGzipFile = class _TempGzipFile {
201
+ uuid;
202
+ filePath;
203
+ gzip;
204
+ writeStream;
205
+ readyPromise;
206
+ constructor() {
207
+ this.uuid = randomUUID();
208
+ this.filePath = join(tmpdir(), `apitally-${this.uuid}.gz`);
209
+ this.writeStream = createWriteStream(this.filePath);
210
+ this.readyPromise = new Promise((resolve, reject) => {
211
+ this.writeStream.once("ready", resolve);
212
+ this.writeStream.once("error", reject);
213
+ });
214
+ this.gzip = createGzip();
215
+ this.gzip.pipe(this.writeStream);
216
+ }
217
+ get size() {
218
+ return this.writeStream.bytesWritten;
219
+ }
220
+ async writeLine(data) {
221
+ await this.readyPromise;
222
+ return new Promise((resolve, reject) => {
223
+ this.gzip.write(Buffer2.concat([
224
+ data,
225
+ Buffer2.from("\n")
226
+ ]), (error) => {
227
+ if (error) {
228
+ reject(error);
229
+ } else {
230
+ resolve();
231
+ }
232
+ });
233
+ });
234
+ }
235
+ async getContent() {
236
+ return new Promise((resolve, reject) => {
237
+ readFile(this.filePath, (error, data) => {
238
+ if (error) {
239
+ reject(error);
240
+ } else {
241
+ resolve(data);
242
+ }
243
+ });
244
+ });
245
+ }
246
+ async close() {
247
+ return new Promise((resolve) => {
248
+ this.gzip.end(() => {
249
+ resolve();
250
+ });
251
+ });
252
+ }
253
+ async delete() {
254
+ await this.close();
255
+ unlinkSync(this.filePath);
256
+ }
257
+ };
258
+ __name(_TempGzipFile, "TempGzipFile");
259
+ var TempGzipFile = _TempGzipFile;
260
+
261
+ // src/common/requestLogger.ts
262
+ var MAX_BODY_SIZE = 5e4;
263
+ var MAX_FILE_SIZE = 1e6;
264
+ var MAX_FILES = 50;
265
+ var MAX_PENDING_WRITES = 100;
266
+ var BODY_TOO_LARGE = Buffer3.from("<body too large>");
267
+ var BODY_MASKED = Buffer3.from("<masked>");
268
+ var MASKED = "******";
269
+ var ALLOWED_CONTENT_TYPES = [
270
+ "application/json",
271
+ "text/plain"
272
+ ];
273
+ var EXCLUDE_PATH_PATTERNS = [
274
+ /\/_?healthz?$/i,
275
+ /\/_?health[_-]?checks?$/i,
276
+ /\/_?heart[_-]?beats?$/i,
277
+ /\/ping$/i,
278
+ /\/ready$/i,
279
+ /\/live$/i
280
+ ];
281
+ var EXCLUDE_USER_AGENT_PATTERNS = [
282
+ /health[-_ ]?check/i,
283
+ /microsoft-azure-application-lb/i,
284
+ /googlehc/i,
285
+ /kube-probe/i
286
+ ];
287
+ var MASK_QUERY_PARAM_PATTERNS = [
288
+ /auth/i,
289
+ /api-?key/i,
290
+ /secret/i,
291
+ /token/i,
292
+ /password/i,
293
+ /pwd/i
294
+ ];
295
+ var MASK_HEADER_PATTERNS = [
296
+ /auth/i,
297
+ /api-?key/i,
298
+ /secret/i,
299
+ /token/i,
300
+ /cookie/i
301
+ ];
302
+ var DEFAULT_CONFIG = {
303
+ enabled: false,
304
+ logQueryParams: true,
305
+ logRequestHeaders: false,
306
+ logRequestBody: false,
307
+ logResponseHeaders: true,
308
+ logResponseBody: false,
309
+ maskQueryParams: [],
310
+ maskHeaders: [],
311
+ excludePaths: []
312
+ };
313
+ var _RequestLogger = class _RequestLogger {
314
+ config;
315
+ enabled;
316
+ suspendUntil = null;
317
+ pendingWrites = [];
318
+ currentFile = null;
319
+ files = [];
320
+ maintainIntervalId;
321
+ constructor(config) {
322
+ this.config = {
323
+ ...DEFAULT_CONFIG,
324
+ ...config
325
+ };
326
+ this.enabled = this.config.enabled && checkWritableFs();
327
+ if (this.enabled) {
328
+ this.maintainIntervalId = setInterval(() => {
329
+ this.maintain();
330
+ }, 1e3);
331
+ }
332
+ }
333
+ shouldExcludePath(urlPath) {
334
+ const patterns = [
335
+ ...this.config.excludePaths,
336
+ ...EXCLUDE_PATH_PATTERNS
337
+ ];
338
+ return matchPatterns(urlPath, patterns);
339
+ }
340
+ shouldExcludeUserAgent(userAgent) {
341
+ return userAgent ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS) : false;
342
+ }
343
+ shouldMaskQueryParam(name) {
344
+ const patterns = [
345
+ ...this.config.maskQueryParams,
346
+ ...MASK_QUERY_PARAM_PATTERNS
347
+ ];
348
+ return matchPatterns(name, patterns);
349
+ }
350
+ shouldMaskHeader(name) {
351
+ const patterns = [
352
+ ...this.config.maskHeaders,
353
+ ...MASK_HEADER_PATTERNS
354
+ ];
355
+ return matchPatterns(name, patterns);
356
+ }
357
+ hasSupportedContentType(headers) {
358
+ var _a2;
359
+ const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
360
+ return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
361
+ }
362
+ maskQueryParams(search) {
363
+ const params = new URLSearchParams(search);
364
+ for (const [key] of params) {
365
+ if (this.shouldMaskQueryParam(key)) {
366
+ params.set(key, MASKED);
367
+ }
368
+ }
369
+ return params.toString();
370
+ }
371
+ maskHeaders(headers) {
372
+ return headers.map(([k, v]) => [
373
+ k,
374
+ this.shouldMaskHeader(k) ? MASKED : v
375
+ ]);
376
+ }
377
+ logRequest(request, response) {
378
+ var _a2, _b, _c;
379
+ if (!this.enabled || this.suspendUntil !== null) return;
380
+ const url = new URL(request.url);
381
+ const path = request.path ?? url.pathname;
382
+ const userAgent = (_a2 = request.headers.find(([k]) => k.toLowerCase() === "user-agent")) == null ? void 0 : _a2[1];
383
+ if (this.shouldExcludePath(path) || this.shouldExcludeUserAgent(userAgent) || (((_c = (_b = this.config).excludeCallback) == null ? void 0 : _c.call(_b, request, response)) ?? false)) {
384
+ return;
385
+ }
386
+ url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
387
+ request.url = url.toString();
388
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
389
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
390
+ if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
391
+ request.body = void 0;
392
+ } else if (request.body) {
393
+ if (request.body.length > MAX_BODY_SIZE) {
394
+ request.body = BODY_TOO_LARGE;
395
+ } else if (this.config.maskRequestBodyCallback) {
396
+ try {
397
+ request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
398
+ if (request.body.length > MAX_BODY_SIZE) {
399
+ request.body = BODY_TOO_LARGE;
400
+ }
401
+ } catch {
402
+ request.body = void 0;
403
+ }
404
+ }
405
+ }
406
+ if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
407
+ response.body = void 0;
408
+ } else if (response.body) {
409
+ if (response.body.length > MAX_BODY_SIZE) {
410
+ response.body = BODY_TOO_LARGE;
411
+ } else if (this.config.maskResponseBodyCallback) {
412
+ try {
413
+ response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
414
+ if (response.body.length > MAX_BODY_SIZE) {
415
+ response.body = BODY_TOO_LARGE;
416
+ }
417
+ } catch {
418
+ response.body = void 0;
419
+ }
420
+ }
421
+ }
422
+ const item = {
423
+ uuid: randomUUID2(),
424
+ request: skipEmptyValues(request),
425
+ response: skipEmptyValues(response)
426
+ };
427
+ [
428
+ item.request.body,
429
+ item.response.body
430
+ ].forEach((body) => {
431
+ if (body) {
432
+ body.toJSON = function() {
433
+ return this.toString("base64");
434
+ };
435
+ }
436
+ });
437
+ this.pendingWrites.push(JSON.stringify(item));
438
+ if (this.pendingWrites.length > MAX_PENDING_WRITES) {
439
+ this.pendingWrites.shift();
440
+ }
441
+ }
442
+ async writeToFile() {
443
+ if (!this.enabled || this.pendingWrites.length === 0) {
444
+ return;
445
+ }
446
+ if (!this.currentFile) {
447
+ this.currentFile = new TempGzipFile();
448
+ }
449
+ while (this.pendingWrites.length > 0) {
450
+ const item = this.pendingWrites.shift();
451
+ if (item) {
452
+ await this.currentFile.writeLine(Buffer3.from(item));
453
+ }
454
+ }
455
+ }
456
+ getFile() {
457
+ return this.files.shift();
458
+ }
459
+ retryFileLater(file) {
460
+ this.files.unshift(file);
461
+ }
462
+ async rotateFile() {
463
+ if (this.currentFile) {
464
+ await this.currentFile.close();
465
+ this.files.push(this.currentFile);
466
+ this.currentFile = null;
467
+ }
468
+ }
469
+ async maintain() {
470
+ await this.writeToFile();
471
+ if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
472
+ await this.rotateFile();
473
+ }
474
+ while (this.files.length > MAX_FILES) {
475
+ const file = this.files.shift();
476
+ file == null ? void 0 : file.delete();
477
+ }
478
+ if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
479
+ this.suspendUntil = null;
480
+ }
481
+ }
482
+ async clear() {
483
+ this.pendingWrites = [];
484
+ await this.rotateFile();
485
+ this.files.forEach((file) => {
486
+ file.delete();
487
+ });
488
+ this.files = [];
489
+ }
490
+ async close() {
491
+ this.enabled = false;
492
+ await this.clear();
493
+ if (this.maintainIntervalId) {
494
+ clearInterval(this.maintainIntervalId);
495
+ }
496
+ }
497
+ };
498
+ __name(_RequestLogger, "RequestLogger");
499
+ var RequestLogger = _RequestLogger;
500
+ function convertHeaders(headers) {
501
+ if (headers instanceof Headers) {
502
+ return Array.from(headers.entries());
503
+ }
504
+ return Object.entries(headers).flatMap(([key, value]) => {
505
+ if (value === void 0) {
506
+ return [];
507
+ }
508
+ if (Array.isArray(value)) {
509
+ return value.map((v) => [
510
+ key,
511
+ v
512
+ ]);
513
+ }
514
+ return [
515
+ [
516
+ key,
517
+ value.toString()
518
+ ]
519
+ ];
520
+ });
521
+ }
522
+ __name(convertHeaders, "convertHeaders");
523
+ function matchPatterns(value, patterns) {
524
+ return patterns.some((pattern) => {
525
+ return pattern.test(value);
526
+ });
527
+ }
528
+ __name(matchPatterns, "matchPatterns");
529
+ function skipEmptyValues(data) {
530
+ return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
531
+ if (v == null || Number.isNaN(v)) return false;
532
+ if (Array.isArray(v) || Buffer3.isBuffer(v) || typeof v === "string") {
533
+ return v.length > 0;
534
+ }
535
+ return true;
536
+ }));
537
+ }
538
+ __name(skipEmptyValues, "skipEmptyValues");
539
+ function checkWritableFs() {
540
+ try {
541
+ const testPath = join2(tmpdir2(), `apitally-${randomUUID2()}`);
542
+ writeFileSync(testPath, "test");
543
+ unlinkSync2(testPath);
544
+ return true;
545
+ } catch (error) {
546
+ return false;
547
+ }
548
+ }
549
+ __name(checkWritableFs, "checkWritableFs");
550
+
186
551
  // src/common/serverErrorCounter.ts
187
552
  import { createHash } from "crypto";
188
553
  var MAX_MSG_LENGTH = 2048;
@@ -355,11 +720,12 @@ var _ApitallyClient = class _ApitallyClient {
355
720
  startupData;
356
721
  startupDataSent = false;
357
722
  requestCounter;
723
+ requestLogger;
358
724
  validationErrorCounter;
359
725
  serverErrorCounter;
360
726
  consumerRegistry;
361
727
  logger;
362
- constructor({ clientId, env = "dev", logger }) {
728
+ constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
363
729
  if (_ApitallyClient.instance) {
364
730
  throw new Error("Apitally client is already initialized");
365
731
  }
@@ -372,9 +738,10 @@ var _ApitallyClient = class _ApitallyClient {
372
738
  _ApitallyClient.instance = this;
373
739
  this.clientId = clientId;
374
740
  this.env = env;
375
- this.instanceUuid = randomUUID();
741
+ this.instanceUuid = randomUUID3();
376
742
  this.syncDataQueue = [];
377
743
  this.requestCounter = new RequestCounter();
744
+ this.requestLogger = new RequestLogger(requestLoggingConfig);
378
745
  this.validationErrorCounter = new ValidationErrorCounter();
379
746
  this.serverErrorCounter = new ServerErrorCounter();
380
747
  this.consumerRegistry = new ConsumerRegistry();
@@ -396,6 +763,8 @@ var _ApitallyClient = class _ApitallyClient {
396
763
  async handleShutdown() {
397
764
  this.stopSync();
398
765
  await this.sendSyncData();
766
+ await this.sendLogData();
767
+ await this.requestLogger.close();
399
768
  _ApitallyClient.instance = void 0;
400
769
  }
401
770
  getHubUrlPrefix() {
@@ -442,7 +811,8 @@ var _ApitallyClient = class _ApitallyClient {
442
811
  async sync() {
443
812
  try {
444
813
  const promises = [
445
- this.sendSyncData()
814
+ this.sendSyncData(),
815
+ this.sendLogData()
446
816
  ];
447
817
  if (!this.startupDataSent) {
448
818
  promises.push(this.sendStartupData());
@@ -470,7 +840,7 @@ var _ApitallyClient = class _ApitallyClient {
470
840
  this.logger.debug("Sending startup data to Apitally Hub");
471
841
  const payload = {
472
842
  instance_uuid: this.instanceUuid,
473
- message_uuid: randomUUID(),
843
+ message_uuid: randomUUID3(),
474
844
  ...this.startupData
475
845
  };
476
846
  try {
@@ -492,7 +862,7 @@ var _ApitallyClient = class _ApitallyClient {
492
862
  const newPayload = {
493
863
  time_offset: 0,
494
864
  instance_uuid: this.instanceUuid,
495
- message_uuid: randomUUID(),
865
+ message_uuid: randomUUID3(),
496
866
  requests: this.requestCounter.getAndResetRequests(),
497
867
  validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
498
868
  server_errors: this.serverErrorCounter.getAndResetServerErrors(),
@@ -511,8 +881,7 @@ var _ApitallyClient = class _ApitallyClient {
511
881
  const timeOffset = Date.now() - time;
512
882
  if (timeOffset <= MAX_QUEUE_TIME) {
513
883
  if (i > 0) {
514
- const waitMs = 100 + Math.random() * 200;
515
- await new Promise((resolve) => setTimeout(resolve, waitMs));
884
+ await this.randomDelay();
516
885
  }
517
886
  payload.time_offset = timeOffset / 1e3;
518
887
  await this.sendData("sync", payload);
@@ -531,6 +900,52 @@ var _ApitallyClient = class _ApitallyClient {
531
900
  }
532
901
  }
533
902
  }
903
+ async sendLogData() {
904
+ this.logger.debug("Sending request log data to Apitally Hub");
905
+ await this.requestLogger.rotateFile();
906
+ const fetchWithRetry = fetchRetry(fetch, {
907
+ retries: 3,
908
+ retryDelay: 1e3,
909
+ retryOn: [
910
+ 408,
911
+ 429,
912
+ 500,
913
+ 502,
914
+ 503,
915
+ 504
916
+ ]
917
+ });
918
+ let i = 0;
919
+ let logFile;
920
+ while (logFile = this.requestLogger.getFile()) {
921
+ if (i > 0) {
922
+ await this.randomDelay();
923
+ }
924
+ try {
925
+ const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
926
+ method: "POST",
927
+ body: await logFile.getContent()
928
+ });
929
+ if (response.status === 402 && response.headers.has("Retry-After")) {
930
+ const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
931
+ if (retryAfter > 0) {
932
+ this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
933
+ this.requestLogger.clear();
934
+ return;
935
+ }
936
+ }
937
+ if (!response.ok) {
938
+ throw new HTTPError(response);
939
+ }
940
+ logFile.delete();
941
+ } catch (error) {
942
+ this.requestLogger.retryFileLater(logFile);
943
+ break;
944
+ }
945
+ i++;
946
+ if (i >= 10) break;
947
+ }
948
+ }
534
949
  handleHubError(error) {
535
950
  if (error instanceof HTTPError) {
536
951
  if (error.response.status === 404) {
@@ -545,6 +960,10 @@ var _ApitallyClient = class _ApitallyClient {
545
960
  }
546
961
  return false;
547
962
  }
963
+ async randomDelay() {
964
+ const delay = 100 + Math.random() * 200;
965
+ await new Promise((resolve) => setTimeout(resolve, delay));
966
+ }
548
967
  };
549
968
  __name(_ApitallyClient, "ApitallyClient");
550
969
  __publicField(_ApitallyClient, "instance");
@@ -577,13 +996,13 @@ var useApitally = /* @__PURE__ */ __name((app, config) => {
577
996
  }, 1e3);
578
997
  }, "useApitally");
579
998
  var getMiddleware = /* @__PURE__ */ __name((client) => {
580
- const zodInstalled = getPackageVersion("zod") !== null;
581
999
  return async (c, next) => {
582
1000
  const startTime = performance.now();
583
1001
  await next();
584
1002
  let response;
585
1003
  const responseTime = performance.now() - startTime;
586
1004
  const [responseSize, newResponse] = await measureResponseSize(c.res);
1005
+ const requestSize = c.req.header("Content-Length");
587
1006
  const consumer = getConsumer(c);
588
1007
  client.consumerRegistry.addOrUpdateConsumer(consumer);
589
1008
  client.requestCounter.addRequest({
@@ -592,11 +1011,11 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
592
1011
  path: c.req.routePath,
593
1012
  statusCode: c.res.status,
594
1013
  responseTime,
595
- requestSize: c.req.header("Content-Length"),
1014
+ requestSize,
596
1015
  responseSize
597
1016
  });
598
1017
  response = newResponse;
599
- if (c.res.status === 400 && zodInstalled) {
1018
+ if (c.res.status === 400) {
600
1019
  const [responseJson, newResponse2] = await getResponseJson(response);
601
1020
  const validationErrors = extractZodErrors(responseJson);
602
1021
  validationErrors.forEach((error) => {
@@ -619,6 +1038,34 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
619
1038
  traceback: c.error.stack || ""
620
1039
  });
621
1040
  }
1041
+ if (client.requestLogger.enabled) {
1042
+ let requestBody;
1043
+ let responseBody;
1044
+ let newResponse2 = response;
1045
+ if (client.requestLogger.config.logRequestBody) {
1046
+ requestBody = Buffer.from(await c.req.arrayBuffer());
1047
+ }
1048
+ if (client.requestLogger.config.logResponseBody) {
1049
+ [responseBody, newResponse2] = await getResponseBody(response);
1050
+ response = newResponse2;
1051
+ }
1052
+ client.requestLogger.logRequest({
1053
+ timestamp: Date.now() / 1e3,
1054
+ method: c.req.method,
1055
+ path: c.req.routePath,
1056
+ url: c.req.url,
1057
+ headers: convertHeaders(c.req.header()),
1058
+ size: Number(requestSize),
1059
+ consumer: consumer == null ? void 0 : consumer.identifier,
1060
+ body: requestBody
1061
+ }, {
1062
+ statusCode: c.res.status,
1063
+ responseTime: responseTime / 1e3,
1064
+ headers: convertHeaders(c.res.headers),
1065
+ size: responseSize,
1066
+ body: responseBody
1067
+ });
1068
+ }
622
1069
  c.res = response;
623
1070
  };
624
1071
  }, "getMiddleware");
@@ -648,6 +1095,14 @@ var measureResponseSize = /* @__PURE__ */ __name(async (response) => {
648
1095
  newResponse1
649
1096
  ];
650
1097
  }, "measureResponseSize");
1098
+ var getResponseBody = /* @__PURE__ */ __name(async (response) => {
1099
+ const [newResponse1, newResponse2] = await teeResponse(response);
1100
+ const responseBuffer = Buffer.from(await newResponse2.arrayBuffer());
1101
+ return [
1102
+ responseBuffer,
1103
+ newResponse1
1104
+ ];
1105
+ }, "getResponseBody");
651
1106
  var getResponseJson = /* @__PURE__ */ __name(async (response) => {
652
1107
  const contentType = response.headers.get("content-type");
653
1108
  if (contentType && contentType.includes("application/json")) {