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
@@ -9,16 +9,20 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
9
9
  });
10
10
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
11
11
 
12
+ // src/nestjs/index.ts
13
+ import { Catch } from "@nestjs/common";
14
+ import { BaseExceptionFilter } from "@nestjs/core";
15
+
12
16
  // src/express/middleware.ts
13
17
  import { performance } from "perf_hooks";
14
18
 
15
19
  // src/common/client.ts
16
- import { randomUUID } from "crypto";
20
+ import { randomUUID as randomUUID3 } from "crypto";
17
21
  import fetchRetry from "fetch-retry";
18
22
 
19
23
  // src/common/consumerRegistry.ts
20
24
  var consumerFromStringOrObject = /* @__PURE__ */ __name((consumer) => {
21
- var _a2, _b;
25
+ var _a3, _b;
22
26
  if (typeof consumer === "string") {
23
27
  consumer = String(consumer).trim().substring(0, 128);
24
28
  return consumer ? {
@@ -26,7 +30,7 @@ var consumerFromStringOrObject = /* @__PURE__ */ __name((consumer) => {
26
30
  } : null;
27
31
  } else {
28
32
  consumer.identifier = String(consumer.identifier).trim().substring(0, 128);
29
- consumer.name = (_a2 = consumer.name) == null ? void 0 : _a2.trim().substring(0, 64);
33
+ consumer.name = (_a3 = consumer.name) == null ? void 0 : _a3.trim().substring(0, 64);
30
34
  consumer.group = (_b = consumer.group) == null ? void 0 : _b.trim().substring(0, 64);
31
35
  return consumer.identifier ? consumer : null;
32
36
  }
@@ -182,6 +186,393 @@ var _RequestCounter = class _RequestCounter {
182
186
  __name(_RequestCounter, "RequestCounter");
183
187
  var RequestCounter = _RequestCounter;
184
188
 
189
+ // src/common/requestLogger.ts
190
+ import { Buffer as Buffer3 } from "buffer";
191
+ import { randomUUID as randomUUID2 } from "crypto";
192
+ import { unlinkSync as unlinkSync2, writeFileSync } from "fs";
193
+ import { tmpdir as tmpdir2 } from "os";
194
+ import { join as join2 } from "path";
195
+
196
+ // src/common/tempGzipFile.ts
197
+ import { Buffer as Buffer2 } from "buffer";
198
+ import { randomUUID } from "crypto";
199
+ import { createWriteStream, readFile, unlinkSync } from "fs";
200
+ import { tmpdir } from "os";
201
+ import { join } from "path";
202
+ import { createGzip } from "zlib";
203
+ var _TempGzipFile = class _TempGzipFile {
204
+ uuid;
205
+ filePath;
206
+ gzip;
207
+ writeStream;
208
+ readyPromise;
209
+ constructor() {
210
+ this.uuid = randomUUID();
211
+ this.filePath = join(tmpdir(), `apitally-${this.uuid}.gz`);
212
+ this.writeStream = createWriteStream(this.filePath);
213
+ this.readyPromise = new Promise((resolve, reject) => {
214
+ this.writeStream.once("ready", resolve);
215
+ this.writeStream.once("error", reject);
216
+ });
217
+ this.gzip = createGzip();
218
+ this.gzip.pipe(this.writeStream);
219
+ }
220
+ get size() {
221
+ return this.writeStream.bytesWritten;
222
+ }
223
+ async writeLine(data) {
224
+ await this.readyPromise;
225
+ return new Promise((resolve, reject) => {
226
+ this.gzip.write(Buffer2.concat([
227
+ data,
228
+ Buffer2.from("\n")
229
+ ]), (error) => {
230
+ if (error) {
231
+ reject(error);
232
+ } else {
233
+ resolve();
234
+ }
235
+ });
236
+ });
237
+ }
238
+ async getContent() {
239
+ return new Promise((resolve, reject) => {
240
+ readFile(this.filePath, (error, data) => {
241
+ if (error) {
242
+ reject(error);
243
+ } else {
244
+ resolve(data);
245
+ }
246
+ });
247
+ });
248
+ }
249
+ async close() {
250
+ return new Promise((resolve) => {
251
+ this.gzip.end(() => {
252
+ resolve();
253
+ });
254
+ });
255
+ }
256
+ async delete() {
257
+ await this.close();
258
+ unlinkSync(this.filePath);
259
+ }
260
+ };
261
+ __name(_TempGzipFile, "TempGzipFile");
262
+ var TempGzipFile = _TempGzipFile;
263
+
264
+ // src/common/requestLogger.ts
265
+ var MAX_BODY_SIZE = 5e4;
266
+ var MAX_FILE_SIZE = 1e6;
267
+ var MAX_FILES = 50;
268
+ var MAX_PENDING_WRITES = 100;
269
+ var BODY_TOO_LARGE = Buffer3.from("<body too large>");
270
+ var BODY_MASKED = Buffer3.from("<masked>");
271
+ var MASKED = "******";
272
+ var ALLOWED_CONTENT_TYPES = [
273
+ "application/json",
274
+ "text/plain"
275
+ ];
276
+ var EXCLUDE_PATH_PATTERNS = [
277
+ /\/_?healthz?$/,
278
+ /\/_?health[_-]?checks?$/,
279
+ /\/_?heart[_-]?beats?$/,
280
+ /\/ping$/,
281
+ /\/ready$/,
282
+ /\/live$/
283
+ ];
284
+ var MASK_QUERY_PARAM_PATTERNS = [
285
+ /auth/i,
286
+ /api-?key/i,
287
+ /secret/i,
288
+ /token/i,
289
+ /password/i,
290
+ /pwd/i
291
+ ];
292
+ var MASK_HEADER_PATTERNS = [
293
+ /auth/i,
294
+ /api-?key/i,
295
+ /secret/i,
296
+ /token/i,
297
+ /cookie/i
298
+ ];
299
+ var DEFAULT_CONFIG = {
300
+ enabled: false,
301
+ logQueryParams: true,
302
+ logRequestHeaders: false,
303
+ logRequestBody: false,
304
+ logResponseHeaders: true,
305
+ logResponseBody: false,
306
+ maskQueryParams: [],
307
+ maskHeaders: [],
308
+ excludePaths: []
309
+ };
310
+ var _RequestLogger = class _RequestLogger {
311
+ config;
312
+ enabled;
313
+ suspendUntil = null;
314
+ pendingWrites = [];
315
+ currentFile = null;
316
+ files = [];
317
+ maintainIntervalId;
318
+ constructor(config) {
319
+ this.config = {
320
+ ...DEFAULT_CONFIG,
321
+ ...config
322
+ };
323
+ this.enabled = this.config.enabled && checkWritableFs();
324
+ if (this.enabled) {
325
+ this.maintainIntervalId = setInterval(() => {
326
+ this.maintain();
327
+ }, 1e3);
328
+ }
329
+ }
330
+ shouldExcludePath(urlPath) {
331
+ const patterns = [
332
+ ...this.config.excludePaths,
333
+ ...EXCLUDE_PATH_PATTERNS
334
+ ];
335
+ return matchPatterns(urlPath, patterns);
336
+ }
337
+ shouldMaskQueryParam(name) {
338
+ const patterns = [
339
+ ...this.config.maskQueryParams,
340
+ ...MASK_QUERY_PARAM_PATTERNS
341
+ ];
342
+ return matchPatterns(name, patterns);
343
+ }
344
+ shouldMaskHeader(name) {
345
+ const patterns = [
346
+ ...this.config.maskHeaders,
347
+ ...MASK_HEADER_PATTERNS
348
+ ];
349
+ return matchPatterns(name, patterns);
350
+ }
351
+ hasSupportedContentType(headers) {
352
+ var _a3;
353
+ const contentType = (_a3 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a3[1];
354
+ return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
355
+ }
356
+ maskQueryParams(search) {
357
+ const params = new URLSearchParams(search);
358
+ for (const [key] of params) {
359
+ if (this.shouldMaskQueryParam(key)) {
360
+ params.set(key, MASKED);
361
+ }
362
+ }
363
+ return params.toString();
364
+ }
365
+ maskHeaders(headers) {
366
+ return headers.map(([k, v]) => [
367
+ k,
368
+ this.shouldMaskHeader(k) ? MASKED : v
369
+ ]);
370
+ }
371
+ logRequest(request, response) {
372
+ var _a3, _b;
373
+ if (!this.enabled || this.suspendUntil !== null) return;
374
+ const url = new URL(request.url);
375
+ const path = request.path ?? url.pathname;
376
+ if (this.shouldExcludePath(path) || (((_b = (_a3 = this.config).excludeCallback) == null ? void 0 : _b.call(_a3, request, response)) ?? false)) {
377
+ return;
378
+ }
379
+ url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
380
+ request.url = url.toString();
381
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
382
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
383
+ if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
384
+ request.body = void 0;
385
+ } else if (request.body) {
386
+ if (request.body.length > MAX_BODY_SIZE) {
387
+ request.body = BODY_TOO_LARGE;
388
+ } else if (this.config.maskRequestBodyCallback) {
389
+ try {
390
+ request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
391
+ if (request.body.length > MAX_BODY_SIZE) {
392
+ request.body = BODY_TOO_LARGE;
393
+ }
394
+ } catch {
395
+ request.body = void 0;
396
+ }
397
+ }
398
+ }
399
+ if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
400
+ response.body = void 0;
401
+ } else if (response.body) {
402
+ if (response.body.length > MAX_BODY_SIZE) {
403
+ response.body = BODY_TOO_LARGE;
404
+ } else if (this.config.maskResponseBodyCallback) {
405
+ try {
406
+ response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
407
+ if (response.body.length > MAX_BODY_SIZE) {
408
+ response.body = BODY_TOO_LARGE;
409
+ }
410
+ } catch {
411
+ response.body = void 0;
412
+ }
413
+ }
414
+ }
415
+ const item = {
416
+ uuid: randomUUID2(),
417
+ request: skipEmptyValues(request),
418
+ response: skipEmptyValues(response)
419
+ };
420
+ [
421
+ item.request.body,
422
+ item.response.body
423
+ ].forEach((body) => {
424
+ if (body) {
425
+ body.toJSON = function() {
426
+ return this.toString("base64");
427
+ };
428
+ }
429
+ });
430
+ this.pendingWrites.push(JSON.stringify(item));
431
+ if (this.pendingWrites.length > MAX_PENDING_WRITES) {
432
+ this.pendingWrites.shift();
433
+ }
434
+ }
435
+ async writeToFile() {
436
+ if (!this.enabled || this.pendingWrites.length === 0) {
437
+ return;
438
+ }
439
+ if (!this.currentFile) {
440
+ this.currentFile = new TempGzipFile();
441
+ }
442
+ while (this.pendingWrites.length > 0) {
443
+ const item = this.pendingWrites.shift();
444
+ if (item) {
445
+ await this.currentFile.writeLine(Buffer3.from(item));
446
+ }
447
+ }
448
+ }
449
+ getFile() {
450
+ return this.files.shift();
451
+ }
452
+ retryFileLater(file) {
453
+ this.files.unshift(file);
454
+ }
455
+ async rotateFile() {
456
+ if (this.currentFile) {
457
+ await this.currentFile.close();
458
+ this.files.push(this.currentFile);
459
+ this.currentFile = null;
460
+ }
461
+ }
462
+ async maintain() {
463
+ await this.writeToFile();
464
+ if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
465
+ await this.rotateFile();
466
+ }
467
+ while (this.files.length > MAX_FILES) {
468
+ const file = this.files.shift();
469
+ file == null ? void 0 : file.delete();
470
+ }
471
+ if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
472
+ this.suspendUntil = null;
473
+ }
474
+ }
475
+ async clear() {
476
+ this.pendingWrites = [];
477
+ await this.rotateFile();
478
+ this.files.forEach((file) => {
479
+ file.delete();
480
+ });
481
+ this.files = [];
482
+ }
483
+ async close() {
484
+ this.enabled = false;
485
+ await this.clear();
486
+ if (this.maintainIntervalId) {
487
+ clearInterval(this.maintainIntervalId);
488
+ }
489
+ }
490
+ };
491
+ __name(_RequestLogger, "RequestLogger");
492
+ var RequestLogger = _RequestLogger;
493
+ function convertHeaders(headers) {
494
+ if (headers instanceof Headers) {
495
+ return Array.from(headers.entries());
496
+ }
497
+ return Object.entries(headers).flatMap(([key, value]) => {
498
+ if (value === void 0) {
499
+ return [];
500
+ }
501
+ if (Array.isArray(value)) {
502
+ return value.map((v) => [
503
+ key,
504
+ v
505
+ ]);
506
+ }
507
+ return [
508
+ [
509
+ key,
510
+ value.toString()
511
+ ]
512
+ ];
513
+ });
514
+ }
515
+ __name(convertHeaders, "convertHeaders");
516
+ function convertBody(body, contentType) {
517
+ if (!body || !contentType) {
518
+ return;
519
+ }
520
+ try {
521
+ if (contentType.startsWith("application/json")) {
522
+ if (isValidJsonString(body)) {
523
+ return Buffer3.from(body);
524
+ } else {
525
+ return Buffer3.from(JSON.stringify(body));
526
+ }
527
+ }
528
+ if (contentType.startsWith("text/") && typeof body === "string") {
529
+ return Buffer3.from(body);
530
+ }
531
+ } catch (error) {
532
+ return;
533
+ }
534
+ }
535
+ __name(convertBody, "convertBody");
536
+ function isValidJsonString(body) {
537
+ if (typeof body !== "string") {
538
+ return false;
539
+ }
540
+ try {
541
+ JSON.parse(body);
542
+ return true;
543
+ } catch {
544
+ return false;
545
+ }
546
+ }
547
+ __name(isValidJsonString, "isValidJsonString");
548
+ function matchPatterns(value, patterns) {
549
+ return patterns.some((pattern) => {
550
+ return pattern.test(value);
551
+ });
552
+ }
553
+ __name(matchPatterns, "matchPatterns");
554
+ function skipEmptyValues(data) {
555
+ return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
556
+ if (v == null || Number.isNaN(v)) return false;
557
+ if (Array.isArray(v) || Buffer3.isBuffer(v) || typeof v === "string") {
558
+ return v.length > 0;
559
+ }
560
+ return true;
561
+ }));
562
+ }
563
+ __name(skipEmptyValues, "skipEmptyValues");
564
+ function checkWritableFs() {
565
+ try {
566
+ const testPath = join2(tmpdir2(), `apitally-${randomUUID2()}`);
567
+ writeFileSync(testPath, "test");
568
+ unlinkSync2(testPath);
569
+ return true;
570
+ } catch (error) {
571
+ return false;
572
+ }
573
+ }
574
+ __name(checkWritableFs, "checkWritableFs");
575
+
185
576
  // src/common/serverErrorCounter.ts
