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
@@ -215,14 +215,116 @@ var RequestCounter = _RequestCounter;
215
215
  // src/common/requestLogger.ts
216
216
  var import_async_lock = __toESM(require("async-lock"), 1);
217
217
  var import_buffer2 = require("buffer");
218
- var import_crypto2 = require("crypto");
218
+ var import_crypto3 = require("crypto");
219
219
  var import_fs2 = require("fs");
220
220
  var import_os2 = require("os");
221
221
  var import_path2 = require("path");
222
222
 
223
+ // src/common/sentry.ts
224
+ var sentry;
225
+ (async () => {
226
+ try {
227
+ sentry = await import("@sentry/node");
228
+ } catch (e) {
229
+ }
230
+ })();
231
+ function getSentryEventId() {
232
+ if (sentry && sentry.lastEventId) {
233
+ return sentry.lastEventId();
234
+ }
235
+ return void 0;
236
+ }
237
+ __name(getSentryEventId, "getSentryEventId");
238
+
239
+ // src/common/serverErrorCounter.ts
240
+ var import_crypto = require("crypto");
241
+ var MAX_MSG_LENGTH = 2048;
242
+ var MAX_STACKTRACE_LENGTH = 65536;
243
+ var _ServerErrorCounter = class _ServerErrorCounter {
244
+ errorCounts;
245
+ errorDetails;
246
+ sentryEventIds;
247
+ constructor() {
248
+ this.errorCounts = /* @__PURE__ */ new Map();
249
+ this.errorDetails = /* @__PURE__ */ new Map();
250
+ this.sentryEventIds = /* @__PURE__ */ new Map();
251
+ }
252
+ addServerError(serverError) {
253
+ const key = this.getKey(serverError);
254
+ if (!this.errorDetails.has(key)) {
255
+ this.errorDetails.set(key, serverError);
256
+ }
257
+ this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
258
+ const sentryEventId = getSentryEventId();
259
+ if (sentryEventId) {
260
+ this.sentryEventIds.set(key, sentryEventId);
261
+ }
262
+ }
263
+ getAndResetServerErrors() {
264
+ const data = [];
265
+ this.errorCounts.forEach((count, key) => {
266
+ const serverError = this.errorDetails.get(key);
267
+ if (serverError) {
268
+ data.push({
269
+ consumer: serverError.consumer || null,
270
+ method: serverError.method,
271
+ path: serverError.path,
272
+ type: serverError.type,
273
+ msg: truncateExceptionMessage(serverError.msg),
274
+ traceback: truncateExceptionStackTrace(serverError.traceback),
275
+ sentry_event_id: this.sentryEventIds.get(key) || null,
276
+ error_count: count
277
+ });
278
+ }
279
+ });
280
+ this.errorCounts.clear();
281
+ this.errorDetails.clear();
282
+ return data;
283
+ }
284
+ getKey(serverError) {
285
+ const hashInput = [
286
+ serverError.consumer || "",
287
+ serverError.method.toUpperCase(),
288
+ serverError.path,
289
+ serverError.type,
290
+ serverError.msg.trim(),
291
+ serverError.traceback.trim()
292
+ ].join("|");
293
+ return (0, import_crypto.createHash)("md5").update(hashInput).digest("hex");
294
+ }
295
+ };
296
+ __name(_ServerErrorCounter, "ServerErrorCounter");
297
+ var ServerErrorCounter = _ServerErrorCounter;
298
+ function truncateExceptionMessage(msg) {
299
+ if (msg.length <= MAX_MSG_LENGTH) {
300
+ return msg;
301
+ }
302
+ const suffix = "... (truncated)";
303
+ const cutoff = MAX_MSG_LENGTH - suffix.length;
304
+ return msg.substring(0, cutoff) + suffix;
305
+ }
306
+ __name(truncateExceptionMessage, "truncateExceptionMessage");
307
+ function truncateExceptionStackTrace(stack) {
308
+ const suffix = "... (truncated) ...";
309
+ const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
310
+ const lines = stack.trim().split("\n");
311
+ const truncatedLines = [];
312
+ let length = 0;
313
+ for (const line of lines) {
314
+ if (length + line.length + 1 > cutoff) {
315
+ truncatedLines.push(suffix);
316
+ break;
317
+ }
318
+ truncatedLines.push(line);
319
+ length += line.length + 1;
320
+ }
321
+ return truncatedLines.join("\n");
322
+ }
323
+ __name(truncateExceptionStackTrace, "truncateExceptionStackTrace");
324
+
223
325
  // src/common/tempGzipFile.ts
