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
  }
@@ -1161,6 +1181,10 @@ var useApitally = /* @__PURE__ */ __name((app, config) => {
1161
1181
  var getMiddleware = /* @__PURE__ */ __name((app, client) => {
1162
1182
  let errorHandlerConfigured = false;
1163
1183
  return (req, res, next) => {
1184
+ if (!client.isEnabled()) {
1185
+ next();
1186
+ return;
1187
+ }
1164
1188
  if (!errorHandlerConfigured) {
1165
1189
  app.use((err, req2, res2, next2) => {
1166
1190
  res2.locals.serverError = err;
@@ -1172,7 +1196,10 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
1172
1196
  const startTime = performance.now();
1173
1197
  const originalSend = res.send;
1174
1198
  res.send = (body) => {
1175
- res.locals.body = body;
1199
+ const contentType = res.get("content-type");
1200
+ if (client.requestLogger.isSupportedContentType(contentType)) {
1201
+ res.locals.body = body;
1202
+ }
1176
1203
  return originalSend.call(res, body);
1177
1204
  };
1178
1205
  res.on("finish", () => {
@@ -1246,7 +1273,7 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
1246
1273
  headers: convertHeaders(res.getHeaders()),
1247
1274
  size: Number(res.get("content-length")),
1248
1275
  body: convertBody(res.locals.body, res.get("content-type"))
1249
- });
1276
+ }, res.locals.serverError);
1250
1277
  }
1251
1278
  } catch (error) {
1252
1279
  client.logger.error("Error while logging request in Apitally middleware.", {