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
package/README.md CHANGED
@@ -93,9 +93,8 @@ const { useApitally } = require("apitally/nestjs");
93
93
  const { AppModule } = require("./app.module");
94
94
 
95
95
  const app = await NestFactory.create(AppModule);
96
- const expressInstance = app.getHttpAdapter().getInstance();
97
96
 
98
- useApitally(expressInstance, {
97
+ useApitally(app, {
99
98
  clientId: "your-client-id",
100
99
  env: "dev", // or "prod" etc.
101
100
  });
@@ -36,7 +36,7 @@ __export(client_exports, {
36
36
  ApitallyClient: () => ApitallyClient
37
37
  });
38
38
  module.exports = __toCommonJS(client_exports);
39
- var import_crypto3 = require("crypto");
39
+ var import_crypto5 = require("crypto");
40
40
  var import_fetch_retry = __toESM(require("fetch-retry"), 1);
41
41
 
42
42
  // src/common/consumerRegistry.ts
@@ -191,8 +191,350 @@ var _RequestCounter = class _RequestCounter {
191
191
  __name(_RequestCounter, "RequestCounter");
192
192
  var RequestCounter = _RequestCounter;
193
193
 
194
- // src/common/serverErrorCounter.ts
194
+ // src/common/requestLogger.ts
195
+ var import_buffer2 = require("buffer");
196
+ var import_crypto2 = require("crypto");
197
+ var import_fs2 = require("fs");
198
+ var import_os2 = require("os");
199
+ var import_path2 = require("path");
200
+
201
+ // src/common/tempGzipFile.ts
202
+ var import_buffer = require("buffer");
195
203
  var import_crypto = require("crypto");
204
+ var import_fs = require("fs");
205
+ var import_os = require("os");
206
+ var import_path = require("path");
207
+ var import_zlib = require("zlib");
208
+ var _TempGzipFile = class _TempGzipFile {
209
+ uuid;
210
+ filePath;
211
+ gzip;
212
+ writeStream;
213
+ readyPromise;
214
+ constructor() {
215
+ this.uuid = (0, import_crypto.randomUUID)();
216
+ this.filePath = (0, import_path.join)((0, import_os.tmpdir)(), `apitally-${this.uuid}.gz`);
217
+ this.writeStream = (0, import_fs.createWriteStream)(this.filePath);
218
+ this.readyPromise = new Promise((resolve, reject) => {
219
+ this.writeStream.once("ready", resolve);
220
+ this.writeStream.once("error", reject);
221
+ });
222
+ this.gzip = (0, import_zlib.createGzip)();
223
+ this.gzip.pipe(this.writeStream);
224
+ }
225
+ get size() {
226
+ return this.writeStream.bytesWritten;
227
+ }
228
+ async writeLine(data) {
229
+ await this.readyPromise;
230
+ return new Promise((resolve, reject) => {
231
+ this.gzip.write(import_buffer.Buffer.concat([
232
+ data,
233
+ import_buffer.Buffer.from("\n")
234
+ ]), (error) => {
235
+ if (error) {
236
+ reject(error);
237
+ } else {
238
+ resolve();
239
+ }
240
+ });
241
+ });
242
+ }
243
+ async getContent() {
244
+ return new Promise((resolve, reject) => {
245
+ (0, import_fs.readFile)(this.filePath, (error, data) => {
246
+ if (error) {
247
+ reject(error);
248
+ } else {
249
+ resolve(data);
250
+ }
251
+ });
252
+ });
253
+ }
254
+ async close() {
255
+ return new Promise((resolve) => {
256
+ this.gzip.end(() => {
257
+ resolve();
258
+ });
259
+ });
260
+ }
261
+ async delete() {
262
+ await this.close();
263
+ (0, import_fs.unlinkSync)(this.filePath);
264
+ }
265
+ };
266
+ __name(_TempGzipFile, "TempGzipFile");
267
+ var TempGzipFile = _TempGzipFile;
268
+
269
+ // src/common/requestLogger.ts
270
+ var MAX_BODY_SIZE = 5e4;
271
+ var MAX_FILE_SIZE = 1e6;
272
+ var MAX_FILES = 50;
273
+ var MAX_PENDING_WRITES = 100;
274
+ var BODY_TOO_LARGE = import_buffer2.Buffer.from("<body too large>");
275
+ var BODY_MASKED = import_buffer2.Buffer.from("<masked>");
276
+ var MASKED = "******";
277
+ var ALLOWED_CONTENT_TYPES = [
278
+ "application/json",
279
+ "text/plain"
280
+ ];
281
+ var EXCLUDE_PATH_PATTERNS = [
282
+ /\/_?healthz?$/i,
283
+ /\/_?health[_-]?checks?$/i,
284
+ /\/_?heart[_-]?beats?$/i,
285
+ /\/ping$/i,
286
+ /\/ready$/i,
287
+ /\/live$/i
288
+ ];
289
+ var EXCLUDE_USER_AGENT_PATTERNS = [
290
+ /health[-_ ]?check/i,
291
+ /microsoft-azure-application-lb/i,
292
+ /googlehc/i,
293
+ /kube-probe/i
294
+ ];
295
+ var MASK_QUERY_PARAM_PATTERNS = [
296
+ /auth/i,
297
+ /api-?key/i,
298
+ /secret/i,
299
+ /token/i,
300
+ /password/i,
301
+ /pwd/i
302
+ ];
303
+ var MASK_HEADER_PATTERNS = [
304
+ /auth/i,
305
+ /api-?key/i,
306
+ /secret/i,
307
+ /token/i,
308
+ /cookie/i
309
+ ];
310
+ var DEFAULT_CONFIG = {
311
+ enabled: false,
312
+ logQueryParams: true,
313
+ logRequestHeaders: false,
314
+ logRequestBody: false,
315
+ logResponseHeaders: true,
316
+ logResponseBody: false,
317
+ maskQueryParams: [],
318
+ maskHeaders: [],
319
+ excludePaths: []
320
+ };
321
+ var _RequestLogger = class _RequestLogger {
322
+ config;
323
+ enabled;
324
+ suspendUntil = null;
325
+ pendingWrites = [];
326
+ currentFile = null;
327
+ files = [];
328
+ maintainIntervalId;
329
+ constructor(config) {
330
+ this.config = {
331
+ ...DEFAULT_CONFIG,
332
+ ...config
333
+ };
334
+ this.enabled = this.config.enabled && checkWritableFs();
335
+ if (this.enabled) {
336
+ this.maintainIntervalId = setInterval(() => {
337
+ this.maintain();
338
+ }, 1e3);
339
+ }
340
+ }
341
+ shouldExcludePath(urlPath) {
342
+ const patterns = [
343
+ ...this.config.excludePaths,
344
+ ...EXCLUDE_PATH_PATTERNS
345
+ ];
346
+ return matchPatterns(urlPath, patterns);
347
+ }
348
+ shouldExcludeUserAgent(userAgent) {
349
+ return userAgent ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS) : false;
350
+ }
351
+ shouldMaskQueryParam(name) {
352
+ const patterns = [
353
+ ...this.config.maskQueryParams,
354
+ ...MASK_QUERY_PARAM_PATTERNS
355
+ ];
356
+ return matchPatterns(name, patterns);
357
+ }
358
+ shouldMaskHeader(name) {
359
+ const patterns = [
360
+ ...this.config.maskHeaders,
361
+ ...MASK_HEADER_PATTERNS
362
+ ];
363
+ return matchPatterns(name, patterns);
364
+ }
365
+ hasSupportedContentType(headers) {
366
+ var _a2;
367
+ const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
368
+ return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
369
+ }
370
+ maskQueryParams(search) {
371
+ const params = new URLSearchParams(search);
372
+ for (const [key] of params) {
373
+ if (this.shouldMaskQueryParam(key)) {
374
+ params.set(key, MASKED);
375
+ }
376
+ }
377
+ return params.toString();
378
+ }
379
+ maskHeaders(headers) {
380
+ return headers.map(([k, v]) => [
381
+ k,
382
+ this.shouldMaskHeader(k) ? MASKED : v
383
+ ]);
384
+ }
385
+ logRequest(request, response) {
386
+ var _a2, _b, _c;
387
+ if (!this.enabled || this.suspendUntil !== null) return;
388
+ const url = new URL(request.url);
389
+ const path = request.path ?? url.pathname;
390
+ const userAgent = (_a2 = request.headers.find(([k]) => k.toLowerCase() === "user-agent")) == null ? void 0 : _a2[1];
391
+ if (this.shouldExcludePath(path) || this.shouldExcludeUserAgent(userAgent) || (((_c = (_b = this.config).excludeCallback) == null ? void 0 : _c.call(_b, request, response)) ?? false)) {
392
+ return;
393
+ }
394
+ url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
395
+ request.url = url.toString();
396
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
397
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
398
+ if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
399
+ request.body = void 0;
400
+ } else if (request.body) {
401
+ if (request.body.length > MAX_BODY_SIZE) {
402
+ request.body = BODY_TOO_LARGE;
403
+ } else if (this.config.maskRequestBodyCallback) {
404
+ try {
405
+ request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
406
+ if (request.body.length > MAX_BODY_SIZE) {
407
+ request.body = BODY_TOO_LARGE;
408
+ }
409
+ } catch {
410
+ request.body = void 0;
411
+ }
412
+ }
413
+ }
414
+ if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
415
+ response.body = void 0;
416
+ } else if (response.body) {
417
+ if (response.body.length > MAX_BODY_SIZE) {
418
+ response.body = BODY_TOO_LARGE;
419
+ } else if (this.config.maskResponseBodyCallback) {
420
+ try {
421
+ response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
422
+ if (response.body.length > MAX_BODY_SIZE) {
423
+ response.body = BODY_TOO_LARGE;
424
+ }
425
+ } catch {
426
+ response.body = void 0;
427
+ }
428
+ }
429
+ }
430
+ const item = {
431
+ uuid: (0, import_crypto2.randomUUID)(),
432
+ request: skipEmptyValues(request),
433
+ response: skipEmptyValues(response)
434
+ };
435
+ [
436
+ item.request.body,
437
+ item.response.body
438
+ ].forEach((body) => {
439
+ if (body) {
440
+ body.toJSON = function() {
441
+ return this.toString("base64");
442
+ };
443
+ }
444
+ });
445
+ this.pendingWrites.push(JSON.stringify(item));
446
+ if (this.pendingWrites.length > MAX_PENDING_WRITES) {
447
+ this.pendingWrites.shift();
448
+ }
449
+ }
450
+ async writeToFile() {
451
+ if (!this.enabled || this.pendingWrites.length === 0) {
452
+ return;
453
+ }
454
+ if (!this.currentFile) {
455
+ this.currentFile = new TempGzipFile();
456
+ }
457
+ while (this.pendingWrites.length > 0) {
458
+ const item = this.pendingWrites.shift();
459
+ if (item) {
460
+ await this.currentFile.writeLine(import_buffer2.Buffer.from(item));
461
+ }
462
+ }
463
+ }
464
+ getFile() {
465
+ return this.files.shift();
466
+ }
467
+ retryFileLater(file) {
468
+ this.files.unshift(file);
469
+ }
470
+ async rotateFile() {
471
+ if (this.currentFile) {
472
+ await this.currentFile.close();
473
+ this.files.push(this.currentFile);
474
+ this.currentFile = null;
475
+ }
476
+ }
477
+ async maintain() {
478
+ await this.writeToFile();
479
+ if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
480
+ await this.rotateFile();
481
+ }
482
+ while (this.files.length > MAX_FILES) {
483
+ const file = this.files.shift();
484
+ file == null ? void 0 : file.delete();
485
+ }
486
+ if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
487
+ this.suspendUntil = null;
488
+ }
489
+ }
490
+ async clear() {
491
+ this.pendingWrites = [];
492
+ await this.rotateFile();
493
+ this.files.forEach((file) => {
494
+ file.delete();
495
+ });
496
+ this.files = [];
497
+ }
498
+ async close() {
499
+ this.enabled = false;
500
+ await this.clear();
501
+ if (this.maintainIntervalId) {
502
+ clearInterval(this.maintainIntervalId);
503
+ }
504
+ }
505
+ };
506
+ __name(_RequestLogger, "RequestLogger");
507
+ var RequestLogger = _RequestLogger;
508
+ function matchPatterns(value, patterns) {
509
+ return patterns.some((pattern) => {
510
+ return pattern.test(value);
511
+ });
512
+ }
513
+ __name(matchPatterns, "matchPatterns");
514
+ function skipEmptyValues(data) {
515
+ return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
516
+ if (v == null || Number.isNaN(v)) return false;
517
+ if (Array.isArray(v) || import_buffer2.Buffer.isBuffer(v) || typeof v === "string") {
518
+ return v.length > 0;
519
+ }
520
+ return true;
521
+ }));
522
+ }
523
+ __name(skipEmptyValues, "skipEmptyValues");
524
+ function checkWritableFs() {
525
+ try {
526
+ const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto2.randomUUID)()}`);
527
+ (0, import_fs2.writeFileSync)(testPath, "test");
528
+ (0, import_fs2.unlinkSync)(testPath);
529
+ return true;
530
+ } catch (error) {
531
+ return false;
532
+ }
533
+ }
534
+ __name(checkWritableFs, "checkWritableFs");
535
+
536
+ // src/common/serverErrorCounter.ts
537
+ var import_crypto3 = require("crypto");
196
538
  var MAX_MSG_LENGTH = 2048;
197
539
  var MAX_STACKTRACE_LENGTH = 65536;
198
540
  var _ServerErrorCounter = class _ServerErrorCounter {
@@ -244,7 +586,7 @@ var _ServerErrorCounter = class _ServerErrorCounter {
244
586
  serverError.msg.trim(),
245
587
  serverError.traceback.trim()
246
588
  ].join("|");
247
- return (0, import_crypto.createHash)("md5").update(hashInput).digest("hex");
589
+ return (0, import_crypto3.createHash)("md5").update(hashInput).digest("hex");
248
590
  }
249
591
  getTruncatedMessage(msg) {
250
592
  msg = msg.trim();
@@ -290,7 +632,7 @@ __name(_ServerErrorCounter, "ServerErrorCounter");
290
632
  var ServerErrorCounter = _ServerErrorCounter;
291
633
 
292
634
  // src/common/validationErrorCounter.ts
293
- var import_crypto2 = require("crypto");
635
+ var import_crypto4 = require("crypto");
294
636
  var _ValidationErrorCounter = class _ValidationErrorCounter {
295
637
  errorCounts;
296
638
  errorDetails;
@@ -334,7 +676,7 @@ var _ValidationErrorCounter = class _ValidationErrorCounter {
334
676
  validationError.msg.trim(),
335
677
  validationError.type
336
678
  ].join("|");
337
- return (0, import_crypto2.createHash)("md5").update(hashInput).digest("hex");
679
+ return (0, import_crypto4.createHash)("md5").update(hashInput).digest("hex");
338
680
  }
339
681
  };
340
682
  __name(_ValidationErrorCounter, "ValidationErrorCounter");
@@ -363,11 +705,12 @@ var _ApitallyClient = class _ApitallyClient {
363
705
  startupData;
364
706
  startupDataSent = false;
365
707
  requestCounter;
708
+ requestLogger;
366
709
  validationErrorCounter;
367
710
  serverErrorCounter;
368
711
  consumerRegistry;
369
712
  logger;
370
- constructor({ clientId, env = "dev", logger }) {
713
+ constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
371
714
  if (_ApitallyClient.instance) {
372
715
  throw new Error("Apitally client is already initialized");
373
716
  }
@@ -380,9 +723,10 @@ var _ApitallyClient = class _ApitallyClient {
380
723
  _ApitallyClient.instance = this;
381
724
  this.clientId = clientId;
382
725
  this.env = env;
383
- this.instanceUuid = (0, import_crypto3.randomUUID)();
726
+ this.instanceUuid = (0, import_crypto5.randomUUID)();
384
727
  this.syncDataQueue = [];
385
728
  this.requestCounter = new RequestCounter();
729
+ this.requestLogger = new RequestLogger(requestLoggingConfig);
386
730
  this.validationErrorCounter = new ValidationErrorCounter();
387
731
  this.serverErrorCounter = new ServerErrorCounter();
388
732
  this.consumerRegistry = new ConsumerRegistry();
@@ -404,6 +748,8 @@ var _ApitallyClient = class _ApitallyClient {
404
748
  async handleShutdown() {
405
749
  this.stopSync();
406
750
  await this.sendSyncData();
751
+ await this.sendLogData();
752
+ await this.requestLogger.close();
407
753
  _ApitallyClient.instance = void 0;
408
754
  }
409
755
  getHubUrlPrefix() {
@@ -450,7 +796,8 @@ var _ApitallyClient = class _ApitallyClient {
450
796
  async sync() {
451
797
  try {
452
798
  const promises = [
453
- this.sendSyncData()
799
+ this.sendSyncData(),
800
+ this.sendLogData()
454
801
  ];
455
802
  if (!this.startupDataSent) {
456
803
  promises.push(this.sendStartupData());
@@ -478,7 +825,7 @@ var _ApitallyClient = class _ApitallyClient {
478
825
  this.logger.debug("Sending startup data to Apitally Hub");
479
826
  const payload = {
480
827
  instance_uuid: this.instanceUuid,
481
- message_uuid: (0, import_crypto3.randomUUID)(),
828
+ message_uuid: (0, import_crypto5.randomUUID)(),
482
829
  ...this.startupData
483
830
  };
484
831
  try {
@@ -500,7 +847,7 @@ var _ApitallyClient = class _ApitallyClient {
500
847
  const newPayload = {
501
848
  time_offset: 0,
502
849
  instance_uuid: this.instanceUuid,
503
- message_uuid: (0, import_crypto3.randomUUID)(),
850
+ message_uuid: (0, import_crypto5.randomUUID)(),
504
851
  requests: this.requestCounter.getAndResetRequests(),
505
852
  validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
506
853
  server_errors: this.serverErrorCounter.getAndResetServerErrors(),
@@ -519,8 +866,7 @@ var _ApitallyClient = class _ApitallyClient {
519
866
  const timeOffset = Date.now() - time;
520
867
  if (timeOffset <= MAX_QUEUE_TIME) {
521
868
  if (i > 0) {
522
- const waitMs = 100 + Math.random() * 200;
523
- await new Promise((resolve) => setTimeout(resolve, waitMs));
869
+ await this.randomDelay();
524
870
  }
525
871
  payload.time_offset = timeOffset / 1e3;
526
872
  await this.sendData("sync", payload);
@@ -539,6 +885,52 @@ var _ApitallyClient = class _ApitallyClient {
539
885
  }
540
886
  }
541
887
  }
888
+ async sendLogData() {
889
+ this.logger.debug("Sending request log data to Apitally Hub");
890
+ await this.requestLogger.rotateFile();
891
+ const fetchWithRetry = (0, import_fetch_retry.default)(fetch, {
892
+ retries: 3,
893
+ retryDelay: 1e3,
894
+ retryOn: [
895
+ 408,
896
+ 429,
897
+ 500,
898
+ 502,
899
+ 503,
900
+ 504
901
+ ]
902
+ });
903
+ let i = 0;
904
+ let logFile;
905
+ while (logFile = this.requestLogger.getFile()) {
906
+ if (i > 0) {
907
+ await this.randomDelay();
908
+ }
909
+ try {
910
+ const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
911
+ method: "POST",
912
+ body: await logFile.getContent()
913
+ });
914
+ if (response.status === 402 && response.headers.has("Retry-After")) {
915
+ const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
916
+ if (retryAfter > 0) {
917
+ this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
918
+ this.requestLogger.clear();
919
+ return;
920
+ }
921
+ }
922
+ if (!response.ok) {
923
+ throw new HTTPError(response);
924
+ }
925
+ logFile.delete();
926
+ } catch (error) {
927
+ this.requestLogger.retryFileLater(logFile);
928
+ break;
929
+ }
930
+ i++;
931
+ if (i >= 10) break;
932
+ }
933
+ }
542
934
  handleHubError(error) {
543
935
  if (error instanceof HTTPError) {
544
936
  if (error.response.status === 404) {
@@ -553,6 +945,10 @@ var _ApitallyClient = class _ApitallyClient {
553
945
  }
554
946
  return false;
555
947
  }
948
+ async randomDelay() {
949
+ const delay = 100 + Math.random() * 200;
950
+ await new Promise((resolve) => setTimeout(resolve, delay));
951
+ }
556
952
  };
557
953
  __name(_ApitallyClient, "ApitallyClient");
558
954
  __publicField(_ApitallyClient, "instance");