apitally 0.9.5 → 0.11.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.
- package/README.md +1 -2
- package/dist/common/client.cjs +398 -12
- package/dist/common/client.cjs.map +1 -1
- package/dist/common/client.d.cts +8 -1
- package/dist/common/client.d.ts +8 -1
- package/dist/common/client.js +394 -8
- package/dist/common/client.js.map +1 -1
- package/dist/common/consumerRegistry.d.cts +4 -0
- package/dist/common/consumerRegistry.d.ts +4 -0
- package/dist/common/requestCounter.d.cts +4 -0
- package/dist/common/requestCounter.d.ts +4 -0
- package/dist/common/requestLogger.cjs +419 -0
- package/dist/common/requestLogger.cjs.map +1 -0
- package/dist/common/requestLogger.d.cts +63 -0
- package/dist/common/requestLogger.d.ts +63 -0
- package/dist/common/requestLogger.js +395 -0
- package/dist/common/requestLogger.js.map +1 -0
- package/dist/common/serverErrorCounter.d.cts +4 -0
- package/dist/common/serverErrorCounter.d.ts +4 -0
- package/dist/common/tempGzipFile.cjs +93 -0
- package/dist/common/tempGzipFile.cjs.map +1 -0
- package/dist/common/tempGzipFile.d.cts +17 -0
- package/dist/common/tempGzipFile.d.ts +17 -0
- package/dist/common/tempGzipFile.js +74 -0
- package/dist/common/tempGzipFile.js.map +1 -0
- package/dist/common/types.cjs.map +1 -1
- package/dist/common/types.d.cts +5 -0
- package/dist/common/types.d.ts +5 -0
- package/dist/common/validationErrorCounter.d.cts +4 -0
- package/dist/common/validationErrorCounter.d.ts +4 -0
- package/dist/express/index.cjs +500 -34
- package/dist/express/index.cjs.map +1 -1
- package/dist/express/index.d.cts +4 -0
- package/dist/express/index.d.ts +4 -0
- package/dist/express/index.js +496 -30
- package/dist/express/index.js.map +1 -1
- package/dist/express/middleware.cjs +500 -34
- package/dist/express/middleware.cjs.map +1 -1
- package/dist/express/middleware.d.cts +4 -0
- package/dist/express/middleware.d.ts +4 -0
- package/dist/express/middleware.js +496 -30
- package/dist/express/middleware.js.map +1 -1
- package/dist/fastify/index.cjs +491 -27
- package/dist/fastify/index.cjs.map +1 -1
- package/dist/fastify/index.d.cts +4 -0
- package/dist/fastify/index.d.ts +4 -0
- package/dist/fastify/index.js +487 -23
- package/dist/fastify/index.js.map +1 -1
- package/dist/fastify/plugin.cjs +491 -27
- package/dist/fastify/plugin.cjs.map +1 -1
- package/dist/fastify/plugin.d.cts +4 -0
- package/dist/fastify/plugin.d.ts +4 -0
- package/dist/fastify/plugin.js +487 -23
- package/dist/fastify/plugin.js.map +1 -1
- package/dist/hono/index.cjs +460 -15
- package/dist/hono/index.cjs.map +1 -1
- package/dist/hono/index.d.cts +4 -0
- package/dist/hono/index.d.ts +4 -0
- package/dist/hono/index.js +456 -11
- package/dist/hono/index.js.map +1 -1
- package/dist/hono/middleware.cjs +460 -15
- package/dist/hono/middleware.cjs.map +1 -1
- package/dist/hono/middleware.d.cts +4 -0
- package/dist/hono/middleware.d.ts +4 -0
- package/dist/hono/middleware.js +456 -11
- package/dist/hono/middleware.js.map +1 -1
- package/dist/koa/index.cjs +475 -15
- package/dist/koa/index.cjs.map +1 -1
- package/dist/koa/index.d.cts +4 -0
- package/dist/koa/index.d.ts +4 -0
- package/dist/koa/index.js +471 -11
- package/dist/koa/index.js.map +1 -1
- package/dist/koa/middleware.cjs +475 -15
- package/dist/koa/middleware.cjs.map +1 -1
- package/dist/koa/middleware.d.cts +4 -0
- package/dist/koa/middleware.d.ts +4 -0
- package/dist/koa/middleware.js +471 -11
- package/dist/koa/middleware.js.map +1 -1
- package/dist/nestjs/index.cjs +536 -41
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.d.cts +10 -2
- package/dist/nestjs/index.d.ts +10 -2
- package/dist/nestjs/index.js +534 -37
- package/dist/nestjs/index.js.map +1 -1
- package/package.json +8 -2
package/dist/nestjs/index.cjs
CHANGED
|
@@ -33,20 +33,22 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
33
33
|
// src/nestjs/index.ts
|
|
34
34
|
var nestjs_exports = {};
|
|
35
35
|
__export(nestjs_exports, {
|
|
36
|
-
useApitally: () =>
|
|
36
|
+
useApitally: () => useApitally2
|
|
37
37
|
});
|
|
38
38
|
module.exports = __toCommonJS(nestjs_exports);
|
|
39
|
+
var import_common = require("@nestjs/common");
|
|
40
|
+
var import_core = require("@nestjs/core");
|
|
39
41
|
|
|
40
42
|
// src/express/middleware.ts
|
|
41
43
|
var import_perf_hooks = require("perf_hooks");
|
|
42
44
|
|
|
43
45
|
// src/common/client.ts
|
|
44
|
-
var
|
|
46
|
+
var import_crypto5 = require("crypto");
|
|
45
47
|
var import_fetch_retry = __toESM(require("fetch-retry"), 1);
|
|
46
48
|
|
|
47
49
|
// src/common/consumerRegistry.ts
|
|
48
50
|
var consumerFromStringOrObject = /* @__PURE__ */ __name((consumer) => {
|
|
49
|
-
var
|
|
51
|
+
var _a3, _b;
|
|
50
52
|
if (typeof consumer === "string") {
|
|
51
53
|
consumer = String(consumer).trim().substring(0, 128);
|
|
52
54
|
return consumer ? {
|
|
@@ -54,7 +56,7 @@ var consumerFromStringOrObject = /* @__PURE__ */ __name((consumer) => {
|
|
|
54
56
|
} : null;
|
|
55
57
|
} else {
|
|
56
58
|
consumer.identifier = String(consumer.identifier).trim().substring(0, 128);
|
|
57
|
-
consumer.name = (
|
|
59
|
+
consumer.name = (_a3 = consumer.name) == null ? void 0 : _a3.trim().substring(0, 64);
|
|
58
60
|
consumer.group = (_b = consumer.group) == null ? void 0 : _b.trim().substring(0, 64);
|
|
59
61
|
return consumer.identifier ? consumer : null;
|
|
60
62
|
}
|
|
@@ -210,8 +212,395 @@ var _RequestCounter = class _RequestCounter {
|
|
|
210
212
|
__name(_RequestCounter, "RequestCounter");
|
|
211
213
|
var RequestCounter = _RequestCounter;
|
|
212
214
|
|
|
213
|
-
// src/common/
|
|
215
|
+
// src/common/requestLogger.ts
|
|
216
|
+
var import_buffer2 = require("buffer");
|
|
217
|
+
var import_crypto2 = require("crypto");
|
|
218
|
+
var import_fs2 = require("fs");
|
|
219
|
+
var import_os2 = require("os");
|
|
220
|
+
var import_path2 = require("path");
|
|
221
|
+
|
|
222
|
+
// src/common/tempGzipFile.ts
|
|
223
|
+
var import_buffer = require("buffer");
|
|
214
224
|
var import_crypto = require("crypto");
|
|
225
|
+
var import_fs = require("fs");
|
|
226
|
+
var import_os = require("os");
|
|
227
|
+
var import_path = require("path");
|
|
228
|
+
var import_zlib = require("zlib");
|
|
229
|
+
var _TempGzipFile = class _TempGzipFile {
|
|
230
|
+
uuid;
|
|
231
|
+
filePath;
|
|
232
|
+
gzip;
|
|
233
|
+
writeStream;
|
|
234
|
+
readyPromise;
|
|
235
|
+
constructor() {
|
|
236
|
+
this.uuid = (0, import_crypto.randomUUID)();
|
|
237
|
+
this.filePath = (0, import_path.join)((0, import_os.tmpdir)(), `apitally-${this.uuid}.gz`);
|
|
238
|
+
this.writeStream = (0, import_fs.createWriteStream)(this.filePath);
|
|
239
|
+
this.readyPromise = new Promise((resolve, reject) => {
|
|
240
|
+
this.writeStream.once("ready", resolve);
|
|
241
|
+
this.writeStream.once("error", reject);
|
|
242
|
+
});
|
|
243
|
+
this.gzip = (0, import_zlib.createGzip)();
|
|
244
|
+
this.gzip.pipe(this.writeStream);
|
|
245
|
+
}
|
|
246
|
+
get size() {
|
|
247
|
+
return this.writeStream.bytesWritten;
|
|
248
|
+
}
|
|
249
|
+
async writeLine(data) {
|
|
250
|
+
await this.readyPromise;
|
|
251
|
+
return new Promise((resolve, reject) => {
|
|
252
|
+
this.gzip.write(import_buffer.Buffer.concat([
|
|
253
|
+
data,
|
|
254
|
+
import_buffer.Buffer.from("\n")
|
|
255
|
+
]), (error) => {
|
|
256
|
+
if (error) {
|
|
257
|
+
reject(error);
|
|
258
|
+
} else {
|
|
259
|
+
resolve();
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
async getContent() {
|
|
265
|
+
return new Promise((resolve, reject) => {
|
|
266
|
+
(0, import_fs.readFile)(this.filePath, (error, data) => {
|
|
267
|
+
if (error) {
|
|
268
|
+
reject(error);
|
|
269
|
+
} else {
|
|
270
|
+
resolve(data);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
async close() {
|
|
276
|
+
return new Promise((resolve) => {
|
|
277
|
+
this.gzip.end(() => {
|
|
278
|
+
resolve();
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
async delete() {
|
|
283
|
+
await this.close();
|
|
284
|
+
(0, import_fs.unlinkSync)(this.filePath);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
__name(_TempGzipFile, "TempGzipFile");
|
|
288
|
+
var TempGzipFile = _TempGzipFile;
|
|
289
|
+
|
|
290
|
+
// src/common/requestLogger.ts
|
|
291
|
+
var MAX_BODY_SIZE = 5e4;
|
|
292
|
+
var MAX_FILE_SIZE = 1e6;
|
|
293
|
+
var MAX_FILES = 50;
|
|
294
|
+
var MAX_PENDING_WRITES = 100;
|
|
295
|
+
var BODY_TOO_LARGE = import_buffer2.Buffer.from("<body too large>");
|
|
296
|
+
var BODY_MASKED = import_buffer2.Buffer.from("<masked>");
|
|
297
|
+
var MASKED = "******";
|
|
298
|
+
var ALLOWED_CONTENT_TYPES = [
|
|
299
|
+
"application/json",
|
|
300
|
+
"text/plain"
|
|
301
|
+
];
|
|
302
|
+
var EXCLUDE_PATH_PATTERNS = [
|
|
303
|
+
/\/_?healthz?$/,
|
|
304
|
+
/\/_?health[_-]?checks?$/,
|
|
305
|
+
/\/_?heart[_-]?beats?$/,
|
|
306
|
+
/\/ping$/,
|
|
307
|
+
/\/ready$/,
|
|
308
|
+
/\/live$/
|
|
309
|
+
];
|
|
310
|
+
var MASK_QUERY_PARAM_PATTERNS = [
|
|
311
|
+
/auth/i,
|
|
312
|
+
/api-?key/i,
|
|
313
|
+
/secret/i,
|
|
314
|
+
/token/i,
|
|
315
|
+
/password/i,
|
|
316
|
+
/pwd/i
|
|
317
|
+
];
|
|
318
|
+
var MASK_HEADER_PATTERNS = [
|
|
319
|
+
/auth/i,
|
|
320
|
+
/api-?key/i,
|
|
321
|
+
/secret/i,
|
|
322
|
+
/token/i,
|
|
323
|
+
/cookie/i
|
|
324
|
+
];
|
|
325
|
+
var DEFAULT_CONFIG = {
|
|
326
|
+
enabled: false,
|
|
327
|
+
logQueryParams: true,
|
|
328
|
+
logRequestHeaders: false,
|
|
329
|
+
logRequestBody: false,
|
|
330
|
+
logResponseHeaders: true,
|
|
331
|
+
logResponseBody: false,
|
|
332
|
+
maskQueryParams: [],
|
|
333
|
+
maskHeaders: [],
|
|
334
|
+
excludePaths: []
|
|
335
|
+
};
|
|
336
|
+
var _RequestLogger = class _RequestLogger {
|
|
337
|
+
config;
|
|
338
|
+
enabled;
|
|
339
|
+
suspendUntil = null;
|
|
340
|
+
pendingWrites = [];
|
|
341
|
+
currentFile = null;
|
|
342
|
+
files = [];
|
|
343
|
+
maintainIntervalId;
|
|
344
|
+
constructor(config) {
|
|
345
|
+
this.config = {
|
|
346
|
+
...DEFAULT_CONFIG,
|
|
347
|
+
...config
|
|
348
|
+
};
|
|
349
|
+
this.enabled = this.config.enabled && checkWritableFs();
|
|
350
|
+
if (this.enabled) {
|
|
351
|
+
this.maintainIntervalId = setInterval(() => {
|
|
352
|
+
this.maintain();
|
|
353
|
+
}, 1e3);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
shouldExcludePath(urlPath) {
|
|
357
|
+
const patterns = [
|
|
358
|
+
...this.config.excludePaths,
|
|
359
|
+
...EXCLUDE_PATH_PATTERNS
|
|
360
|
+
];
|
|
361
|
+
return matchPatterns(urlPath, patterns);
|
|
362
|
+
}
|
|
363
|
+
shouldMaskQueryParam(name) {
|
|
364
|
+
const patterns = [
|
|
365
|
+
...this.config.maskQueryParams,
|
|
366
|
+
...MASK_QUERY_PARAM_PATTERNS
|
|
367
|
+
];
|
|
368
|
+
return matchPatterns(name, patterns);
|
|
369
|
+
}
|
|
370
|
+
shouldMaskHeader(name) {
|
|
371
|
+
const patterns = [
|
|
372
|
+
...this.config.maskHeaders,
|
|
373
|
+
...MASK_HEADER_PATTERNS
|
|
374
|
+
];
|
|
375
|
+
return matchPatterns(name, patterns);
|
|
376
|
+
}
|
|
377
|
+
hasSupportedContentType(headers) {
|
|
378
|
+
var _a3;
|
|
379
|
+
const contentType = (_a3 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a3[1];
|
|
380
|
+
return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
|
|
381
|
+
}
|
|
382
|
+
maskQueryParams(search) {
|
|
383
|
+
const params = new URLSearchParams(search);
|
|
384
|
+
for (const [key] of params) {
|
|
385
|
+
if (this.shouldMaskQueryParam(key)) {
|
|
386
|
+
params.set(key, MASKED);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return params.toString();
|
|
390
|
+
}
|
|
391
|
+
maskHeaders(headers) {
|
|
392
|
+
return headers.map(([k, v]) => [
|
|
393
|
+
k,
|
|
394
|
+
this.shouldMaskHeader(k) ? MASKED : v
|
|
395
|
+
]);
|
|
396
|
+
}
|
|
397
|
+
logRequest(request, response) {
|
|
398
|
+
var _a3, _b;
|
|
399
|
+
if (!this.enabled || this.suspendUntil !== null) return;
|
|
400
|
+
const url = new URL(request.url);
|
|
401
|
+
const path = request.path ?? url.pathname;
|
|
402
|
+
if (this.shouldExcludePath(path) || (((_b = (_a3 = this.config).excludeCallback) == null ? void 0 : _b.call(_a3, request, response)) ?? false)) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
|
|
406
|
+
request.url = url.toString();
|
|
407
|
+
request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
|
|
408
|
+
response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
|
|
409
|
+
if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
|
|
410
|
+
request.body = void 0;
|
|
411
|
+
} else if (request.body) {
|
|
412
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
413
|
+
request.body = BODY_TOO_LARGE;
|
|
414
|
+
} else if (this.config.maskRequestBodyCallback) {
|
|
415
|
+
try {
|
|
416
|
+
request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
|
|
417
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
418
|
+
request.body = BODY_TOO_LARGE;
|
|
419
|
+
}
|
|
420
|
+
} catch {
|
|
421
|
+
request.body = void 0;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
|
|
426
|
+
response.body = void 0;
|
|
427
|
+
} else if (response.body) {
|
|
428
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
429
|
+
response.body = BODY_TOO_LARGE;
|
|
430
|
+
} else if (this.config.maskResponseBodyCallback) {
|
|
431
|
+
try {
|
|
432
|
+
response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
|
|
433
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
434
|
+
response.body = BODY_TOO_LARGE;
|
|
435
|
+
}
|
|
436
|
+
} catch {
|
|
437
|
+
response.body = void 0;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
const item = {
|
|
442
|
+
uuid: (0, import_crypto2.randomUUID)(),
|
|
443
|
+
request: skipEmptyValues(request),
|
|
444
|
+
response: skipEmptyValues(response)
|
|
445
|
+
};
|
|
446
|
+
[
|
|
447
|
+
item.request.body,
|
|
448
|
+
item.response.body
|
|
449
|
+
].forEach((body) => {
|
|
450
|
+
if (body) {
|
|
451
|
+
body.toJSON = function() {
|
|
452
|
+
return this.toString("base64");
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
this.pendingWrites.push(JSON.stringify(item));
|
|
457
|
+
if (this.pendingWrites.length > MAX_PENDING_WRITES) {
|
|
458
|
+
this.pendingWrites.shift();
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
async writeToFile() {
|
|
462
|
+
if (!this.enabled || this.pendingWrites.length === 0) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (!this.currentFile) {
|
|
466
|
+
this.currentFile = new TempGzipFile();
|
|
467
|
+
}
|
|
468
|
+
while (this.pendingWrites.length > 0) {
|
|
469
|
+
const item = this.pendingWrites.shift();
|
|
470
|
+
if (item) {
|
|
471
|
+
await this.currentFile.writeLine(import_buffer2.Buffer.from(item));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
getFile() {
|
|
476
|
+
return this.files.shift();
|
|
477
|
+
}
|
|
478
|
+
retryFileLater(file) {
|
|
479
|
+
this.files.unshift(file);
|
|
480
|
+
}
|
|
481
|
+
async rotateFile() {
|
|
482
|
+
if (this.currentFile) {
|
|
483
|
+
await this.currentFile.close();
|
|
484
|
+
this.files.push(this.currentFile);
|
|
485
|
+
this.currentFile = null;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
async maintain() {
|
|
489
|
+
await this.writeToFile();
|
|
490
|
+
if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
|
|
491
|
+
await this.rotateFile();
|
|
492
|
+
}
|
|
493
|
+
while (this.files.length > MAX_FILES) {
|
|
494
|
+
const file = this.files.shift();
|
|
495
|
+
file == null ? void 0 : file.delete();
|
|
496
|
+
}
|
|
497
|
+
if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
|
|
498
|
+
this.suspendUntil = null;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
async clear() {
|
|
502
|
+
this.pendingWrites = [];
|
|
503
|
+
await this.rotateFile();
|
|
504
|
+
this.files.forEach((file) => {
|
|
505
|
+
file.delete();
|
|
506
|
+
});
|
|
507
|
+
this.files = [];
|
|
508
|
+
}
|
|
509
|
+
async close() {
|
|
510
|
+
this.enabled = false;
|
|
511
|
+
await this.clear();
|
|
512
|
+
if (this.maintainIntervalId) {
|
|
513
|
+
clearInterval(this.maintainIntervalId);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
__name(_RequestLogger, "RequestLogger");
|
|
518
|
+
var RequestLogger = _RequestLogger;
|
|
519
|
+
function convertHeaders(headers) {
|
|
520
|
+
if (headers instanceof Headers) {
|
|
521
|
+
return Array.from(headers.entries());
|
|
522
|
+
}
|
|
523
|
+
return Object.entries(headers).flatMap(([key, value]) => {
|
|
524
|
+
if (value === void 0) {
|
|
525
|
+
return [];
|
|
526
|
+
}
|
|
527
|
+
if (Array.isArray(value)) {
|
|
528
|
+
return value.map((v) => [
|
|
529
|
+
key,
|
|
530
|
+
v
|
|
531
|
+
]);
|
|
532
|
+
}
|
|
533
|
+
return [
|
|
534
|
+
[
|
|
535
|
+
key,
|
|
536
|
+
value.toString()
|
|
537
|
+
]
|
|
538
|
+
];
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
__name(convertHeaders, "convertHeaders");
|
|
542
|
+
function convertBody(body, contentType) {
|
|
543
|
+
if (!body || !contentType) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
try {
|
|
547
|
+
if (contentType.startsWith("application/json")) {
|
|
548
|
+
if (isValidJsonString(body)) {
|
|
549
|
+
return import_buffer2.Buffer.from(body);
|
|
550
|
+
} else {
|
|
551
|
+
return import_buffer2.Buffer.from(JSON.stringify(body));
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (contentType.startsWith("text/") && typeof body === "string") {
|
|
555
|
+
return import_buffer2.Buffer.from(body);
|
|
556
|
+
}
|
|
557
|
+
} catch (error) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
__name(convertBody, "convertBody");
|
|
562
|
+
function isValidJsonString(body) {
|
|
563
|
+
if (typeof body !== "string") {
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
try {
|
|
567
|
+
JSON.parse(body);
|
|
568
|
+
return true;
|
|
569
|
+
} catch {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
__name(isValidJsonString, "isValidJsonString");
|
|
574
|
+
function matchPatterns(value, patterns) {
|
|
575
|
+
return patterns.some((pattern) => {
|
|
576
|
+
return pattern.test(value);
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
__name(matchPatterns, "matchPatterns");
|
|
580
|
+
function skipEmptyValues(data) {
|
|
581
|
+
return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
|
|
582
|
+
if (v == null || Number.isNaN(v)) return false;
|
|
583
|
+
if (Array.isArray(v) || import_buffer2.Buffer.isBuffer(v) || typeof v === "string") {
|
|
584
|
+
return v.length > 0;
|
|
585
|
+
}
|
|
586
|
+
return true;
|
|
587
|
+
}));
|
|
588
|
+
}
|
|
589
|
+
__name(skipEmptyValues, "skipEmptyValues");
|
|
590
|
+
function checkWritableFs() {
|
|
591
|
+
try {
|
|
592
|
+
const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto2.randomUUID)()}`);
|
|
593
|
+
(0, import_fs2.writeFileSync)(testPath, "test");
|
|
594
|
+
(0, import_fs2.unlinkSync)(testPath);
|
|
595
|
+
return true;
|
|
596
|
+
} catch (error) {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
__name(checkWritableFs, "checkWritableFs");
|
|
601
|
+
|
|
602
|
+
// src/common/serverErrorCounter.ts
|
|
603
|
+
var import_crypto3 = require("crypto");
|
|
215
604
|
var MAX_MSG_LENGTH = 2048;
|
|
216
605
|
var MAX_STACKTRACE_LENGTH = 65536;
|
|
217
606
|
var _ServerErrorCounter = class _ServerErrorCounter {
|
|
@@ -263,7 +652,7 @@ var _ServerErrorCounter = class _ServerErrorCounter {
|
|
|
263
652
|
serverError.msg.trim(),
|
|
264
653
|
serverError.traceback.trim()
|
|
265
654
|
].join("|");
|
|
266
|
-
return (0,
|
|
655
|
+
return (0, import_crypto3.createHash)("md5").update(hashInput).digest("hex");
|
|
267
656
|
}
|
|
268
657
|
getTruncatedMessage(msg) {
|
|
269
658
|
msg = msg.trim();
|
|
@@ -309,7 +698,7 @@ __name(_ServerErrorCounter, "ServerErrorCounter");
|
|
|
309
698
|
var ServerErrorCounter = _ServerErrorCounter;
|
|
310
699
|
|
|
311
700
|
// src/common/validationErrorCounter.ts
|
|
312
|
-
var
|
|
701
|
+
var import_crypto4 = require("crypto");
|
|
313
702
|
var _ValidationErrorCounter = class _ValidationErrorCounter {
|
|
314
703
|
errorCounts;
|
|
315
704
|
errorDetails;
|
|
@@ -353,7 +742,7 @@ var _ValidationErrorCounter = class _ValidationErrorCounter {
|
|
|
353
742
|
validationError.msg.trim(),
|
|
354
743
|
validationError.type
|
|
355
744
|
].join("|");
|
|
356
|
-
return (0,
|
|
745
|
+
return (0, import_crypto4.createHash)("md5").update(hashInput).digest("hex");
|
|
357
746
|
}
|
|
358
747
|
};
|
|
359
748
|
__name(_ValidationErrorCounter, "ValidationErrorCounter");
|
|
@@ -382,11 +771,12 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
382
771
|
startupData;
|
|
383
772
|
startupDataSent = false;
|
|
384
773
|
requestCounter;
|
|
774
|
+
requestLogger;
|
|
385
775
|
validationErrorCounter;
|
|
386
776
|
serverErrorCounter;
|
|
387
777
|
consumerRegistry;
|
|
388
778
|
logger;
|
|
389
|
-
constructor({ clientId, env = "dev", logger }) {
|
|
779
|
+
constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
|
|
390
780
|
if (_ApitallyClient.instance) {
|
|
391
781
|
throw new Error("Apitally client is already initialized");
|
|
392
782
|
}
|
|
@@ -399,9 +789,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
399
789
|
_ApitallyClient.instance = this;
|
|
400
790
|
this.clientId = clientId;
|
|
401
791
|
this.env = env;
|
|
402
|
-
this.instanceUuid = (0,
|
|
792
|
+
this.instanceUuid = (0, import_crypto5.randomUUID)();
|
|
403
793
|
this.syncDataQueue = [];
|
|
404
794
|
this.requestCounter = new RequestCounter();
|
|
795
|
+
this.requestLogger = new RequestLogger(requestLoggingConfig);
|
|
405
796
|
this.validationErrorCounter = new ValidationErrorCounter();
|
|
406
797
|
this.serverErrorCounter = new ServerErrorCounter();
|
|
407
798
|
this.consumerRegistry = new ConsumerRegistry();
|
|
@@ -423,6 +814,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
423
814
|
async handleShutdown() {
|
|
424
815
|
this.stopSync();
|
|
425
816
|
await this.sendSyncData();
|
|
817
|
+
await this.sendLogData();
|
|
818
|
+
await this.requestLogger.close();
|
|
426
819
|
_ApitallyClient.instance = void 0;
|
|
427
820
|
}
|
|
428
821
|
getHubUrlPrefix() {
|
|
@@ -469,7 +862,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
469
862
|
async sync() {
|
|
470
863
|
try {
|
|
471
864
|
const promises = [
|
|
472
|
-
this.sendSyncData()
|
|
865
|
+
this.sendSyncData(),
|
|
866
|
+
this.sendLogData()
|
|
473
867
|
];
|
|
474
868
|
if (!this.startupDataSent) {
|
|
475
869
|
promises.push(this.sendStartupData());
|
|
@@ -497,7 +891,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
497
891
|
this.logger.debug("Sending startup data to Apitally Hub");
|
|
498
892
|
const payload = {
|
|
499
893
|
instance_uuid: this.instanceUuid,
|
|
500
|
-
message_uuid: (0,
|
|
894
|
+
message_uuid: (0, import_crypto5.randomUUID)(),
|
|
501
895
|
...this.startupData
|
|
502
896
|
};
|
|
503
897
|
try {
|
|
@@ -519,7 +913,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
519
913
|
const newPayload = {
|
|
520
914
|
time_offset: 0,
|
|
521
915
|
instance_uuid: this.instanceUuid,
|
|
522
|
-
message_uuid: (0,
|
|
916
|
+
message_uuid: (0, import_crypto5.randomUUID)(),
|
|
523
917
|
requests: this.requestCounter.getAndResetRequests(),
|
|
524
918
|
validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
|
|
525
919
|
server_errors: this.serverErrorCounter.getAndResetServerErrors(),
|
|
@@ -538,8 +932,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
538
932
|
const timeOffset = Date.now() - time;
|
|
539
933
|
if (timeOffset <= MAX_QUEUE_TIME) {
|
|
540
934
|
if (i > 0) {
|
|
541
|
-
|
|
542
|
-
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
935
|
+
await this.randomDelay();
|
|
543
936
|
}
|
|
544
937
|
payload.time_offset = timeOffset / 1e3;
|
|
545
938
|
await this.sendData("sync", payload);
|
|
@@ -558,6 +951,52 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
558
951
|
}
|
|
559
952
|
}
|
|
560
953
|
}
|
|
954
|
+
async sendLogData() {
|
|
955
|
+
this.logger.debug("Sending request log data to Apitally Hub");
|
|
956
|
+
await this.requestLogger.rotateFile();
|
|
957
|
+
const fetchWithRetry = (0, import_fetch_retry.default)(fetch, {
|
|
958
|
+
retries: 3,
|
|
959
|
+
retryDelay: 1e3,
|
|
960
|
+
retryOn: [
|
|
961
|
+
408,
|
|
962
|
+
429,
|
|
963
|
+
500,
|
|
964
|
+
502,
|
|
965
|
+
503,
|
|
966
|
+
504
|
|
967
|
+
]
|
|
968
|
+
});
|
|
969
|
+
let i = 0;
|
|
970
|
+
let logFile;
|
|
971
|
+
while (logFile = this.requestLogger.getFile()) {
|
|
972
|
+
if (i > 0) {
|
|
973
|
+
await this.randomDelay();
|
|
974
|
+
}
|
|
975
|
+
try {
|
|
976
|
+
const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
|
|
977
|
+
method: "POST",
|
|
978
|
+
body: await logFile.getContent()
|
|
979
|
+
});
|
|
980
|
+
if (response.status === 402 && response.headers.has("Retry-After")) {
|
|
981
|
+
const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
|
|
982
|
+
if (retryAfter > 0) {
|
|
983
|
+
this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
|
|
984
|
+
this.requestLogger.clear();
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
if (!response.ok) {
|
|
989
|
+
throw new HTTPError(response);
|
|
990
|
+
}
|
|
991
|
+
logFile.delete();
|
|
992
|
+
} catch (error) {
|
|
993
|
+
this.requestLogger.retryFileLater(logFile);
|
|
994
|
+
break;
|
|
995
|
+
}
|
|
996
|
+
i++;
|
|
997
|
+
if (i >= 10) break;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
561
1000
|
handleHubError(error) {
|
|
562
1001
|
if (error instanceof HTTPError) {
|
|
563
1002
|
if (error.response.status === 404) {
|
|
@@ -572,6 +1011,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
572
1011
|
}
|
|
573
1012
|
return false;
|
|
574
1013
|
}
|
|
1014
|
+
async randomDelay() {
|
|
1015
|
+
const delay = 100 + Math.random() * 200;
|
|
1016
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1017
|
+
}
|
|
575
1018
|
};
|
|
576
1019
|
__name(_ApitallyClient, "ApitallyClient");
|
|
577
1020
|
__publicField(_ApitallyClient, "instance");
|
|
@@ -742,18 +1185,18 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
742
1185
|
}
|
|
743
1186
|
try {
|
|
744
1187
|
const startTime = import_perf_hooks.performance.now();
|
|
745
|
-
const
|
|
746
|
-
res.
|
|
1188
|
+
const originalSend = res.send;
|
|
1189
|
+
res.send = (body) => {
|
|
747
1190
|
res.locals.body = body;
|
|
748
|
-
return
|
|
1191
|
+
return originalSend.call(res, body);
|
|
749
1192
|
};
|
|
750
1193
|
res.on("finish", () => {
|
|
751
1194
|
try {
|
|
1195
|
+
const responseTime = import_perf_hooks.performance.now() - startTime;
|
|
752
1196
|
const path = getRoutePath(req);
|
|
1197
|
+
const consumer = getConsumer(req);
|
|
1198
|
+
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
753
1199
|
if (path) {
|
|
754
|
-
const responseTime = import_perf_hooks.performance.now() - startTime;
|
|
755
|
-
const consumer = getConsumer(req);
|
|
756
|
-
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
757
1200
|
client.requestCounter.addRequest({
|
|
758
1201
|
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
759
1202
|
method: req.method,
|
|
@@ -764,24 +1207,31 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
764
1207
|
responseSize: res.get("content-length")
|
|
765
1208
|
});
|
|
766
1209
|
if ((res.statusCode === 400 || res.statusCode === 422) && res.locals.body) {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
}
|
|
771
|
-
if (validationErrors.length === 0) {
|
|
772
|
-
validationErrors.push(...extractCelebrateErrors(res.locals.body));
|
|
1210
|
+
let jsonBody;
|
|
1211
|
+
try {
|
|
1212
|
+
jsonBody = JSON.parse(res.locals.body);
|
|
1213
|
+
} catch {
|
|
773
1214
|
}
|
|
774
|
-
if (
|
|
775
|
-
validationErrors
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
1215
|
+
if (jsonBody) {
|
|
1216
|
+
const validationErrors = [];
|
|
1217
|
+
if (validationErrors.length === 0) {
|
|
1218
|
+
validationErrors.push(...extractExpressValidatorErrors(jsonBody));
|
|
1219
|
+
}
|
|
1220
|
+
if (validationErrors.length === 0) {
|
|
1221
|
+
validationErrors.push(...extractCelebrateErrors(jsonBody));
|
|
1222
|
+
}
|
|
1223
|
+
if (validationErrors.length === 0) {
|
|
1224
|
+
validationErrors.push(...extractNestValidationErrors(jsonBody));
|
|
1225
|
+
}
|
|
1226
|
+
validationErrors.forEach((error) => {
|
|
1227
|
+
client.validationErrorCounter.addValidationError({
|
|
1228
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1229
|
+
method: req.method,
|
|
1230
|
+
path: req.route.path,
|
|
1231
|
+
...error
|
|
1232
|
+
});
|
|
783
1233
|
});
|
|
784
|
-
}
|
|
1234
|
+
}
|
|
785
1235
|
}
|
|
786
1236
|
if (res.statusCode === 500 && res.locals.serverError) {
|
|
787
1237
|
const serverError = res.locals.serverError;
|
|
@@ -795,6 +1245,24 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
795
1245
|
});
|
|
796
1246
|
}
|
|
797
1247
|
}
|
|
1248
|
+
if (client.requestLogger.enabled) {
|
|
1249
|
+
client.requestLogger.logRequest({
|
|
1250
|
+
timestamp: Date.now() / 1e3,
|
|
1251
|
+
method: req.method,
|
|
1252
|
+
path,
|
|
1253
|
+
url: `${req.protocol}://${req.host}${req.originalUrl}`,
|
|
1254
|
+
headers: convertHeaders(req.headers),
|
|
1255
|
+
size: Number(req.get("content-length")),
|
|
1256
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1257
|
+
body: convertBody(req.body, req.get("content-type"))
|
|
1258
|
+
}, {
|
|
1259
|
+
statusCode: res.statusCode,
|
|
1260
|
+
responseTime: responseTime / 1e3,
|
|
1261
|
+
headers: convertHeaders(res.getHeaders()),
|
|
1262
|
+
size: Number(res.get("content-length")),
|
|
1263
|
+
body: convertBody(res.locals.body, res.get("content-type"))
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
798
1266
|
} catch (error) {
|
|
799
1267
|
client.logger.error("Error while logging request in Apitally middleware.", {
|
|
800
1268
|
request: req,
|
|
@@ -825,12 +1293,12 @@ var getRoutePath = /* @__PURE__ */ __name((req) => {
|
|
|
825
1293
|
return req.route.path;
|
|
826
1294
|
}, "getRoutePath");
|
|
827
1295
|
var getRouterPath = /* @__PURE__ */ __name((stack, baseUrl) => {
|
|
828
|
-
var
|
|
1296
|
+
var _a3;
|
|
829
1297
|
const routerPaths = [];
|
|
830
1298
|
while (stack && stack.length > 0) {
|
|
831
1299
|
const routerLayer = stack.find((layer) => {
|
|
832
|
-
var
|
|
833
|
-
return layer.name === "router" && layer.path && ((
|
|
1300
|
+
var _a4;
|
|
1301
|
+
return layer.name === "router" && layer.path && ((_a4 = layer.regexp) == null ? void 0 : _a4.test(baseUrl));
|
|
834
1302
|
});
|
|
835
1303
|
if (routerLayer) {
|
|
836
1304
|
if (routerLayer.keys.length > 0) {
|
|
@@ -839,7 +1307,7 @@ var getRouterPath = /* @__PURE__ */ __name((stack, baseUrl) => {
|
|
|
839
1307
|
} else {
|
|
840
1308
|
routerPaths.push(routerLayer.path);
|
|
841
1309
|
}
|
|
842
|
-
stack = (
|
|
1310
|
+
stack = (_a3 = routerLayer.handle) == null ? void 0 : _a3.stack;
|
|
843
1311
|
baseUrl = baseUrl.slice(routerLayer.path.length);
|
|
844
1312
|
} else {
|
|
845
1313
|
break;
|
|
@@ -957,6 +1425,33 @@ var getAppInfo = /* @__PURE__ */ __name((app, basePath, appVersion) => {
|
|
|
957
1425
|
client: "js:express"
|
|
958
1426
|
};
|
|
959
1427
|
}, "getAppInfo");
|
|
1428
|
+
|
|
1429
|
+
// src/nestjs/index.ts
|
|
1430
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
1431
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1432
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1433
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1434
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1435
|
+
}
|
|
1436
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
1437
|
+
var useApitally2 = /* @__PURE__ */ __name((app, config) => {
|
|
1438
|
+
const httpAdapter = app.getHttpAdapter();
|
|
1439
|
+
const expressInstance = httpAdapter.getInstance();
|
|
1440
|
+
useApitally(expressInstance, config);
|
|
1441
|
+
app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));
|
|
1442
|
+
}, "useApitally");
|
|
1443
|
+
var _a2;
|
|
1444
|
+
var AllExceptionsFilter = (_a2 = class extends import_core.BaseExceptionFilter {
|
|
1445
|
+
catch(exception, host) {
|
|
1446
|
+
const ctx = host.switchToHttp();
|
|
1447
|
+
const res = ctx.getResponse();
|
|
1448
|
+
res.locals.serverError = exception;
|
|
1449
|
+
super.catch(exception, host);
|
|
1450
|
+
}
|
|
1451
|
+
}, __name(_a2, "AllExceptionsFilter"), _a2);
|
|
1452
|
+
AllExceptionsFilter = _ts_decorate([
|
|
1453
|
+
(0, import_common.Catch)()
|
|
1454
|
+
], AllExceptionsFilter);
|
|
960
1455
|
// Annotate the CommonJS export names for ESM import in node:
|
|
961
1456
|
0 && (module.exports = {
|
|
962
1457
|
useApitally
|