apitally 0.11.4 → 0.12.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 (81) hide show
  1. package/README.md +27 -14
  2. package/dist/common/client.cjs +130 -110
  3. package/dist/common/client.cjs.map +1 -1
  4. package/dist/common/client.d.cts +2 -0
  5. package/dist/common/client.d.ts +2 -0
  6. package/dist/common/client.js +125 -105
  7. package/dist/common/client.js.map +1 -1
  8. package/dist/common/consumerRegistry.cjs.map +1 -1
  9. package/dist/common/consumerRegistry.js.map +1 -1
  10. package/dist/common/logging.cjs.map +1 -1
  11. package/dist/common/logging.js.map +1 -1
  12. package/dist/common/packageVersions.cjs.map +1 -1
  13. package/dist/common/packageVersions.js.map +1 -1
  14. package/dist/common/paramValidation.cjs.map +1 -1
  15. package/dist/common/paramValidation.js.map +1 -1
  16. package/dist/common/requestCounter.cjs.map +1 -1
  17. package/dist/common/requestCounter.js.map +1 -1
  18. package/dist/common/requestLogger.cjs +63 -7
  19. package/dist/common/requestLogger.cjs.map +1 -1
  20. package/dist/common/requestLogger.d.cts +3 -1
  21. package/dist/common/requestLogger.d.ts +3 -1
  22. package/dist/common/requestLogger.js +63 -7
  23. package/dist/common/requestLogger.js.map +1 -1
  24. package/dist/common/sentry.cjs +55 -0
  25. package/dist/common/sentry.cjs.map +1 -0
  26. package/dist/common/sentry.d.cts +6 -0
  27. package/dist/common/sentry.d.ts +6 -0
  28. package/dist/common/sentry.js +22 -0
  29. package/dist/common/sentry.js.map +1 -0
  30. package/dist/common/serverErrorCounter.cjs +58 -45
  31. package/dist/common/serverErrorCounter.cjs.map +1 -1
  32. package/dist/common/serverErrorCounter.d.cts +3 -6
  33. package/dist/common/serverErrorCounter.d.ts +3 -6
  34. package/dist/common/serverErrorCounter.js +53 -45
  35. package/dist/common/serverErrorCounter.js.map +1 -1
  36. package/dist/common/tempGzipFile.cjs +2 -2
  37. package/dist/common/tempGzipFile.cjs.map +1 -1
  38. package/dist/common/tempGzipFile.js +2 -2
  39. package/dist/common/tempGzipFile.js.map +1 -1
  40. package/dist/common/types.cjs.map +1 -1
  41. package/dist/common/validationErrorCounter.cjs.map +1 -1
  42. package/dist/common/validationErrorCounter.js.map +1 -1
  43. package/dist/express/index.cjs +139 -112
  44. package/dist/express/index.cjs.map +1 -1
  45. package/dist/express/index.js +134 -107
  46. package/dist/express/index.js.map +1 -1
  47. package/dist/express/middleware.cjs +139 -112
  48. package/dist/express/middleware.cjs.map +1 -1
  49. package/dist/express/middleware.js +134 -107
  50. package/dist/express/middleware.js.map +1 -1
  51. package/dist/express/utils.cjs.map +1 -1
  52. package/dist/express/utils.js.map +1 -1
  53. package/dist/fastify/index.cjs +136 -113
  54. package/dist/fastify/index.cjs.map +1 -1
  55. package/dist/fastify/index.js +131 -108
  56. package/dist/fastify/index.js.map +1 -1
  57. package/dist/fastify/plugin.cjs +136 -113
  58. package/dist/fastify/plugin.cjs.map +1 -1
  59. package/dist/fastify/plugin.js +131 -108
  60. package/dist/fastify/plugin.js.map +1 -1
  61. package/dist/hono/index.cjs +137 -112
  62. package/dist/hono/index.cjs.map +1 -1
  63. package/dist/hono/index.js +132 -107
  64. package/dist/hono/index.js.map +1 -1
  65. package/dist/hono/middleware.cjs +137 -112
  66. package/dist/hono/middleware.cjs.map +1 -1
  67. package/dist/hono/middleware.js +132 -107
  68. package/dist/hono/middleware.js.map +1 -1
  69. package/dist/koa/index.cjs +137 -111
  70. package/dist/koa/index.cjs.map +1 -1
  71. package/dist/koa/index.js +132 -106
  72. package/dist/koa/index.js.map +1 -1
  73. package/dist/koa/middleware.cjs +137 -111
  74. package/dist/koa/middleware.cjs.map +1 -1
  75. package/dist/koa/middleware.js +132 -106
  76. package/dist/koa/middleware.js.map +1 -1
  77. package/dist/nestjs/index.cjs +139 -112
  78. package/dist/nestjs/index.cjs.map +1 -1
  79. package/dist/nestjs/index.js +134 -107
  80. package/dist/nestjs/index.js.map +1 -1
  81. package/package.json +1 -1
