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
@@ -187,6 +187,108 @@ import { unlinkSync as unlinkSync2, writeFileSync } from "fs";
187
187
  import { tmpdir as tmpdir2 } from "os";
188
188
  import { join as join2 } from "path";
189
189
 
190
+ // src/common/sentry.ts
191
+ var sentry;
192
+ (async () => {
193
+ try {
194
+ sentry = await import("@sentry/node");
195
+ } catch (e) {
196
+ }
197
+ })();
198
+ function getSentryEventId() {
199
+ if (sentry && sentry.lastEventId) {
200
+ return sentry.lastEventId();
201
+ }
202
+ return void 0;
203
+ }
204
+ __name(getSentryEventId, "getSentryEventId");
205
+
206
+ // src/common/serverErrorCounter.ts
207
+ import { createHash } from "crypto";
208
+ var MAX_MSG_LENGTH = 2048;
209
+ var MAX_STACKTRACE_LENGTH = 65536;
210
+ var _ServerErrorCounter = class _ServerErrorCounter {
211
+ errorCounts;
212
+ errorDetails;
213
+ sentryEventIds;
214
+ constructor() {
215
+ this.errorCounts = /* @__PURE__ */ new Map();
216
+ this.errorDetails = /* @__PURE__ */ new Map();
217
+ this.sentryEventIds = /* @__PURE__ */ new Map();
218
+ }
219
+ addServerError(serverError) {
220
+ const key = this.getKey(serverError);
221
+ if (!this.errorDetails.has(key)) {
222
+ this.errorDetails.set(key, serverError);
223
+ }
224
+ this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
225
+ const sentryEventId = getSentryEventId();
226
+ if (sentryEventId) {
227
+ this.sentryEventIds.set(key, sentryEventId);
228
+ }
229
+ }
230
+ getAndResetServerErrors() {
231
+ const data = [];
232
+ this.errorCounts.forEach((count, key) => {
233
+ const serverError = this.errorDetails.get(key);
234
+ if (serverError) {
235
+ data.push({
236
+ consumer: serverError.consumer || null,
237
+ method: serverError.method,
238
+ path: serverError.path,
239
+ type: serverError.type,
240
+ msg: truncateExceptionMessage(serverError.msg),
241
+ traceback: truncateExceptionStackTrace(serverError.traceback),
242
+ sentry_event_id: this.sentryEventIds.get(key) || null,
243
+ error_count: count
244
+ });
245
+ }
246
+ });
247
+ this.errorCounts.clear();
248
+ this.errorDetails.clear();
249
+ return data;
250
+ }
251
+ getKey(serverError) {
252
+ const hashInput = [
253
+ serverError.consumer || "",
254
+ serverError.method.toUpperCase(),
255
+ serverError.path,
256
+ serverError.type,
257
+ serverError.msg.trim(),
258
+ serverError.traceback.trim()
259
+ ].join("|");
260
+ return createHash("md5").update(hashInput).digest("hex");
261
+ }
262
+ };
263
+ __name(_ServerErrorCounter, "ServerErrorCounter");
264
+ var ServerErrorCounter = _ServerErrorCounter;
265
+ function truncateExceptionMessage(msg) {
266
+ if (msg.length <= MAX_MSG_LENGTH) {
267
+ return msg;
268
+ }
269
+ const suffix = "... (truncated)";
270
+ const cutoff = MAX_MSG_LENGTH - suffix.length;
271
+ return msg.substring(0, cutoff) + suffix;
272
+ }
273
+ __name(truncateExceptionMessage, "truncateExceptionMessage");
274
+ function truncateExceptionStackTrace(stack) {
275
+ const suffix = "... (truncated) ...";
276
+ const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
277
+ const lines = stack.trim().split("\n");
278
+ const truncatedLines = [];
279
+ let length = 0;
280
+ for (const line of lines) {
281
+ if (length + line.length + 1 > cutoff) {
282
+ truncatedLines.push(suffix);
283
+ break;
284
+ }
285
+ truncatedLines.push(line);
286
+ length += line.length + 1;
287
+ }
288
+ return truncatedLines.join("\n");
289
+ }
290
+ __name(truncateExceptionStackTrace, "truncateExceptionStackTrace");
291
+
190
292
  // src/common/tempGzipFile.ts
191
293
  import { Buffer as Buffer2 } from "buffer";
192
294
  import { randomUUID } from "crypto";
