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