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
@@ -194,6 +194,108 @@ import { unlinkSync as unlinkSync2, writeFileSync } from "fs";
194
194
  import { tmpdir as tmpdir2 } from "os";
195
195
  import { join as join2 } from "path";
196
196
 
197
+ // src/common/sentry.ts
198
+ var sentry;
199
+ (async () => {
200
+ try {
201
+ sentry = await import("@sentry/node");
202
+ } catch (e) {
203
+ }
204
+ })();
205
+ function getSentryEventId() {
206
+ if (sentry && sentry.lastEventId) {
207
+ return sentry.lastEventId();
208
+ }
209
+ return void 0;
210
+ }
211
+ __name(getSentryEventId, "getSentryEventId");
212
+
213
+ // src/common/serverErrorCounter.ts
214
+ import { createHash } from "crypto";
215
+ var MAX_MSG_LENGTH = 2048;
216
+ var MAX_STACKTRACE_LENGTH = 65536;
217
+ var _ServerErrorCounter = class _ServerErrorCounter {
218
+ errorCounts;
219
+ errorDetails;
220
+ sentryEventIds;
221
+ constructor() {
222
+ this.errorCounts = /* @__PURE__ */ new Map();
223
+ this.errorDetails = /* @__PURE__ */ new Map();
224
+ this.sentryEventIds = /* @__PURE__ */ new Map();
225
+ }
226
+ addServerError(serverError) {
227
+ const key = this.getKey(serverError);
228
+ if (!this.errorDetails.has(key)) {
229
+ this.errorDetails.set(key, serverError);
230
+ }
231
+ this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
232
+ const sentryEventId = getSentryEventId();
233
+ if (sentryEventId) {
234
+ this.sentryEventIds.set(key, sentryEventId);
235
+ }
236
+ }
237
+ getAndResetServerErrors() {
238
+ const data = [];
239
+ this.errorCounts.forEach((count, key) => {
240
+ const serverError = this.errorDetails.get(key);
241
+ if (serverError) {
242
+ data.push({
243
+ consumer: serverError.consumer || null,
244
+ method: serverError.method,
245
+ path: serverError.path,
246
+ type: serverError.type,
247
+ msg: truncateExceptionMessage(serverError.msg),
248
+ traceback: truncateExceptionStackTrace(serverError.traceback),
249
+ sentry_event_id: this.sentryEventIds.get(key) || null,
250
+ error_count: count
251
+ });
252
+ }
253
+ });
254
+ this.errorCounts.clear();
255
+ this.errorDetails.clear();
256
+ return data;
257
+ }
258
+ getKey(serverError) {
259
+ const hashInput = [
260
+ serverError.consumer || "",
261
+ serverError.method.toUpperCase(),
262
+ serverError.path,
263
+ serverError.type,
264
+ serverError.msg.trim(),
265
+ serverError.traceback.trim()
266
+ ].join("|");
267
+ return createHash("md5").update(hashInput).digest("hex");
268
+ }
269
+ };
270
+ __name(_ServerErrorCounter, "ServerErrorCounter");
271
+ var ServerErrorCounter = _ServerErrorCounter;
272
+ function truncateExceptionMessage(msg) {
273
+ if (msg.length <= MAX_MSG_LENGTH) {
274
+ return msg;
275
+ }
276
+ const suffix = "... (truncated)";
277
+ const cutoff = MAX_MSG_LENGTH - suffix.length;
278
+ return msg.substring(0, cutoff) + suffix;
279
+ }
280
+ __name(truncateExceptionMessage, "truncateExceptionMessage");
281
+ function truncateExceptionStackTrace(stack) {
282
+ const suffix = "... (truncated) ...";
283
+ const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
284
+ const lines = stack.trim().split("\n");
285
+ const truncatedLines = [];
286
+ let length = 0;
287
+ for (const line of lines) {
288
+ if (length + line.length + 1 > cutoff) {
289
+ truncatedLines.push(suffix);
290
+ break;
291
+ }
292
+ truncatedLines.push(line);
293
+ length += line.length + 1;
294
+ }
295
+ return truncatedLines.join("\n");
296
+ }
297
+ __name(truncateExceptionStackTrace, "truncateExceptionStackTrace");
298
+
197
299
  // src/common/tempGzipFile.ts
198
300
  import { Buffer as Buffer2 } from "buffer";
199
301
  import { randomUUID } from "crypto";
@@ -216,9 +318,9 @@ var _TempGzipFile = class _TempGzipFile {
216
318
  this.writeStream.once("ready", resolve);
217
319
  this.writeStream.once("error", reject);
218
320
  });