186
577
  import { createHash } from "crypto";
187
578
  var MAX_MSG_LENGTH = 2048;
@@ -354,11 +745,12 @@ var _ApitallyClient = class _ApitallyClient {
354
745
  startupData;
355
746
  startupDataSent = false;
356
747
  requestCounter;
748
+ requestLogger;
357
749
  validationErrorCounter;
358
750
  serverErrorCounter;
359
751
  consumerRegistry;
360
752
  logger;
361
- constructor({ clientId, env = "dev", logger }) {
753
+ constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
362
754
  if (_ApitallyClient.instance) {
363
755
  throw new Error("Apitally client is already initialized");
364
756
  }
@@ -371,9 +763,10 @@ var _ApitallyClient = class _ApitallyClient {
371
763
  _ApitallyClient.instance = this;
372
764
  this.clientId = clientId;
373
765
  this.env = env;
374
- this.instanceUuid = randomUUID();
766
+ this.instanceUuid = randomUUID3();
375
767
  this.syncDataQueue = [];
376
768
  this.requestCounter = new RequestCounter();
769
+ this.requestLogger = new RequestLogger(requestLoggingConfig);
377
770
  this.validationErrorCounter = new ValidationErrorCounter();
378
771
  this.serverErrorCounter = new ServerErrorCounter();
379
772
  this.consumerRegistry = new ConsumerRegistry();
@@ -395,6 +788,8 @@ var _ApitallyClient = class _ApitallyClient {
395
788
  async handleShutdown() {
396
789
  this.stopSync();
397
790
  await this.sendSyncData();
791
+ await this.sendLogData();
792
+ await this.requestLogger.close();
398
793
  _ApitallyClient.instance = void 0;
399
794
  }
400
795
  getHubUrlPrefix() {
@@ -441,7 +836,8 @@ var _ApitallyClient = class _ApitallyClient {
441
836
  async sync() {
442
837
  try {
443
838
  const promises = [
444
- this.sendSyncData()
839
+ this.sendSyncData(),
840
+ this.sendLogData()
445
841
  ];
446
842
  if (!this.startupDataSent) {
447
843
  promises.push(this.sendStartupData());
@@ -469,7 +865,7 @@ var _ApitallyClient = class _ApitallyClient {
469
865
  this.logger.debug("Sending startup data to Apitally Hub");
470
866
  const payload = {
471
867
  instance_uuid: this.instanceUuid,
472
- message_uuid: randomUUID(),
868
+ message_uuid: randomUUID3(),
473
869
  ...this.startupData
474
870
  };
475
871
  try {
@@ -491,7 +887,7 @@ var _ApitallyClient = class _ApitallyClient {
491
887
  const newPayload = {
492
888
  time_offset: 0,
493
889
  instance_uuid: this.instanceUuid,
494
- message_uuid: randomUUID(),
890
+ message_uuid: randomUUID3(),
495
891
  requests: this.requestCounter.getAndResetRequests(),
496
892
  validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
497
893
  server_errors: this.serverErrorCounter.getAndResetServerErrors(),
@@ -510,8 +906,7 @@ var _ApitallyClient = class _ApitallyClient {
510
906
  const timeOffset = Date.now() - time;
511
907
  if (timeOffset <= MAX_QUEUE_TIME) {
512
908
  if (i > 0) {
513
- const waitMs = 100 + Math.random() * 200;
514
- await new Promise((resolve) => setTimeout(resolve, waitMs));
909
+ await this.randomDelay();
515
910
  }
516
911
  payload.time_offset = timeOffset / 1e3;
517
912
  await this.sendData("sync", payload);
@@ -530,6 +925,52 @@ var _ApitallyClient = class _ApitallyClient {
530
925
  }
531
926
  }
532
927
  }
928
+ async sendLogData() {
929
+ this.logger.debug("Sending request log data to Apitally Hub");
930
+ await this.requestLogger.rotateFile();
931
+ const fetchWithRetry = fetchRetry(fetch, {
932
+ retries: 3,
933
+ retryDelay: 1e3,
934
+ retryOn: [
935
+ 408,
936
+ 429,
937
+ 500,
938
+ 502,
939
+ 503,
940
+ 504
941
+ ]
942
+ });
943
+ let i = 0;
944
+ let logFile;
945
+ while (logFile = this.requestLogger.getFile()) {
946
+ if (i > 0) {
947
+ await this.randomDelay();
948
+ }
949
+ try {
950
+ const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
951
+ method: "POST",
952
+ body: await logFile.getContent()
953
+ });
954
+ if (response.status === 402 && response.headers.has("Retry-After")) {
955
+ const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
956
+ if (retryAfter > 0) {
957
+ this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
958
+ this.requestLogger.clear();
959
+ return;
960
+ }
961
+ }
962
+ if (!response.ok) {
963
+ throw new HTTPError(response);
964
+ }
965
+ logFile.delete();
966
+ } catch (error) {
967
+ this.requestLogger.retryFileLater(logFile);
968
+ break;
969
+ }
970
+ i++;
971
+ if (i >= 10) break;
972
+ }
973
+ }
533
974
  handleHubError(error) {
534
975
  if (error instanceof HTTPError) {
535
976
  if (error.response.status === 404) {
@@ -544,6 +985,10 @@ var _ApitallyClient = class _ApitallyClient {
544
985
  }
545
986
  return false;
546
987
  }
988
+ async randomDelay() {
989
+ const delay = 100 + Math.random() * 200;
990
+ await new Promise((resolve) => setTimeout(resolve, delay));
991
+ }
547
992
  };
548
993
  __name(_ApitallyClient, "ApitallyClient");
549
994
  __publicField(_ApitallyClient, "instance");
@@ -713,18 +1158,18 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
713
1158
  }
714
1159
  try {
715
1160
  const startTime = performance.now();
716
- const originalJson = res.json;
717
- res.json = (body) => {
1161
+ const originalSend = res.send;
1162
+ res.send = (body) => {
718
1163
  res.locals.body = body;
719
- return originalJson.call(res, body);
1164
+ return originalSend.call(res, body);
720
1165
  };
721
1166
  res.on("finish", () => {
722
1167
  try {
1168
+ const responseTime = performance.now() - startTime;
723
1169
  const path = getRoutePath(req);
1170
+ const consumer = getConsumer(req);
1171
+ client.consumerRegistry.addOrUpdateConsumer(consumer);
724
1172
  if (path) {
725
- const responseTime = performance.now() - startTime;
726
- const consumer = getConsumer(req);
727
- client.consumerRegistry.addOrUpdateConsumer(consumer);
728
1173
  client.requestCounter.addRequest({
729
1174
  consumer: consumer == null ? void 0 : consumer.identifier,
730
1175
  method: req.method,
@@ -735,24 +1180,31 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
735
1180
  responseSize: res.get("content-length")
736
1181
  });
737
1182
  if ((res.statusCode === 400 || res.statusCode === 422) && res.locals.body) {
738
- const validationErrors = [];
739
- if (validationErrors.length === 0) {
740
- validationErrors.push(...extractExpressValidatorErrors(res.locals.body));
741
- }
742
- if (validationErrors.length === 0) {
743
- validationErrors.push(...extractCelebrateErrors(res.locals.body));
1183
+ let jsonBody;
1184
+ try {
1185
+ jsonBody = JSON.parse(res.locals.body);
1186
+ } catch {
744
1187
  }
745
- if (validationErrors.length === 0) {
746
- validationErrors.push(...extractNestValidationErrors(res.locals.body));
747
- }
748
- validationErrors.forEach((error) => {
749
- client.validationErrorCounter.addValidationError({
750
- consumer: consumer == null ? void 0 : consumer.identifier,
751
- method: req.method,
752
- path: req.route.path,
753
- ...error
1188
+ if (jsonBody) {
1189
+ const validationErrors = [];
1190
+ if (validationErrors.length === 0) {
1191
+ validationErrors.push(...extractExpressValidatorErrors(jsonBody));
1192
+ }
1193
+ if (validationErrors.length === 0) {
1194
+ validationErrors.push(...extractCelebrateErrors(jsonBody));
1195
+ }
1196
+ if (validationErrors.length === 0) {
1197
+ validationErrors.push(...extractNestValidationErrors(jsonBody));
1198
+ }
1199
+ validationErrors.forEach((error) => {
1200
+ client.validationErrorCounter.addValidationError({
1201
+ consumer: consumer == null ? void 0 : consumer.identifier,
1202
+ method: req.method,
1203
+ path: req.route.path,
1204
+ ...error
1205
+ });
754
1206
  });
755
- });
1207
+ }
756
1208
  }
757
1209
  if (res.statusCode === 500 && res.locals.serverError) {
758
1210
  const serverError = res.locals.serverError;
@@ -766,6 +1218,24 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
766
1218
  });
767
1219
  }
768
1220
  }
1221
+ if (client.requestLogger.enabled) {
1222
+ client.requestLogger.logRequest({
1223
+ timestamp: Date.now() / 1e3,
1224
+ method: req.method,
1225
+ path,
1226
+ url: `${req.protocol}://${req.host}${req.originalUrl}`,
1227
+ headers: convertHeaders(req.headers),
1228
+ size: Number(req.get("content-length")),
1229
+ consumer: consumer == null ? void 0 : consumer.identifier,
1230
+ body: convertBody(req.body, req.get("content-type"))
1231
+ }, {
1232
+ statusCode: res.statusCode,
1233
+ responseTime: responseTime / 1e3,
1234
+ headers: convertHeaders(res.getHeaders()),
1235
+ size: Number(res.get("content-length")),
1236
+ body: convertBody(res.locals.body, res.get("content-type"))
1237
+ });
1238
+ }
769
1239
  } catch (error) {
770
1240
  client.logger.error("Error while logging request in Apitally middleware.", {
771
1241
  request: req,
@@ -796,12 +1266,12 @@ var getRoutePath = /* @__PURE__ */ __name((req) => {
796
1266
  return req.route.path;
797
1267
  }, "getRoutePath");
798
1268
  var getRouterPath = /* @__PURE__ */ __name((stack, baseUrl) => {
799
- var _a2;
1269
+ var _a3;
800
1270
  const routerPaths = [];
801
1271
  while (stack && stack.length > 0) {
802
1272
  const routerLayer = stack.find((layer) => {
803
- var _a3;
804
- return layer.name === "router" && layer.path && ((_a3 = layer.regexp) == null ? void 0 : _a3.test(baseUrl));
1273
+ var _a4;
1274
+ return layer.name === "router" && layer.path && ((_a4 = layer.regexp) == null ? void 0 : _a4.test(baseUrl));
805
1275
  });
806
1276
  if (routerLayer) {
807
1277
  if (routerLayer.keys.length > 0) {
@@ -810,7 +1280,7 @@ var getRouterPath = /* @__PURE__ */ __name((stack, baseUrl) => {
810
1280
  } else {
811
1281
  routerPaths.push(routerLayer.path);
812
1282
  }
813
- stack = (_a2 = routerLayer.handle) == null ? void 0 : _a2.stack;
1283
+ stack = (_a3 = routerLayer.handle) == null ? void 0 : _a3.stack;
814
1284
  baseUrl = baseUrl.slice(routerLayer.path.length);
815
1285
  } else {
816
1286
  break;
@@ -928,7 +1398,34 @@ var getAppInfo = /* @__PURE__ */ __name((app, basePath, appVersion) => {
928
1398
  client: "js:express"
929
1399
  };
930
1400
  }, "getAppInfo");
1401
+
1402
+ // src/nestjs/index.ts
1403
+ function _ts_decorate(decorators, target, key, desc) {
1404
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1405
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1406
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1407
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1408
+ }
1409
+ __name(_ts_decorate, "_ts_decorate");
1410
+ var useApitally2 = /* @__PURE__ */ __name((app, config) => {
1411
+ const httpAdapter = app.getHttpAdapter();
1412
+ const expressInstance = httpAdapter.getInstance();
1413
+ useApitally(expressInstance, config);
1414
+ app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));
1415
+ }, "useApitally");
1416
+ var _a2;
1417
+ var AllExceptionsFilter = (_a2 = class extends BaseExceptionFilter {
1418
+ catch(exception, host) {
1419
+ const ctx = host.switchToHttp();
1420
+ const res = ctx.getResponse();
1421
+ res.locals.serverError = exception;
1422
+ super.catch(exception, host);
1423
+ }
1424
+ }, __name(_a2, "AllExceptionsFilter"), _a2);
1425
+ AllExceptionsFilter = _ts_decorate([
1426
+ Catch()
1427
+ ], AllExceptionsFilter);
931
1428
  export {
932
- useApitally
1429
+ useApitally2 as useApitally
933
1430
  };
934
1431
  //# sourceMappingURL=index.js.map