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
@@ -190,6 +190,108 @@ import { unlinkSync as unlinkSync2, writeFileSync } from "fs";
190
190
  import { tmpdir as tmpdir2 } from "os";
191
191
  import { join as join2 } from "path";
192
192
 
193
+ // src/common/sentry.ts
194
+ var sentry;
195
+ (async () => {
196
+ try {
197
+ sentry = await import("@sentry/node");
198
+ } catch (e) {
199
+ }
200
+ })();
201
+ function getSentryEventId() {
202
+ if (sentry && sentry.lastEventId) {
203
+ return sentry.lastEventId();
204
+ }
205
+ return void 0;
206
+ }
207
+ __name(getSentryEventId, "getSentryEventId");
208
+
209
+ // src/common/serverErrorCounter.ts
210
+ import { createHash } from "crypto";
211
+ var MAX_MSG_LENGTH = 2048;
212
+ var MAX_STACKTRACE_LENGTH = 65536;
213
+ var _ServerErrorCounter = class _ServerErrorCounter {
214
+ errorCounts;
215
+ errorDetails;
216
+ sentryEventIds;
217
+ constructor() {
218
+ this.errorCounts = /* @__PURE__ */ new Map();
219
+ this.errorDetails = /* @__PURE__ */ new Map();
220
+ this.sentryEventIds = /* @__PURE__ */ new Map();
221
+ }
222
+ addServerError(serverError) {
223
+ const key = this.getKey(serverError);
224
+ if (!this.errorDetails.has(key)) {
225
+ this.errorDetails.set(key, serverError);
226
+ }
227
+ this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
228
+ const sentryEventId = getSentryEventId();
229
+ if (sentryEventId) {
230
+ this.sentryEventIds.set(key, sentryEventId);
231
+ }
232
+ }
233
+ getAndResetServerErrors() {
234
+ const data = [];
235
+ this.errorCounts.forEach((count, key) => {
236
+ const serverError = this.errorDetails.get(key);
237
+ if (serverError) {
238
+ data.push({
239
+ consumer: serverError.consumer || null,
240
+ method: serverError.method,
241
+ path: serverError.path,
242
+ type: serverError.type,
243
+ msg: truncateExceptionMessage(serverError.msg),
244
+ traceback: truncateExceptionStackTrace(serverError.traceback),
245
+ sentry_event_id: this.sentryEventIds.get(key) || null,
246
+ error_count: count
247
+ });
248
+ }
249
+ });
250
+ this.errorCounts.clear();
251
+ this.errorDetails.clear();
252
+ return data;
253
+ }
254
+ getKey(serverError) {
255
+ const hashInput = [
256
+ serverError.consumer || "",
257
+ serverError.method.toUpperCase(),
258
+ serverError.path,
259
+ serverError.type,
260
+ serverError.msg.trim(),
261
+ serverError.traceback.trim()
262
+ ].join("|");
263
+ return createHash("md5").update(hashInput).digest("hex");
264
+ }
265
+ };
266
+ __name(_ServerErrorCounter, "ServerErrorCounter");
267
+ var ServerErrorCounter = _ServerErrorCounter;
268
+ function truncateExceptionMessage(msg) {
269
+ if (msg.length <= MAX_MSG_LENGTH) {
270
+ return msg;
271
+ }
272
+ const suffix = "... (truncated)";
273
+ const cutoff = MAX_MSG_LENGTH - suffix.length;
274
+ return msg.substring(0, cutoff) + suffix;
275
+ }
276
+ __name(truncateExceptionMessage, "truncateExceptionMessage");
277
+ function truncateExceptionStackTrace(stack) {
278
+ const suffix = "... (truncated) ...";
279
+ const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
280
+ const lines = stack.trim().split("\n");
281
+ const truncatedLines = [];
282
+ let length = 0;
283
+ for (const line of lines) {
284
+ if (length + line.length + 1 > cutoff) {
285
+ truncatedLines.push(suffix);
286
+ break;
287
+ }
288
+ truncatedLines.push(line);
289
+ length += line.length + 1;
290
+ }
291
+ return truncatedLines.join("\n");
292
+ }
293
+ __name(truncateExceptionStackTrace, "truncateExceptionStackTrace");
294
+
193
295
  // src/common/tempGzipFile.ts
194
296
  import { Buffer as Buffer2 } from "buffer";
195
297
  import { randomUUID } from "crypto";
@@ -212,9 +314,9 @@ var _TempGzipFile = class _TempGzipFile {
212
314
  this.writeStream.once("ready", resolve);
213
315
  this.writeStream.once("error", reject);
214
316
  });
