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
@@ -212,14 +212,116 @@ var RequestCounter = _RequestCounter;
212
212
  // src/common/requestLogger.ts
213
213
  var import_async_lock = __toESM(require("async-lock"), 1);
214
214
  var import_buffer2 = require("buffer");
215
- var import_crypto2 = require("crypto");
215
+ var import_crypto3 = require("crypto");
216
216
  var import_fs2 = require("fs");
217
217
  var import_os2 = require("os");
218
218
  var import_path2 = require("path");
219
219
 
220
+ // src/common/sentry.ts
221
+ var sentry;
222
+ (async () => {
223
+ try {
224
+ sentry = await import("@sentry/node");
225
+ } catch (e) {
226
+ }
227
+ })();
228
+ function getSentryEventId() {
229
+ if (sentry && sentry.lastEventId) {
230
+ return sentry.lastEventId();
231
+ }
232
+ return void 0;
233
+ }
234
+ __name(getSentryEventId, "getSentryEventId");
235
+
236
+ // src/common/serverErrorCounter.ts
237
+ var import_crypto = require("crypto");
238
+ var MAX_MSG_LENGTH = 2048;
239
+ var MAX_STACKTRACE_LENGTH = 65536;
240
+ var _ServerErrorCounter = class _ServerErrorCounter {
241
+ errorCounts;
242
+ errorDetails;
243
+ sentryEventIds;
244
+ constructor() {
245
+ this.errorCounts = /* @__PURE__ */ new Map();
246
+ this.errorDetails = /* @__PURE__ */ new Map();
247
+ this.sentryEventIds = /* @__PURE__ */ new Map();
248
+ }
249
+ addServerError(serverError) {
250
+ const key = this.getKey(serverError);
251
+ if (!this.errorDetails.has(key)) {
252
+ this.errorDetails.set(key, serverError);
253
+ }
254
+ this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
255
+ const sentryEventId = getSentryEventId();
256
+ if (sentryEventId) {
257
+ this.sentryEventIds.set(key, sentryEventId);
258
+ }
259
+ }
260
+ getAndResetServerErrors() {
261
+ const data = [];
262
+ this.errorCounts.forEach((count, key) => {
263
+ const serverError = this.errorDetails.get(key);
264
+ if (serverError) {
265
+ data.push({
266
+ consumer: serverError.consumer || null,
267
+ method: serverError.method,
268
+ path: serverError.path,
269
+ type: serverError.type,
270
+ msg: truncateExceptionMessage(serverError.msg),
271
+ traceback: truncateExceptionStackTrace(serverError.traceback),
272
+ sentry_event_id: this.sentryEventIds.get(key) || null,
273
+ error_count: count
274
+ });
275
+ }
276
+ });
277
+ this.errorCounts.clear();
278
+ this.errorDetails.clear();
279
+ return data;
280
+ }
281
+ getKey(serverError) {
282
+ const hashInput = [
283
+ serverError.consumer || "",
284
+ serverError.method.toUpperCase(),
285
+ serverError.path,
286
+ serverError.type,
287
+ serverError.msg.trim(),
288
+ serverError.traceback.trim()
289
+ ].join("|");
290
+ return (0, import_crypto.createHash)("md5").update(hashInput).digest("hex");
291
+ }
292
+ };
293
+ __name(_ServerErrorCounter, "ServerErrorCounter");
294
+ var ServerErrorCounter = _ServerErrorCounter;
295
+ function truncateExceptionMessage(msg) {
296
+ if (msg.length <= MAX_MSG_LENGTH) {
297
+ return msg;
298
+ }
299
+ const suffix = "... (truncated)";
300
+ const cutoff = MAX_MSG_LENGTH - suffix.length;
301
+ return msg.substring(0, cutoff) + suffix;
302
+ }
303
+ __name(truncateExceptionMessage, "truncateExceptionMessage");
304
+ function truncateExceptionStackTrace(stack) {
305
+ const suffix = "... (truncated) ...";
306
+ const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
307
+ const lines = stack.trim().split("\n");
308
+ const truncatedLines = [];
309
+ let length = 0;
310
+ for (const line of lines) {
311
+ if (length + line.length + 1 > cutoff) {
312
+ truncatedLines.push(suffix);
313
+ break;
314
+ }
315
+ truncatedLines.push(line);
316
+ length += line.length + 1;
317
+ }
318
+ return truncatedLines.join("\n");
319
+ }
320
+ __name(truncateExceptionStackTrace, "truncateExceptionStackTrace");
321
+
220
322
  // src/common/tempGzipFile.ts
