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
@@ -210,14 +210,116 @@ var RequestCounter = _RequestCounter;
210
210
  // src/common/requestLogger.ts
211
211
  var import_async_lock = __toESM(require("async-lock"), 1);
212
212
  var import_buffer2 = require("buffer");
213
- var import_crypto2 = require("crypto");
213
+ var import_crypto3 = require("crypto");
214
214
  var import_fs2 = require("fs");
215
215
  var import_os2 = require("os");
216
216
  var import_path2 = require("path");
217
217
 
218
+ // src/common/sentry.ts
219
+ var sentry;
220
+ (async () => {
221
+ try {
222
+ sentry = await import("@sentry/node");
223
+ } catch (e) {
224
+ }
225
+ })();
226
+ function getSentryEventId() {
227
+ if (sentry && sentry.lastEventId) {
228
+ return sentry.lastEventId();
229
+ }
230
+ return void 0;
231
+ }
232
+ __name(getSentryEventId, "getSentryEventId");
233
+
234
+ // src/common/serverErrorCounter.ts
235
+ var import_crypto = require("crypto");
236
+ var MAX_MSG_LENGTH = 2048;
237
+ var MAX_STACKTRACE_LENGTH = 65536;
238
+ var _ServerErrorCounter = class _ServerErrorCounter {
239
+ errorCounts;
240
+ errorDetails;
241
+ sentryEventIds;
242
+ constructor() {
243
+ this.errorCounts = /* @__PURE__ */ new Map();
244
+ this.errorDetails = /* @__PURE__ */ new Map();
245
+ this.sentryEventIds = /* @__PURE__ */ new Map();
246
+ }
247
+ addServerError(serverError) {
248
+ const key = this.getKey(serverError);
249
+ if (!this.errorDetails.has(key)) {
250
+ this.errorDetails.set(key, serverError);
251
+ }
252
+ this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
253
+ const sentryEventId = getSentryEventId();
254
+ if (sentryEventId) {
255
+ this.sentryEventIds.set(key, sentryEventId);
256
+ }
257
+ }
258
+ getAndResetServerErrors() {
259
+ const data = [];
260
+ this.errorCounts.forEach((count, key) => {
261
+ const serverError = this.errorDetails.get(key);
262
+ if (serverError) {
263
+ data.push({
264
+ consumer: serverError.consumer || null,
265
+ method: serverError.method,
266
+ path: serverError.path,
267
+ type: serverError.type,
268
+ msg: truncateExceptionMessage(serverError.msg),
269
+ traceback: truncateExceptionStackTrace(serverError.traceback),
270
+ sentry_event_id: this.sentryEventIds.get(key) || null,
271
+ error_count: count
272
+ });
273
+ }
274
+ });
275
+ this.errorCounts.clear();
276
+ this.errorDetails.clear();
277
+ return data;
278
+ }
279
+ getKey(serverError) {
280
+ const hashInput = [
281
+ serverError.consumer || "",
282
+ serverError.method.toUpperCase(),
283
+ serverError.path,
284
+ serverError.type,
285
+ serverError.msg.trim(),
286
+ serverError.traceback.trim()
287
+ ].join("|");
288
+ return (0, import_crypto.createHash)("md5").update(hashInput).digest("hex");
289
+ }
290
+ };
291
+ __name(_ServerErrorCounter, "ServerErrorCounter");
292
+ var ServerErrorCounter = _ServerErrorCounter;
293
+ function truncateExceptionMessage(msg) {
294
+ if (msg.length <= MAX_MSG_LENGTH) {
295
+ return msg;
296
+ }
297
+ const suffix = "... (truncated)";
298
+ const cutoff = MAX_MSG_LENGTH - suffix.length;
299
+ return msg.substring(0, cutoff) + suffix;
300
+ }
301
+ __name(truncateExceptionMessage, "truncateExceptionMessage");
302
+ function truncateExceptionStackTrace(stack) {
303
+ const suffix = "... (truncated) ...";
304
+ const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
305
+ const lines = stack.trim().split("\n");
306
+ const truncatedLines = [];
307
+ let length = 0;
308
+ for (const line of lines) {
309
+ if (length + line.length + 1 > cutoff) {
310
+ truncatedLines.push(suffix);
311
+ break;
312
+ }
313
+ truncatedLines.push(line);
314
+ length += line.length + 1;
315
+ }
316
+ return truncatedLines.join("\n");
317
+ }
318
+ __name(truncateExceptionStackTrace, "truncateExceptionStackTrace");
319
+
218
320
  // src/common/tempGzipFile.ts
