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
@@ -13,7 +13,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
13
13
  import fp from "fastify-plugin";
14
14
 
15
15
  // src/common/client.ts
16
- import { randomUUID } from "crypto";
16
+ import { randomUUID as randomUUID3 } from "crypto";
17
17
  import fetchRetry from "fetch-retry";
18
18
 
19
19
  // src/common/consumerRegistry.ts
@@ -182,6 +182,393 @@ var _RequestCounter = class _RequestCounter {
182
182
  __name(_RequestCounter, "RequestCounter");
183
183
  var RequestCounter = _RequestCounter;
184
184
 
185
+ // src/common/requestLogger.ts
186
+ import { Buffer as Buffer3 } from "buffer";
187
+ import { randomUUID as randomUUID2 } from "crypto";
188
+ import { unlinkSync as unlinkSync2, writeFileSync } from "fs";
189
+ import { tmpdir as tmpdir2 } from "os";
190
+ import { join as join2 } from "path";
191
+
192
+ // src/common/tempGzipFile.ts
193
+ import { Buffer as Buffer2 } from "buffer";
194
+ import { randomUUID } from "crypto";
195
+ import { createWriteStream, readFile, unlinkSync } from "fs";
196
+ import { tmpdir } from "os";
197
+ import { join } from "path";
198
+ import { createGzip } from "zlib";
199
+ var _TempGzipFile = class _TempGzipFile {
200
+ uuid;
201
+ filePath;
202
+ gzip;
203
+ writeStream;
204
+ readyPromise;
205
+ constructor() {
206
+ this.uuid = randomUUID();
207
+ this.filePath = join(tmpdir(), `apitally-${this.uuid}.gz`);
208
+ this.writeStream = createWriteStream(this.filePath);
209
+ this.readyPromise = new Promise((resolve, reject) => {
210
+ this.writeStream.once("ready", resolve);
211
+ this.writeStream.once("error", reject);
212
+ });
213
+ this.gzip = createGzip();
214
+ this.gzip.pipe(this.writeStream);
215
+ }
216
+ get size() {
217
+ return this.writeStream.bytesWritten;
218
+ }
219
+ async writeLine(data) {
220
+ await this.readyPromise;
221
+ return new Promise((resolve, reject) => {
222
+ this.gzip.write(Buffer2.concat([
223
+ data,
224
+ Buffer2.from("\n")
225
+ ]), (error) => {
226
+ if (error) {
227
+ reject(error);
228
+ } else {
229
+ resolve();
230
+ }
231
+ });
232
+ });
233
+ }
234
+ async getContent() {
235
+ return new Promise((resolve, reject) => {
236
+ readFile(this.filePath, (error, data) => {
237
+ if (error) {
238
+ reject(error);
239
+ } else {
240
+ resolve(data);
241
+ }
242
+ });
243
+ });
244
+ }
245
+ async close() {
246
+ return new Promise((resolve) => {
247
+ this.gzip.end(() => {
248
+ resolve();
249
+ });
250
+ });
251
+ }
252
+ async delete() {
253
+ await this.close();
254
+ unlinkSync(this.filePath);
255
+ }
256
+ };
257
+ __name(_TempGzipFile, "TempGzipFile");
258
+ var TempGzipFile = _TempGzipFile;
259
+
260
+ // src/common/requestLogger.ts
261
+ var MAX_BODY_SIZE = 5e4;
262
+ var MAX_FILE_SIZE = 1e6;
263
+ var MAX_FILES = 50;
264
+ var MAX_PENDING_WRITES = 100;
265
+ var BODY_TOO_LARGE = Buffer3.from("<body too large>");
266
+ var BODY_MASKED = Buffer3.from("<masked>");
267
+ var MASKED = "******";
268
+ var ALLOWED_CONTENT_TYPES = [
269
+ "application/json",
270
+ "text/plain"
271
+ ];
272
+ var EXCLUDE_PATH_PATTERNS = [
273
+ /\/_?healthz?$/,
274
+ /\/_?health[_-]?checks?$/,
275
+ /\/_?heart[_-]?beats?$/,
276
+ /\/ping$/,
277
+ /\/ready$/,
278
+ /\/live$/
279
+ ];
280
+ var MASK_QUERY_PARAM_PATTERNS = [
281
+ /auth/i,
282
+ /api-?key/i,
283
+ /secret/i,
284
+ /token/i,
285
+ /password/i,
286
+ /pwd/i
287
+ ];
288
+ var MASK_HEADER_PATTERNS = [
289
+ /auth/i,
290
+ /api-?key/i,
291
+ /secret/i,
292
+ /token/i,
293
+ /cookie/i
294
+ ];
295
+ var DEFAULT_CONFIG = {
296
+ enabled: false,
297
+ logQueryParams: true,
298
+ logRequestHeaders: false,
299
+ logRequestBody: false,
300
+ logResponseHeaders: true,
301
+ logResponseBody: false,
302
+ maskQueryParams: [],
303
+ maskHeaders: [],
304
+ excludePaths: []
305
+ };
306
+ var _RequestLogger = class _RequestLogger {
307
+ config;
308
+ enabled;
309
+ suspendUntil = null;
310
+ pendingWrites = [];
311
+ currentFile = null;
312
+ files = [];
313
+ maintainIntervalId;
314
+ constructor(config) {
315
+ this.config = {
316
+ ...DEFAULT_CONFIG,
317
+ ...config
318
+ };
319
+ this.enabled = this.config.enabled && checkWritableFs();
320
+ if (this.enabled) {
321
+ this.maintainIntervalId = setInterval(() => {
322
+ this.maintain();
323
+ }, 1e3);
324
+ }
325
+ }
326
+ shouldExcludePath(urlPath) {
327
+ const patterns = [
328
+ ...this.config.excludePaths,
329
+ ...EXCLUDE_PATH_PATTERNS
330
+ ];
331
+ return matchPatterns(urlPath, patterns);
332
+ }
333
+ shouldMaskQueryParam(name) {
334
+ const patterns = [
335
+ ...this.config.maskQueryParams,
336
+ ...MASK_QUERY_PARAM_PATTERNS
337
+ ];
338
+ return matchPatterns(name, patterns);
339
+ }
340
+ shouldMaskHeader(name) {
341
+ const patterns = [
342
+ ...this.config.maskHeaders,
343
+ ...MASK_HEADER_PATTERNS
344
+ ];
345
+ return matchPatterns(name, patterns);
346
+ }
347
+ hasSupportedContentType(headers) {
348
+ var _a2;
349
+ const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
350
+ return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
351
+ }
352
+ maskQueryParams(search) {
353
+ const params = new URLSearchParams(search);
354
+ for (const [key] of params) {
355
+ if (this.shouldMaskQueryParam(key)) {
356
+ params.set(key, MASKED);
357
+ }
358
+ }
359
+ return params.toString();
360
+ }
361
+ maskHeaders(headers) {
362
+ return headers.map(([k, v]) => [
363
+ k,
364
+ this.shouldMaskHeader(k) ? MASKED : v
365
+ ]);
366
+ }
367
+ logRequest(request, response) {
368
+ var _a2, _b;
369
+ if (!this.enabled || this.suspendUntil !== null) return;
370
+ const url = new URL(request.url);
371
+ const path = request.path ?? url.pathname;
372
+ if (this.shouldExcludePath(path) || (((_b = (_a2 = this.config).excludeCallback) == null ? void 0 : _b.call(_a2, request, response)) ?? false)) {
373
+ return;
374
+ }
375
+ url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
376
+ request.url = url.toString();
377
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
378
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
379
+ if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
380
+ request.body = void 0;
381
+ } else if (request.body) {
382
+ if (request.body.length > MAX_BODY_SIZE) {
383
+ request.body = BODY_TOO_LARGE;
384
+ } else if (this.config.maskRequestBodyCallback) {
385
+ try {
386
+ request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
387
+ if (request.body.length > MAX_BODY_SIZE) {
388
+ request.body = BODY_TOO_LARGE;
389
+ }
390
+ } catch {
391
+ request.body = void 0;
392
+ }
393
+ }
394
+ }
395
+ if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
396
+ response.body = void 0;
397
+ } else if (response.body) {
398
+ if (response.body.length > MAX_BODY_SIZE) {
399
+ response.body = BODY_TOO_LARGE;
400
+ } else if (this.config.maskResponseBodyCallback) {
401
+ try {
402
+ response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
403
+ if (response.body.length > MAX_BODY_SIZE) {
404
+ response.body = BODY_TOO_LARGE;
405
+ }
406
+ } catch {
407
+ response.body = void 0;
408
+ }
409
+ }
410
+ }
411
+ const item = {
412
+ uuid: randomUUID2(),
413
+ request: skipEmptyValues(request),
414
+ response: skipEmptyValues(response)
415
+ };
416
+ [
417
+ item.request.body,
418
+ item.response.body
419
+ ].forEach((body) => {
420
+ if (body) {
421
+ body.toJSON = function() {
422
+ return this.toString("base64");
423
+ };
424
+ }
425
+ });
426
+ this.pendingWrites.push(JSON.stringify(item));
427
+ if (this.pendingWrites.length > MAX_PENDING_WRITES) {
428
+ this.pendingWrites.shift();
429
+ }
430
+ }
431
+ async writeToFile() {
432
+ if (!this.enabled || this.pendingWrites.length === 0) {
433
+ return;
434
+ }
435
+ if (!this.currentFile) {
436
+ this.currentFile = new TempGzipFile();
437
+ }
438
+ while (this.pendingWrites.length > 0) {
439
+ const item = this.pendingWrites.shift();
440
+ if (item) {
441
+ await this.currentFile.writeLine(Buffer3.from(item));
442
+ }
443
+ }
444
+ }
445
+ getFile() {
446
+ return this.files.shift();
447
+ }
448
+ retryFileLater(file) {
449
+ this.files.unshift(file);
450
+ }
451
+ async rotateFile() {
452
+ if (this.currentFile) {
453
+ await this.currentFile.close();
454
+ this.files.push(this.currentFile);
455
+ this.currentFile = null;
456
+ }
457
+ }
458
+ async maintain() {
459
+ await this.writeToFile();
460
+ if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
461
+ await this.rotateFile();
462
+ }
463
+ while (this.files.length > MAX_FILES) {
464
+ const file = this.files.shift();
465
+ file == null ? void 0 : file.delete();
466
+ }
467
+ if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
468
+ this.suspendUntil = null;
469
+ }
470
+ }
471
+ async clear() {
472
+ this.pendingWrites = [];
473
+ await this.rotateFile();
474
+ this.files.forEach((file) => {
475
+ file.delete();
476
+ });
477
+ this.files = [];
478
+ }
479
+ async close() {
480
+ this.enabled = false;
481
+ await this.clear();
482
+ if (this.maintainIntervalId) {
483
+ clearInterval(this.maintainIntervalId);
484
+ }
485
+ }
486
+ };
487
+ __name(_RequestLogger, "RequestLogger");
488
+ var RequestLogger = _RequestLogger;
489
+ function convertHeaders(headers) {
490
+ if (headers instanceof Headers) {
491
+ return Array.from(headers.entries());
492
+ }
493
+ return Object.entries(headers).flatMap(([key, value]) => {
494
+ if (value === void 0) {
495
+ return [];
496
+ }
497
+ if (Array.isArray(value)) {
498
+ return value.map((v) => [
499
+ key,
500
+ v
501
+ ]);
502
+ }
503
+ return [
504
+ [
505
+ key,
506
+ value.toString()
507
+ ]
508
+ ];
509
+ });
510
+ }
511
+ __name(convertHeaders, "convertHeaders");
512
+ function convertBody(body, contentType) {
513
+ if (!body || !contentType) {
514
+ return;
515
+ }
516
+ try {
517
+ if (contentType.startsWith("application/json")) {
518
+ if (isValidJsonString(body)) {
519
+ return Buffer3.from(body);
520
+ } else {
521
+ return Buffer3.from(JSON.stringify(body));
522
+ }
523
+ }
524
+ if (contentType.startsWith("text/") && typeof body === "string") {
525
+ return Buffer3.from(body);
526
+ }
527
+ } catch (error) {
528
+ return;
529
+ }
530
+ }
531
+ __name(convertBody, "convertBody");
532
+ function isValidJsonString(body) {
533
+ if (typeof body !== "string") {
534
+ return false;
535
+ }
536
+ try {
537
+ JSON.parse(body);
538
+ return true;
539
+ } catch {
540
+ return false;
541
+ }
542
+ }
543
+ __name(isValidJsonString, "isValidJsonString");
544
+ function matchPatterns(value, patterns) {
545
+ return patterns.some((pattern) => {
546
+ return pattern.test(value);
547
+ });
548
+ }
549
+ __name(matchPatterns, "matchPatterns");
550
+ function skipEmptyValues(data) {
551
+ return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
552
+ if (v == null || Number.isNaN(v)) return false;
553
+ if (Array.isArray(v) || Buffer3.isBuffer(v) || typeof v === "string") {
554
+ return v.length > 0;
555
+ }
556
+ return true;
557
+ }));
558
+ }
559
+ __name(skipEmptyValues, "skipEmptyValues");
560
+ function checkWritableFs() {
561
+ try {
562
+ const testPath = join2(tmpdir2(), `apitally-${randomUUID2()}`);
563
+ writeFileSync(testPath, "test");
564
+ unlinkSync2(testPath);
565
+ return true;
566
+ } catch (error) {
567
+ return false;
568
+ }
569
+ }
570
+ __name(checkWritableFs, "checkWritableFs");
571
+
185
572
  // src/common/serverErrorCounter.ts
