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