apitally 0.10.0 → 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 +500 -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 +496 -30
- package/dist/nestjs/index.js.map +1 -1
- package/package.json +8 -2
package/dist/fastify/plugin.cjs
CHANGED
|
@@ -39,7 +39,7 @@ module.exports = __toCommonJS(plugin_exports);
|
|
|
39
39
|
var import_fastify_plugin = __toESM(require("fastify-plugin"), 1);
|
|
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,395 @@ 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?$/,
|
|
300
|
+
/\/_?health[_-]?checks?$/,
|
|
301
|
+
/\/_?heart[_-]?beats?$/,
|
|
302
|
+
/\/ping$/,
|
|
303
|
+
/\/ready$/,
|
|
304
|
+
/\/live$/
|
|
305
|
+
];
|
|
306
|
+
var MASK_QUERY_PARAM_PATTERNS = [
|
|
307
|
+
/auth/i,
|
|
308
|
+
/api-?key/i,
|
|
309
|
+
/secret/i,
|
|
310
|
+
/token/i,
|
|
311
|
+
/password/i,
|
|
312
|
+
/pwd/i
|
|
313
|
+
];
|
|
314
|
+
var MASK_HEADER_PATTERNS = [
|
|
315
|
+
/auth/i,
|
|
316
|
+
/api-?key/i,
|
|
317
|
+
/secret/i,
|
|
318
|
+
/token/i,
|
|
319
|
+
/cookie/i
|
|
320
|
+
];
|
|
321
|
+
var DEFAULT_CONFIG = {
|
|
322
|
+
enabled: false,
|
|
323
|
+
logQueryParams: true,
|
|
324
|
+
logRequestHeaders: false,
|
|
325
|
+
logRequestBody: false,
|
|
326
|
+
logResponseHeaders: true,
|
|
327
|
+
logResponseBody: false,
|
|
328
|
+
maskQueryParams: [],
|
|
329
|
+
maskHeaders: [],
|
|
330
|
+
excludePaths: []
|
|
331
|
+
};
|
|
332
|
+
var _RequestLogger = class _RequestLogger {
|
|
333
|
+
config;
|
|
334
|
+
enabled;
|
|
335
|
+
suspendUntil = null;
|
|
336
|
+
pendingWrites = [];
|
|
337
|
+
currentFile = null;
|
|
338
|
+
files = [];
|
|
339
|
+
maintainIntervalId;
|
|
340
|
+
constructor(config) {
|
|
341
|
+
this.config = {
|
|
342
|
+
...DEFAULT_CONFIG,
|
|
343
|
+
...config
|
|
344
|
+
};
|
|
345
|
+
this.enabled = this.config.enabled && checkWritableFs();
|
|
346
|
+
if (this.enabled) {
|
|
347
|
+
this.maintainIntervalId = setInterval(() => {
|
|
348
|
+
this.maintain();
|
|
349
|
+
}, 1e3);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
shouldExcludePath(urlPath) {
|
|
353
|
+
const patterns = [
|
|
354
|
+
...this.config.excludePaths,
|
|
355
|
+
...EXCLUDE_PATH_PATTERNS
|
|
356
|
+
];
|
|
357
|
+
return matchPatterns(urlPath, patterns);
|
|
358
|
+
}
|
|
359
|
+
shouldMaskQueryParam(name) {
|
|
360
|
+
const patterns = [
|
|
361
|
+
...this.config.maskQueryParams,
|
|
362
|
+
...MASK_QUERY_PARAM_PATTERNS
|
|
363
|
+
];
|
|
364
|
+
return matchPatterns(name, patterns);
|
|
365
|
+
}
|
|
366
|
+
shouldMaskHeader(name) {
|
|
367
|
+
const patterns = [
|
|
368
|
+
...this.config.maskHeaders,
|
|
369
|
+
...MASK_HEADER_PATTERNS
|
|
370
|
+
];
|
|
371
|
+
return matchPatterns(name, patterns);
|
|
372
|
+
}
|
|
373
|
+
hasSupportedContentType(headers) {
|
|
374
|
+
var _a2;
|
|
375
|
+
const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
|
|
376
|
+
return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
|
|
377
|
+
}
|
|
378
|
+
maskQueryParams(search) {
|
|
379
|
+
const params = new URLSearchParams(search);
|
|
380
|
+
for (const [key] of params) {
|
|
381
|
+
if (this.shouldMaskQueryParam(key)) {
|
|
382
|
+
params.set(key, MASKED);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return params.toString();
|
|
386
|
+
}
|
|
387
|
+
maskHeaders(headers) {
|
|
388
|
+
return headers.map(([k, v]) => [
|
|
389
|
+
k,
|
|
390
|
+
this.shouldMaskHeader(k) ? MASKED : v
|
|
391
|
+
]);
|
|
392
|
+
}
|
|
393
|
+
logRequest(request, response) {
|
|
394
|
+
var _a2, _b;
|
|
395
|
+
if (!this.enabled || this.suspendUntil !== null) return;
|
|
396
|
+
const url = new URL(request.url);
|
|
397
|
+
const path = request.path ?? url.pathname;
|
|
398
|
+
if (this.shouldExcludePath(path) || (((_b = (_a2 = this.config).excludeCallback) == null ? void 0 : _b.call(_a2, request, response)) ?? false)) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
|
|
402
|
+
request.url = url.toString();
|
|
403
|
+
request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
|
|
404
|
+
response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
|
|
405
|
+
if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
|
|
406
|
+
request.body = void 0;
|
|
407
|
+
} else if (request.body) {
|
|
408
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
409
|
+
request.body = BODY_TOO_LARGE;
|
|
410
|
+
} else if (this.config.maskRequestBodyCallback) {
|
|
411
|
+
try {
|
|
412
|
+
request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
|
|
413
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
414
|
+
request.body = BODY_TOO_LARGE;
|
|
415
|
+
}
|
|
416
|
+
} catch {
|
|
417
|
+
request.body = void 0;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
|
|
422
|
+
response.body = void 0;
|
|
423
|
+
} else if (response.body) {
|
|
424
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
425
|
+
response.body = BODY_TOO_LARGE;
|
|
426
|
+
} else if (this.config.maskResponseBodyCallback) {
|
|
427
|
+
try {
|
|
428
|
+
response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
|
|
429
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
430
|
+
response.body = BODY_TOO_LARGE;
|
|
431
|
+
}
|
|
432
|
+
} catch {
|
|
433
|
+
response.body = void 0;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const item = {
|
|
438
|
+
uuid: (0, import_crypto2.randomUUID)(),
|
|
439
|
+
request: skipEmptyValues(request),
|
|
440
|
+
response: skipEmptyValues(response)
|
|
441
|
+
};
|
|
442
|
+
[
|
|
443
|
+
item.request.body,
|
|
444
|
+
item.response.body
|
|
445
|
+
].forEach((body) => {
|
|
446
|
+
if (body) {
|
|
447
|
+
body.toJSON = function() {
|
|
448
|
+
return this.toString("base64");
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
this.pendingWrites.push(JSON.stringify(item));
|
|
453
|
+
if (this.pendingWrites.length > MAX_PENDING_WRITES) {
|
|
454
|
+
this.pendingWrites.shift();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
async writeToFile() {
|
|
458
|
+
if (!this.enabled || this.pendingWrites.length === 0) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (!this.currentFile) {
|
|
462
|
+
this.currentFile = new TempGzipFile();
|
|
463
|
+
}
|
|
464
|
+
while (this.pendingWrites.length > 0) {
|
|
465
|
+
const item = this.pendingWrites.shift();
|
|
466
|
+
if (item) {
|
|
467
|
+
await this.currentFile.writeLine(import_buffer2.Buffer.from(item));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
getFile() {
|
|
472
|
+
return this.files.shift();
|
|
473
|
+
}
|
|
474
|
+
retryFileLater(file) {
|
|
475
|
+
this.files.unshift(file);
|
|
476
|
+
}
|
|
477
|
+
async rotateFile() {
|
|
478
|
+
if (this.currentFile) {
|
|
479
|
+
await this.currentFile.close();
|
|
480
|
+
this.files.push(this.currentFile);
|
|
481
|
+
this.currentFile = null;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
async maintain() {
|
|
485
|
+
await this.writeToFile();
|
|
486
|
+
if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
|
|
487
|
+
await this.rotateFile();
|
|
488
|
+
}
|
|
489
|
+
while (this.files.length > MAX_FILES) {
|
|
490
|
+
const file = this.files.shift();
|
|
491
|
+
file == null ? void 0 : file.delete();
|
|
492
|
+
}
|
|
493
|
+
if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
|
|
494
|
+
this.suspendUntil = null;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
async clear() {
|
|
498
|
+
this.pendingWrites = [];
|
|
499
|
+
await this.rotateFile();
|
|
500
|
+
this.files.forEach((file) => {
|
|
501
|
+
file.delete();
|
|
502
|
+
});
|
|
503
|
+
this.files = [];
|
|
504
|
+
}
|
|
505
|
+
async close() {
|
|
506
|
+
this.enabled = false;
|
|
507
|
+
await this.clear();
|
|
508
|
+
if (this.maintainIntervalId) {
|
|
509
|
+
clearInterval(this.maintainIntervalId);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
__name(_RequestLogger, "RequestLogger");
|
|
514
|
+
var RequestLogger = _RequestLogger;
|
|
515
|
+
function convertHeaders(headers) {
|
|
516
|
+
if (headers instanceof Headers) {
|
|
517
|
+
return Array.from(headers.entries());
|
|
518
|
+
}
|
|
519
|
+
return Object.entries(headers).flatMap(([key, value]) => {
|
|
520
|
+
if (value === void 0) {
|
|
521
|
+
return [];
|
|
522
|
+
}
|
|
523
|
+
if (Array.isArray(value)) {
|
|
524
|
+
return value.map((v) => [
|
|
525
|
+
key,
|
|
526
|
+
v
|
|
527
|
+
]);
|
|
528
|
+
}
|
|
529
|
+
return [
|
|
530
|
+
[
|
|
531
|
+
key,
|
|
532
|
+
value.toString()
|
|
533
|
+
]
|
|
534
|
+
];
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
__name(convertHeaders, "convertHeaders");
|
|
538
|
+
function convertBody(body, contentType) {
|
|
539
|
+
if (!body || !contentType) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
try {
|
|
543
|
+
if (contentType.startsWith("application/json")) {
|
|
544
|
+
if (isValidJsonString(body)) {
|
|
545
|
+
return import_buffer2.Buffer.from(body);
|
|
546
|
+
} else {
|
|
547
|
+
return import_buffer2.Buffer.from(JSON.stringify(body));
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (contentType.startsWith("text/") && typeof body === "string") {
|
|
551
|
+
return import_buffer2.Buffer.from(body);
|
|
552
|
+
}
|
|
553
|
+
} catch (error) {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
__name(convertBody, "convertBody");
|
|
558
|
+
function isValidJsonString(body) {
|
|
559
|
+
if (typeof body !== "string") {
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
try {
|
|
563
|
+
JSON.parse(body);
|
|
564
|
+
return true;
|
|
565
|
+
} catch {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
__name(isValidJsonString, "isValidJsonString");
|
|
570
|
+
function matchPatterns(value, patterns) {
|
|
571
|
+
return patterns.some((pattern) => {
|
|
572
|
+
return pattern.test(value);
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
__name(matchPatterns, "matchPatterns");
|
|
576
|
+
function skipEmptyValues(data) {
|
|
577
|
+
return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
|
|
578
|
+
if (v == null || Number.isNaN(v)) return false;
|
|
579
|
+
if (Array.isArray(v) || import_buffer2.Buffer.isBuffer(v) || typeof v === "string") {
|
|
580
|
+
return v.length > 0;
|
|
581
|
+
}
|
|
582
|
+
return true;
|
|
583
|
+
}));
|
|
584
|
+
}
|
|
585
|
+
__name(skipEmptyValues, "skipEmptyValues");
|
|
586
|
+
function checkWritableFs() {
|
|
587
|
+
try {
|
|
588
|
+
const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto2.randomUUID)()}`);
|
|
589
|
+
(0, import_fs2.writeFileSync)(testPath, "test");
|
|
590
|
+
(0, import_fs2.unlinkSync)(testPath);
|
|
591
|
+
return true;
|
|
592
|
+
} catch (error) {
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
__name(checkWritableFs, "checkWritableFs");
|
|
597
|
+
|
|
598
|
+
// src/common/serverErrorCounter.ts
|
|
599
|
+
var import_crypto3 = require("crypto");
|
|
213
600
|
var MAX_MSG_LENGTH = 2048;
|
|
214
601
|
var MAX_STACKTRACE_LENGTH = 65536;
|
|
215
602
|
var _ServerErrorCounter = class _ServerErrorCounter {
|
|
@@ -261,7 +648,7 @@ var _ServerErrorCounter = class _ServerErrorCounter {
|
|
|
261
648
|
serverError.msg.trim(),
|
|
262
649
|
serverError.traceback.trim()
|
|
263
650
|
].join("|");
|
|
264
|
-
return (0,
|
|
651
|
+
return (0, import_crypto3.createHash)("md5").update(hashInput).digest("hex");
|
|
265
652
|
}
|
|
266
653
|
getTruncatedMessage(msg) {
|
|
267
654
|
msg = msg.trim();
|
|
@@ -307,7 +694,7 @@ __name(_ServerErrorCounter, "ServerErrorCounter");
|
|
|
307
694
|
var ServerErrorCounter = _ServerErrorCounter;
|
|
308
695
|
|
|
309
696
|
// src/common/validationErrorCounter.ts
|
|
310
|
-
var
|
|
697
|
+
var import_crypto4 = require("crypto");
|
|
311
698
|
var _ValidationErrorCounter = class _ValidationErrorCounter {
|
|
312
699
|
errorCounts;
|
|
313
700
|
errorDetails;
|
|
@@ -351,7 +738,7 @@ var _ValidationErrorCounter = class _ValidationErrorCounter {
|
|
|
351
738
|
validationError.msg.trim(),
|
|
352
739
|
validationError.type
|
|
353
740
|
].join("|");
|
|
354
|
-
return (0,
|
|
741
|
+
return (0, import_crypto4.createHash)("md5").update(hashInput).digest("hex");
|
|
355
742
|
}
|
|
356
743
|
};
|
|
357
744
|
__name(_ValidationErrorCounter, "ValidationErrorCounter");
|
|
@@ -380,11 +767,12 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
380
767
|
startupData;
|
|
381
768
|
startupDataSent = false;
|
|
382
769
|
requestCounter;
|
|
770
|
+
requestLogger;
|
|
383
771
|
validationErrorCounter;
|
|
384
772
|
serverErrorCounter;
|
|
385
773
|
consumerRegistry;
|
|
386
774
|
logger;
|
|
387
|
-
constructor({ clientId, env = "dev", logger }) {
|
|
775
|
+
constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
|
|
388
776
|
if (_ApitallyClient.instance) {
|
|
389
777
|
throw new Error("Apitally client is already initialized");
|
|
390
778
|
}
|
|
@@ -397,9 +785,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
397
785
|
_ApitallyClient.instance = this;
|
|
398
786
|
this.clientId = clientId;
|
|
399
787
|
this.env = env;
|
|
400
|
-
this.instanceUuid = (0,
|
|
788
|
+
this.instanceUuid = (0, import_crypto5.randomUUID)();
|
|
401
789
|
this.syncDataQueue = [];
|
|
402
790
|
this.requestCounter = new RequestCounter();
|
|
791
|
+
this.requestLogger = new RequestLogger(requestLoggingConfig);
|
|
403
792
|
this.validationErrorCounter = new ValidationErrorCounter();
|
|
404
793
|
this.serverErrorCounter = new ServerErrorCounter();
|
|
405
794
|
this.consumerRegistry = new ConsumerRegistry();
|
|
@@ -421,6 +810,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
421
810
|
async handleShutdown() {
|
|
422
811
|
this.stopSync();
|
|
423
812
|
await this.sendSyncData();
|
|
813
|
+
await this.sendLogData();
|
|
814
|
+
await this.requestLogger.close();
|
|
424
815
|
_ApitallyClient.instance = void 0;
|
|
425
816
|
}
|
|
426
817
|
getHubUrlPrefix() {
|
|
@@ -467,7 +858,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
467
858
|
async sync() {
|
|
468
859
|
try {
|
|
469
860
|
const promises = [
|
|
470
|
-
this.sendSyncData()
|
|
861
|
+
this.sendSyncData(),
|
|
862
|
+
this.sendLogData()
|
|
471
863
|
];
|
|
472
864
|
if (!this.startupDataSent) {
|
|
473
865
|
promises.push(this.sendStartupData());
|
|
@@ -495,7 +887,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
495
887
|
this.logger.debug("Sending startup data to Apitally Hub");
|
|
496
888
|
const payload = {
|
|
497
889
|
instance_uuid: this.instanceUuid,
|
|
498
|
-
message_uuid: (0,
|
|
890
|
+
message_uuid: (0, import_crypto5.randomUUID)(),
|
|
499
891
|
...this.startupData
|
|
500
892
|
};
|
|
501
893
|
try {
|
|
@@ -517,7 +909,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
517
909
|
const newPayload = {
|
|
518
910
|
time_offset: 0,
|
|
519
911
|
instance_uuid: this.instanceUuid,
|
|
520
|
-
message_uuid: (0,
|
|
912
|
+
message_uuid: (0, import_crypto5.randomUUID)(),
|
|
521
913
|
requests: this.requestCounter.getAndResetRequests(),
|
|
522
914
|
validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
|
|
523
915
|
server_errors: this.serverErrorCounter.getAndResetServerErrors(),
|
|
@@ -536,8 +928,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
536
928
|
const timeOffset = Date.now() - time;
|
|
537
929
|
if (timeOffset <= MAX_QUEUE_TIME) {
|
|
538
930
|
if (i > 0) {
|
|
539
|
-
|
|
540
|
-
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
931
|
+
await this.randomDelay();
|
|
541
932
|
}
|
|
542
933
|
payload.time_offset = timeOffset / 1e3;
|
|
543
934
|
await this.sendData("sync", payload);
|
|
@@ -556,6 +947,52 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
556
947
|
}
|
|
557
948
|
}
|
|
558
949
|
}
|
|
950
|
+
async sendLogData() {
|
|
951
|
+
this.logger.debug("Sending request log data to Apitally Hub");
|
|
952
|
+
await this.requestLogger.rotateFile();
|
|
953
|
+
const fetchWithRetry = (0, import_fetch_retry.default)(fetch, {
|
|
954
|
+
retries: 3,
|
|
955
|
+
retryDelay: 1e3,
|
|
956
|
+
retryOn: [
|
|
957
|
+
408,
|
|
958
|
+
429,
|
|
959
|
+
500,
|
|
960
|
+
502,
|
|
961
|
+
503,
|
|
962
|
+
504
|
|
963
|
+
]
|
|
964
|
+
});
|
|
965
|
+
let i = 0;
|
|
966
|
+
let logFile;
|
|
967
|
+
while (logFile = this.requestLogger.getFile()) {
|
|
968
|
+
if (i > 0) {
|
|
969
|
+
await this.randomDelay();
|
|
970
|
+
}
|
|
971
|
+
try {
|
|
972
|
+
const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
|
|
973
|
+
method: "POST",
|
|
974
|
+
body: await logFile.getContent()
|
|
975
|
+
});
|
|
976
|
+
if (response.status === 402 && response.headers.has("Retry-After")) {
|
|
977
|
+
const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
|
|
978
|
+
if (retryAfter > 0) {
|
|
979
|
+
this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
|
|
980
|
+
this.requestLogger.clear();
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
if (!response.ok) {
|
|
985
|
+
throw new HTTPError(response);
|
|
986
|
+
}
|
|
987
|
+
logFile.delete();
|
|
988
|
+
} catch (error) {
|
|
989
|
+
this.requestLogger.retryFileLater(logFile);
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
i++;
|
|
993
|
+
if (i >= 10) break;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
559
996
|
handleHubError(error) {
|
|
560
997
|
if (error instanceof HTTPError) {
|
|
561
998
|
if (error.response.status === 404) {
|
|
@@ -570,6 +1007,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
570
1007
|
}
|
|
571
1008
|
return false;
|
|
572
1009
|
}
|
|
1010
|
+
async randomDelay() {
|
|
1011
|
+
const delay = 100 + Math.random() * 200;
|
|
1012
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1013
|
+
}
|
|
573
1014
|
};
|
|
574
1015
|
__name(_ApitallyClient, "ApitallyClient");
|
|
575
1016
|
__publicField(_ApitallyClient, "instance");
|
|
@@ -623,10 +1064,7 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
|
|
|
623
1064
|
await client.handleShutdown();
|
|
624
1065
|
});
|
|
625
1066
|
fastify.addHook("onSend", (request, reply, payload, done) => {
|
|
626
|
-
|
|
627
|
-
reply.payload = JSON.parse(payload);
|
|
628
|
-
} catch (error) {
|
|
629
|
-
}
|
|
1067
|
+
reply.payload = payload;
|
|
630
1068
|
done();
|
|
631
1069
|
});
|
|
632
1070
|
fastify.addHook("onError", (request, reply, error, done) => {
|
|
@@ -636,6 +1074,7 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
|
|
|
636
1074
|
done();
|
|
637
1075
|
});
|
|
638
1076
|
fastify.addHook("onResponse", (request, reply, done) => {
|
|
1077
|
+
var _a2;
|
|
639
1078
|
if (request.method.toUpperCase() !== "OPTIONS") {
|
|
640
1079
|
const consumer = getConsumer(request);
|
|
641
1080
|
const path = "routeOptions" in request ? request.routeOptions.url : request.routerPath;
|
|
@@ -644,26 +1083,33 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
|
|
|
644
1083
|
if (Array.isArray(responseSize)) {
|
|
645
1084
|
responseSize = responseSize[0];
|
|
646
1085
|
}
|
|
1086
|
+
const responseTime = getResponseTime(reply);
|
|
647
1087
|
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
648
1088
|
client.requestCounter.addRequest({
|
|
649
1089
|
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
650
1090
|
method: request.method,
|
|
651
1091
|
path,
|
|
652
1092
|
statusCode: reply.statusCode,
|
|
653
|
-
responseTime
|
|
1093
|
+
responseTime,
|
|
654
1094
|
requestSize,
|
|
655
1095
|
responseSize
|
|
656
1096
|
});
|
|
657
|
-
if ((reply.statusCode === 400 || reply.statusCode === 422) && reply.payload
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1097
|
+
if ((reply.statusCode === 400 || reply.statusCode === 422) && reply.payload) {
|
|
1098
|
+
try {
|
|
1099
|
+
const parsedPayload = JSON.parse(reply.payload);
|
|
1100
|
+
if ((!parsedPayload.code || parsedPayload.code === "FST_ERR_VALIDATION") && typeof parsedPayload.message === "string") {
|
|
1101
|
+
const validationErrors = extractAjvErrors(parsedPayload.message);
|
|
1102
|
+
validationErrors.forEach((error) => {
|
|
1103
|
+
client.validationErrorCounter.addValidationError({
|
|
1104
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1105
|
+
method: request.method,
|
|
1106
|
+
path,
|
|
1107
|
+
...error
|
|
1108
|
+
});
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
} catch (error) {
|
|
1112
|
+
}
|
|
667
1113
|
}
|
|
668
1114
|
if (reply.statusCode === 500 && reply.serverError) {
|
|
669
1115
|
client.serverErrorCounter.addServerError({
|
|
@@ -675,6 +1121,24 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
|
|
|
675
1121
|
traceback: reply.serverError.stack || ""
|
|
676
1122
|
});
|
|
677
1123
|
}
|
|
1124
|
+
if (client.requestLogger.enabled) {
|
|
1125
|
+
client.requestLogger.logRequest({
|
|
1126
|
+
timestamp: Date.now() / 1e3,
|
|
1127
|
+
method: request.method,
|
|
1128
|
+
path,
|
|
1129
|
+
url: `${request.protocol}://${request.host ?? request.hostname}${request.originalUrl ?? request.url}`,
|
|
1130
|
+
headers: convertHeaders(request.headers),
|
|
1131
|
+
size: Number(requestSize),
|
|
1132
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1133
|
+
body: convertBody(request.body, request.headers["content-type"])
|
|
1134
|
+
}, {
|
|
1135
|
+
statusCode: reply.statusCode,
|
|
1136
|
+
responseTime: responseTime / 1e3,
|
|
1137
|
+
headers: convertHeaders(reply.getHeaders()),
|
|
1138
|
+
size: Number(responseSize),
|
|
1139
|
+
body: convertBody(reply.payload, (_a2 = reply.getHeader("content-type")) == null ? void 0 : _a2.toString())
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
678
1142
|
}
|
|
679
1143
|
done();
|
|
680
1144
|
});
|