221
323
  var import_buffer = require("buffer");
222
- var import_crypto = require("crypto");
324
+ var import_crypto2 = require("crypto");
223
325
  var import_fs = require("fs");
224
326
  var import_os = require("os");
225
327
  var import_path = require("path");
@@ -232,16 +334,16 @@ var _TempGzipFile = class _TempGzipFile {
232
334
  readyPromise;
233
335
  closedPromise;
234
336
  constructor() {
235
- this.uuid = (0, import_crypto.randomUUID)();
337
+ this.uuid = (0, import_crypto2.randomUUID)();
236
338
  this.filePath = (0, import_path.join)((0, import_os.tmpdir)(), `apitally-${this.uuid}.gz`);
237
339
  this.writeStream = (0, import_fs.createWriteStream)(this.filePath);
238
340
  this.readyPromise = new Promise((resolve, reject) => {
239
341
  this.writeStream.once("ready", resolve);
240
342
  this.writeStream.once("error", reject);
241
343
  });
242
- this.closedPromise = new Promise((resolve) => {
344
+ this.closedPromise = new Promise((resolve, reject) => {
243
345
  this.writeStream.once("close", resolve);
244
- this.writeStream.once("error", resolve);
346
+ this.writeStream.once("error", reject);
245
347
  });
246
348
  this.gzip = (0, import_zlib.createGzip)();
247
349
  this.gzip.pipe(this.writeStream);
@@ -339,6 +441,7 @@ var DEFAULT_CONFIG = {
339
441
  logRequestBody: false,
340
442
  logResponseHeaders: true,
341
443
  logResponseBody: false,
444
+ logException: true,
342
445
  maskQueryParams: [],
343
446
  maskHeaders: [],
344
447
  excludePaths: []
@@ -391,7 +494,10 @@ var _RequestLogger = class _RequestLogger {
391
494
  hasSupportedContentType(headers) {
392
495
  var _a2;
393
496
  const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
394
- return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
497
+ return this.isSupportedContentType(contentType);
498
+ }
499
+ isSupportedContentType(contentType) {
500
+ return typeof contentType === "string" && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
395
501
  }
396
502
  maskQueryParams(search) {
397
503
  const params = new URLSearchParams(search);
@@ -408,7 +514,7 @@ var _RequestLogger = class _RequestLogger {
408
514
  this.shouldMaskHeader(k) ? MASKED : v
409
515
  ]);
410
516
  }
411
- logRequest(request, response) {
517
+ logRequest(request, response, error) {
412
518
  var _a2, _b, _c;
413
519
  if (!this.enabled || this.suspendUntil !== null) return;
414
520
  const url = new URL(request.url);
@@ -419,8 +525,6 @@ var _RequestLogger = class _RequestLogger {
419
525
  }
420
526
  url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
421
527
  request.url = url.toString();
422
- request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
423
- response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
424
528
  if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
425
529
  request.body = void 0;
426
530
  } else if (request.body) {
@@ -453,10 +557,18 @@ var _RequestLogger = class _RequestLogger {
453
557
  }
454
558
  }
455
559
  }
560
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
561
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
456
562
  const item = {
457
- uuid: (0, import_crypto2.randomUUID)(),
563
+ uuid: (0, import_crypto3.randomUUID)(),
458
564
  request: skipEmptyValues(request),
459
- response: skipEmptyValues(response)
565
+ response: skipEmptyValues(response),
566
+ exception: error && this.config.logException ? {
567
+ type: error.name,
568
+ message: truncateExceptionMessage(error.message),
569
+ stacktrace: truncateExceptionStackTrace(error.stack || ""),
570
+ sentryEventId: getSentryEventId()
571
+ } : null
460
572
  };
461
573
  [
462
574
  item.request.body,
@@ -576,7 +688,7 @@ function skipEmptyValues(data) {
576
688
  __name(skipEmptyValues, "skipEmptyValues");
577
689
  function checkWritableFs() {
578
690
  try {
579
- const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto2.randomUUID)()}`);
691
+ const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto3.randomUUID)()}`);
580
692
  (0, import_fs2.writeFileSync)(testPath, "test");
581
693
  (0, import_fs2.unlinkSync)(testPath);
582
694
  return true;
@@ -586,104 +698,6 @@ function checkWritableFs() {
586
698
  }
587
699
  __name(checkWritableFs, "checkWritableFs");
588
700
 
589
- // src/common/serverErrorCounter.ts
590
- var import_crypto3 = require("crypto");
591
- var MAX_MSG_LENGTH = 2048;
592
- var MAX_STACKTRACE_LENGTH = 65536;
593
- var _ServerErrorCounter = class _ServerErrorCounter {
594
- errorCounts;
595
- errorDetails;
596
- sentryEventIds;
597
- sentry;
598
- constructor() {
599
- this.errorCounts = /* @__PURE__ */ new Map();
600
- this.errorDetails = /* @__PURE__ */ new Map();
601
- this.sentryEventIds = /* @__PURE__ */ new Map();
602
- this.tryImportSentry();
603
- }
604
- addServerError(serverError) {
605
- const key = this.getKey(serverError);
606
- if (!this.errorDetails.has(key)) {
607
- this.errorDetails.set(key, serverError);
608
- }
609
- this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
610
- this.captureSentryEventId(key);
611
- }
612
- getAndResetServerErrors() {
613
- const data = [];
614
- this.errorCounts.forEach((count, key) => {
615
- const serverError = this.errorDetails.get(key);
616
- if (serverError) {
617
- data.push({
618
- consumer: serverError.consumer || null,
619
- method: serverError.method,
620
- path: serverError.path,
621
- type: serverError.type,
622
- msg: this.getTruncatedMessage(serverError.msg),
623
- traceback: this.getTruncatedStack(serverError.traceback),
624
- sentry_event_id: this.sentryEventIds.get(key) || null,
625
- error_count: count
626
- });
627
- }
628
- });
629
- this.errorCounts.clear();
630
- this.errorDetails.clear();
631
- return data;
632
- }
633
- getKey(serverError) {
634
- const hashInput = [
635
- serverError.consumer || "",
636
- serverError.method.toUpperCase(),
637
- serverError.path,
638
- serverError.type,
639
- serverError.msg.trim(),
640
- serverError.traceback.trim()
641
- ].join("|");
642
- return (0, import_crypto3.createHash)("md5").update(hashInput).digest("hex");
643
- }
644
- getTruncatedMessage(msg) {
645
- msg = msg.trim();
646
- if (msg.length <= MAX_MSG_LENGTH) {
647
- return msg;
648
- }
649
- const suffix = "... (truncated)";
650
- const cutoff = MAX_MSG_LENGTH - suffix.length;
651
- return msg.substring(0, cutoff) + suffix;
652
- }
653
- getTruncatedStack(stack) {
654
- const suffix = "... (truncated) ...";
655
- const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
656
- const lines = stack.trim().split("\n");
657
- const truncatedLines = [];
658
- let length = 0;
659
- for (const line of lines) {
660
- if (length + line.length + 1 > cutoff) {
661
- truncatedLines.push(suffix);
662
- break;
663
- }
664
- truncatedLines.push(line);
665
- length += line.length + 1;
666
- }
667
- return truncatedLines.join("\n");
668
- }
669
- captureSentryEventId(serverErrorKey) {
670
- if (this.sentry && this.sentry.lastEventId) {
671
- const eventId = this.sentry.lastEventId();
672
- if (eventId) {
673
- this.sentryEventIds.set(serverErrorKey, eventId);
674
- }
675
- }
676
- }
677
- async tryImportSentry() {
678
- try {
679
- this.sentry = await import("@sentry/node");
680
- } catch (e) {
681
- }
682
- }
683
- };
684
- __name(_ServerErrorCounter, "ServerErrorCounter");
685
- var ServerErrorCounter = _ServerErrorCounter;
686
-
687
701
  // src/common/validationErrorCounter.ts
688
702
  var import_crypto4 = require("crypto");
689
703
  var _ValidationErrorCounter = class _ValidationErrorCounter {
@@ -757,6 +771,7 @@ var _ApitallyClient = class _ApitallyClient {
757
771
  syncIntervalId;
758
772
  startupData;
759
773
  startupDataSent = false;
774
+ enabled = true;
760
775
  requestCounter;
761
776
  requestLogger;
762
777
  validationErrorCounter;
@@ -793,12 +808,16 @@ var _ApitallyClient = class _ApitallyClient {
793
808
  }
794
809
  return _ApitallyClient.instance;
795
810
  }
811
+ isEnabled() {
812
+ return this.enabled;
813
+ }
796
814
  static async shutdown() {
797
815
  if (_ApitallyClient.instance) {
798
816
  await _ApitallyClient.instance.handleShutdown();
799
817
  }
800
818
  }
801
819
  async handleShutdown() {
820
+ this.enabled = false;
802
821
  this.stopSync();
803
822
  await this.sendSyncData();
804
823
  await this.sendLogData();
@@ -982,6 +1001,7 @@ var _ApitallyClient = class _ApitallyClient {
982
1001
  if (error instanceof HTTPError) {
983
1002
  if (error.response.status === 404) {
984
1003
  this.logger.error(`Invalid Apitally client ID: '${this.clientId}'`);
1004
+ this.enabled = false;
985
1005
  this.stopSync();
986
1006
  return true;
987
1007
  }
@@ -1030,6 +1050,10 @@ var useApitally = /* @__PURE__ */ __name((app, config) => {
1030
1050
  }, "useApitally");
1031
1051
  var getMiddleware = /* @__PURE__ */ __name((client) => {
1032
1052
  return async (c, next) => {
1053
+ if (!client.isEnabled()) {
1054
+ await next();
1055
+ return;
1056
+ }
1033
1057
  const startTime = import_perf_hooks.performance.now();
1034
1058
  await next();
1035
1059
  let response;
@@ -1075,10 +1099,11 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
1075
1099
  let requestBody;
1076
1100
  let responseBody;
1077
1101
  let newResponse2 = response;
1102
+ const responseContentType = c.res.headers.get("content-type");
1078
1103
  if (client.requestLogger.config.logRequestBody) {
1079
1104
  requestBody = Buffer.from(await c.req.arrayBuffer());
1080
1105
  }
1081
- if (client.requestLogger.config.logResponseBody) {
1106
+ if (client.requestLogger.config.logResponseBody && client.requestLogger.isSupportedContentType(responseContentType)) {
1082
1107
  [responseBody, newResponse2] = await getResponseBody(response);
1083
1108
  response = newResponse2;
1084
1109
  }
@@ -1097,7 +1122,7 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
1097
1122
  headers: convertHeaders(c.res.headers),
1098
1123
  size: responseSize,
1099
1124
  body: responseBody
1100
- });
1125
+ }, c.error);
1101
1126
  }
1102
1127
  c.res = response;
1103
1128
  };