@@ -214,14 +214,116 @@ var RequestCounter = _RequestCounter;
214
214
  // src/common/requestLogger.ts
215
215
  var import_async_lock = __toESM(require("async-lock"), 1);
216
216
  var import_buffer2 = require("buffer");
217
- var import_crypto2 = require("crypto");
217
+ var import_crypto3 = require("crypto");
218
218
  var import_fs2 = require("fs");
219
219
  var import_os2 = require("os");
220
220
  var import_path2 = require("path");
221
221
 
222
+ // src/common/sentry.ts
223
+ var sentry;
224
+ (async () => {
225
+ try {
226
+ sentry = await import("@sentry/node");
227
+ } catch (e) {
228
+ }
229
+ })();
230
+ function getSentryEventId() {
231
+ if (sentry && sentry.lastEventId) {
232
+ return sentry.lastEventId();
233
+ }
234
+ return void 0;
235
+ }
236
+ __name(getSentryEventId, "getSentryEventId");
237
+
238
+ // src/common/serverErrorCounter.ts
239
+ var import_crypto = require("crypto");
240
+ var MAX_MSG_LENGTH = 2048;
241
+ var MAX_STACKTRACE_LENGTH = 65536;
242
+ var _ServerErrorCounter = class _ServerErrorCounter {
243
+ errorCounts;
244
+ errorDetails;
245
+ sentryEventIds;
246
+ constructor() {
247
+ this.errorCounts = /* @__PURE__ */ new Map();
248
+ this.errorDetails = /* @__PURE__ */ new Map();
249
+ this.sentryEventIds = /* @__PURE__ */ new Map();
250
+ }
251
+ addServerError(serverError) {
252
+ const key = this.getKey(serverError);
253
+ if (!this.errorDetails.has(key)) {
254
+ this.errorDetails.set(key, serverError);
255
+ }
256
+ this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
257
+ const sentryEventId = getSentryEventId();
258
+ if (sentryEventId) {
259
+ this.sentryEventIds.set(key, sentryEventId);
260
+ }
261
+ }
262
+ getAndResetServerErrors() {
263
+ const data = [];
264
+ this.errorCounts.forEach((count, key) => {
265
+ const serverError = this.errorDetails.get(key);
266
+ if (serverError) {
267
+ data.push({
268
+ consumer: serverError.consumer || null,
269
+ method: serverError.method,
270
+ path: serverError.path,
271
+ type: serverError.type,
272
+ msg: truncateExceptionMessage(serverError.msg),
273
+ traceback: truncateExceptionStackTrace(serverError.traceback),
274
+ sentry_event_id: this.sentryEventIds.get(key) || null,
275
+ error_count: count
276
+ });
277
+ }
278
+ });
279
+ this.errorCounts.clear();
280
+ this.errorDetails.clear();
281
+ return data;
282
+ }
283
+ getKey(serverError) {
284
+ const hashInput = [
285
+ serverError.consumer || "",
286
+ serverError.method.toUpperCase(),
287
+ serverError.path,
288
+ serverError.type,
289
+ serverError.msg.trim(),
290
+ serverError.traceback.trim()
291
+ ].join("|");
292
+ return (0, import_crypto.createHash)("md5").update(hashInput).digest("hex");
293
+ }
294
+ };
295
+ __name(_ServerErrorCounter, "ServerErrorCounter");
296
+ var ServerErrorCounter = _ServerErrorCounter;
297
+ function truncateExceptionMessage(msg) {
298
+ if (msg.length <= MAX_MSG_LENGTH) {
299
+ return msg;
300
+ }
301
+ const suffix = "... (truncated)";
302
+ const cutoff = MAX_MSG_LENGTH - suffix.length;
303
+ return msg.substring(0, cutoff) + suffix;
304
+ }
305
+ __name(truncateExceptionMessage, "truncateExceptionMessage");
306
+ function truncateExceptionStackTrace(stack) {
307
+ const suffix = "... (truncated) ...";
308
+ const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
309
+ const lines = stack.trim().split("\n");
310
+ const truncatedLines = [];
311
+ let length = 0;
312
+ for (const line of lines) {
313
+ if (length + line.length + 1 > cutoff) {
314
+ truncatedLines.push(suffix);
315
+ break;
316
+ }
317
+ truncatedLines.push(line);
318
+ length += line.length + 1;
319
+ }
320
+ return truncatedLines.join("\n");
321
+ }
322
+ __name(truncateExceptionStackTrace, "truncateExceptionStackTrace");
323
+
222
324
  // src/common/tempGzipFile.ts
