apitally 0.10.0 → 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 +500 -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 +496 -30
  84. package/dist/nestjs/index.js.map +1 -1
  85. package/package.json +8 -2
package/dist/koa/index.js CHANGED
@@ -10,7 +10,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
10
10
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
11
11
 
12
12
  // src/common/client.ts
13
- import { randomUUID } from "crypto";
13
+ import { randomUUID as randomUUID3 } from "crypto";
14
14
  import fetchRetry from "fetch-retry";
15
15
 
16
16
  // src/common/consumerRegistry.ts
@@ -179,6 +179,393 @@ var _RequestCounter = class _RequestCounter {
179
179
  __name(_RequestCounter, "RequestCounter");
180
180
  var RequestCounter = _RequestCounter;
181
181
 
182
+ // src/common/requestLogger.ts
183
+ import { Buffer as Buffer3 } from "buffer";
184
+ import { randomUUID as randomUUID2 } from "crypto";
185
+ import { unlinkSync as unlinkSync2, writeFileSync } from "fs";
186
+ import { tmpdir as tmpdir2 } from "os";
187
+ import { join as join2 } from "path";
188
+
189
+ // src/common/tempGzipFile.ts
190
+ import { Buffer as Buffer2 } from "buffer";
191
+ import { randomUUID } from "crypto";
192
+ import { createWriteStream, readFile, unlinkSync } from "fs";
193
+ import { tmpdir } from "os";
194
+ import { join } from "path";
195
+ import { createGzip } from "zlib";
196
+ var _TempGzipFile = class _TempGzipFile {
197
+ uuid;
198
+ filePath;
199
+ gzip;
200
+ writeStream;
201
+ readyPromise;
202
+ constructor() {
203
+ this.uuid = randomUUID();
204
+ this.filePath = join(tmpdir(), `apitally-${this.uuid}.gz`);
205
+ this.writeStream = createWriteStream(this.filePath);
206
+ this.readyPromise = new Promise((resolve, reject) => {
207
+ this.writeStream.once("ready", resolve);
208
+ this.writeStream.once("error", reject);
209
+ });
210
+ this.gzip = createGzip();
211
+ this.gzip.pipe(this.writeStream);
212
+ }
213
+ get size() {
214
+ return this.writeStream.bytesWritten;
215
+ }
216
+ async writeLine(data) {
217
+ await this.readyPromise;
218
+ return new Promise((resolve, reject) => {
219
+ this.gzip.write(Buffer2.concat([
220
+ data,
221
+ Buffer2.from("\n")
222
+ ]), (error) => {
223
+ if (error) {
224
+ reject(error);
225
+ } else {
226
+ resolve();
227
+ }
228
+ });
229
+ });
230
+ }
231
+ async getContent() {
232
+ return new Promise((resolve, reject) => {
233
+ readFile(this.filePath, (error, data) => {
234
+ if (error) {
235
+ reject(error);
236
+ } else {
237
+ resolve(data);
238
+ }
239
+ });
240
+ });
241
+ }
242
+ async close() {
243
+ return new Promise((resolve) => {
244
+ this.gzip.end(() => {
245
+ resolve();
246
+ });
247
+ });
248
+ }
249
+ async delete() {
250
+ await this.close();
251
+ unlinkSync(this.filePath);
252
+ }
253
+ };
254
+ __name(_TempGzipFile, "TempGzipFile");
255
+ var TempGzipFile = _TempGzipFile;
256
+
257
+ // src/common/requestLogger.ts
258
+ var MAX_BODY_SIZE = 5e4;
259
+ var MAX_FILE_SIZE = 1e6;
260
+ var MAX_FILES = 50;
261
+ var MAX_PENDING_WRITES = 100;
262
+ var BODY_TOO_LARGE = Buffer3.from("<body too large>");
263
+ var BODY_MASKED = Buffer3.from("<masked>");
264
+ var MASKED = "******";
265
+ var ALLOWED_CONTENT_TYPES = [
266
+ "application/json",
267
+ "text/plain"
268
+ ];
269
+ var EXCLUDE_PATH_PATTERNS = [
270
+ /\/_?healthz?$/,
271
+ /\/_?health[_-]?checks?$/,
272
+ /\/_?heart[_-]?beats?$/,
273
+ /\/ping$/,
274
+ /\/ready$/,
275
+ /\/live$/
276
+ ];
277
+ var MASK_QUERY_PARAM_PATTERNS = [
278
+ /auth/i,
279
+ /api-?key/i,
280
+ /secret/i,
281
+ /token/i,
282
+ /password/i,
283
+ /pwd/i
284
+ ];
285
+ var MASK_HEADER_PATTERNS = [
286
+ /auth/i,
287
+ /api-?key/i,
288
+ /secret/i,
289
+ /token/i,
290
+ /cookie/i
291
+ ];
292
+ var DEFAULT_CONFIG = {
293
+ enabled: false,
294
+ logQueryParams: true,
295
+ logRequestHeaders: false,
296
+ logRequestBody: false,
297
+ logResponseHeaders: true,
298
+ logResponseBody: false,
299
+ maskQueryParams: [],
300
+ maskHeaders: [],
301
+ excludePaths: []
302
+ };
303
+ var _RequestLogger = class _RequestLogger {
304
+ config;
305
+ enabled;
306
+ suspendUntil = null;
307
+ pendingWrites = [];
308
+ currentFile = null;
309
+ files = [];
310
+ maintainIntervalId;
311
+ constructor(config) {
312
+ this.config = {
313
+ ...DEFAULT_CONFIG,
314
+ ...config
315
+ };
316
+ this.enabled = this.config.enabled && checkWritableFs();
317
+ if (this.enabled) {
318
+ this.maintainIntervalId = setInterval(() => {
319
+ this.maintain();
320
+ }, 1e3);
321
+ }
322
+ }
323
+ shouldExcludePath(urlPath) {
324
+ const patterns = [
325
+ ...this.config.excludePaths,
326
+ ...EXCLUDE_PATH_PATTERNS
327
+ ];
328
+ return matchPatterns(urlPath, patterns);
329
+ }
330
+ shouldMaskQueryParam(name) {
331
+ const patterns = [
332
+ ...this.config.maskQueryParams,
333
+ ...MASK_QUERY_PARAM_PATTERNS
334
+ ];
335
+ return matchPatterns(name, patterns);
336
+ }
337
+ shouldMaskHeader(name) {
338
+ const patterns = [
339
+ ...this.config.maskHeaders,
340
+ ...MASK_HEADER_PATTERNS
341
+ ];
342
+ return matchPatterns(name, patterns);
343
+ }
344
+ hasSupportedContentType(headers) {
345
+ var _a2;
346
+ const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
347
+ return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
348
+ }
349
+ maskQueryParams(search) {
350
+ const params = new URLSearchParams(search);
351
+ for (const [key] of params) {
352
+ if (this.shouldMaskQueryParam(key)) {
353
+ params.set(key, MASKED);
354
+ }
355
+ }
356
+ return params.toString();
357
+ }
358
+ maskHeaders(headers) {
359
+ return headers.map(([k, v]) => [
360
+ k,
361
+ this.shouldMaskHeader(k) ? MASKED : v
362
+ ]);
363
+ }
364
+ logRequest(request, response) {
365
+ var _a2, _b;
366
+ if (!this.enabled || this.suspendUntil !== null) return;
367
+ const url = new URL(request.url);
368
+ const path = request.path ?? url.pathname;
369
+ if (this.shouldExcludePath(path) || (((_b = (_a2 = this.config).excludeCallback) == null ? void 0 : _b.call(_a2, request, response)) ?? false)) {
370
+ return;
371
+ }
372
+ url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
373
+ request.url = url.toString();
374
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
375
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
376
+ if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
377
+ request.body = void 0;
378
+ } else if (request.body) {
379
+ if (request.body.length > MAX_BODY_SIZE) {
380
+ request.body = BODY_TOO_LARGE;
381
+ } else if (this.config.maskRequestBodyCallback) {
382
+ try {
383
+ request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
384
+ if (request.body.length > MAX_BODY_SIZE) {
385
+ request.body = BODY_TOO_LARGE;
386
+ }
387
+ } catch {
388
+ request.body = void 0;
389
+ }
390
+ }
391
+ }
392
+ if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
393
+ response.body = void 0;
394
+ } else if (response.body) {
395
+ if (response.body.length > MAX_BODY_SIZE) {
396
+ response.body = BODY_TOO_LARGE;
397
+ } else if (this.config.maskResponseBodyCallback) {
398
+ try {
399
+ response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
400
+ if (response.body.length > MAX_BODY_SIZE) {
401
+ response.body = BODY_TOO_LARGE;
402
+ }
403
+ } catch {
404
+ response.body = void 0;
405
+ }
406
+ }
407
+ }
408
+ const item = {
409
+ uuid: randomUUID2(),
410
+ request: skipEmptyValues(request),
411
+ response: skipEmptyValues(response)
412
+ };
413
+ [
414
+ item.request.body,
415
+ item.response.body
416
+ ].forEach((body) => {
417
+ if (body) {
418
+ body.toJSON = function() {
419
+ return this.toString("base64");
420
+ };
421
+ }
422
+ });
423
+ this.pendingWrites.push(JSON.stringify(item));
424
+ if (this.pendingWrites.length > MAX_PENDING_WRITES) {
425
+ this.pendingWrites.shift();
426
+ }
427
+ }
428
+ async writeToFile() {
429
+ if (!this.enabled || this.pendingWrites.length === 0) {
430
+ return;
431
+ }
432
+ if (!this.currentFile) {
433
+ this.currentFile = new TempGzipFile();
434
+ }
435
+ while (this.pendingWrites.length > 0) {
436
+ const item = this.pendingWrites.shift();
437
+ if (item) {
438
+ await this.currentFile.writeLine(Buffer3.from(item));
439
+ }
440
+ }
441
+ }
442
+ getFile() {
443
+ return this.files.shift();
444
+ }
445
+ retryFileLater(file) {
446
+ this.files.unshift(file);
447
+ }
448
+ async rotateFile() {
449
+ if (this.currentFile) {
450
+ await this.currentFile.close();
451
+ this.files.push(this.currentFile);
452
+ this.currentFile = null;
453
+ }
454
+ }
455
+ async maintain() {
456
+ await this.writeToFile();
457
+ if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
458
+ await this.rotateFile();
459
+ }
460
+ while (this.files.length > MAX_FILES) {
461
+ const file = this.files.shift();
462
+ file == null ? void 0 : file.delete();
463
+ }
464
+ if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
465
+ this.suspendUntil = null;
466
+ }
467
+ }
468
+ async clear() {
469
+ this.pendingWrites = [];
470
+ await this.rotateFile();
471
+ this.files.forEach((file) => {
472
+ file.delete();
473
+ });
474
+ this.files = [];
475
+ }
476
+ async close() {
477
+ this.enabled = false;
478
+ await this.clear();
479
+ if (this.maintainIntervalId) {
480
+ clearInterval(this.maintainIntervalId);
481
+ }
482
+ }
483
+ };
484
+ __name(_RequestLogger, "RequestLogger");
485
+ var RequestLogger = _RequestLogger;
486
+ function convertHeaders(headers) {
487
+ if (headers instanceof Headers) {
488
+ return Array.from(headers.entries());
489
+ }
490
+ return Object.entries(headers).flatMap(([key, value]) => {
491
+ if (value === void 0) {
492
+ return [];
493
+ }
494
+ if (Array.isArray(value)) {
495
+ return value.map((v) => [
496
+ key,
497
+ v
498
+ ]);
499
+ }
500
+ return [
501
+ [
502
+ key,
503
+ value.toString()
504
+ ]
505
+ ];
506
+ });
507
+ }
508
+ __name(convertHeaders, "convertHeaders");
509
+ function convertBody(body, contentType) {
510
+ if (!body || !contentType) {
511
+ return;
512
+ }
513
+ try {
514
+ if (contentType.startsWith("application/json")) {
515
+ if (isValidJsonString(body)) {
516
+ return Buffer3.from(body);
517
+ } else {
518
+ return Buffer3.from(JSON.stringify(body));
519
+ }
520
+ }
521
+ if (contentType.startsWith("text/") && typeof body === "string") {
522
+ return Buffer3.from(body);
523
+ }
524
+ } catch (error) {
525
+ return;
526
+ }
527
+ }
528
+ __name(convertBody, "convertBody");
529
+ function isValidJsonString(body) {
530
+ if (typeof body !== "string") {
531
+ return false;
532
+ }
533
+ try {
534
+ JSON.parse(body);
535
+ return true;
536
+ } catch {
537
+ return false;
538
+ }
539
+ }
540
+ __name(isValidJsonString, "isValidJsonString");
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) || Buffer3.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 = join2(tmpdir2(), `apitally-${randomUUID2()}`);
560
+ writeFileSync(testPath, "test");
561
+ unlinkSync2(testPath);
562
+ return true;
563
+ } catch (error) {
564
+ return false;
565
+ }
566
+ }
567
+ __name(checkWritableFs, "checkWritableFs");
568
+
182
569
  // src/common/serverErrorCounter.ts
