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
@@ -211,14 +211,116 @@ var RequestCounter = _RequestCounter;
211
211
  // src/common/requestLogger.ts
212
212
  var import_async_lock = __toESM(require("async-lock"), 1);
213
213
  var import_buffer2 = require("buffer");
214
- var import_crypto2 = require("crypto");
214
+ var import_crypto3 = require("crypto");
215
215
  var import_fs2 = require("fs");
216
216
  var import_os2 = require("os");
217
217
  var import_path2 = require("path");
218
218
 
219
+ // src/common/sentry.ts
220
+ var sentry;
221
+ (async () => {
222
+ try {
223
+ sentry = await import("@sentry/node");
224
+ } catch (e) {
225
+ }
226
+ })();
227
+ function getSentryEventId() {
228
+ if (sentry && sentry.lastEventId) {
229
+ return sentry.lastEventId();
230
+ }
231
+ return void 0;
232
+ }
233
+ __name(getSentryEventId, "getSentryEventId");
234
+
235
+ // src/common/serverErrorCounter.ts
236
+ var import_crypto = require("crypto");
237
+ var MAX_MSG_LENGTH = 2048;
238
+ var MAX_STACKTRACE_LENGTH = 65536;
239
+ var _ServerErrorCounter = class _ServerErrorCounter {
240
+ errorCounts;
241
+ errorDetails;
242
+ sentryEventIds;
243
+ constructor() {
244
+ this.errorCounts = /* @__PURE__ */ new Map();
245
+ this.errorDetails = /* @__PURE__ */ new Map();
246
+ this.sentryEventIds = /* @__PURE__ */ new Map();
247
+ }
248
+ addServerError(serverError) {
249
+ const key = this.getKey(serverError);
250
+ if (!this.errorDetails.has(key)) {
251
+ this.errorDetails.set(key, serverError);
252
+ }
253
+ this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
254
+ const sentryEventId = getSentryEventId();
255
+ if (sentryEventId) {
256
+ this.sentryEventIds.set(key, sentryEventId);
257
+ }
258
+ }
259
+ getAndResetServerErrors() {
260
+ const data = [];
261
+ this.errorCounts.forEach((count, key) => {
262
+ const serverError = this.errorDetails.get(key);
263
+ if (serverError) {
264
+ data.push({
265
+ consumer: serverError.consumer || null,
266
+ method: serverError.method,
267
+ path: serverError.path,
268
+ type: serverError.type,
269
+ msg: truncateExceptionMessage(serverError.msg),
270
+ traceback: truncateExceptionStackTrace(serverError.traceback),
271
+ sentry_event_id: this.sentryEventIds.get(key) || null,
272
+ error_count: count
273
+ });
274
+ }
275
+ });
276
+ this.errorCounts.clear();
277
+ this.errorDetails.clear();
278
+ return data;
279
+ }
280
+ getKey(serverError) {
281
+ const hashInput = [
282
+ serverError.consumer || "",
283
+ serverError.method.toUpperCase(),
284
+ serverError.path,
285
+ serverError.type,
286
+ serverError.msg.trim(),
287
+ serverError.traceback.trim()
288
+ ].join("|");
289
+ return (0, import_crypto.createHash)("md5").update(hashInput).digest("hex");
290
+ }
291
+ };
292
+ __name(_ServerErrorCounter, "ServerErrorCounter");
293
+ var ServerErrorCounter = _ServerErrorCounter;
294
+ function truncateExceptionMessage(msg) {
295
+ if (msg.length <= MAX_MSG_LENGTH) {
296
+ return msg;
297
+ }
298
+ const suffix = "... (truncated)";
299
+ const cutoff = MAX_MSG_LENGTH - suffix.length;
300
+ return msg.substring(0, cutoff) + suffix;
301
+ }
302
+ __name(truncateExceptionMessage, "truncateExceptionMessage");
303
+ function truncateExceptionStackTrace(stack) {
304
+ const suffix = "... (truncated) ...";
305
+ const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
306
+ const lines = stack.trim().split("\n");
307
+ const truncatedLines = [];
308
+ let length = 0;
309
+ for (const line of lines) {
310
+ if (length + line.length + 1 > cutoff) {
311
+ truncatedLines.push(suffix);
312
+ break;
313
+ }
314
+ truncatedLines.push(line);
315
+ length += line.length + 1;
316
+ }
317
+ return truncatedLines.join("\n");
318
+ }
319
+ __name(truncateExceptionStackTrace, "truncateExceptionStackTrace");
320
+
219
321
  // src/common/tempGzipFile.ts