215
- this.closedPromise = new Promise((resolve) => {
317
+ this.closedPromise = new Promise((resolve, reject) => {
216
318
  this.writeStream.once("close", resolve);
217
- this.writeStream.once("error", resolve);
319
+ this.writeStream.once("error", reject);
218
320
  });
219
321
  this.gzip = createGzip();
220
322
  this.gzip.pipe(this.writeStream);
@@ -312,6 +414,7 @@ var DEFAULT_CONFIG = {
312
414
  logRequestBody: false,
313
415
  logResponseHeaders: true,
314
416
  logResponseBody: false,
417
+ logException: true,
315
418
  maskQueryParams: [],
316
419
  maskHeaders: [],
317
420
  excludePaths: []
@@ -364,7 +467,10 @@ var _RequestLogger = class _RequestLogger {
364
467
  hasSupportedContentType(headers) {
365
468
  var _a2;
366
469
  const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
367
- return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
470
+ return this.isSupportedContentType(contentType);
471
+ }
472
+ isSupportedContentType(contentType) {
473
+ return typeof contentType === "string" && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
368
474
  }
369
475
  maskQueryParams(search) {
370
476
  const params = new URLSearchParams(search);
@@ -381,7 +487,7 @@ var _RequestLogger = class _RequestLogger {
381
487
  this.shouldMaskHeader(k) ? MASKED : v
382
488
  ]);
383
489
  }
384
- logRequest(request, response) {
490
+ logRequest(request, response, error) {
385
491
  var _a2, _b, _c;
386
492
  if (!this.enabled || this.suspendUntil !== null) return;
387
493
  const url = new URL(request.url);
@@ -392,8 +498,6 @@ var _RequestLogger = class _RequestLogger {
392
498
  }
393
499
  url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
394
500
  request.url = url.toString();
395
- request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
396
- response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
397
501
  if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
398
502
  request.body = void 0;
399
503
  } else if (request.body) {
@@ -426,10 +530,18 @@ var _RequestLogger = class _RequestLogger {
426
530
  }
427
531
  }
428
532
  }
533
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
534
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
429
535
  const item = {
430
536
  uuid: randomUUID2(),
431
537
  request: skipEmptyValues(request),
432
- response: skipEmptyValues(response)
538
+ response: skipEmptyValues(response),
539
+ exception: error && this.config.logException ? {
540
+ type: error.name,
541
+ message: truncateExceptionMessage(error.message),
542
+ stacktrace: truncateExceptionStackTrace(error.stack || ""),
543
+ sentryEventId: getSentryEventId()
544
+ } : null
433
545
  };
434
546
  [
435
547
  item.request.body,
@@ -591,104 +703,6 @@ function checkWritableFs() {
591
703
  }
592
704
  __name(checkWritableFs, "checkWritableFs");
593
705
 
594
- // src/common/serverErrorCounter.ts
595
- import { createHash } from "crypto";
596
- var MAX_MSG_LENGTH = 2048;
597
- var MAX_STACKTRACE_LENGTH = 65536;
598
- var _ServerErrorCounter = class _ServerErrorCounter {
599
- errorCounts;
600
- errorDetails;
601
- sentryEventIds;
602
- sentry;
603
- constructor() {
604
- this.errorCounts = /* @__PURE__ */ new Map();
605
- this.errorDetails = /* @__PURE__ */ new Map();
606
- this.sentryEventIds = /* @__PURE__ */ new Map();
607
- this.tryImportSentry();
608
- }
609
- addServerError(serverError) {
610
- const key = this.getKey(serverError);
611
- if (!this.errorDetails.has(key)) {
612
- this.errorDetails.set(key, serverError);
613
- }
614
- this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
615
- this.captureSentryEventId(key);
616
- }
617
- getAndResetServerErrors() {
618
- const data = [];
619
- this.errorCounts.forEach((count, key) => {
620
- const serverError = this.errorDetails.get(key);
621
- if (serverError) {
622
- data.push({
623
- consumer: serverError.consumer || null,
624
- method: serverError.method,
625
- path: serverError.path,
626
- type: serverError.type,
627
- msg: this.getTruncatedMessage(serverError.msg),
628
- traceback: this.getTruncatedStack(serverError.traceback),
629
- sentry_event_id: this.sentryEventIds.get(key) || null,
630
- error_count: count
631
- });
632
- }
633
- });
634
- this.errorCounts.clear();
635
- this.errorDetails.clear();
636
- return data;
637
- }
638
- getKey(serverError) {
639
- const hashInput = [
640
- serverError.consumer || "",
641
- serverError.method.toUpperCase(),
642
- serverError.path,
643
- serverError.type,
644
- serverError.msg.trim(),
645
- serverError.traceback.trim()
646
- ].join("|");
647
- return createHash("md5").update(hashInput).digest("hex");
648
- }
649
- getTruncatedMessage(msg) {
650
- msg = msg.trim();
651
- if (msg.length <= MAX_MSG_LENGTH) {
652
- return msg;
653
- }
654
- const suffix = "... (truncated)";
655
- const cutoff = MAX_MSG_LENGTH - suffix.length;
656
- return msg.substring(0, cutoff) + suffix;
657
- }
658
- getTruncatedStack(stack) {
659
- const suffix = "... (truncated) ...";
660
- const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
661
- const lines = stack.trim().split("\n");
662
- const truncatedLines = [];
663
- let length = 0;
664
- for (const line of lines) {
665
- if (length + line.length + 1 > cutoff) {
666
- truncatedLines.push(suffix);
667
- break;
668
- }
669
- truncatedLines.push(line);
670
- length += line.length + 1;
671
- }
672
- return truncatedLines.join("\n");
673
- }
674
- captureSentryEventId(serverErrorKey) {
675
- if (this.sentry && this.sentry.lastEventId) {
676
- const eventId = this.sentry.lastEventId();
677
- if (eventId) {
678
- this.sentryEventIds.set(serverErrorKey, eventId);
679
- }
680
- }
681
- }
682
- async tryImportSentry() {
683
- try {
684
- this.sentry = await import("@sentry/node");
685
- } catch (e) {
686
- }
687
- }
688
- };
689
- __name(_ServerErrorCounter, "ServerErrorCounter");
690
- var ServerErrorCounter = _ServerErrorCounter;
691
-
692
706
  // src/common/validationErrorCounter.ts
693
707
  import { createHash as createHash2 } from "crypto";
694
708
  var _ValidationErrorCounter = class _ValidationErrorCounter {
@@ -762,6 +776,7 @@ var _ApitallyClient = class _ApitallyClient {
762
776
  syncIntervalId;
763
777
  startupData;
764
778
  startupDataSent = false;
779
+ enabled = true;
765
780
  requestCounter;
766
781
  requestLogger;
767
782
  validationErrorCounter;
@@ -798,12 +813,16 @@ var _ApitallyClient = class _ApitallyClient {
798
813
  }
799
814
  return _ApitallyClient.instance;
800
815
  }
816
+ isEnabled() {
817
+ return this.enabled;
818
+ }
801
819
  static async shutdown() {
802
820
  if (_ApitallyClient.instance) {
803
821
  await _ApitallyClient.instance.handleShutdown();
804
822
  }
805
823
  }
806
824
  async handleShutdown() {
825
+ this.enabled = false;
807
826
  this.stopSync();
808
827
  await this.sendSyncData();
809
828
  await this.sendLogData();
@@ -987,6 +1006,7 @@ var _ApitallyClient = class _ApitallyClient {
987
1006
  if (error instanceof HTTPError) {
988
1007
  if (error.response.status === 404) {
989
1008
  this.logger.error(`Invalid Apitally client ID: '${this.clientId}'`);
1009
+ this.enabled = false;
990
1010
  this.stopSync();
991
1011
  return true;
992
1012
  }
@@ -1053,7 +1073,10 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
1053
1073
  await client.handleShutdown();
1054
1074
  });
1055
1075
  fastify.addHook("onSend", (request, reply, payload, done) => {
1056
- reply.payload = payload;
1076
+ const contentType = reply.getHeader("content-type");
1077
+ if (client.requestLogger.isSupportedContentType(contentType)) {
1078
+ reply.payload = payload;
1079
+ }
1057
1080
  done();
1058
1081
  });
1059
1082
  fastify.addHook("onError", (request, reply, error, done) => {
@@ -1064,7 +1087,7 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
1064
1087
  });
1065
1088
  fastify.addHook("onResponse", (request, reply, done) => {
1066
1089
  var _a2;
1067
- if (request.method.toUpperCase() !== "OPTIONS") {
1090
+ if (client.isEnabled() && request.method.toUpperCase() !== "OPTIONS") {
1068
1091
  const consumer = getConsumer(request);
1069
1092
  const path = "routeOptions" in request ? request.routeOptions.url : request.routerPath;
1070
1093
  const requestSize = request.headers["content-length"];
@@ -1126,7 +1149,7 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
1126
1149
  headers: convertHeaders(reply.getHeaders()),
1127
1150
  size: Number(responseSize),
1128
1151
  body: convertBody(reply.payload, (_a2 = reply.getHeader("content-type")) == null ? void 0 : _a2.toString())
1129
- });
1152
+ }, reply.serverError);
1130
1153
  }
1131
1154
  }
1132
1155
  done();