@@ -209,9 +311,9 @@ var _TempGzipFile = class _TempGzipFile {
209
311
  this.writeStream.once("ready", resolve);
210
312
  this.writeStream.once("error", reject);
211
313
  });
212
- this.closedPromise = new Promise((resolve) => {
314
+ this.closedPromise = new Promise((resolve, reject) => {
213
315
  this.writeStream.once("close", resolve);
214
- this.writeStream.once("error", resolve);
316
+ this.writeStream.once("error", reject);
215
317
  });
216
318
  this.gzip = createGzip();
217
319
  this.gzip.pipe(this.writeStream);
@@ -309,6 +411,7 @@ var DEFAULT_CONFIG = {
309
411
  logRequestBody: false,
310
412
  logResponseHeaders: true,
311
413
  logResponseBody: false,
414
+ logException: true,
312
415
  maskQueryParams: [],
313
416
  maskHeaders: [],
314
417
  excludePaths: []
@@ -361,7 +464,10 @@ var _RequestLogger = class _RequestLogger {
361
464
  hasSupportedContentType(headers) {
362
465
  var _a2;
363
466
  const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
364
- return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
467
+ return this.isSupportedContentType(contentType);
468
+ }
469
+ isSupportedContentType(contentType) {
470
+ return typeof contentType === "string" && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
365
471
  }
366
472
  maskQueryParams(search) {
367
473
  const params = new URLSearchParams(search);
@@ -378,7 +484,7 @@ var _RequestLogger = class _RequestLogger {
378
484
  this.shouldMaskHeader(k) ? MASKED : v
379
485
  ]);
380
486
  }
381
- logRequest(request, response) {
487
+ logRequest(request, response, error) {
382
488
  var _a2, _b, _c;
383
489
  if (!this.enabled || this.suspendUntil !== null) return;
384
490
  const url = new URL(request.url);
@@ -389,8 +495,6 @@ var _RequestLogger = class _RequestLogger {
389
495
  }
390
496
  url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
391
497
  request.url = url.toString();
392
- request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
393
- response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
394
498
  if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
395
499
  request.body = void 0;
396
500
  } else if (request.body) {
@@ -423,10 +527,18 @@ var _RequestLogger = class _RequestLogger {
423
527
  }
424
528
  }
425
529
  }
530
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
531
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
426
532
  const item = {
427
533
  uuid: randomUUID2(),
428
534
  request: skipEmptyValues(request),
429
- response: skipEmptyValues(response)
535
+ response: skipEmptyValues(response),
536
+ exception: error && this.config.logException ? {
537
+ type: error.name,
538
+ message: truncateExceptionMessage(error.message),
539
+ stacktrace: truncateExceptionStackTrace(error.stack || ""),
540
+ sentryEventId: getSentryEventId()
541
+ } : null
430
542
  };
431
543
  [
432
544
  item.request.body,
@@ -588,104 +700,6 @@ function checkWritableFs() {
588
700
  }
589
701
  __name(checkWritableFs, "checkWritableFs");
590
702
 
591
- // src/common/serverErrorCounter.ts
592
- import { createHash } from "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 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
  import { createHash as createHash2 } from "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,8 +1052,13 @@ var useApitally = /* @__PURE__ */ __name((app, config) => {
1032
1052
  var getMiddleware = /* @__PURE__ */ __name((client) => {
1033
1053
  return async (ctx, next) => {
1034
1054
  var _a2;
1055
+ if (!client.isEnabled()) {
1056
+ await next();
1057
+ return;
1058
+ }
1035
1059
  let path;
1036
1060
  let statusCode;
1061
+ let serverError;
1037
1062
  const startTime = performance.now();
1038
1063
  try {
1039
1064
  await next();
@@ -1041,6 +1066,7 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
1041
1066
  path = getPath(ctx);
1042
1067
  statusCode = error.statusCode || error.status || 500;
1043
1068
  if (path && statusCode === 500 && error instanceof Error) {
1069
+ serverError = error;
1044
1070
  client.serverErrorCounter.addServerError({
1045
1071
  consumer: (_a2 = getConsumer(ctx)) == null ? void 0 : _a2.identifier,
1046
1072
  method: ctx.request.method,
@@ -1092,7 +1118,7 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
1092
1118
  headers: convertHeaders(ctx.response.headers),
1093
1119
  size: Number(ctx.response.headers["content-length"]),
1094
1120
  body: convertBody(ctx.response.body, ctx.response.get("content-type"))
1095
- });
1121
+ }, serverError);
1096
1122
  }
1097
1123
  }
1098
1124
  };