219
321
  var import_buffer = require("buffer");
220
- var import_crypto = require("crypto");
322
+ var import_crypto2 = require("crypto");
221
323
  var import_fs = require("fs");
222
324
  var import_os = require("os");
223
325
  var import_path = require("path");
@@ -230,16 +332,16 @@ var _TempGzipFile = class _TempGzipFile {
230
332
  readyPromise;
231
333
  closedPromise;
232
334
  constructor() {
233
- this.uuid = (0, import_crypto.randomUUID)();
335
+ this.uuid = (0, import_crypto2.randomUUID)();
234
336
  this.filePath = (0, import_path.join)((0, import_os.tmpdir)(), `apitally-${this.uuid}.gz`);
235
337
  this.writeStream = (0, import_fs.createWriteStream)(this.filePath);
236
338
  this.readyPromise = new Promise((resolve, reject) => {
237
339
  this.writeStream.once("ready", resolve);
238
340
  this.writeStream.once("error", reject);
239
341
  });
240
- this.closedPromise = new Promise((resolve) => {
342
+ this.closedPromise = new Promise((resolve, reject) => {
241
343
  this.writeStream.once("close", resolve);
242
- this.writeStream.once("error", resolve);
344
+ this.writeStream.once("error", reject);
243
345
  });
244
346
  this.gzip = (0, import_zlib.createGzip)();
245
347
  this.gzip.pipe(this.writeStream);
@@ -337,6 +439,7 @@ var DEFAULT_CONFIG = {
337
439
  logRequestBody: false,
338
440
  logResponseHeaders: true,
339
441
  logResponseBody: false,
442
+ logException: true,
340
443
  maskQueryParams: [],
341
444
  maskHeaders: [],
342
445
  excludePaths: []
@@ -389,7 +492,10 @@ var _RequestLogger = class _RequestLogger {
389
492
  hasSupportedContentType(headers) {
390
493
  var _a2;
391
494
  const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
392
- return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
495
+ return this.isSupportedContentType(contentType);
496
+ }
497
+ isSupportedContentType(contentType) {
498
+ return typeof contentType === "string" && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
393
499
  }
394
500
  maskQueryParams(search) {
395
501
  const params = new URLSearchParams(search);
@@ -406,7 +512,7 @@ var _RequestLogger = class _RequestLogger {
406
512
  this.shouldMaskHeader(k) ? MASKED : v
407
513
  ]);
408
514
  }
409
- logRequest(request, response) {
515
+ logRequest(request, response, error) {
410
516
  var _a2, _b, _c;
411
517
  if (!this.enabled || this.suspendUntil !== null) return;
412
518
  const url = new URL(request.url);
@@ -417,8 +523,6 @@ var _RequestLogger = class _RequestLogger {
417
523
  }
418
524
  url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
419
525
  request.url = url.toString();
420
- request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
421
- response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
422
526
  if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
423
527
  request.body = void 0;
424
528
  } else if (request.body) {
@@ -451,10 +555,18 @@ var _RequestLogger = class _RequestLogger {
451
555
  }
452
556
  }
453
557
  }
558
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
559
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
454
560
  const item = {
455
- uuid: (0, import_crypto2.randomUUID)(),
561
+ uuid: (0, import_crypto3.randomUUID)(),
456
562
  request: skipEmptyValues(request),
457
- response: skipEmptyValues(response)
563
+ response: skipEmptyValues(response),
564
+ exception: error && this.config.logException ? {
565
+ type: error.name,
566
+ message: truncateExceptionMessage(error.message),
567
+ stacktrace: truncateExceptionStackTrace(error.stack || ""),
568
+ sentryEventId: getSentryEventId()
569
+ } : null
458
570
  };
459
571
  [
460
572
  item.request.body,
@@ -606,7 +718,7 @@ function skipEmptyValues(data) {
606
718
  __name(skipEmptyValues, "skipEmptyValues");
607
719
  function checkWritableFs() {
608
720
  try {
609
- const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto2.randomUUID)()}`);
721
+ const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto3.randomUUID)()}`);
610
722
  (0, import_fs2.writeFileSync)(testPath, "test");
611
723
  (0, import_fs2.unlinkSync)(testPath);
612
724
  return true;
@@ -616,104 +728,6 @@ function checkWritableFs() {
616
728
  }
617
729
  __name(checkWritableFs, "checkWritableFs");
618
730
 
619
- // src/common/serverErrorCounter.ts
620
- var import_crypto3 = require("crypto");
621
- var MAX_MSG_LENGTH = 2048;
622
- var MAX_STACKTRACE_LENGTH = 65536;
623
- var _ServerErrorCounter = class _ServerErrorCounter {
624
- errorCounts;
625
- errorDetails;
626
- sentryEventIds;
627
- sentry;
628
- constructor() {
629
- this.errorCounts = /* @__PURE__ */ new Map();
630
- this.errorDetails = /* @__PURE__ */ new Map();
631
- this.sentryEventIds = /* @__PURE__ */ new Map();
632
- this.tryImportSentry();
633
- }
634
- addServerError(serverError) {
635
- const key = this.getKey(serverError);
636
- if (!this.errorDetails.has(key)) {
637
- this.errorDetails.set(key, serverError);
638
- }
639
- this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
640
- this.captureSentryEventId(key);
641
- }
642
- getAndResetServerErrors() {
643
- const data = [];
644
- this.errorCounts.forEach((count, key) => {
645
- const serverError = this.errorDetails.get(key);
646
- if (serverError) {
647
- data.push({
648
- consumer: serverError.consumer || null,
649
- method: serverError.method,
650
- path: serverError.path,
651
- type: serverError.type,
652
- msg: this.getTruncatedMessage(serverError.msg),
653
- traceback: this.getTruncatedStack(serverError.traceback),
654
- sentry_event_id: this.sentryEventIds.get(key) || null,
655
- error_count: count
656
- });
657
- }
658
- });
659
- this.errorCounts.clear();
660
- this.errorDetails.clear();
661
- return data;
662
- }
663
- getKey(serverError) {
664
- const hashInput = [
665
- serverError.consumer || "",
666
- serverError.method.toUpperCase(),
667
- serverError.path,
668
- serverError.type,
669
- serverError.msg.trim(),
670
- serverError.traceback.trim()
671
- ].join("|");
672
- return (0, import_crypto3.createHash)("md5").update(hashInput).digest("hex");
673
- }
674
- getTruncatedMessage(msg) {
675
- msg = msg.trim();
676
- if (msg.length <= MAX_MSG_LENGTH) {
677
- return msg;
678
- }
679
- const suffix = "... (truncated)";
680
- const cutoff = MAX_MSG_LENGTH - suffix.length;
681
- return msg.substring(0, cutoff) + suffix;
682
- }
683
- getTruncatedStack(stack) {
684
- const suffix = "... (truncated) ...";
685
- const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
686
- const lines = stack.trim().split("\n");
687
- const truncatedLines = [];
688
- let length = 0;
689
- for (const line of lines) {
690
- if (length + line.length + 1 > cutoff) {
691
- truncatedLines.push(suffix);
692
- break;
693
- }
694
- truncatedLines.push(line);
695
- length += line.length + 1;
696
- }
697
- return truncatedLines.join("\n");
698
- }
699
- captureSentryEventId(serverErrorKey) {
700
- if (this.sentry && this.sentry.lastEventId) {
701
- const eventId = this.sentry.lastEventId();
702
- if (eventId) {
703
- this.sentryEventIds.set(serverErrorKey, eventId);
704
- }
705
- }
706
- }
707
- async tryImportSentry() {
708
- try {
709
- this.sentry = await import("@sentry/node");
710
- } catch (e) {
711
- }
712
- }
713
- };
714
- __name(_ServerErrorCounter, "ServerErrorCounter");
715
- var ServerErrorCounter = _ServerErrorCounter;
716
-
717
731
  // src/common/validationErrorCounter.ts
718
732
  var import_crypto4 = require("crypto");
719
733
  var _ValidationErrorCounter = class _ValidationErrorCounter {
@@ -787,6 +801,7 @@ var _ApitallyClient = class _ApitallyClient {
787
801
  syncIntervalId;
788
802
  startupData;
789
803
  startupDataSent = false;
804
+ enabled = true;
790
805
  requestCounter;
791
806
  requestLogger;
792
807
  validationErrorCounter;
@@ -823,12 +838,16 @@ var _ApitallyClient = class _ApitallyClient {
823
838
  }
824
839
  return _ApitallyClient.instance;
825
840
  }
841
+ isEnabled() {
842
+ return this.enabled;
843
+ }
826
844
  static async shutdown() {
827
845
  if (_ApitallyClient.instance) {
828
846
  await _ApitallyClient.instance.handleShutdown();
829
847
  }
830
848
  }
831
849
  async handleShutdown() {
850
+ this.enabled = false;
832
851
  this.stopSync();
833
852
  await this.sendSyncData();
834
853
  await this.sendLogData();
@@ -1012,6 +1031,7 @@ var _ApitallyClient = class _ApitallyClient {
1012
1031
  if (error instanceof HTTPError) {
1013
1032
  if (error.response.status === 404) {
1014
1033
  this.logger.error(`Invalid Apitally client ID: '${this.clientId}'`);
1034
+ this.enabled = false;
1015
1035
  this.stopSync();
1016
1036
  return true;
1017
1037
  }
@@ -1061,8 +1081,13 @@ var useApitally = /* @__PURE__ */ __name((app, config) => {
1061
1081
  var getMiddleware = /* @__PURE__ */ __name((client) => {
1062
1082
  return async (ctx, next) => {
1063
1083
  var _a2;
1084
+ if (!client.isEnabled()) {
1085
+ await next();
1086
+ return;
1087
+ }
1064
1088
  let path;
1065
1089
  let statusCode;
1090
+ let serverError;
1066
1091
  const startTime = performance.now();
1067
1092
  try {
1068
1093
  await next();
@@ -1070,6 +1095,7 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
1070
1095
  path = getPath(ctx);
1071
1096
  statusCode = error.statusCode || error.status || 500;
1072
1097
  if (path && statusCode === 500 && error instanceof Error) {
1098
+ serverError = error;
1073
1099
  client.serverErrorCounter.addServerError({
1074
1100
  consumer: (_a2 = getConsumer(ctx)) == null ? void 0 : _a2.identifier,
1075
1101
  method: ctx.request.method,
@@ -1121,7 +1147,7 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
1121
1147
  headers: convertHeaders(ctx.response.headers),
1122
1148
  size: Number(ctx.response.headers["content-length"]),
1123
1149
  body: convertBody(ctx.response.body, ctx.response.get("content-type"))
1124
- });
1150
+ }, serverError);
1125
1151
  }
1126
1152
  }
1127
1153
  };