186
573
  import { createHash } from "crypto";
187
574
  var MAX_MSG_LENGTH = 2048;
@@ -354,11 +741,12 @@ var _ApitallyClient = class _ApitallyClient {
354
741
  startupData;
355
742
  startupDataSent = false;
356
743
  requestCounter;
744
+ requestLogger;
357
745
  validationErrorCounter;
358
746
  serverErrorCounter;
359
747
  consumerRegistry;
360
748
  logger;
361
- constructor({ clientId, env = "dev", logger }) {
749
+ constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
362
750
  if (_ApitallyClient.instance) {
363
751
  throw new Error("Apitally client is already initialized");
364
752
  }
@@ -371,9 +759,10 @@ var _ApitallyClient = class _ApitallyClient {
371
759
  _ApitallyClient.instance = this;
372
760
  this.clientId = clientId;
373
761
  this.env = env;
374
- this.instanceUuid = randomUUID();
762
+ this.instanceUuid = randomUUID3();
375
763
  this.syncDataQueue = [];
376
764
  this.requestCounter = new RequestCounter();
765
+ this.requestLogger = new RequestLogger(requestLoggingConfig);
377
766
  this.validationErrorCounter = new ValidationErrorCounter();
378
767
  this.serverErrorCounter = new ServerErrorCounter();
379
768
  this.consumerRegistry = new ConsumerRegistry();
@@ -395,6 +784,8 @@ var _ApitallyClient = class _ApitallyClient {
395
784
  async handleShutdown() {
396
785
  this.stopSync();
397
786
  await this.sendSyncData();
787
+ await this.sendLogData();
788
+ await this.requestLogger.close();
398
789
  _ApitallyClient.instance = void 0;
399
790
  }
400
791
  getHubUrlPrefix() {
@@ -441,7 +832,8 @@ var _ApitallyClient = class _ApitallyClient {
441
832
  async sync() {
442
833
  try {
443
834
  const promises = [
444
- this.sendSyncData()
835
+ this.sendSyncData(),
836
+ this.sendLogData()
445
837
  ];
446
838
  if (!this.startupDataSent) {
447
839
  promises.push(this.sendStartupData());
@@ -469,7 +861,7 @@ var _ApitallyClient = class _ApitallyClient {
469
861
  this.logger.debug("Sending startup data to Apitally Hub");
470
862
  const payload = {
471
863
  instance_uuid: this.instanceUuid,
472
- message_uuid: randomUUID(),
864
+ message_uuid: randomUUID3(),
473
865
  ...this.startupData
474
866
  };
475
867
  try {
@@ -491,7 +883,7 @@ var _ApitallyClient = class _ApitallyClient {
491
883
  const newPayload = {
492
884
  time_offset: 0,
493
885
  instance_uuid: this.instanceUuid,
494
- message_uuid: randomUUID(),
886
+ message_uuid: randomUUID3(),
495
887
  requests: this.requestCounter.getAndResetRequests(),
496
888
  validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
497
889
  server_errors: this.serverErrorCounter.getAndResetServerErrors(),
@@ -510,8 +902,7 @@ var _ApitallyClient = class _ApitallyClient {
510
902
  const timeOffset = Date.now() - time;
511
903
  if (timeOffset <= MAX_QUEUE_TIME) {
512
904
  if (i > 0) {
513
- const waitMs = 100 + Math.random() * 200;
514
- await new Promise((resolve) => setTimeout(resolve, waitMs));
905
+ await this.randomDelay();
515
906
  }
516
907
  payload.time_offset = timeOffset / 1e3;
517
908
  await this.sendData("sync", payload);
@@ -530,6 +921,52 @@ var _ApitallyClient = class _ApitallyClient {
530
921
  }
531
922
  }
532
923
  }
924
+ async sendLogData() {
925
+ this.logger.debug("Sending request log data to Apitally Hub");
926
+ await this.requestLogger.rotateFile();
927
+ const fetchWithRetry = fetchRetry(fetch, {
928
+ retries: 3,
929
+ retryDelay: 1e3,
930
+ retryOn: [
931
+ 408,
932
+ 429,
933
+ 500,
934
+ 502,
935
+ 503,
936
+ 504
937
+ ]
938
+ });
939
+ let i = 0;
940
+ let logFile;
941
+ while (logFile = this.requestLogger.getFile()) {
942
+ if (i > 0) {
943
+ await this.randomDelay();
944
+ }
945
+ try {
946
+ const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
947
+ method: "POST",
948
+ body: await logFile.getContent()
949
+ });
950
+ if (response.status === 402 && response.headers.has("Retry-After")) {
951
+ const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
952
+ if (retryAfter > 0) {
953
+ this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
954
+ this.requestLogger.clear();
955
+ return;
956
+ }
957
+ }
958
+ if (!response.ok) {
959
+ throw new HTTPError(response);
960
+ }
961
+ logFile.delete();
962
+ } catch (error) {
963
+ this.requestLogger.retryFileLater(logFile);
964
+ break;
965
+ }
966
+ i++;
967
+ if (i >= 10) break;
968
+ }
969
+ }
533
970
  handleHubError(error) {
534
971
  if (error instanceof HTTPError) {
535
972
  if (error.response.status === 404) {
@@ -544,6 +981,10 @@ var _ApitallyClient = class _ApitallyClient {
544
981
  }
545
982
  return false;
546
983
  }
984
+ async randomDelay() {
985
+ const delay = 100 + Math.random() * 200;
986
+ await new Promise((resolve) => setTimeout(resolve, delay));
987
+ }
547
988
  };
548
989
  __name(_ApitallyClient, "ApitallyClient");
549
990
  __publicField(_ApitallyClient, "instance");
@@ -596,10 +1037,7 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
596
1037
  await client.handleShutdown();
597
1038
  });
598
1039
  fastify.addHook("onSend", (request, reply, payload, done) => {
599
- try {
600
- reply.payload = JSON.parse(payload);
601
- } catch (error) {
602
- }
1040
+ reply.payload = payload;
603
1041
  done();
604
1042
  });
605
1043
  fastify.addHook("onError", (request, reply, error, done) => {
@@ -609,6 +1047,7 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
609
1047
  done();
610
1048
  });
611
1049
  fastify.addHook("onResponse", (request, reply, done) => {
1050
+ var _a2;
612
1051
  if (request.method.toUpperCase() !== "OPTIONS") {
613
1052
  const consumer = getConsumer(request);
614
1053
  const path = "routeOptions" in request ? request.routeOptions.url : request.routerPath;
@@ -617,26 +1056,33 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
617
1056
  if (Array.isArray(responseSize)) {
618
1057
  responseSize = responseSize[0];
619
1058
  }
1059
+ const responseTime = getResponseTime(reply);
620
1060
  client.consumerRegistry.addOrUpdateConsumer(consumer);
621
1061
  client.requestCounter.addRequest({
622
1062
  consumer: consumer == null ? void 0 : consumer.identifier,
623
1063
  method: request.method,
624
1064
  path,
625
1065
  statusCode: reply.statusCode,
626
- responseTime: getResponseTime(reply),
1066
+ responseTime,
627
1067
  requestSize,
628
1068
  responseSize
629
1069
  });
630
- if ((reply.statusCode === 400 || reply.statusCode === 422) && reply.payload && (!reply.payload.code || reply.payload.code === "FST_ERR_VALIDATION") && typeof reply.payload.message === "string") {
631
- const validationErrors = extractAjvErrors(reply.payload.message);
632
- validationErrors.forEach((error) => {
633
- client.validationErrorCounter.addValidationError({
634
- consumer: consumer == null ? void 0 : consumer.identifier,
635
- method: request.method,
636
- path,
637
- ...error
638
- });
639
- });
1070
+ if ((reply.statusCode === 400 || reply.statusCode === 422) && reply.payload) {
1071
+ try {
1072
+ const parsedPayload = JSON.parse(reply.payload);
1073
+ if ((!parsedPayload.code || parsedPayload.code === "FST_ERR_VALIDATION") && typeof parsedPayload.message === "string") {
1074
+ const validationErrors = extractAjvErrors(parsedPayload.message);
1075
+ validationErrors.forEach((error) => {
1076
+ client.validationErrorCounter.addValidationError({
1077
+ consumer: consumer == null ? void 0 : consumer.identifier,
1078
+ method: request.method,
1079
+ path,
1080
+ ...error
1081
+ });
1082
+ });
1083
+ }
1084
+ } catch (error) {
1085
+ }
640
1086
  }
641
1087
  if (reply.statusCode === 500 && reply.serverError) {
642
1088
  client.serverErrorCounter.addServerError({
@@ -648,6 +1094,24 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
648
1094
  traceback: reply.serverError.stack || ""
649
1095
  });
650
1096
  }
1097
+ if (client.requestLogger.enabled) {
1098
+ client.requestLogger.logRequest({
1099
+ timestamp: Date.now() / 1e3,
1100
+ method: request.method,
1101
+ path,
1102
+ url: `${request.protocol}://${request.host ?? request.hostname}${request.originalUrl ?? request.url}`,
1103
+ headers: convertHeaders(request.headers),
1104
+ size: Number(requestSize),
1105
+ consumer: consumer == null ? void 0 : consumer.identifier,
1106
+ body: convertBody(request.body, request.headers["content-type"])
1107
+ }, {
1108
+ statusCode: reply.statusCode,
1109
+ responseTime: responseTime / 1e3,
1110
+ headers: convertHeaders(reply.getHeaders()),
1111
+ size: Number(responseSize),
1112
+ body: convertBody(reply.payload, (_a2 = reply.getHeader("content-type")) == null ? void 0 : _a2.toString())
1113
+ });
1114
+ }
651
1115
  }
652
1116
  done();
653
1117
  });