apitally 0.10.0 → 0.11.1
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 +408 -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 +404 -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 +429 -0
- package/dist/common/requestLogger.cjs.map +1 -0
- package/dist/common/requestLogger.d.cts +64 -0
- package/dist/common/requestLogger.d.ts +64 -0
- package/dist/common/requestLogger.js +405 -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 +510 -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 +506 -30
- package/dist/express/index.js.map +1 -1
- package/dist/express/middleware.cjs +510 -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 +506 -30
- package/dist/express/middleware.js.map +1 -1
- package/dist/fastify/index.cjs +501 -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 +497 -23
- package/dist/fastify/index.js.map +1 -1
- package/dist/fastify/plugin.cjs +501 -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 +497 -23
- package/dist/fastify/plugin.js.map +1 -1
- package/dist/hono/index.cjs +470 -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 +466 -11
- package/dist/hono/index.js.map +1 -1
- package/dist/hono/middleware.cjs +470 -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 +466 -11
- package/dist/hono/middleware.js.map +1 -1
- package/dist/koa/index.cjs +485 -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 +481 -11
- package/dist/koa/index.js.map +1 -1
- package/dist/koa/middleware.cjs +485 -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 +481 -11
- package/dist/koa/middleware.js.map +1 -1
- package/dist/nestjs/index.cjs +510 -34
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.d.cts +4 -0
- package/dist/nestjs/index.d.ts +4 -0
- package/dist/nestjs/index.js +506 -30
- package/dist/nestjs/index.js.map +1 -1
- package/package.json +8 -2
|
@@ -39,7 +39,7 @@ module.exports = __toCommonJS(middleware_exports);
|
|
|
39
39
|
var import_perf_hooks = require("perf_hooks");
|
|
40
40
|
|
|
41
41
|
// src/common/client.ts
|
|
42
|
-
var
|
|
42
|
+
var import_crypto5 = require("crypto");
|
|
43
43
|
var import_fetch_retry = __toESM(require("fetch-retry"), 1);
|
|
44
44
|
|
|
45
45
|
// src/common/consumerRegistry.ts
|
|
@@ -208,8 +208,405 @@ var _RequestCounter = class _RequestCounter {
|
|
|
208
208
|
__name(_RequestCounter, "RequestCounter");
|
|
209
209
|
var RequestCounter = _RequestCounter;
|
|
210
210
|
|
|
211
|
-
// src/common/
|
|
211
|
+
// src/common/requestLogger.ts
|
|
212
|
+
var import_buffer2 = require("buffer");
|
|
213
|
+
var import_crypto2 = require("crypto");
|
|
214
|
+
var import_fs2 = require("fs");
|
|
215
|
+
var import_os2 = require("os");
|
|
216
|
+
var import_path2 = require("path");
|
|
217
|
+
|
|
218
|
+
// src/common/tempGzipFile.ts
|
|
219
|
+
var import_buffer = require("buffer");
|
|
212
220
|
var import_crypto = require("crypto");
|
|
221
|
+
var import_fs = require("fs");
|
|
222
|
+
var import_os = require("os");
|
|
223
|
+
var import_path = require("path");
|
|
224
|
+
var import_zlib = require("zlib");
|
|
225
|
+
var _TempGzipFile = class _TempGzipFile {
|
|
226
|
+
uuid;
|
|
227
|
+
filePath;
|
|
228
|
+
gzip;
|
|
229
|
+
writeStream;
|
|
230
|
+
readyPromise;
|
|
231
|
+
constructor() {
|
|
232
|
+
this.uuid = (0, import_crypto.randomUUID)();
|
|
233
|
+
this.filePath = (0, import_path.join)((0, import_os.tmpdir)(), `apitally-${this.uuid}.gz`);
|
|
234
|
+
this.writeStream = (0, import_fs.createWriteStream)(this.filePath);
|
|
235
|
+
this.readyPromise = new Promise((resolve, reject) => {
|
|
236
|
+
this.writeStream.once("ready", resolve);
|
|
237
|
+
this.writeStream.once("error", reject);
|
|
238
|
+
});
|
|
239
|
+
this.gzip = (0, import_zlib.createGzip)();
|
|
240
|
+
this.gzip.pipe(this.writeStream);
|
|
241
|
+
}
|
|
242
|
+
get size() {
|
|
243
|
+
return this.writeStream.bytesWritten;
|
|
244
|
+
}
|
|
245
|
+
async writeLine(data) {
|
|
246
|
+
await this.readyPromise;
|
|
247
|
+
return new Promise((resolve, reject) => {
|
|
248
|
+
this.gzip.write(import_buffer.Buffer.concat([
|
|
249
|
+
data,
|
|
250
|
+
import_buffer.Buffer.from("\n")
|
|
251
|
+
]), (error) => {
|
|
252
|
+
if (error) {
|
|
253
|
+
reject(error);
|
|
254
|
+
} else {
|
|
255
|
+
resolve();
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
async getContent() {
|
|
261
|
+
return new Promise((resolve, reject) => {
|
|
262
|
+
(0, import_fs.readFile)(this.filePath, (error, data) => {
|
|
263
|
+
if (error) {
|
|
264
|
+
reject(error);
|
|
265
|
+
} else {
|
|
266
|
+
resolve(data);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
async close() {
|
|
272
|
+
return new Promise((resolve) => {
|
|
273
|
+
this.gzip.end(() => {
|
|
274
|
+
resolve();
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
async delete() {
|
|
279
|
+
await this.close();
|
|
280
|
+
(0, import_fs.unlinkSync)(this.filePath);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
__name(_TempGzipFile, "TempGzipFile");
|
|
284
|
+
var TempGzipFile = _TempGzipFile;
|
|
285
|
+
|
|
286
|
+
// src/common/requestLogger.ts
|
|
287
|
+
var MAX_BODY_SIZE = 5e4;
|
|
288
|
+
var MAX_FILE_SIZE = 1e6;
|
|
289
|
+
var MAX_FILES = 50;
|
|
290
|
+
var MAX_PENDING_WRITES = 100;
|
|
291
|
+
var BODY_TOO_LARGE = import_buffer2.Buffer.from("<body too large>");
|
|
292
|
+
var BODY_MASKED = import_buffer2.Buffer.from("<masked>");
|
|
293
|
+
var MASKED = "******";
|
|
294
|
+
var ALLOWED_CONTENT_TYPES = [
|
|
295
|
+
"application/json",
|
|
296
|
+
"text/plain"
|
|
297
|
+
];
|
|
298
|
+
var EXCLUDE_PATH_PATTERNS = [
|
|
299
|
+
/\/_?healthz?$/i,
|
|
300
|
+
/\/_?health[_-]?checks?$/i,
|
|
301
|
+
/\/_?heart[_-]?beats?$/i,
|
|
302
|
+
/\/ping$/i,
|
|
303
|
+
/\/ready$/i,
|
|
304
|
+
/\/live$/i
|
|
305
|
+
];
|
|
306
|
+
var EXCLUDE_USER_AGENT_PATTERNS = [
|
|
307
|
+
/health[-_ ]?check/i,
|
|
308
|
+
/microsoft-azure-application-lb/i,
|
|
309
|
+
/googlehc/i,
|
|
310
|
+
/kube-probe/i
|
|
311
|
+
];
|
|
312
|
+
var MASK_QUERY_PARAM_PATTERNS = [
|
|
313
|
+
/auth/i,
|
|
314
|
+
/api-?key/i,
|
|
315
|
+
/secret/i,
|
|
316
|
+
/token/i,
|
|
317
|
+
/password/i,
|
|
318
|
+
/pwd/i
|
|
319
|
+
];
|
|
320
|
+
var MASK_HEADER_PATTERNS = [
|
|
321
|
+
/auth/i,
|
|
322
|
+
/api-?key/i,
|
|
323
|
+
/secret/i,
|
|
324
|
+
/token/i,
|
|
325
|
+
/cookie/i
|
|
326
|
+
];
|
|
327
|
+
var DEFAULT_CONFIG = {
|
|
328
|
+
enabled: false,
|
|
329
|
+
logQueryParams: true,
|
|
330
|
+
logRequestHeaders: false,
|
|
331
|
+
logRequestBody: false,
|
|
332
|
+
logResponseHeaders: true,
|
|
333
|
+
logResponseBody: false,
|
|
334
|
+
maskQueryParams: [],
|
|
335
|
+
maskHeaders: [],
|
|
336
|
+
excludePaths: []
|
|
337
|
+
};
|
|
338
|
+
var _RequestLogger = class _RequestLogger {
|
|
339
|
+
config;
|
|
340
|
+
enabled;
|
|
341
|
+
suspendUntil = null;
|
|
342
|
+
pendingWrites = [];
|
|
343
|
+
currentFile = null;
|
|
344
|
+
files = [];
|
|
345
|
+
maintainIntervalId;
|
|
346
|
+
constructor(config) {
|
|
347
|
+
this.config = {
|
|
348
|
+
...DEFAULT_CONFIG,
|
|
349
|
+
...config
|
|
350
|
+
};
|
|
351
|
+
this.enabled = this.config.enabled && checkWritableFs();
|
|
352
|
+
if (this.enabled) {
|
|
353
|
+
this.maintainIntervalId = setInterval(() => {
|
|
354
|
+
this.maintain();
|
|
355
|
+
}, 1e3);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
shouldExcludePath(urlPath) {
|
|
359
|
+
const patterns = [
|
|
360
|
+
...this.config.excludePaths,
|
|
361
|
+
...EXCLUDE_PATH_PATTERNS
|
|
362
|
+
];
|
|
363
|
+
return matchPatterns(urlPath, patterns);
|
|
364
|
+
}
|
|
365
|
+
shouldExcludeUserAgent(userAgent) {
|
|
366
|
+
return userAgent ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS) : false;
|
|
367
|
+
}
|
|
368
|
+
shouldMaskQueryParam(name) {
|
|
369
|
+
const patterns = [
|
|
370
|
+
...this.config.maskQueryParams,
|
|
371
|
+
...MASK_QUERY_PARAM_PATTERNS
|
|
372
|
+
];
|
|
373
|
+
return matchPatterns(name, patterns);
|
|
374
|
+
}
|
|
375
|
+
shouldMaskHeader(name) {
|
|
376
|
+
const patterns = [
|
|
377
|
+
...this.config.maskHeaders,
|
|
378
|
+
...MASK_HEADER_PATTERNS
|
|
379
|
+
];
|
|
380
|
+
return matchPatterns(name, patterns);
|
|
381
|
+
}
|
|
382
|
+
hasSupportedContentType(headers) {
|
|
383
|
+
var _a2;
|
|
384
|
+
const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
|
|
385
|
+
return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
|
|
386
|
+
}
|
|
387
|
+
maskQueryParams(search) {
|
|
388
|
+
const params = new URLSearchParams(search);
|
|
389
|
+
for (const [key] of params) {
|
|
390
|
+
if (this.shouldMaskQueryParam(key)) {
|
|
391
|
+
params.set(key, MASKED);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return params.toString();
|
|
395
|
+
}
|
|
396
|
+
maskHeaders(headers) {
|
|
397
|
+
return headers.map(([k, v]) => [
|
|
398
|
+
k,
|
|
399
|
+
this.shouldMaskHeader(k) ? MASKED : v
|
|
400
|
+
]);
|
|
401
|
+
}
|
|
402
|
+
logRequest(request, response) {
|
|
403
|
+
var _a2, _b, _c;
|
|
404
|
+
if (!this.enabled || this.suspendUntil !== null) return;
|
|
405
|
+
const url = new URL(request.url);
|
|
406
|
+
const path = request.path ?? url.pathname;
|
|
407
|
+
const userAgent = (_a2 = request.headers.find(([k]) => k.toLowerCase() === "user-agent")) == null ? void 0 : _a2[1];
|
|
408
|
+
if (this.shouldExcludePath(path) || this.shouldExcludeUserAgent(userAgent) || (((_c = (_b = this.config).excludeCallback) == null ? void 0 : _c.call(_b, request, response)) ?? false)) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
|
|
412
|
+
request.url = url.toString();
|
|
413
|
+
request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
|
|
414
|
+
response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
|
|
415
|
+
if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
|
|
416
|
+
request.body = void 0;
|
|
417
|
+
} else if (request.body) {
|
|
418
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
419
|
+
request.body = BODY_TOO_LARGE;
|
|
420
|
+
} else if (this.config.maskRequestBodyCallback) {
|
|
421
|
+
try {
|
|
422
|
+
request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
|
|
423
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
424
|
+
request.body = BODY_TOO_LARGE;
|
|
425
|
+
}
|
|
426
|
+
} catch {
|
|
427
|
+
request.body = void 0;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
|
|
432
|
+
response.body = void 0;
|
|
433
|
+
} else if (response.body) {
|
|
434
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
435
|
+
response.body = BODY_TOO_LARGE;
|
|
436
|
+
} else if (this.config.maskResponseBodyCallback) {
|
|
437
|
+
try {
|
|
438
|
+
response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
|
|
439
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
440
|
+
response.body = BODY_TOO_LARGE;
|
|
441
|
+
}
|
|
442
|
+
} catch {
|
|
443
|
+
response.body = void 0;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
const item = {
|
|
448
|
+
uuid: (0, import_crypto2.randomUUID)(),
|
|
449
|
+
request: skipEmptyValues(request),
|
|
450
|
+
response: skipEmptyValues(response)
|
|
451
|
+
};
|
|
452
|
+
[
|
|
453
|
+
item.request.body,
|
|
454
|
+
item.response.body
|
|
455
|
+
].forEach((body) => {
|
|
456
|
+
if (body) {
|
|
457
|
+
body.toJSON = function() {
|
|
458
|
+
return this.toString("base64");
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
this.pendingWrites.push(JSON.stringify(item));
|
|
463
|
+
if (this.pendingWrites.length > MAX_PENDING_WRITES) {
|
|
464
|
+
this.pendingWrites.shift();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
async writeToFile() {
|
|
468
|
+
if (!this.enabled || this.pendingWrites.length === 0) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (!this.currentFile) {
|
|
472
|
+
this.currentFile = new TempGzipFile();
|
|
473
|
+
}
|
|
474
|
+
while (this.pendingWrites.length > 0) {
|
|
475
|
+
const item = this.pendingWrites.shift();
|
|
476
|
+
if (item) {
|
|
477
|
+
await this.currentFile.writeLine(import_buffer2.Buffer.from(item));
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
getFile() {
|
|
482
|
+
return this.files.shift();
|
|
483
|
+
}
|
|
484
|
+
retryFileLater(file) {
|
|
485
|
+
this.files.unshift(file);
|
|
486
|
+
}
|
|
487
|
+
async rotateFile() {
|
|
488
|
+
if (this.currentFile) {
|
|
489
|
+
await this.currentFile.close();
|
|
490
|
+
this.files.push(this.currentFile);
|
|
491
|
+
this.currentFile = null;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
async maintain() {
|
|
495
|
+
await this.writeToFile();
|
|
496
|
+
if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
|
|
497
|
+
await this.rotateFile();
|
|
498
|
+
}
|
|
499
|
+
while (this.files.length > MAX_FILES) {
|
|
500
|
+
const file = this.files.shift();
|
|
501
|
+
file == null ? void 0 : file.delete();
|
|
502
|
+
}
|
|
503
|
+
if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
|
|
504
|
+
this.suspendUntil = null;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
async clear() {
|
|
508
|
+
this.pendingWrites = [];
|
|
509
|
+
await this.rotateFile();
|
|
510
|
+
this.files.forEach((file) => {
|
|
511
|
+
file.delete();
|
|
512
|
+
});
|
|
513
|
+
this.files = [];
|
|
514
|
+
}
|
|
515
|
+
async close() {
|
|
516
|
+
this.enabled = false;
|
|
517
|
+
await this.clear();
|
|
518
|
+
if (this.maintainIntervalId) {
|
|
519
|
+
clearInterval(this.maintainIntervalId);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
__name(_RequestLogger, "RequestLogger");
|
|
524
|
+
var RequestLogger = _RequestLogger;
|
|
525
|
+
function convertHeaders(headers) {
|
|
526
|
+
if (headers instanceof Headers) {
|
|
527
|
+
return Array.from(headers.entries());
|
|
528
|
+
}
|
|
529
|
+
return Object.entries(headers).flatMap(([key, value]) => {
|
|
530
|
+
if (value === void 0) {
|
|
531
|
+
return [];
|
|
532
|
+
}
|
|
533
|
+
if (Array.isArray(value)) {
|
|
534
|
+
return value.map((v) => [
|
|
535
|
+
key,
|
|
536
|
+
v
|
|
537
|
+
]);
|
|
538
|
+
}
|
|
539
|
+
return [
|
|
540
|
+
[
|
|
541
|
+
key,
|
|
542
|
+
value.toString()
|
|
543
|
+
]
|
|
544
|
+
];
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
__name(convertHeaders, "convertHeaders");
|
|
548
|
+
function convertBody(body, contentType) {
|
|
549
|
+
if (!body || !contentType) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
try {
|
|
553
|
+
if (contentType.startsWith("application/json")) {
|
|
554
|
+
if (isValidJsonString(body)) {
|
|
555
|
+
return import_buffer2.Buffer.from(body);
|
|
556
|
+
} else {
|
|
557
|
+
return import_buffer2.Buffer.from(JSON.stringify(body));
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (contentType.startsWith("text/") && typeof body === "string") {
|
|
561
|
+
return import_buffer2.Buffer.from(body);
|
|
562
|
+
}
|
|
563
|
+
} catch (error) {
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
__name(convertBody, "convertBody");
|
|
568
|
+
function isValidJsonString(body) {
|
|
569
|
+
if (typeof body !== "string") {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
JSON.parse(body);
|
|
574
|
+
return true;
|
|
575
|
+
} catch {
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
__name(isValidJsonString, "isValidJsonString");
|
|
580
|
+
function matchPatterns(value, patterns) {
|
|
581
|
+
return patterns.some((pattern) => {
|
|
582
|
+
return pattern.test(value);
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
__name(matchPatterns, "matchPatterns");
|
|
586
|
+
function skipEmptyValues(data) {
|
|
587
|
+
return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
|
|
588
|
+
if (v == null || Number.isNaN(v)) return false;
|
|
589
|
+
if (Array.isArray(v) || import_buffer2.Buffer.isBuffer(v) || typeof v === "string") {
|
|
590
|
+
return v.length > 0;
|
|
591
|
+
}
|
|
592
|
+
return true;
|
|
593
|
+
}));
|
|
594
|
+
}
|
|
595
|
+
__name(skipEmptyValues, "skipEmptyValues");
|
|
596
|
+
function checkWritableFs() {
|
|
597
|
+
try {
|
|
598
|
+
const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto2.randomUUID)()}`);
|
|
599
|
+
(0, import_fs2.writeFileSync)(testPath, "test");
|
|
600
|
+
(0, import_fs2.unlinkSync)(testPath);
|
|
601
|
+
return true;
|
|
602
|
+
} catch (error) {
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
__name(checkWritableFs, "checkWritableFs");
|
|
607
|
+
|
|
608
|
+
// src/common/serverErrorCounter.ts
|
|
609
|
+
var import_crypto3 = require("crypto");
|
|
213
610
|
var MAX_MSG_LENGTH = 2048;
|
|
214
611
|
var MAX_STACKTRACE_LENGTH = 65536;
|
|
215
612
|
var _ServerErrorCounter = class _ServerErrorCounter {
|
|
@@ -261,7 +658,7 @@ var _ServerErrorCounter = class _ServerErrorCounter {
|
|
|
261
658
|
serverError.msg.trim(),
|
|
262
659
|
serverError.traceback.trim()
|
|
263
660
|
].join("|");
|
|
264
|
-
return (0,
|
|
661
|
+
return (0, import_crypto3.createHash)("md5").update(hashInput).digest("hex");
|
|
265
662
|
}
|
|
266
663
|
getTruncatedMessage(msg) {
|
|
267
664
|
msg = msg.trim();
|
|
@@ -307,7 +704,7 @@ __name(_ServerErrorCounter, "ServerErrorCounter");
|
|
|
307
704
|
var ServerErrorCounter = _ServerErrorCounter;
|
|
308
705
|
|
|
309
706
|
// src/common/validationErrorCounter.ts
|
|
310
|
-
var
|
|
707
|
+
var import_crypto4 = require("crypto");
|
|
311
708
|
var _ValidationErrorCounter = class _ValidationErrorCounter {
|
|
312
709
|
errorCounts;
|
|
313
710
|
errorDetails;
|
|
@@ -351,7 +748,7 @@ var _ValidationErrorCounter = class _ValidationErrorCounter {
|
|
|
351
748
|
validationError.msg.trim(),
|
|
352
749
|
validationError.type
|
|
353
750
|
].join("|");
|
|
354
|
-
return (0,
|
|
751
|
+
return (0, import_crypto4.createHash)("md5").update(hashInput).digest("hex");
|
|
355
752
|
}
|
|
356
753
|
};
|
|
357
754
|
__name(_ValidationErrorCounter, "ValidationErrorCounter");
|
|
@@ -380,11 +777,12 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
380
777
|
startupData;
|
|
381
778
|
startupDataSent = false;
|
|
382
779
|
requestCounter;
|
|
780
|
+
requestLogger;
|
|
383
781
|
validationErrorCounter;
|
|
384
782
|
serverErrorCounter;
|
|
385
783
|
consumerRegistry;
|
|
386
784
|
logger;
|
|
387
|
-
constructor({ clientId, env = "dev", logger }) {
|
|
785
|
+
constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
|
|
388
786
|
if (_ApitallyClient.instance) {
|
|
389
787
|
throw new Error("Apitally client is already initialized");
|
|
390
788
|
}
|
|
@@ -397,9 +795,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
397
795
|
_ApitallyClient.instance = this;
|
|
398
796
|
this.clientId = clientId;
|
|
399
797
|
this.env = env;
|
|
400
|
-
this.instanceUuid = (0,
|
|
798
|
+
this.instanceUuid = (0, import_crypto5.randomUUID)();
|
|
401
799
|
this.syncDataQueue = [];
|
|
402
800
|
this.requestCounter = new RequestCounter();
|
|
801
|
+
this.requestLogger = new RequestLogger(requestLoggingConfig);
|
|
403
802
|
this.validationErrorCounter = new ValidationErrorCounter();
|
|
404
803
|
this.serverErrorCounter = new ServerErrorCounter();
|
|
405
804
|
this.consumerRegistry = new ConsumerRegistry();
|
|
@@ -421,6 +820,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
421
820
|
async handleShutdown() {
|
|
422
821
|
this.stopSync();
|
|
423
822
|
await this.sendSyncData();
|
|
823
|
+
await this.sendLogData();
|
|
824
|
+
await this.requestLogger.close();
|
|
424
825
|
_ApitallyClient.instance = void 0;
|
|
425
826
|
}
|
|
426
827
|
getHubUrlPrefix() {
|
|
@@ -467,7 +868,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
467
868
|
async sync() {
|
|
468
869
|
try {
|
|
469
870
|
const promises = [
|
|
470
|
-
this.sendSyncData()
|
|
871
|
+
this.sendSyncData(),
|
|
872
|
+
this.sendLogData()
|
|
471
873
|
];
|
|
472
874
|
if (!this.startupDataSent) {
|
|
473
875
|
promises.push(this.sendStartupData());
|
|
@@ -495,7 +897,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
495
897
|
this.logger.debug("Sending startup data to Apitally Hub");
|
|
496
898
|
const payload = {
|
|
497
899
|
instance_uuid: this.instanceUuid,
|
|
498
|
-
message_uuid: (0,
|
|
900
|
+
message_uuid: (0, import_crypto5.randomUUID)(),
|
|
499
901
|
...this.startupData
|
|
500
902
|
};
|
|
501
903
|
try {
|
|
@@ -517,7 +919,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
517
919
|
const newPayload = {
|
|
518
920
|
time_offset: 0,
|
|
519
921
|
instance_uuid: this.instanceUuid,
|
|
520
|
-
message_uuid: (0,
|
|
922
|
+
message_uuid: (0, import_crypto5.randomUUID)(),
|
|
521
923
|
requests: this.requestCounter.getAndResetRequests(),
|
|
522
924
|
validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
|
|
523
925
|
server_errors: this.serverErrorCounter.getAndResetServerErrors(),
|
|
@@ -536,8 +938,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
536
938
|
const timeOffset = Date.now() - time;
|
|
537
939
|
if (timeOffset <= MAX_QUEUE_TIME) {
|
|
538
940
|
if (i > 0) {
|
|
539
|
-
|
|
540
|
-
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
941
|
+
await this.randomDelay();
|
|
541
942
|
}
|
|
542
943
|
payload.time_offset = timeOffset / 1e3;
|
|
543
944
|
await this.sendData("sync", payload);
|
|
@@ -556,6 +957,52 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
556
957
|
}
|
|
557
958
|
}
|
|
558
959
|
}
|
|
960
|
+
async sendLogData() {
|
|
961
|
+
this.logger.debug("Sending request log data to Apitally Hub");
|
|
962
|
+
await this.requestLogger.rotateFile();
|
|
963
|
+
const fetchWithRetry = (0, import_fetch_retry.default)(fetch, {
|
|
964
|
+
retries: 3,
|
|
965
|
+
retryDelay: 1e3,
|
|
966
|
+
retryOn: [
|
|
967
|
+
408,
|
|
968
|
+
429,
|
|
969
|
+
500,
|
|
970
|
+
502,
|
|
971
|
+
503,
|
|
972
|
+
504
|
|
973
|
+
]
|
|
974
|
+
});
|
|
975
|
+
let i = 0;
|
|
976
|
+
let logFile;
|
|
977
|
+
while (logFile = this.requestLogger.getFile()) {
|
|
978
|
+
if (i > 0) {
|
|
979
|
+
await this.randomDelay();
|
|
980
|
+
}
|
|
981
|
+
try {
|
|
982
|
+
const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
|
|
983
|
+
method: "POST",
|
|
984
|
+
body: await logFile.getContent()
|
|
985
|
+
});
|
|
986
|
+
if (response.status === 402 && response.headers.has("Retry-After")) {
|
|
987
|
+
const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
|
|
988
|
+
if (retryAfter > 0) {
|
|
989
|
+
this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
|
|
990
|
+
this.requestLogger.clear();
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
if (!response.ok) {
|
|
995
|
+
throw new HTTPError(response);
|
|
996
|
+
}
|
|
997
|
+
logFile.delete();
|
|
998
|
+
} catch (error) {
|
|
999
|
+
this.requestLogger.retryFileLater(logFile);
|
|
1000
|
+
break;
|
|
1001
|
+
}
|
|
1002
|
+
i++;
|
|
1003
|
+
if (i >= 10) break;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
559
1006
|
handleHubError(error) {
|
|
560
1007
|
if (error instanceof HTTPError) {
|
|
561
1008
|
if (error.response.status === 404) {
|
|
@@ -570,6 +1017,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
570
1017
|
}
|
|
571
1018
|
return false;
|
|
572
1019
|
}
|
|
1020
|
+
async randomDelay() {
|
|
1021
|
+
const delay = 100 + Math.random() * 200;
|
|
1022
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1023
|
+
}
|
|
573
1024
|
};
|
|
574
1025
|
__name(_ApitallyClient, "ApitallyClient");
|
|
575
1026
|
__publicField(_ApitallyClient, "instance");
|
|
@@ -740,18 +1191,18 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
740
1191
|
}
|
|
741
1192
|
try {
|
|
742
1193
|
const startTime = import_perf_hooks.performance.now();
|
|
743
|
-
const
|
|
744
|
-
res.
|
|
1194
|
+
const originalSend = res.send;
|
|
1195
|
+
res.send = (body) => {
|
|
745
1196
|
res.locals.body = body;
|
|
746
|
-
return
|
|
1197
|
+
return originalSend.call(res, body);
|
|
747
1198
|
};
|
|
748
1199
|
res.on("finish", () => {
|
|
749
1200
|
try {
|
|
1201
|
+
const responseTime = import_perf_hooks.performance.now() - startTime;
|
|
750
1202
|
const path = getRoutePath(req);
|
|
1203
|
+
const consumer = getConsumer(req);
|
|
1204
|
+
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
751
1205
|
if (path) {
|
|
752
|
-
const responseTime = import_perf_hooks.performance.now() - startTime;
|
|
753
|
-
const consumer = getConsumer(req);
|
|
754
|
-
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
755
1206
|
client.requestCounter.addRequest({
|
|
756
1207
|
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
757
1208
|
method: req.method,
|
|
@@ -762,24 +1213,31 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
762
1213
|
responseSize: res.get("content-length")
|
|
763
1214
|
});
|
|
764
1215
|
if ((res.statusCode === 400 || res.statusCode === 422) && res.locals.body) {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
}
|
|
769
|
-
if (validationErrors.length === 0) {
|
|
770
|
-
validationErrors.push(...extractCelebrateErrors(res.locals.body));
|
|
1216
|
+
let jsonBody;
|
|
1217
|
+
try {
|
|
1218
|
+
jsonBody = JSON.parse(res.locals.body);
|
|
1219
|
+
} catch {
|
|
771
1220
|
}
|
|
772
|
-
if (
|
|
773
|
-
validationErrors
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1221
|
+
if (jsonBody) {
|
|
1222
|
+
const validationErrors = [];
|
|
1223
|
+
if (validationErrors.length === 0) {
|
|
1224
|
+
validationErrors.push(...extractExpressValidatorErrors(jsonBody));
|
|
1225
|
+
}
|
|
1226
|
+
if (validationErrors.length === 0) {
|
|
1227
|
+
validationErrors.push(...extractCelebrateErrors(jsonBody));
|
|
1228
|
+
}
|
|
1229
|
+
if (validationErrors.length === 0) {
|
|
1230
|
+
validationErrors.push(...extractNestValidationErrors(jsonBody));
|
|
1231
|
+
}
|
|
1232
|
+
validationErrors.forEach((error) => {
|
|
1233
|
+
client.validationErrorCounter.addValidationError({
|
|
1234
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1235
|
+
method: req.method,
|
|
1236
|
+
path: req.route.path,
|
|
1237
|
+
...error
|
|
1238
|
+
});
|
|
781
1239
|
});
|
|
782
|
-
}
|
|
1240
|
+
}
|
|
783
1241
|
}
|
|
784
1242
|
if (res.statusCode === 500 && res.locals.serverError) {
|
|
785
1243
|
const serverError = res.locals.serverError;
|
|
@@ -793,6 +1251,24 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
793
1251
|
});
|
|
794
1252
|
}
|
|
795
1253
|
}
|
|
1254
|
+
if (client.requestLogger.enabled) {
|
|
1255
|
+
client.requestLogger.logRequest({
|
|
1256
|
+
timestamp: Date.now() / 1e3,
|
|
1257
|
+
method: req.method,
|
|
1258
|
+
path,
|
|
1259
|
+
url: `${req.protocol}://${req.host}${req.originalUrl}`,
|
|
1260
|
+
headers: convertHeaders(req.headers),
|
|
1261
|
+
size: Number(req.get("content-length")),
|
|
1262
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1263
|
+
body: convertBody(req.body, req.get("content-type"))
|
|
1264
|
+
}, {
|
|
1265
|
+
statusCode: res.statusCode,
|
|
1266
|
+
responseTime: responseTime / 1e3,
|
|
1267
|
+
headers: convertHeaders(res.getHeaders()),
|
|
1268
|
+
size: Number(res.get("content-length")),
|
|
1269
|
+
body: convertBody(res.locals.body, res.get("content-type"))
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
796
1272
|
} catch (error) {
|
|
797
1273
|
client.logger.error("Error while logging request in Apitally middleware.", {
|
|
798
1274
|
request: req,
|