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
@@ -40,7 +40,7 @@ var import_handler = require("hono/utils/handler");
40
40
  var import_perf_hooks = require("perf_hooks");
41
41
 
42
42
  // src/common/client.ts
43
- var import_crypto3 = require("crypto");
43
+ var import_crypto5 = require("crypto");
44
44
  var import_fetch_retry = __toESM(require("fetch-retry"), 1);
45
45
 
46
46
  // src/common/consumerRegistry.ts
@@ -209,8 +209,373 @@ var _RequestCounter = class _RequestCounter {
209
209
  __name(_RequestCounter, "RequestCounter");
210
210
  var RequestCounter = _RequestCounter;
211
211
 
212
- // src/common/serverErrorCounter.ts
212
+ // src/common/requestLogger.ts
213
+ var import_buffer2 = require("buffer");
214
+ var import_crypto2 = require("crypto");
215
+ var import_fs2 = require("fs");
216
+ var import_os2 = require("os");
217
+ var import_path2 = require("path");
218
+
219
+ // src/common/tempGzipFile.ts
220
+ var import_buffer = require("buffer");
213
221
  var import_crypto = require("crypto");
222
+ var import_fs = require("fs");
223
+ var import_os = require("os");
224
+ var import_path = require("path");
225
+ var import_zlib = require("zlib");
226
+ var _TempGzipFile = class _TempGzipFile {
227
+ uuid;
228
+ filePath;
229
+ gzip;
230
+ writeStream;
231
+ readyPromise;
232
+ constructor() {
233
+ this.uuid = (0, import_crypto.randomUUID)();
234
+ this.filePath = (0, import_path.join)((0, import_os.tmpdir)(), `apitally-${this.uuid}.gz`);
235
+ this.writeStream = (0, import_fs.createWriteStream)(this.filePath);
236
+ this.readyPromise = new Promise((resolve, reject) => {
237
+ this.writeStream.once("ready", resolve);
238
+ this.writeStream.once("error", reject);
239
+ });
240
+ this.gzip = (0, import_zlib.createGzip)();
241
+ this.gzip.pipe(this.writeStream);
242
+ }
243
+ get size() {
244
+ return this.writeStream.bytesWritten;
245
+ }
246
+ async writeLine(data) {
247
+ await this.readyPromise;
248
+ return new Promise((resolve, reject) => {
249
+ this.gzip.write(import_buffer.Buffer.concat([
250
+ data,
251
+ import_buffer.Buffer.from("\n")
252
+ ]), (error) => {
253
+ if (error) {
254
+ reject(error);
255
+ } else {
256
+ resolve();
257
+ }
258
+ });
259
+ });
260
+ }
261
+ async getContent() {
262
+ return new Promise((resolve, reject) => {
263
+ (0, import_fs.readFile)(this.filePath, (error, data) => {
264
+ if (error) {
265
+ reject(error);
266
+ } else {
267
+ resolve(data);
268
+ }
269
+ });
270
+ });
271
+ }
272
+ async close() {
273
+ return new Promise((resolve) => {
274
+ this.gzip.end(() => {
275
+ resolve();
276
+ });
277
+ });
278
+ }
279
+ async delete() {
280
+ await this.close();
281
+ (0, import_fs.unlinkSync)(this.filePath);
282
+ }
283
+ };
284
+ __name(_TempGzipFile, "TempGzipFile");
285
+ var TempGzipFile = _TempGzipFile;
286
+
287
+ // src/common/requestLogger.ts
288
+ var MAX_BODY_SIZE = 5e4;
289
+ var MAX_FILE_SIZE = 1e6;
290
+ var MAX_FILES = 50;
291
+ var MAX_PENDING_WRITES = 100;
292
+ var BODY_TOO_LARGE = import_buffer2.Buffer.from("<body too large>");
293
+ var BODY_MASKED = import_buffer2.Buffer.from("<masked>");
294
+ var MASKED = "******";
295
+ var ALLOWED_CONTENT_TYPES = [
296
+ "application/json",
297
+ "text/plain"
298
+ ];
299
+ var EXCLUDE_PATH_PATTERNS = [
300
+ /\/_?healthz?$/i,
301
+ /\/_?health[_-]?checks?$/i,
302
+ /\/_?heart[_-]?beats?$/i,
303
+ /\/ping$/i,
304
+ /\/ready$/i,
305
+ /\/live$/i
306
+ ];
307
+ var EXCLUDE_USER_AGENT_PATTERNS = [
308
+ /health[-_ ]?check/i,
309
+ /microsoft-azure-application-lb/i,
310
+ /googlehc/i,
311
+ /kube-probe/i
312
+ ];
313
+ var MASK_QUERY_PARAM_PATTERNS = [
314
+ /auth/i,
315
+ /api-?key/i,
316
+ /secret/i,
317
+ /token/i,
318
+ /password/i,
319
+ /pwd/i
320
+ ];
321
+ var MASK_HEADER_PATTERNS = [
322
+ /auth/i,
323
+ /api-?key/i,
324
+ /secret/i,
325
+ /token/i,
326
+ /cookie/i
327
+ ];
328
+ var DEFAULT_CONFIG = {
329
+ enabled: false,
330
+ logQueryParams: true,
331
+ logRequestHeaders: false,
332
+ logRequestBody: false,
333
+ logResponseHeaders: true,
334
+ logResponseBody: false,
335
+ maskQueryParams: [],
336
+ maskHeaders: [],
337
+ excludePaths: []
338
+ };
339
+ var _RequestLogger = class _RequestLogger {
340
+ config;
341
+ enabled;
342
+ suspendUntil = null;
343
+ pendingWrites = [];
344
+ currentFile = null;
345
+ files = [];
346
+ maintainIntervalId;
347
+ constructor(config) {
348
+ this.config = {
349
+ ...DEFAULT_CONFIG,
350
+ ...config
351
+ };
352
+ this.enabled = this.config.enabled && checkWritableFs();
353
+ if (this.enabled) {
354
+ this.maintainIntervalId = setInterval(() => {
355
+ this.maintain();
356
+ }, 1e3);
357
+ }
358
+ }
359
+ shouldExcludePath(urlPath) {
360
+ const patterns = [
361
+ ...this.config.excludePaths,
362
+ ...EXCLUDE_PATH_PATTERNS
363
+ ];
364
+ return matchPatterns(urlPath, patterns);
365
+ }
366
+ shouldExcludeUserAgent(userAgent) {
367
+ return userAgent ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS) : false;
368
+ }
369
+ shouldMaskQueryParam(name) {
370
+ const patterns = [
371
+ ...this.config.maskQueryParams,
372
+ ...MASK_QUERY_PARAM_PATTERNS
373
+ ];
374
+ return matchPatterns(name, patterns);
375
+ }
376
+ shouldMaskHeader(name) {
377
+ const patterns = [
378
+ ...this.config.maskHeaders,
379
+ ...MASK_HEADER_PATTERNS
380
+ ];
381
+ return matchPatterns(name, patterns);
382
+ }
383
+ hasSupportedContentType(headers) {
384
+ var _a2;
385
+ const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
386
+ return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
387
+ }
388
+ maskQueryParams(search) {
389
+ const params = new URLSearchParams(search);
390
+ for (const [key] of params) {
391
+ if (this.shouldMaskQueryParam(key)) {
392
+ params.set(key, MASKED);
393
+ }
394
+ }
395
+ return params.toString();
396
+ }
397
+ maskHeaders(headers) {
398
+ return headers.map(([k, v]) => [
399
+ k,
400
+ this.shouldMaskHeader(k) ? MASKED : v
401
+ ]);
402
+ }
403
+ logRequest(request, response) {
404
+ var _a2, _b, _c;
405
+ if (!this.enabled || this.suspendUntil !== null) return;
406
+ const url = new URL(request.url);
407
+ const path = request.path ?? url.pathname;
408
+ const userAgent = (_a2 = request.headers.find(([k]) => k.toLowerCase() === "user-agent")) == null ? void 0 : _a2[1];
409
+ if (this.shouldExcludePath(path) || this.shouldExcludeUserAgent(userAgent) || (((_c = (_b = this.config).excludeCallback) == null ? void 0 : _c.call(_b, request, response)) ?? false)) {
410
+ return;
411
+ }
412
+ url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
413
+ request.url = url.toString();
414
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
415
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
416
+ if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
417
+ request.body = void 0;
418
+ } else if (request.body) {
419
+ if (request.body.length > MAX_BODY_SIZE) {
420
+ request.body = BODY_TOO_LARGE;
421
+ } else if (this.config.maskRequestBodyCallback) {
422
+ try {
423
+ request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
424
+ if (request.body.length > MAX_BODY_SIZE) {
425
+ request.body = BODY_TOO_LARGE;
426
+ }
427
+ } catch {
428
+ request.body = void 0;
429
+ }
430
+ }
431
+ }
432
+ if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
433
+ response.body = void 0;
434
+ } else if (response.body) {
435
+ if (response.body.length > MAX_BODY_SIZE) {
436
+ response.body = BODY_TOO_LARGE;
437
+ } else if (this.config.maskResponseBodyCallback) {
438
+ try {
439
+ response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
440
+ if (response.body.length > MAX_BODY_SIZE) {
441
+ response.body = BODY_TOO_LARGE;
442
+ }
443
+ } catch {
444
+ response.body = void 0;
445
+ }
446
+ }
447
+ }
448
+ const item = {
449
+ uuid: (0, import_crypto2.randomUUID)(),
450
+ request: skipEmptyValues(request),
451
+ response: skipEmptyValues(response)
452
+ };
453
+ [
454
+ item.request.body,
455
+ item.response.body
456
+ ].forEach((body) => {
457
+ if (body) {
458
+ body.toJSON = function() {
459
+ return this.toString("base64");
460
+ };
461
+ }
462
+ });
463
+ this.pendingWrites.push(JSON.stringify(item));
464
+ if (this.pendingWrites.length > MAX_PENDING_WRITES) {
465
+ this.pendingWrites.shift();
466
+ }
467
+ }
468
+ async writeToFile() {
469
+ if (!this.enabled || this.pendingWrites.length === 0) {
470
+ return;
471
+ }
472
+ if (!this.currentFile) {
473
+ this.currentFile = new TempGzipFile();
474
+ }
475
+ while (this.pendingWrites.length > 0) {
476
+ const item = this.pendingWrites.shift();
477
+ if (item) {
478
+ await this.currentFile.writeLine(import_buffer2.Buffer.from(item));
479
+ }
480
+ }
481
+ }
482
+ getFile() {
483
+ return this.files.shift();
484
+ }
485
+ retryFileLater(file) {
486
+ this.files.unshift(file);
487
+ }
488
+ async rotateFile() {
489
+ if (this.currentFile) {
490
+ await this.currentFile.close();
491
+ this.files.push(this.currentFile);
492
+ this.currentFile = null;
493
+ }
494
+ }
495
+ async maintain() {
496
+ await this.writeToFile();
497
+ if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
498
+ await this.rotateFile();
499
+ }
500
+ while (this.files.length > MAX_FILES) {
501
+ const file = this.files.shift();
502
+ file == null ? void 0 : file.delete();
503
+ }
504
+ if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
505
+ this.suspendUntil = null;
506
+ }
507
+ }
508
+ async clear() {
509
+ this.pendingWrites = [];
510
+ await this.rotateFile();
511
+ this.files.forEach((file) => {
512
+ file.delete();
513
+ });
514
+ this.files = [];
515
+ }
516
+ async close() {
517
+ this.enabled = false;
518
+ await this.clear();
519
+ if (this.maintainIntervalId) {
520
+ clearInterval(this.maintainIntervalId);
521
+ }
522
+ }
523
+ };
524
+ __name(_RequestLogger, "RequestLogger");
525
+ var RequestLogger = _RequestLogger;
526
+ function convertHeaders(headers) {
527
+ if (headers instanceof Headers) {
528
+ return Array.from(headers.entries());
529
+ }
530
+ return Object.entries(headers).flatMap(([key, value]) => {
531
+ if (value === void 0) {
532
+ return [];
533
+ }
534
+ if (Array.isArray(value)) {
535
+ return value.map((v) => [
536
+ key,
537
+ v
538
+ ]);
539
+ }
540
+ return [
541
+ [
542
+ key,
543
+ value.toString()
544
+ ]
545
+ ];
546
+ });
547
+ }
548
+ __name(convertHeaders, "convertHeaders");
549
+ function matchPatterns(value, patterns) {
550
+ return patterns.some((pattern) => {
551
+ return pattern.test(value);
552
+ });
553
+ }
554
+ __name(matchPatterns, "matchPatterns");
555
+ function skipEmptyValues(data) {
556
+ return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
557
+ if (v == null || Number.isNaN(v)) return false;
558
+ if (Array.isArray(v) || import_buffer2.Buffer.isBuffer(v) || typeof v === "string") {
559
+ return v.length > 0;
560
+ }
561
+ return true;
562
+ }));
563
+ }
564
+ __name(skipEmptyValues, "skipEmptyValues");
565
+ function checkWritableFs() {
566
+ try {
567
+ const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto2.randomUUID)()}`);
568
+ (0, import_fs2.writeFileSync)(testPath, "test");
569
+ (0, import_fs2.unlinkSync)(testPath);
570
+ return true;
571
+ } catch (error) {
572
+ return false;
573
+ }
574
+ }
575
+ __name(checkWritableFs, "checkWritableFs");
576
+
577
+ // src/common/serverErrorCounter.ts
578
+ var import_crypto3 = require("crypto");
214
579
  var MAX_MSG_LENGTH = 2048;
215
580
  var MAX_STACKTRACE_LENGTH = 65536;
216
581
  var _ServerErrorCounter = class _ServerErrorCounter {
@@ -262,7 +627,7 @@ var _ServerErrorCounter = class _ServerErrorCounter {
262
627
  serverError.msg.trim(),
263
628
  serverError.traceback.trim()
264
629
  ].join("|");
265
- return (0, import_crypto.createHash)("md5").update(hashInput).digest("hex");
630
+ return (0, import_crypto3.createHash)("md5").update(hashInput).digest("hex");
266
631
  }
267
632
  getTruncatedMessage(msg) {
268
633
  msg = msg.trim();
@@ -308,7 +673,7 @@ __name(_ServerErrorCounter, "ServerErrorCounter");
308
673
  var ServerErrorCounter = _ServerErrorCounter;
309
674
 
310
675
  // src/common/validationErrorCounter.ts
311
- var import_crypto2 = require("crypto");
676
+ var import_crypto4 = require("crypto");
312
677
  var _ValidationErrorCounter = class _ValidationErrorCounter {
313
678
  errorCounts;
314
679
  errorDetails;
@@ -352,7 +717,7 @@ var _ValidationErrorCounter = class _ValidationErrorCounter {
352
717
  validationError.msg.trim(),
353
718
  validationError.type
354
719
  ].join("|");
355
- return (0, import_crypto2.createHash)("md5").update(hashInput).digest("hex");
720
+ return (0, import_crypto4.createHash)("md5").update(hashInput).digest("hex");
356
721
  }
357
722
  };
358
723
  __name(_ValidationErrorCounter, "ValidationErrorCounter");
@@ -381,11 +746,12 @@ var _ApitallyClient = class _ApitallyClient {
381
746
  startupData;
382
747
  startupDataSent = false;
383
748
  requestCounter;
749
+ requestLogger;
384
750
  validationErrorCounter;
385
751
  serverErrorCounter;
386
752
  consumerRegistry;
387
753
  logger;
388
- constructor({ clientId, env = "dev", logger }) {
754
+ constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
389
755
  if (_ApitallyClient.instance) {
390
756
  throw new Error("Apitally client is already initialized");
391
757
  }
@@ -398,9 +764,10 @@ var _ApitallyClient = class _ApitallyClient {
398
764
  _ApitallyClient.instance = this;
399
765
  this.clientId = clientId;
400
766
  this.env = env;
401
- this.instanceUuid = (0, import_crypto3.randomUUID)();
767
+ this.instanceUuid = (0, import_crypto5.randomUUID)();
402
768
  this.syncDataQueue = [];
403
769
  this.requestCounter = new RequestCounter();
770
+ this.requestLogger = new RequestLogger(requestLoggingConfig);
404
771
  this.validationErrorCounter = new ValidationErrorCounter();
405
772
  this.serverErrorCounter = new ServerErrorCounter();
406
773
  this.consumerRegistry = new ConsumerRegistry();
@@ -422,6 +789,8 @@ var _ApitallyClient = class _ApitallyClient {
422
789
  async handleShutdown() {
423
790
  this.stopSync();
424
791
  await this.sendSyncData();
792
+ await this.sendLogData();
793
+ await this.requestLogger.close();
425
794
  _ApitallyClient.instance = void 0;
426
795
  }
427
796
  getHubUrlPrefix() {
@@ -468,7 +837,8 @@ var _ApitallyClient = class _ApitallyClient {
468
837
  async sync() {
469
838
  try {
470
839
  const promises = [
471
- this.sendSyncData()
840
+ this.sendSyncData(),
841
+ this.sendLogData()
472
842
  ];
473
843
  if (!this.startupDataSent) {
474
844
  promises.push(this.sendStartupData());
@@ -496,7 +866,7 @@ var _ApitallyClient = class _ApitallyClient {
496
866
  this.logger.debug("Sending startup data to Apitally Hub");
497
867
  const payload = {
498
868
  instance_uuid: this.instanceUuid,
499
- message_uuid: (0, import_crypto3.randomUUID)(),
869
+ message_uuid: (0, import_crypto5.randomUUID)(),
500
870
  ...this.startupData
501
871
  };
502
872
  try {
@@ -518,7 +888,7 @@ var _ApitallyClient = class _ApitallyClient {
518
888
  const newPayload = {
519
889
  time_offset: 0,
520
890
  instance_uuid: this.instanceUuid,
521
- message_uuid: (0, import_crypto3.randomUUID)(),
891
+ message_uuid: (0, import_crypto5.randomUUID)(),
522
892
  requests: this.requestCounter.getAndResetRequests(),
523
893
  validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
524
894
  server_errors: this.serverErrorCounter.getAndResetServerErrors(),
@@ -537,8 +907,7 @@ var _ApitallyClient = class _ApitallyClient {
537
907
  const timeOffset = Date.now() - time;
538
908
  if (timeOffset <= MAX_QUEUE_TIME) {
539
909
  if (i > 0) {
540
- const waitMs = 100 + Math.random() * 200;
541
- await new Promise((resolve) => setTimeout(resolve, waitMs));
910
+ await this.randomDelay();
542
911
  }
543
912
  payload.time_offset = timeOffset / 1e3;
544
913
  await this.sendData("sync", payload);
@@ -557,6 +926,52 @@ var _ApitallyClient = class _ApitallyClient {
557
926
  }
558
927
  }
559
928
  }
929
+ async sendLogData() {
930
+ this.logger.debug("Sending request log data to Apitally Hub");
931
+ await this.requestLogger.rotateFile();
932
+ const fetchWithRetry = (0, import_fetch_retry.default)(fetch, {
933
+ retries: 3,
934
+ retryDelay: 1e3,
935
+ retryOn: [
936
+ 408,
937
+ 429,
938
+ 500,
939
+ 502,
940
+ 503,
941
+ 504
942
+ ]
943
+ });
944
+ let i = 0;
945
+ let logFile;
946
+ while (logFile = this.requestLogger.getFile()) {
947
+ if (i > 0) {
948
+ await this.randomDelay();
949
+ }
950
+ try {
951
+ const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
952
+ method: "POST",
953
+ body: await logFile.getContent()
954
+ });
955
+ if (response.status === 402 && response.headers.has("Retry-After")) {
956
+ const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
957
+ if (retryAfter > 0) {
958
+ this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
959
+ this.requestLogger.clear();
960
+ return;
961
+ }
962
+ }
963
+ if (!response.ok) {
964
+ throw new HTTPError(response);
965
+ }
966
+ logFile.delete();
967
+ } catch (error) {
968
+ this.requestLogger.retryFileLater(logFile);
969
+ break;
970
+ }
971
+ i++;
972
+ if (i >= 10) break;
973
+ }
974
+ }
560
975
  handleHubError(error) {
561
976
  if (error instanceof HTTPError) {
562
977
  if (error.response.status === 404) {
@@ -571,6 +986,10 @@ var _ApitallyClient = class _ApitallyClient {
571
986
  }
572
987
  return false;
573
988
  }
989
+ async randomDelay() {
990
+ const delay = 100 + Math.random() * 200;
991
+ await new Promise((resolve) => setTimeout(resolve, delay));
992
+ }
574
993
  };
575
994
  __name(_ApitallyClient, "ApitallyClient");
576
995
  __publicField(_ApitallyClient, "instance");
@@ -604,13 +1023,13 @@ var useApitally = /* @__PURE__ */ __name((app, config) => {
604
1023
  }, 1e3);
605
1024
  }, "useApitally");
606
1025
  var getMiddleware = /* @__PURE__ */ __name((client) => {
607
- const zodInstalled = getPackageVersion("zod") !== null;
608
1026
  return async (c, next) => {
609
1027
  const startTime = import_perf_hooks.performance.now();
610
1028
  await next();
611
1029
  let response;
612
1030
  const responseTime = import_perf_hooks.performance.now() - startTime;
613
1031
  const [responseSize, newResponse] = await measureResponseSize(c.res);
1032
+ const requestSize = c.req.header("Content-Length");
614
1033
  const consumer = getConsumer(c);
615
1034
  client.consumerRegistry.addOrUpdateConsumer(consumer);
616
1035
  client.requestCounter.addRequest({
@@ -619,11 +1038,11 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
619
1038
  path: c.req.routePath,
620
1039
  statusCode: c.res.status,
621
1040
  responseTime,
622
- requestSize: c.req.header("Content-Length"),
1041
+ requestSize,
623
1042
  responseSize
624
1043
  });
625
1044
  response = newResponse;
626
- if (c.res.status === 400 && zodInstalled) {
1045
+ if (c.res.status === 400) {
627
1046
  const [responseJson, newResponse2] = await getResponseJson(response);
628
1047
  const validationErrors = extractZodErrors(responseJson);
629
1048
  validationErrors.forEach((error) => {
@@ -646,6 +1065,34 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
646
1065
  traceback: c.error.stack || ""
647
1066
  });
648
1067
  }
1068
+ if (client.requestLogger.enabled) {
1069
+ let requestBody;
1070
+ let responseBody;
1071
+ let newResponse2 = response;
1072
+ if (client.requestLogger.config.logRequestBody) {
1073
+ requestBody = Buffer.from(await c.req.arrayBuffer());
1074
+ }
1075
+ if (client.requestLogger.config.logResponseBody) {
1076
+ [responseBody, newResponse2] = await getResponseBody(response);
1077
+ response = newResponse2;
1078
+ }
1079
+ client.requestLogger.logRequest({
1080
+ timestamp: Date.now() / 1e3,
1081
+ method: c.req.method,
1082
+ path: c.req.routePath,
1083
+ url: c.req.url,
1084
+ headers: convertHeaders(c.req.header()),
1085
+ size: Number(requestSize),
1086
+ consumer: consumer == null ? void 0 : consumer.identifier,
1087
+ body: requestBody
1088
+ }, {
1089
+ statusCode: c.res.status,
1090
+ responseTime: responseTime / 1e3,
1091
+ headers: convertHeaders(c.res.headers),
1092
+ size: responseSize,
1093
+ body: responseBody
1094
+ });
1095
+ }
649
1096
  c.res = response;
650
1097
  };
651
1098
  }, "getMiddleware");
@@ -675,6 +1122,14 @@ var measureResponseSize = /* @__PURE__ */ __name(async (response) => {
675
1122
  newResponse1
676
1123
  ];
677
1124
  }, "measureResponseSize");
1125
+ var getResponseBody = /* @__PURE__ */ __name(async (response) => {
1126
+ const [newResponse1, newResponse2] = await teeResponse(response);
1127
+ const responseBuffer = Buffer.from(await newResponse2.arrayBuffer());
1128
+ return [
1129
+ responseBuffer,
1130
+ newResponse1
1131
+ ];
1132
+ }, "getResponseBody");
678
1133
  var getResponseJson = /* @__PURE__ */ __name(async (response) => {
679
1134
  const contentType = response.headers.get("content-type");
680
1135
  if (contentType && contentType.includes("application/json")) {