220
322
  var import_buffer = require("buffer");
221
- var import_crypto = require("crypto");
323
+ var import_crypto2 = require("crypto");
222
324
  var import_fs = require("fs");
223
325
  var import_os = require("os");
224
326
  var import_path = require("path");
@@ -231,16 +333,16 @@ var _TempGzipFile = class _TempGzipFile {
231
333
  readyPromise;
232
334
  closedPromise;
233
335
  constructor() {
234
- this.uuid = (0, import_crypto.randomUUID)();
336
+ this.uuid = (0, import_crypto2.randomUUID)();
235
337
  this.filePath = (0, import_path.join)((0, import_os.tmpdir)(), `apitally-${this.uuid}.gz`);
236
338
  this.writeStream = (0, import_fs.createWriteStream)(this.filePath);
237
339
  this.readyPromise = new Promise((resolve, reject) => {
238
340
  this.writeStream.once("ready", resolve);
239
341
  this.writeStream.once("error", reject);
240
342
  });
241
- this.closedPromise = new Promise((resolve) => {
343
+ this.closedPromise = new Promise((resolve, reject) => {
242
344
  this.writeStream.once("close", resolve);
243
- this.writeStream.once("error", resolve);
345
+ this.writeStream.once("error", reject);
244
346
  });
245
347
  this.gzip = (0, import_zlib.createGzip)();
246
348
  this.gzip.pipe(this.writeStream);
@@ -338,6 +440,7 @@ var DEFAULT_CONFIG = {
338
440
  logRequestBody: false,
339
441
  logResponseHeaders: true,
340
442
  logResponseBody: false,
443
+ logException: true,
341
444
  maskQueryParams: [],
342
445
  maskHeaders: [],
343
446
  excludePaths: []
@@ -390,7 +493,10 @@ var _RequestLogger = class _RequestLogger {
390
493
  hasSupportedContentType(headers) {
391
494
  var _a2;
392
495
  const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
393
- return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
496
+ return this.isSupportedContentType(contentType);
497
+ }
498
+ isSupportedContentType(contentType) {
499
+ return typeof contentType === "string" && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
394
500
  }
395
501
  maskQueryParams(search) {
396
502
  const params = new URLSearchParams(search);
@@ -407,7 +513,7 @@ var _RequestLogger = class _RequestLogger {
407
513
  this.shouldMaskHeader(k) ? MASKED : v
408
514
  ]);
409
515
  }
410
- logRequest(request, response) {
516
+ logRequest(request, response, error) {
411
517
  var _a2, _b, _c;
412
518
  if (!this.enabled || this.suspendUntil !== null) return;
413
519
  const url = new URL(request.url);
@@ -418,8 +524,6 @@ var _RequestLogger = class _RequestLogger {
418
524
  }
419
525
  url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
420
526
  request.url = url.toString();
421
- request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
422
- response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
423
527
  if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
424
528
  request.body = void 0;
425
529
  } else if (request.body) {
@@ -452,10 +556,18 @@ var _RequestLogger = class _RequestLogger {
452
556
  }
453
557
  }
454
558
  }
559
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
560
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
455
561
  const item = {
456
- uuid: (0, import_crypto2.randomUUID)(),
562
+ uuid: (0, import_crypto3.randomUUID)(),
457
563
  request: skipEmptyValues(request),
458
- response: skipEmptyValues(response)
564
+ response: skipEmptyValues(response),
565
+ exception: error && this.config.logException ? {
566
+ type: error.name,
567
+ message: truncateExceptionMessage(error.message),
568
+ stacktrace: truncateExceptionStackTrace(error.stack || ""),
569
+ sentryEventId: getSentryEventId()
570
+ } : null
459
571
  };
460
572
  [
461
573
  item.request.body,
@@ -607,7 +719,7 @@ function skipEmptyValues(data) {
607
719
  __name(skipEmptyValues, "skipEmptyValues");
608
720
  function checkWritableFs() {
609
721
  try {
610
- const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto2.randomUUID)()}`);
722
+ const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto3.randomUUID)()}`);
611
723
  (0, import_fs2.writeFileSync)(testPath, "test");
612
724
  (0, import_fs2.unlinkSync)(testPath);
613
725
  return true;
@@ -617,104 +729,6 @@ function checkWritableFs() {
617
729
  }
618
730
  __name(checkWritableFs, "checkWritableFs");
619
731
 
620
- // src/common/serverErrorCounter.ts
621
- var import_crypto3 = require("crypto");
622
- var MAX_MSG_LENGTH = 2048;
623
- var MAX_STACKTRACE_LENGTH = 65536;
624
- var _ServerErrorCounter = class _ServerErrorCounter {
625
- errorCounts;
626
- errorDetails;
627
- sentryEventIds;
628
- sentry;
629
- constructor() {
630
- this.errorCounts = /* @__PURE__ */ new Map();
631
- this.errorDetails = /* @__PURE__ */ new Map();
632
- this.sentryEventIds = /* @__PURE__ */ new Map();
633
- this.tryImportSentry();
634
- }
635
- addServerError(serverError) {
636
- const key = this.getKey(serverError);
637
- if (!this.errorDetails.has(key)) {
638
- this.errorDetails.set(key, serverError);
639
- }
640
- this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
641
- this.captureSentryEventId(key);
642
- }
643
- getAndResetServerErrors() {
644
- const data = [];
645
- this.errorCounts.forEach((count, key) => {
646
- const serverError = this.errorDetails.get(key);
647
- if (serverError) {
648
- data.push({
649
- consumer: serverError.consumer || null,
650
- method: serverError.method,
651
- path: serverError.path,
652
- type: serverError.type,
653
- msg: this.getTruncatedMessage(serverError.msg),
654
- traceback: this.getTruncatedStack(serverError.traceback),
655
- sentry_event_id: this.sentryEventIds.get(key) || null,
656
- error_count: count
657
- });
658
- }
659
- });
660
- this.errorCounts.clear();
661
- this.errorDetails.clear();
662
- return data;
663
- }
664
- getKey(serverError) {
665
- const hashInput = [
666
- serverError.consumer || "",
667
- serverError.method.toUpperCase(),
668
- serverError.path,
669
- serverError.type,
670
- serverError.msg.trim(),
671
- serverError.traceback.trim()
672
- ].join("|");
673
- return (0, import_crypto3.createHash)("md5").update(hashInput).digest("hex");
674
- }
675
- getTruncatedMessage(msg) {
676
- msg = msg.trim();
677
- if (msg.length <= MAX_MSG_LENGTH) {
678
- return msg;
679
- }
680
- const suffix = "... (truncated)";
681
- const cutoff = MAX_MSG_LENGTH - suffix.length;
682
- return msg.substring(0, cutoff) + suffix;
683
- }
684
- getTruncatedStack(stack) {
685
- const suffix = "... (truncated) ...";
686
- const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
687
- const lines = stack.trim().split("\n");
688
- const truncatedLines = [];
689
- let length = 0;
690
- for (const line of lines) {
691
- if (length + line.length + 1 > cutoff) {
692
- truncatedLines.push(suffix);
693
- break;
694
- }
695
- truncatedLines.push(line);
696
- length += line.length + 1;
697
- }
698
- return truncatedLines.join("\n");
699
- }
700
- captureSentryEventId(serverErrorKey) {
701
- if (this.sentry && this.sentry.lastEventId) {
702
- const eventId = this.sentry.lastEventId();
703
- if (eventId) {
704
- this.sentryEventIds.set(serverErrorKey, eventId);
705
- }
706
- }
707
- }
708
- async tryImportSentry() {
709
- try {
710
- this.sentry = await import("@sentry/node");
711
- } catch (e) {
712
- }
713
- }
714
- };
715
- __name(_ServerErrorCounter, "ServerErrorCounter");
716
- var ServerErrorCounter = _ServerErrorCounter;
717
-
718
732
  // src/common/validationErrorCounter.ts
719
733
  var import_crypto4 = require("crypto");
720
734
  var _ValidationErrorCounter = class _ValidationErrorCounter {
@@ -788,6 +802,7 @@ var _ApitallyClient = class _ApitallyClient {
788
802
  syncIntervalId;
789
803
  startupData;
790
804
  startupDataSent = false;
805
+ enabled = true;
791
806
  requestCounter;
792
807
  requestLogger;
793
808
  validationErrorCounter;
@@ -824,12 +839,16 @@ var _ApitallyClient = class _ApitallyClient {
824
839
  }
825
840
  return _ApitallyClient.instance;
826
841
  }
842
+ isEnabled() {
843
+ return this.enabled;
844
+ }
827
845
  static async shutdown() {
828
846
  if (_ApitallyClient.instance) {
829
847
  await _ApitallyClient.instance.handleShutdown();
830
848
  }
831
849
  }
832
850
  async handleShutdown() {
851
+ this.enabled = false;
833
852
  this.stopSync();
834
853
  await this.sendSyncData();
835
854
  await this.sendLogData();
@@ -1013,6 +1032,7 @@ var _ApitallyClient = class _ApitallyClient {
1013
1032
  if (error instanceof HTTPError) {
1014
1033
  if (error.response.status === 404) {
1015
1034
  this.logger.error(`Invalid Apitally client ID: '${this.clientId}'`);
1035
+ this.enabled = false;
1016
1036
  this.stopSync();
1017
1037
  return true;
1018
1038
  }
@@ -1188,6 +1208,10 @@ var useApitally = /* @__PURE__ */ __name((app, config) => {
1188
1208
  var getMiddleware = /* @__PURE__ */ __name((app, client) => {
1189
1209
  let errorHandlerConfigured = false;
1190
1210
  return (req, res, next) => {
1211
+ if (!client.isEnabled()) {
1212
+ next();
1213
+ return;
1214
+ }
1191
1215
  if (!errorHandlerConfigured) {
1192
1216
  app.use((err, req2, res2, next2) => {
1193
1217
  res2.locals.serverError = err;
@@ -1199,7 +1223,10 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
1199
1223
  const startTime = import_perf_hooks.performance.now();
1200
1224
  const originalSend = res.send;
1201
1225
  res.send = (body) => {
1202
- res.locals.body = body;
1226
+ const contentType = res.get("content-type");
1227
+ if (client.requestLogger.isSupportedContentType(contentType)) {
1228
+ res.locals.body = body;
1229
+ }
1203
1230
  return originalSend.call(res, body);
1204
1231
  };
1205
1232
  res.on("finish", () => {
@@ -1273,7 +1300,7 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
1273
1300
  headers: convertHeaders(res.getHeaders()),
1274
1301
  size: Number(res.get("content-length")),
1275
1302
  body: convertBody(res.locals.body, res.get("content-type"))
1276
- });
1303
+ }, res.locals.serverError);
1277
1304
  }
1278
1305
  } catch (error) {
1279
1306
  client.logger.error("Error while logging request in Apitally middleware.", {