219
- this.closedPromise = new Promise((resolve) => {
321
+ this.closedPromise = new Promise((resolve, reject) => {
220
322
  this.writeStream.once("close", resolve);
221
- this.writeStream.once("error", resolve);
323
+ this.writeStream.once("error", reject);
222
324
  });
223
325
  this.gzip = createGzip();
224
326
  this.gzip.pipe(this.writeStream);
@@ -316,6 +418,7 @@ var DEFAULT_CONFIG = {
316
418
  logRequestBody: false,
317
419
  logResponseHeaders: true,
318
420
  logResponseBody: false,
421
+ logException: true,
319
422
  maskQueryParams: [],
320
423
  maskHeaders: [],
321
424
  excludePaths: []
@@ -368,7 +471,10 @@ var _RequestLogger = class _RequestLogger {
368
471
  hasSupportedContentType(headers) {
369
472
  var _a3;
370
473
  const contentType = (_a3 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a3[1];
371
- return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
474
+ return this.isSupportedContentType(contentType);
475
+ }
476
+ isSupportedContentType(contentType) {
477
+ return typeof contentType === "string" && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
372
478
  }
373
479
  maskQueryParams(search) {
374
480
  const params = new URLSearchParams(search);
@@ -385,7 +491,7 @@ var _RequestLogger = class _RequestLogger {
385
491
  this.shouldMaskHeader(k) ? MASKED : v
386
492
  ]);
387
493
  }
388
- logRequest(request, response) {
494
+ logRequest(request, response, error) {
389
495
  var _a3, _b, _c;
390
496
  if (!this.enabled || this.suspendUntil !== null) return;
391
497
  const url = new URL(request.url);
@@ -396,8 +502,6 @@ var _RequestLogger = class _RequestLogger {
396
502
  }
397
503
  url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
398
504
  request.url = url.toString();
399
- request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
400
- response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
401
505
  if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
402
506
  request.body = void 0;
403
507
  } else if (request.body) {
@@ -430,10 +534,18 @@ var _RequestLogger = class _RequestLogger {
430
534
  }
431
535
  }
432
536
  }
537
+ request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
538
+ response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
433
539
  const item = {
434
540
  uuid: randomUUID2(),
435
541
  request: skipEmptyValues(request),
436
- response: skipEmptyValues(response)
542
+ response: skipEmptyValues(response),
543
+ exception: error && this.config.logException ? {
544
+ type: error.name,
545
+ message: truncateExceptionMessage(error.message),
546
+ stacktrace: truncateExceptionStackTrace(error.stack || ""),
547
+ sentryEventId: getSentryEventId()
548
+ } : null
437
549
  };
438
550
  [
439
551
  item.request.body,
@@ -595,104 +707,6 @@ function checkWritableFs() {
595
707
  }
596
708
  __name(checkWritableFs, "checkWritableFs");
597
709
 
598
- // src/common/serverErrorCounter.ts
599
- import { createHash } from "crypto";
600
- var MAX_MSG_LENGTH = 2048;
601
- var MAX_STACKTRACE_LENGTH = 65536;
602
- var _ServerErrorCounter = class _ServerErrorCounter {
603
- errorCounts;
604
- errorDetails;
605
- sentryEventIds;
606
- sentry;
607
- constructor() {
608
- this.errorCounts = /* @__PURE__ */ new Map();
609
- this.errorDetails = /* @__PURE__ */ new Map();
610
- this.sentryEventIds = /* @__PURE__ */ new Map();
611
- this.tryImportSentry();
612
- }
613
- addServerError(serverError) {
614
- const key = this.getKey(serverError);
615
- if (!this.errorDetails.has(key)) {
616
- this.errorDetails.set(key, serverError);
617
- }
618
- this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
619
- this.captureSentryEventId(key);
620
- }
621
- getAndResetServerErrors() {
622
- const data = [];
623
- this.errorCounts.forEach((count, key) => {
624
- const serverError = this.errorDetails.get(key);
625
- if (serverError) {
626
- data.push({
627
- consumer: serverError.consumer || null,
628
- method: serverError.method,
629
- path: serverError.path,
630
- type: serverError.type,
631
- msg: this.getTruncatedMessage(serverError.msg),
632
- traceback: this.getTruncatedStack(serverError.traceback),
633
- sentry_event_id: this.sentryEventIds.get(key) || null,
634
- error_count: count
635
- });
636
- }
637
- });
638
- this.errorCounts.clear();
639
- this.errorDetails.clear();
640
- return data;
641
- }
642
- getKey(serverError) {
643
- const hashInput = [
644
- serverError.consumer || "",
645
- serverError.method.toUpperCase(),
646
- serverError.path,
647
- serverError.type,
648
- serverError.msg.trim(),
649
- serverError.traceback.trim()
650
- ].join("|");
651
- return createHash("md5").update(hashInput).digest("hex");
652
- }
653
- getTruncatedMessage(msg) {
654
- msg = msg.trim();
655
- if (msg.length <= MAX_MSG_LENGTH) {
656
- return msg;
657
- }
658
- const suffix = "... (truncated)";
659
- const cutoff = MAX_MSG_LENGTH - suffix.length;
660
- return msg.substring(0, cutoff) + suffix;
661
- }
662
- getTruncatedStack(stack) {
663
- const suffix = "... (truncated) ...";
664
- const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
665
- const lines = stack.trim().split("\n");
666
- const truncatedLines = [];
667
- let length = 0;
668
- for (const line of lines) {
669
- if (length + line.length + 1 > cutoff) {
670
- truncatedLines.push(suffix);
671
- break;
672
- }
673
- truncatedLines.push(line);
674
- length += line.length + 1;
675
- }
676
- return truncatedLines.join("\n");
677
- }
678
- captureSentryEventId(serverErrorKey) {
679
- if (this.sentry && this.sentry.lastEventId) {
680
- const eventId = this.sentry.lastEventId();
681
- if (eventId) {
682
- this.sentryEventIds.set(serverErrorKey, eventId);
683
- }
684
- }
685
- }
686
- async tryImportSentry() {
687
- try {
688
- this.sentry = await import("@sentry/node");
689
- } catch (e) {
690
- }
691
- }
692
- };
693
- __name(_ServerErrorCounter, "ServerErrorCounter");
694
- var ServerErrorCounter = _ServerErrorCounter;
695
-
696
710
  // src/common/validationErrorCounter.ts
697
711
  import { createHash as createHash2 } from "crypto";
698
712
  var _ValidationErrorCounter = class _ValidationErrorCounter {
@@ -766,6 +780,7 @@ var _ApitallyClient = class _ApitallyClient {
766
780
  syncIntervalId;
767
781
  startupData;
768
782
  startupDataSent = false;
783
+ enabled = true;
769
784
  requestCounter;
770
785
  requestLogger;
771
786
  validationErrorCounter;
@@ -802,12 +817,16 @@ var _ApitallyClient = class _ApitallyClient {
802
817
  }
803
818
  return _ApitallyClient.instance;
804
819
  }
820
+ isEnabled() {
821
+ return this.enabled;
822
+ }
805
823
  static async shutdown() {
806
824
  if (_ApitallyClient.instance) {
807
825
  await _ApitallyClient.instance.handleShutdown();
808
826
  }
809
827
  }
810
828
  async handleShutdown() {
829
+ this.enabled = false;
811
830
  this.stopSync();
812
831
  await this.sendSyncData();
813
832
  await this.sendLogData();
@@ -991,6 +1010,7 @@ var _ApitallyClient = class _ApitallyClient {
991
1010
  if (error instanceof HTTPError) {
992
1011
  if (error.response.status === 404) {
993
1012
  this.logger.error(`Invalid Apitally client ID: '${this.clientId}'`);
1013
+ this.enabled = false;
994
1014
  this.stopSync();
995
1015
  return true;
996
1016
  }
@@ -1165,6 +1185,10 @@ var useApitally = /* @__PURE__ */ __name((app, config) => {
1165
1185
  var getMiddleware = /* @__PURE__ */ __name((app, client) => {
1166
1186
  let errorHandlerConfigured = false;
1167
1187
  return (req, res, next) => {
1188
+ if (!client.isEnabled()) {
1189
+ next();
1190
+ return;
1191
+ }
1168
1192
  if (!errorHandlerConfigured) {
1169
1193
  app.use((err, req2, res2, next2) => {
1170
1194
  res2.locals.serverError = err;
@@ -1176,7 +1200,10 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
1176
1200
  const startTime = performance.now();
1177
1201
  const originalSend = res.send;
1178
1202
  res.send = (body) => {
1179
- res.locals.body = body;
1203
+ const contentType = res.get("content-type");
1204
+ if (client.requestLogger.isSupportedContentType(contentType)) {
1205
+ res.locals.body = body;
1206
+ }
1180
1207
  return originalSend.call(res, body);
1181
1208
  };
1182
1209
  res.on("finish", () => {
@@ -1250,7 +1277,7 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
1250
1277
  headers: convertHeaders(res.getHeaders()),
1251
1278
  size: Number(res.get("content-length")),
1252
1279
  body: convertBody(res.locals.body, res.get("content-type"))
1253
- });
1280
+ }, res.locals.serverError);
1254
1281
  }
1255
1282
  } catch (error) {
1256
1283
  client.logger.error("Error while logging request in Apitally middleware.", {