224
326
  var import_buffer = require("buffer");
225
- var import_crypto = require("crypto");
327
+ var import_crypto2 = require("crypto");
226
328
  var import_fs = require("fs");
227
329
  var import_os = require("os");
228
330
  var import_path = require("path");
@@ -235,16 +337,16 @@ var _TempGzipFile = class _TempGzipFile {
235
337
  readyPromise;
236
338
  closedPromise;
237
339
  constructor() {
238
- this.uuid = (0, import_crypto.randomUUID)();
340
+ this.uuid = (0, import_crypto2.randomUUID)();
239
341
  this.filePath = (0, import_path.join)((0, import_os.tmpdir)(), `apitally-${this.uuid}.gz`);
240
342
  this.writeStream = (0, import_fs.createWriteStream)(this.filePath);
241
343
  this.readyPromise = new Promise((resolve, reject) => {
242
344
  this.writeStream.once("ready", resolve);
243
345
  this.writeStream.once("error", reject);
244
346
  });
245
- this.closedPromise = new Promise((resolve) => {
347
+ this.closedPromise = new Promise((resolve, reject) => {
246
348
  this.writeStream.once("close", resolve);
247
- this.writeStream.once("error", resolve);
349
+ this.writeStream.once("error", reject);
248
350
  });
249
351
  this.gzip = (0, import_zlib.createGzip)();
250
352
  this.gzip.pipe(this.writeStream);
@@ -342,6 +444,7 @@ var DEFAULT_CONFIG = {
342
444
  logRequestBody: false,
343
445
  logResponseHeaders: true,
344
446
  logResponseBody: false,
447
+ logException: true,
345
448
  maskQueryParams: [],
346
449
  maskHeaders: [],
347
450
  excludePaths: []
@@ -394,7 +497,10 @@ var _RequestLogger = class _RequestLogger {
394
497
  hasSupportedContentType(headers) {
395
498
  var _a3;
396
499
  const contentType = (_a3 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a3[1];
397
- return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
500
+ return this.isSupportedContentType(contentType);
501
+ }
502
+ isSupportedContentType(contentType) {
503
+ return typeof contentType === "string" && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
398
504
  }
399
505
  maskQueryParams(search) {
400
506
  const params = new URLSearchParams(search);
@@ -411,7 +517,7 @@ var _RequestLogger = class _RequestLogger {
411
517
  this.shouldMaskHeader(k) ? MASKED : v
412
518
  ]);
413
519
  }
414
- logRequest(request, response) {
520
+ logRequest(request, response, error) {
415
521
  var _a3, _b, _c;
416
522
  if (!this.enabled || this.suspendUntil !== null) return;
417
523
  const url = new URL(request.url);
@@ -422,8 +528,6 @@ var _RequestLogger = class _RequestLogger {
422
528
  }
423
529
  url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
424
530
  request.url = url.toString();
425
- request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
426
- response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
427
531
  if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
428
532
  request.body = void 0;
429
533
  } else if (request.body) {
@@ -456,10 +560,18 @@ var _RequestLogger = class _RequestLogger {
456
560
  }
457
561
  }
458
562
  }
563
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
564
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
459
565
  const item = {
460
- uuid: (0, import_crypto2.randomUUID)(),
566
+ uuid: (0, import_crypto3.randomUUID)(),
461
567
  request: skipEmptyValues(request),
462
- response: skipEmptyValues(response)
568
+ response: skipEmptyValues(response),
569
+ exception: error && this.config.logException ? {
570
+ type: error.name,
571
+ message: truncateExceptionMessage(error.message),
572
+ stacktrace: truncateExceptionStackTrace(error.stack || ""),
573
+ sentryEventId: getSentryEventId()
574
+ } : null
463
575
  };
464
576
  [
465
577
  item.request.body,
@@ -611,7 +723,7 @@ function skipEmptyValues(data) {
611
723
  __name(skipEmptyValues, "skipEmptyValues");
612
724
  function checkWritableFs() {
613
725
  try {
614
- const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto2.randomUUID)()}`);
726
+ const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto3.randomUUID)()}`);
615
727
  (0, import_fs2.writeFileSync)(testPath, "test");
616
728
  (0, import_fs2.unlinkSync)(testPath);
617
729
  return true;
@@ -621,104 +733,6 @@ function checkWritableFs() {
621
733
  }
622
734
  __name(checkWritableFs, "checkWritableFs");
623
735
 
624
- // src/common/serverErrorCounter.ts
625
- var import_crypto3 = require("crypto");
626
- var MAX_MSG_LENGTH = 2048;
627
- var MAX_STACKTRACE_LENGTH = 65536;
628
- var _ServerErrorCounter = class _ServerErrorCounter {
629
- errorCounts;
630
- errorDetails;
631
- sentryEventIds;
632
- sentry;
633
- constructor() {
634
- this.errorCounts = /* @__PURE__ */ new Map();
635
- this.errorDetails = /* @__PURE__ */ new Map();
636
- this.sentryEventIds = /* @__PURE__ */ new Map();
637
- this.tryImportSentry();
638
- }
639
- addServerError(serverError) {
640
- const key = this.getKey(serverError);
641
- if (!this.errorDetails.has(key)) {
642
- this.errorDetails.set(key, serverError);
643
- }
644
- this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
645
- this.captureSentryEventId(key);
646
- }
647
- getAndResetServerErrors() {
648
- const data = [];
649
- this.errorCounts.forEach((count, key) => {
650
- const serverError = this.errorDetails.get(key);
651
- if (serverError) {
652
- data.push({
653
- consumer: serverError.consumer || null,
654
- method: serverError.method,
655
- path: serverError.path,
656
- type: serverError.type,
657
- msg: this.getTruncatedMessage(serverError.msg),
658
- traceback: this.getTruncatedStack(serverError.traceback),
659
- sentry_event_id: this.sentryEventIds.get(key) || null,
660
- error_count: count
661
- });
662
- }
663
- });
664
- this.errorCounts.clear();
665
- this.errorDetails.clear();
666
- return data;
667
- }
668
- getKey(serverError) {
669
- const hashInput = [
670
- serverError.consumer || "",
671
- serverError.method.toUpperCase(),
672
- serverError.path,
673
- serverError.type,
674
- serverError.msg.trim(),
675
- serverError.traceback.trim()
676
- ].join("|");
677
- return (0, import_crypto3.createHash)("md5").update(hashInput).digest("hex");
678
- }
679
- getTruncatedMessage(msg) {
680
- msg = msg.trim();
681
- if (msg.length <= MAX_MSG_LENGTH) {
682
- return msg;
683
- }
684
- const suffix = "... (truncated)";
685
- const cutoff = MAX_MSG_LENGTH - suffix.length;
686
- return msg.substring(0, cutoff) + suffix;
687
- }
688
- getTruncatedStack(stack) {
689
- const suffix = "... (truncated) ...";
690
- const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
691
- const lines = stack.trim().split("\n");
692
- const truncatedLines = [];
693
- let length = 0;
694
- for (const line of lines) {
695
- if (length + line.length + 1 > cutoff) {
696
- truncatedLines.push(suffix);
697
- break;
698
- }
699
- truncatedLines.push(line);
700
- length += line.length + 1;
701
- }
702
- return truncatedLines.join("\n");
703
- }
704
- captureSentryEventId(serverErrorKey) {
705
- if (this.sentry && this.sentry.lastEventId) {
706
- const eventId = this.sentry.lastEventId();
707
- if (eventId) {
708
- this.sentryEventIds.set(serverErrorKey, eventId);
709
- }
710
- }
711
- }
712
- async tryImportSentry() {
713
- try {
714
- this.sentry = await import("@sentry/node");
715
- } catch (e) {
716
- }
717
- }
718
- };
719
- __name(_ServerErrorCounter, "ServerErrorCounter");
720
- var ServerErrorCounter = _ServerErrorCounter;
721
-
722
736
  // src/common/validationErrorCounter.ts
723
737
  var import_crypto4 = require("crypto");
724
738
  var _ValidationErrorCounter = class _ValidationErrorCounter {
@@ -792,6 +806,7 @@ var _ApitallyClient = class _ApitallyClient {
792
806
  syncIntervalId;
793
807
  startupData;
794
808
  startupDataSent = false;
809
+ enabled = true;
795
810
  requestCounter;
796
811
  requestLogger;
797
812
  validationErrorCounter;
@@ -828,12 +843,16 @@ var _ApitallyClient = class _ApitallyClient {
828
843
  }
829
844
  return _ApitallyClient.instance;
830
845
  }
846
+ isEnabled() {
847
+ return this.enabled;
848
+ }
831
849
  static async shutdown() {
832
850
  if (_ApitallyClient.instance) {
833
851
  await _ApitallyClient.instance.handleShutdown();
834
852
  }
835
853
  }
836
854
  async handleShutdown() {
855
+ this.enabled = false;
837
856
  this.stopSync();
838
857
  await this.sendSyncData();
839
858
  await this.sendLogData();
@@ -1017,6 +1036,7 @@ var _ApitallyClient = class _ApitallyClient {
1017
1036
  if (error instanceof HTTPError) {
1018
1037
  if (error.response.status === 404) {
1019
1038
  this.logger.error(`Invalid Apitally client ID: '${this.clientId}'`);
1039
+ this.enabled = false;
1020
1040
  this.stopSync();
1021
1041
  return true;
1022
1042
  }
@@ -1192,6 +1212,10 @@ var useApitally = /* @__PURE__ */ __name((app, config) => {
1192
1212
  var getMiddleware = /* @__PURE__ */ __name((app, client) => {
1193
1213
  let errorHandlerConfigured = false;
1194
1214
  return (req, res, next) => {
1215
+ if (!client.isEnabled()) {
1216
+ next();
1217
+ return;
1218
+ }
1195
1219
  if (!errorHandlerConfigured) {
1196
1220
  app.use((err, req2, res2, next2) => {
1197
1221
  res2.locals.serverError = err;
@@ -1203,7 +1227,10 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
1203
1227
  const startTime = import_perf_hooks.performance.now();
1204
1228
  const originalSend = res.send;
1205
1229
  res.send = (body) => {
1206
- res.locals.body = body;
1230
+ const contentType = res.get("content-type");
1231
+ if (client.requestLogger.isSupportedContentType(contentType)) {
1232
+ res.locals.body = body;
1233
+ }
1207
1234
  return originalSend.call(res, body);
1208
1235
  };
1209
1236
  res.on("finish", () => {
@@ -1277,7 +1304,7 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
1277
1304
  headers: convertHeaders(res.getHeaders()),
1278
1305
  size: Number(res.get("content-length")),
1279
1306
  body: convertBody(res.locals.body, res.get("content-type"))
1280
- });
1307
+ }, res.locals.serverError);
1281
1308
  }
1282
1309
  } catch (error) {
1283
1310
  client.logger.error("Error while logging request in Apitally middleware.", {