223
325
  var import_buffer = require("buffer");
224
- var import_crypto = require("crypto");
326
+ var import_crypto2 = require("crypto");
225
327
  var import_fs = require("fs");
226
328
  var import_os = require("os");
227
329
  var import_path = require("path");
@@ -234,16 +336,16 @@ var _TempGzipFile = class _TempGzipFile {
234
336
  readyPromise;
235
337
  closedPromise;
236
338
  constructor() {
237
- this.uuid = (0, import_crypto.randomUUID)();
339
+ this.uuid = (0, import_crypto2.randomUUID)();
238
340
  this.filePath = (0, import_path.join)((0, import_os.tmpdir)(), `apitally-${this.uuid}.gz`);
239
341
  this.writeStream = (0, import_fs.createWriteStream)(this.filePath);
240
342
  this.readyPromise = new Promise((resolve, reject) => {
241
343
  this.writeStream.once("ready", resolve);
242
344
  this.writeStream.once("error", reject);
243
345
  });
244
- this.closedPromise = new Promise((resolve) => {
346
+ this.closedPromise = new Promise((resolve, reject) => {
245
347
  this.writeStream.once("close", resolve);
246
- this.writeStream.once("error", resolve);
348
+ this.writeStream.once("error", reject);
247
349
  });
248
350
  this.gzip = (0, import_zlib.createGzip)();
249
351
  this.gzip.pipe(this.writeStream);
@@ -341,6 +443,7 @@ var DEFAULT_CONFIG = {
341
443
  logRequestBody: false,
342
444
  logResponseHeaders: true,
343
445
  logResponseBody: false,
446
+ logException: true,
344
447
  maskQueryParams: [],
345
448
  maskHeaders: [],
346
449
  excludePaths: []
@@ -393,7 +496,10 @@ var _RequestLogger = class _RequestLogger {
393
496
  hasSupportedContentType(headers) {
394
497
  var _a2;
395
498
  const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
396
- return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
499
+ return this.isSupportedContentType(contentType);
500
+ }
501
+ isSupportedContentType(contentType) {
502
+ return typeof contentType === "string" && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
397
503
  }
398
504
  maskQueryParams(search) {
399
505
  const params = new URLSearchParams(search);
@@ -410,7 +516,7 @@ var _RequestLogger = class _RequestLogger {
410
516
  this.shouldMaskHeader(k) ? MASKED : v
411
517
  ]);
412
518
  }
413
- logRequest(request, response) {
519
+ logRequest(request, response, error) {
414
520
  var _a2, _b, _c;
415
521
  if (!this.enabled || this.suspendUntil !== null) return;
416
522
  const url = new URL(request.url);
@@ -421,8 +527,6 @@ var _RequestLogger = class _RequestLogger {
421
527
  }
422
528
  url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
423
529
  request.url = url.toString();
424
- request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
425
- response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
426
530
  if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
427
531
  request.body = void 0;
428
532
  } else if (request.body) {
@@ -455,10 +559,18 @@ var _RequestLogger = class _RequestLogger {
455
559
  }
456
560
  }
457
561
  }
562
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
563
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
458
564
  const item = {
459
- uuid: (0, import_crypto2.randomUUID)(),
565
+ uuid: (0, import_crypto3.randomUUID)(),
460
566
  request: skipEmptyValues(request),
461
- response: skipEmptyValues(response)
567
+ response: skipEmptyValues(response),
568
+ exception: error && this.config.logException ? {
569
+ type: error.name,
570
+ message: truncateExceptionMessage(error.message),
571
+ stacktrace: truncateExceptionStackTrace(error.stack || ""),
572
+ sentryEventId: getSentryEventId()
573
+ } : null
462
574
  };
463
575
  [
464
576
  item.request.body,
@@ -578,7 +690,7 @@ function skipEmptyValues(data) {
578
690
  __name(skipEmptyValues, "skipEmptyValues");
579
691
  function checkWritableFs() {
580
692
  try {
581
- const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto2.randomUUID)()}`);
693
+ const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto3.randomUUID)()}`);
582
694
  (0, import_fs2.writeFileSync)(testPath, "test");
583
695
  (0, import_fs2.unlinkSync)(testPath);
584
696
  return true;
@@ -588,104 +700,6 @@ function checkWritableFs() {
588
700
  }
589
701
  __name(checkWritableFs, "checkWritableFs");
590
702
 
591
- // src/common/serverErrorCounter.ts
592
- var import_crypto3 = require("crypto");
593
- var MAX_MSG_LENGTH = 2048;
594
- var MAX_STACKTRACE_LENGTH = 65536;
595
- var _ServerErrorCounter = class _ServerErrorCounter {
596
- errorCounts;
597
- errorDetails;
598
- sentryEventIds;
599
- sentry;
600
- constructor() {
601
- this.errorCounts = /* @__PURE__ */ new Map();
602
- this.errorDetails = /* @__PURE__ */ new Map();
603
- this.sentryEventIds = /* @__PURE__ */ new Map();
604
- this.tryImportSentry();
605
- }
606
- addServerError(serverError) {
607
- const key = this.getKey(serverError);
608
- if (!this.errorDetails.has(key)) {
609
- this.errorDetails.set(key, serverError);
610
- }
611
- this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
612
- this.captureSentryEventId(key);
613
- }
614
- getAndResetServerErrors() {
615
- const data = [];
616
- this.errorCounts.forEach((count, key) => {
617
- const serverError = this.errorDetails.get(key);
618
- if (serverError) {
619
- data.push({
620
- consumer: serverError.consumer || null,
621
- method: serverError.method,
622
- path: serverError.path,
623
- type: serverError.type,
624
- msg: this.getTruncatedMessage(serverError.msg),
625
- traceback: this.getTruncatedStack(serverError.traceback),
626
- sentry_event_id: this.sentryEventIds.get(key) || null,
627
- error_count: count
628
- });
629
- }
630
- });
631
- this.errorCounts.clear();
632
- this.errorDetails.clear();
633
- return data;
634
- }
635
- getKey(serverError) {
636
- const hashInput = [
637
- serverError.consumer || "",
638
- serverError.method.toUpperCase(),
639
- serverError.path,
640
- serverError.type,
641
- serverError.msg.trim(),
642
- serverError.traceback.trim()
643
- ].join("|");
644
- return (0, import_crypto3.createHash)("md5").update(hashInput).digest("hex");
645
- }
646
- getTruncatedMessage(msg) {
647
- msg = msg.trim();
648
- if (msg.length <= MAX_MSG_LENGTH) {
649
- return msg;
650
- }
651
- const suffix = "... (truncated)";
652
- const cutoff = MAX_MSG_LENGTH - suffix.length;
653
- return msg.substring(0, cutoff) + suffix;
654
- }
655
- getTruncatedStack(stack) {
656
- const suffix = "... (truncated) ...";
657
- const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
658
- const lines = stack.trim().split("\n");
659
- const truncatedLines = [];
660
- let length = 0;
661
- for (const line of lines) {
662
- if (length + line.length + 1 > cutoff) {
663
- truncatedLines.push(suffix);
664
- break;
665
- }
666
- truncatedLines.push(line);
667
- length += line.length + 1;
668
- }
669
- return truncatedLines.join("\n");
670
- }
671
- captureSentryEventId(serverErrorKey) {
672
- if (this.sentry && this.sentry.lastEventId) {
673
- const eventId = this.sentry.lastEventId();
674
- if (eventId) {
675
- this.sentryEventIds.set(serverErrorKey, eventId);
676
- }
677
- }
678
- }
679
- async tryImportSentry() {
680
- try {
681
- this.sentry = await import("@sentry/node");
682
- } catch (e) {
683
- }
684
- }
685
- };
686
- __name(_ServerErrorCounter, "ServerErrorCounter");
687
- var ServerErrorCounter = _ServerErrorCounter;
688
-
689
703
  // src/common/validationErrorCounter.ts
690
704
  var import_crypto4 = require("crypto");
691
705
  var _ValidationErrorCounter = class _ValidationErrorCounter {
@@ -759,6 +773,7 @@ var _ApitallyClient = class _ApitallyClient {
759
773
  syncIntervalId;
760
774
  startupData;
761
775
  startupDataSent = false;
776
+ enabled = true;
762
777
  requestCounter;
763
778
  requestLogger;
764
779
  validationErrorCounter;
@@ -795,12 +810,16 @@ var _ApitallyClient = class _ApitallyClient {
795
810
  }
796
811
  return _ApitallyClient.instance;
797
812
  }
813
+ isEnabled() {
814
+ return this.enabled;
815
+ }
798
816
  static async shutdown() {
799
817
  if (_ApitallyClient.instance) {
800
818
  await _ApitallyClient.instance.handleShutdown();
801
819
  }
802
820
  }
803
821
  async handleShutdown() {
822
+ this.enabled = false;
804
823
  this.stopSync();
805
824
  await this.sendSyncData();
806
825
  await this.sendLogData();
@@ -984,6 +1003,7 @@ var _ApitallyClient = class _ApitallyClient {
984
1003
  if (error instanceof HTTPError) {
985
1004
  if (error.response.status === 404) {
986
1005
  this.logger.error(`Invalid Apitally client ID: '${this.clientId}'`);
1006
+ this.enabled = false;
987
1007
  this.stopSync();
988
1008
  return true;
989
1009
  }
@@ -1032,6 +1052,10 @@ var useApitally = /* @__PURE__ */ __name((app, config) => {
1032
1052
  }, "useApitally");
1033
1053
  var getMiddleware = /* @__PURE__ */ __name((client) => {
1034
1054
  return async (c, next) => {
1055
+ if (!client.isEnabled()) {
1056
+ await next();
1057
+ return;
1058
+ }
1035
1059
  const startTime = import_perf_hooks.performance.now();
1036
1060
  await next();
1037
1061
  let response;
@@ -1077,10 +1101,11 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
1077
1101
  let requestBody;
1078
1102
  let responseBody;
1079
1103
  let newResponse2 = response;
1104
+ const responseContentType = c.res.headers.get("content-type");
1080
1105
  if (client.requestLogger.config.logRequestBody) {
1081
1106
  requestBody = Buffer.from(await c.req.arrayBuffer());
1082
1107
  }
1083
- if (client.requestLogger.config.logResponseBody) {
1108
+ if (client.requestLogger.config.logResponseBody && client.requestLogger.isSupportedContentType(responseContentType)) {
1084
1109
  [responseBody, newResponse2] = await getResponseBody(response);
1085
1110
  response = newResponse2;
1086
1111
  }
@@ -1099,7 +1124,7 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
1099
1124
  headers: convertHeaders(c.res.headers),
1100
1125
  size: responseSize,
1101
1126
  body: responseBody
1102
- });
1127
+ }, c.error);
1103
1128
  }
1104
1129
  c.res = response;
1105
1130
  };