183
570
  import { createHash } from "crypto";
184
571
  var MAX_MSG_LENGTH = 2048;
@@ -351,11 +738,12 @@ var _ApitallyClient = class _ApitallyClient {
351
738
  startupData;
352
739
  startupDataSent = false;
353
740
  requestCounter;
741
+ requestLogger;
354
742
  validationErrorCounter;
355
743
  serverErrorCounter;
356
744
  consumerRegistry;
357
745
  logger;
358
- constructor({ clientId, env = "dev", logger }) {
746
+ constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
359
747
  if (_ApitallyClient.instance) {
360
748
  throw new Error("Apitally client is already initialized");
361
749
  }
@@ -368,9 +756,10 @@ var _ApitallyClient = class _ApitallyClient {
368
756
  _ApitallyClient.instance = this;
369
757
  this.clientId = clientId;
370
758
  this.env = env;
371
- this.instanceUuid = randomUUID();
759
+ this.instanceUuid = randomUUID3();
372
760
  this.syncDataQueue = [];
373
761
  this.requestCounter = new RequestCounter();
762
+ this.requestLogger = new RequestLogger(requestLoggingConfig);
374
763
  this.validationErrorCounter = new ValidationErrorCounter();
375
764
  this.serverErrorCounter = new ServerErrorCounter();
376
765
  this.consumerRegistry = new ConsumerRegistry();
@@ -392,6 +781,8 @@ var _ApitallyClient = class _ApitallyClient {
392
781
  async handleShutdown() {
393
782
  this.stopSync();
394
783
  await this.sendSyncData();
784
+ await this.sendLogData();
785
+ await this.requestLogger.close();
395
786
  _ApitallyClient.instance = void 0;
396
787
  }
397
788
  getHubUrlPrefix() {
@@ -438,7 +829,8 @@ var _ApitallyClient = class _ApitallyClient {
438
829
  async sync() {
439
830
  try {
440
831
  const promises = [
441
- this.sendSyncData()
832
+ this.sendSyncData(),
833
+ this.sendLogData()
442
834
  ];
443
835
  if (!this.startupDataSent) {
444
836
  promises.push(this.sendStartupData());
@@ -466,7 +858,7 @@ var _ApitallyClient = class _ApitallyClient {
466
858
  this.logger.debug("Sending startup data to Apitally Hub");
467
859
  const payload = {
468
860
  instance_uuid: this.instanceUuid,
469
- message_uuid: randomUUID(),
861
+ message_uuid: randomUUID3(),
470
862
  ...this.startupData
471
863
  };
472
864
  try {
@@ -488,7 +880,7 @@ var _ApitallyClient = class _ApitallyClient {
488
880
  const newPayload = {
489
881
  time_offset: 0,
490
882
  instance_uuid: this.instanceUuid,
491
- message_uuid: randomUUID(),
883
+ message_uuid: randomUUID3(),
492
884
  requests: this.requestCounter.getAndResetRequests(),
493
885
  validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
494
886
  server_errors: this.serverErrorCounter.getAndResetServerErrors(),
@@ -507,8 +899,7 @@ var _ApitallyClient = class _ApitallyClient {
507
899
  const timeOffset = Date.now() - time;
508
900
  if (timeOffset <= MAX_QUEUE_TIME) {
509
901
  if (i > 0) {
510
- const waitMs = 100 + Math.random() * 200;
511
- await new Promise((resolve) => setTimeout(resolve, waitMs));
902
+ await this.randomDelay();
512
903
  }
513
904
  payload.time_offset = timeOffset / 1e3;
514
905
  await this.sendData("sync", payload);
@@ -527,6 +918,52 @@ var _ApitallyClient = class _ApitallyClient {
527
918
  }
528
919
  }
529
920
  }
921
+ async sendLogData() {
922
+ this.logger.debug("Sending request log data to Apitally Hub");
923
+ await this.requestLogger.rotateFile();
924
+ const fetchWithRetry = fetchRetry(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
+ }
530
967
  handleHubError(error) {
531
968
  if (error instanceof HTTPError) {
532
969
  if (error.response.status === 404) {
@@ -541,6 +978,10 @@ var _ApitallyClient = class _ApitallyClient {
541
978
  }
542
979
  return false;
543
980
  }
981
+ async randomDelay() {
982
+ const delay = 100 + Math.random() * 200;
983
+ await new Promise((resolve) => setTimeout(resolve, delay));
984
+ }
544
985
  };
545
986
  __name(_ApitallyClient, "ApitallyClient");
546
987
  __publicField(_ApitallyClient, "instance");
@@ -595,19 +1036,20 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
595
1036
  }
596
1037
  throw error;
597
1038
  } finally {
1039
+ const responseTime = performance.now() - startTime;
1040
+ const consumer = getConsumer(ctx);
1041
+ client.consumerRegistry.addOrUpdateConsumer(consumer);
598
1042
  if (!path) {
599
1043
  path = getPath(ctx);
600
1044
  }
601
1045
  if (path) {
602
1046
  try {
603
- const consumer = getConsumer(ctx);
604
- client.consumerRegistry.addOrUpdateConsumer(consumer);
605
1047
  client.requestCounter.addRequest({
606
1048
  consumer: consumer == null ? void 0 : consumer.identifier,
607
1049
  method: ctx.request.method,
608
1050
  path,
609
1051
  statusCode: statusCode || ctx.response.status,
610
- responseTime: performance.now() - startTime,
1052
+ responseTime,
611
1053
  requestSize: ctx.request.length,
612
1054
  responseSize: ctx.response.length
613
1055
  });
@@ -618,6 +1060,24 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
618
1060
  });
619
1061
  }
620
1062
  }
1063
+ if (client.requestLogger.enabled) {
1064
+ client.requestLogger.logRequest({
1065
+ timestamp: Date.now() / 1e3,
1066
+ method: ctx.request.method,
1067
+ path,
1068
+ url: ctx.request.href,
1069
+ headers: convertHeaders(ctx.request.headers),
1070
+ size: Number(ctx.request.headers["content-length"]),
1071
+ consumer: consumer == null ? void 0 : consumer.identifier,
1072
+ body: convertBody(ctx.request.body, ctx.request.get("content-type"))
1073
+ }, {
1074
+ statusCode: statusCode || ctx.response.status,
1075
+ responseTime: responseTime / 1e3,
1076
+ headers: convertHeaders(ctx.response.headers),
1077
+ size: Number(ctx.response.headers["content-length"]),
1078
+ body: convertBody(ctx.response.body, ctx.response.get("content-type"))
1079
+ });
1080
+ }
621
1081
  }
622
1082
  };
623
1083
  }, "getMiddleware");