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.js
CHANGED
|
@@ -13,7 +13,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
13
13
|
import fp from "fastify-plugin";
|
|
14
14
|
|
|
15
15
|
// src/common/client.ts
|
|
16
|
-
import { randomUUID } from "crypto";
|
|
16
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
17
17
|
import fetchRetry from "fetch-retry";
|
|
18
18
|
|
|
19
19
|
// src/common/consumerRegistry.ts
|
|
@@ -182,6 +182,393 @@ var _RequestCounter = class _RequestCounter {
|
|
|
182
182
|
__name(_RequestCounter, "RequestCounter");
|
|
183
183
|
var RequestCounter = _RequestCounter;
|
|
184
184
|
|
|
185
|
+
// src/common/requestLogger.ts
|
|
186
|
+
import { Buffer as Buffer3 } from "buffer";
|
|
187
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
188
|
+
import { unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
189
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
190
|
+
import { join as join2 } from "path";
|
|
191
|
+
|
|
192
|
+
// src/common/tempGzipFile.ts
|
|
193
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
194
|
+
import { randomUUID } from "crypto";
|
|
195
|
+
import { createWriteStream, readFile, unlinkSync } from "fs";
|
|
196
|
+
import { tmpdir } from "os";
|
|
197
|
+
import { join } from "path";
|
|
198
|
+
import { createGzip } from "zlib";
|
|
199
|
+
var _TempGzipFile = class _TempGzipFile {
|
|
200
|
+
uuid;
|
|
201
|
+
filePath;
|
|
202
|
+
gzip;
|
|
203
|
+
writeStream;
|
|
204
|
+
readyPromise;
|
|
205
|
+
constructor() {
|
|
206
|
+
this.uuid = randomUUID();
|
|
207
|
+
this.filePath = join(tmpdir(), `apitally-${this.uuid}.gz`);
|
|
208
|
+
this.writeStream = createWriteStream(this.filePath);
|
|
209
|
+
this.readyPromise = new Promise((resolve, reject) => {
|
|
210
|
+
this.writeStream.once("ready", resolve);
|
|
211
|
+
this.writeStream.once("error", reject);
|
|
212
|
+
});
|
|
213
|
+
this.gzip = createGzip();
|
|
214
|
+
this.gzip.pipe(this.writeStream);
|
|
215
|
+
}
|
|
216
|
+
get size() {
|
|
217
|
+
return this.writeStream.bytesWritten;
|
|
218
|
+
}
|
|
219
|
+
async writeLine(data) {
|
|
220
|
+
await this.readyPromise;
|
|
221
|
+
return new Promise((resolve, reject) => {
|
|
222
|
+
this.gzip.write(Buffer2.concat([
|
|
223
|
+
data,
|
|
224
|
+
Buffer2.from("\n")
|
|
225
|
+
]), (error) => {
|
|
226
|
+
if (error) {
|
|
227
|
+
reject(error);
|
|
228
|
+
} else {
|
|
229
|
+
resolve();
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
async getContent() {
|
|
235
|
+
return new Promise((resolve, reject) => {
|
|
236
|
+
readFile(this.filePath, (error, data) => {
|
|
237
|
+
if (error) {
|
|
238
|
+
reject(error);
|
|
239
|
+
} else {
|
|
240
|
+
resolve(data);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
async close() {
|
|
246
|
+
return new Promise((resolve) => {
|
|
247
|
+
this.gzip.end(() => {
|
|
248
|
+
resolve();
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
async delete() {
|
|
253
|
+
await this.close();
|
|
254
|
+
unlinkSync(this.filePath);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
__name(_TempGzipFile, "TempGzipFile");
|
|
258
|
+
var TempGzipFile = _TempGzipFile;
|
|
259
|
+
|
|
260
|
+
// src/common/requestLogger.ts
|
|
261
|
+
var MAX_BODY_SIZE = 5e4;
|
|
262
|
+
var MAX_FILE_SIZE = 1e6;
|
|
263
|
+
var MAX_FILES = 50;
|
|
264
|
+
var MAX_PENDING_WRITES = 100;
|
|
265
|
+
var BODY_TOO_LARGE = Buffer3.from("<body too large>");
|
|
266
|
+
var BODY_MASKED = Buffer3.from("<masked>");
|
|
267
|
+
var MASKED = "******";
|
|
268
|
+
var ALLOWED_CONTENT_TYPES = [
|
|
269
|
+
"application/json",
|
|
270
|
+
"text/plain"
|
|
271
|
+
];
|
|
272
|
+
var EXCLUDE_PATH_PATTERNS = [
|
|
273
|
+
/\/_?healthz?$/,
|
|
274
|
+
/\/_?health[_-]?checks?$/,
|
|
275
|
+
/\/_?heart[_-]?beats?$/,
|
|
276
|
+
/\/ping$/,
|
|
277
|
+
/\/ready$/,
|
|
278
|
+
/\/live$/
|
|
279
|
+
];
|
|
280
|
+
var MASK_QUERY_PARAM_PATTERNS = [
|
|
281
|
+
/auth/i,
|
|
282
|
+
/api-?key/i,
|
|
283
|
+
/secret/i,
|
|
284
|
+
/token/i,
|
|
285
|
+
/password/i,
|
|
286
|
+
/pwd/i
|
|
287
|
+
];
|
|
288
|
+
var MASK_HEADER_PATTERNS = [
|
|
289
|
+
/auth/i,
|
|
290
|
+
/api-?key/i,
|
|
291
|
+
/secret/i,
|
|
292
|
+
/token/i,
|
|
293
|
+
/cookie/i
|
|
294
|
+
];
|
|
295
|
+
var DEFAULT_CONFIG = {
|
|
296
|
+
enabled: false,
|
|
297
|
+
logQueryParams: true,
|
|
298
|
+
logRequestHeaders: false,
|
|
299
|
+
logRequestBody: false,
|
|
300
|
+
logResponseHeaders: true,
|
|
301
|
+
logResponseBody: false,
|
|
302
|
+
maskQueryParams: [],
|
|
303
|
+
maskHeaders: [],
|
|
304
|
+
excludePaths: []
|
|
305
|
+
};
|
|
306
|
+
var _RequestLogger = class _RequestLogger {
|
|
307
|
+
config;
|
|
308
|
+
enabled;
|
|
309
|
+
suspendUntil = null;
|
|
310
|
+
pendingWrites = [];
|
|
311
|
+
currentFile = null;
|
|
312
|
+
files = [];
|
|
313
|
+
maintainIntervalId;
|
|
314
|
+
constructor(config) {
|
|
315
|
+
this.config = {
|
|
316
|
+
...DEFAULT_CONFIG,
|
|
317
|
+
...config
|
|
318
|
+
};
|
|
319
|
+
this.enabled = this.config.enabled && checkWritableFs();
|
|
320
|
+
if (this.enabled) {
|
|
321
|
+
this.maintainIntervalId = setInterval(() => {
|
|
322
|
+
this.maintain();
|
|
323
|
+
}, 1e3);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
shouldExcludePath(urlPath) {
|
|
327
|
+
const patterns = [
|
|
328
|
+
...this.config.excludePaths,
|
|
329
|
+
...EXCLUDE_PATH_PATTERNS
|
|
330
|
+
];
|
|
331
|
+
return matchPatterns(urlPath, patterns);
|
|
332
|
+
}
|
|
333
|
+
shouldMaskQueryParam(name) {
|
|
334
|
+
const patterns = [
|
|
335
|
+
...this.config.maskQueryParams,
|
|
336
|
+
...MASK_QUERY_PARAM_PATTERNS
|
|
337
|
+
];
|
|
338
|
+
return matchPatterns(name, patterns);
|
|
339
|
+
}
|
|
340
|
+
shouldMaskHeader(name) {
|
|
341
|
+
const patterns = [
|
|
342
|
+
...this.config.maskHeaders,
|
|
343
|
+
...MASK_HEADER_PATTERNS
|
|
344
|
+
];
|
|
345
|
+
return matchPatterns(name, patterns);
|
|
346
|
+
}
|
|
347
|
+
hasSupportedContentType(headers) {
|
|
348
|
+
var _a2;
|
|
349
|
+
const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
|
|
350
|
+
return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
|
|
351
|
+
}
|
|
352
|
+
maskQueryParams(search) {
|
|
353
|
+
const params = new URLSearchParams(search);
|
|
354
|
+
for (const [key] of params) {
|
|
355
|
+
if (this.shouldMaskQueryParam(key)) {
|
|
356
|
+
params.set(key, MASKED);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return params.toString();
|
|
360
|
+
}
|
|
361
|
+
maskHeaders(headers) {
|
|
362
|
+
return headers.map(([k, v]) => [
|
|
363
|
+
k,
|
|
364
|
+
this.shouldMaskHeader(k) ? MASKED : v
|
|
365
|
+
]);
|
|
366
|
+
}
|
|
367
|
+
logRequest(request, response) {
|
|
368
|
+
var _a2, _b;
|
|
369
|
+
if (!this.enabled || this.suspendUntil !== null) return;
|
|
370
|
+
const url = new URL(request.url);
|
|
371
|
+
const path = request.path ?? url.pathname;
|
|
372
|
+
if (this.shouldExcludePath(path) || (((_b = (_a2 = this.config).excludeCallback) == null ? void 0 : _b.call(_a2, request, response)) ?? false)) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
|
|
376
|
+
request.url = url.toString();
|
|
377
|
+
request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
|
|
378
|
+
response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
|
|
379
|
+
if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
|
|
380
|
+
request.body = void 0;
|
|
381
|
+
} else if (request.body) {
|
|
382
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
383
|
+
request.body = BODY_TOO_LARGE;
|
|
384
|
+
} else if (this.config.maskRequestBodyCallback) {
|
|
385
|
+
try {
|
|
386
|
+
request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
|
|
387
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
388
|
+
request.body = BODY_TOO_LARGE;
|
|
389
|
+
}
|
|
390
|
+
} catch {
|
|
391
|
+
request.body = void 0;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
|
|
396
|
+
response.body = void 0;
|
|
397
|
+
} else if (response.body) {
|
|
398
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
399
|
+
response.body = BODY_TOO_LARGE;
|
|
400
|
+
} else if (this.config.maskResponseBodyCallback) {
|
|
401
|
+
try {
|
|
402
|
+
response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
|
|
403
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
404
|
+
response.body = BODY_TOO_LARGE;
|
|
405
|
+
}
|
|
406
|
+
} catch {
|
|
407
|
+
response.body = void 0;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
const item = {
|
|
412
|
+
uuid: randomUUID2(),
|
|
413
|
+
request: skipEmptyValues(request),
|
|
414
|
+
response: skipEmptyValues(response)
|
|
415
|
+
};
|
|
416
|
+
[
|
|
417
|
+
item.request.body,
|
|
418
|
+
item.response.body
|
|
419
|
+
].forEach((body) => {
|
|
420
|
+
if (body) {
|
|
421
|
+
body.toJSON = function() {
|
|
422
|
+
return this.toString("base64");
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
this.pendingWrites.push(JSON.stringify(item));
|
|
427
|
+
if (this.pendingWrites.length > MAX_PENDING_WRITES) {
|
|
428
|
+
this.pendingWrites.shift();
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
async writeToFile() {
|
|
432
|
+
if (!this.enabled || this.pendingWrites.length === 0) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (!this.currentFile) {
|
|
436
|
+
this.currentFile = new TempGzipFile();
|
|
437
|
+
}
|
|
438
|
+
while (this.pendingWrites.length > 0) {
|
|
439
|
+
const item = this.pendingWrites.shift();
|
|
440
|
+
if (item) {
|
|
441
|
+
await this.currentFile.writeLine(Buffer3.from(item));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
getFile() {
|
|
446
|
+
return this.files.shift();
|
|
447
|
+
}
|
|
448
|
+
retryFileLater(file) {
|
|
449
|
+
this.files.unshift(file);
|
|
450
|
+
}
|
|
451
|
+
async rotateFile() {
|
|
452
|
+
if (this.currentFile) {
|
|
453
|
+
await this.currentFile.close();
|
|
454
|
+
this.files.push(this.currentFile);
|
|
455
|
+
this.currentFile = null;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
async maintain() {
|
|
459
|
+
await this.writeToFile();
|
|
460
|
+
if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
|
|
461
|
+
await this.rotateFile();
|
|
462
|
+
}
|
|
463
|
+
while (this.files.length > MAX_FILES) {
|
|
464
|
+
const file = this.files.shift();
|
|
465
|
+
file == null ? void 0 : file.delete();
|
|
466
|
+
}
|
|
467
|
+
if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
|
|
468
|
+
this.suspendUntil = null;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
async clear() {
|
|
472
|
+
this.pendingWrites = [];
|
|
473
|
+
await this.rotateFile();
|
|
474
|
+
this.files.forEach((file) => {
|
|
475
|
+
file.delete();
|
|
476
|
+
});
|
|
477
|
+
this.files = [];
|
|
478
|
+
}
|
|
479
|
+
async close() {
|
|
480
|
+
this.enabled = false;
|
|
481
|
+
await this.clear();
|
|
482
|
+
if (this.maintainIntervalId) {
|
|
483
|
+
clearInterval(this.maintainIntervalId);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
__name(_RequestLogger, "RequestLogger");
|
|
488
|
+
var RequestLogger = _RequestLogger;
|
|
489
|
+
function convertHeaders(headers) {
|
|
490
|
+
if (headers instanceof Headers) {
|
|
491
|
+
return Array.from(headers.entries());
|
|
492
|
+
}
|
|
493
|
+
return Object.entries(headers).flatMap(([key, value]) => {
|
|
494
|
+
if (value === void 0) {
|
|
495
|
+
return [];
|
|
496
|
+
}
|
|
497
|
+
if (Array.isArray(value)) {
|
|
498
|
+
return value.map((v) => [
|
|
499
|
+
key,
|
|
500
|
+
v
|
|
501
|
+
]);
|
|
502
|
+
}
|
|
503
|
+
return [
|
|
504
|
+
[
|
|
505
|
+
key,
|
|
506
|
+
value.toString()
|
|
507
|
+
]
|
|
508
|
+
];
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
__name(convertHeaders, "convertHeaders");
|
|
512
|
+
function convertBody(body, contentType) {
|
|
513
|
+
if (!body || !contentType) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
try {
|
|
517
|
+
if (contentType.startsWith("application/json")) {
|
|
518
|
+
if (isValidJsonString(body)) {
|
|
519
|
+
return Buffer3.from(body);
|
|
520
|
+
} else {
|
|
521
|
+
return Buffer3.from(JSON.stringify(body));
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (contentType.startsWith("text/") && typeof body === "string") {
|
|
525
|
+
return Buffer3.from(body);
|
|
526
|
+
}
|
|
527
|
+
} catch (error) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
__name(convertBody, "convertBody");
|
|
532
|
+
function isValidJsonString(body) {
|
|
533
|
+
if (typeof body !== "string") {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
try {
|
|
537
|
+
JSON.parse(body);
|
|
538
|
+
return true;
|
|
539
|
+
} catch {
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
__name(isValidJsonString, "isValidJsonString");
|
|
544
|
+
function matchPatterns(value, patterns) {
|
|
545
|
+
return patterns.some((pattern) => {
|
|
546
|
+
return pattern.test(value);
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
__name(matchPatterns, "matchPatterns");
|
|
550
|
+
function skipEmptyValues(data) {
|
|
551
|
+
return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
|
|
552
|
+
if (v == null || Number.isNaN(v)) return false;
|
|
553
|
+
if (Array.isArray(v) || Buffer3.isBuffer(v) || typeof v === "string") {
|
|
554
|
+
return v.length > 0;
|
|
555
|
+
}
|
|
556
|
+
return true;
|
|
557
|
+
}));
|
|
558
|
+
}
|
|
559
|
+
__name(skipEmptyValues, "skipEmptyValues");
|
|
560
|
+
function checkWritableFs() {
|
|
561
|
+
try {
|
|
562
|
+
const testPath = join2(tmpdir2(), `apitally-${randomUUID2()}`);
|
|
563
|
+
writeFileSync(testPath, "test");
|
|
564
|
+
unlinkSync2(testPath);
|
|
565
|
+
return true;
|
|
566
|
+
} catch (error) {
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
__name(checkWritableFs, "checkWritableFs");
|
|
571
|
+
|
|
185
572
|
// src/common/serverErrorCounter.ts
|
|
186
573
|
import { createHash } from "crypto";
|
|
187
574
|
var MAX_MSG_LENGTH = 2048;
|
|
@@ -354,11 +741,12 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
354
741
|
startupData;
|
|
355
742
|
startupDataSent = false;
|
|
356
743
|
requestCounter;
|
|
744
|
+
requestLogger;
|
|
357
745
|
validationErrorCounter;
|
|
358
746
|
serverErrorCounter;
|
|
359
747
|
consumerRegistry;
|
|
360
748
|
logger;
|
|
361
|
-
constructor({ clientId, env = "dev", logger }) {
|
|
749
|
+
constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
|
|
362
750
|
if (_ApitallyClient.instance) {
|
|
363
751
|
throw new Error("Apitally client is already initialized");
|
|
364
752
|
}
|
|
@@ -371,9 +759,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
371
759
|
_ApitallyClient.instance = this;
|
|
372
760
|
this.clientId = clientId;
|
|
373
761
|
this.env = env;
|
|
374
|
-
this.instanceUuid =
|
|
762
|
+
this.instanceUuid = randomUUID3();
|
|
375
763
|
this.syncDataQueue = [];
|
|
376
764
|
this.requestCounter = new RequestCounter();
|
|
765
|
+
this.requestLogger = new RequestLogger(requestLoggingConfig);
|
|
377
766
|
this.validationErrorCounter = new ValidationErrorCounter();
|
|
378
767
|
this.serverErrorCounter = new ServerErrorCounter();
|
|
379
768
|
this.consumerRegistry = new ConsumerRegistry();
|
|
@@ -395,6 +784,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
395
784
|
async handleShutdown() {
|
|
396
785
|
this.stopSync();
|
|
397
786
|
await this.sendSyncData();
|
|
787
|
+
await this.sendLogData();
|
|
788
|
+
await this.requestLogger.close();
|
|
398
789
|
_ApitallyClient.instance = void 0;
|
|
399
790
|
}
|
|
400
791
|
getHubUrlPrefix() {
|
|
@@ -441,7 +832,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
441
832
|
async sync() {
|
|
442
833
|
try {
|
|
443
834
|
const promises = [
|
|
444
|
-
this.sendSyncData()
|
|
835
|
+
this.sendSyncData(),
|
|
836
|
+
this.sendLogData()
|
|
445
837
|
];
|
|
446
838
|
if (!this.startupDataSent) {
|
|
447
839
|
promises.push(this.sendStartupData());
|
|
@@ -469,7 +861,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
469
861
|
this.logger.debug("Sending startup data to Apitally Hub");
|
|
470
862
|
const payload = {
|
|
471
863
|
instance_uuid: this.instanceUuid,
|
|
472
|
-
message_uuid:
|
|
864
|
+
message_uuid: randomUUID3(),
|
|
473
865
|
...this.startupData
|
|
474
866
|
};
|
|
475
867
|
try {
|
|
@@ -491,7 +883,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
491
883
|
const newPayload = {
|
|
492
884
|
time_offset: 0,
|
|
493
885
|
instance_uuid: this.instanceUuid,
|
|
494
|
-
message_uuid:
|
|
886
|
+
message_uuid: randomUUID3(),
|
|
495
887
|
requests: this.requestCounter.getAndResetRequests(),
|
|
496
888
|
validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
|
|
497
889
|
server_errors: this.serverErrorCounter.getAndResetServerErrors(),
|
|
@@ -510,8 +902,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
510
902
|
const timeOffset = Date.now() - time;
|
|
511
903
|
if (timeOffset <= MAX_QUEUE_TIME) {
|
|
512
904
|
if (i > 0) {
|
|
513
|
-
|
|
514
|
-
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
905
|
+
await this.randomDelay();
|
|
515
906
|
}
|
|
516
907
|
payload.time_offset = timeOffset / 1e3;
|
|
517
908
|
await this.sendData("sync", payload);
|
|
@@ -530,6 +921,52 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
530
921
|
}
|
|
531
922
|
}
|
|
532
923
|
}
|
|
924
|
+
async sendLogData() {
|
|
925
|
+
this.logger.debug("Sending request log data to Apitally Hub");
|
|
926
|
+
await this.requestLogger.rotateFile();
|
|
927
|
+
const fetchWithRetry = fetchRetry(fetch, {
|
|
928
|
+
retries: 3,
|
|
929
|
+
retryDelay: 1e3,
|
|
930
|
+
retryOn: [
|
|
931
|
+
408,
|
|
932
|
+
429,
|
|
933
|
+
500,
|
|
934
|
+
502,
|
|
935
|
+
503,
|
|
936
|
+
504
|
|
937
|
+
]
|
|
938
|
+
});
|
|
939
|
+
let i = 0;
|
|
940
|
+
let logFile;
|
|
941
|
+
while (logFile = this.requestLogger.getFile()) {
|
|
942
|
+
if (i > 0) {
|
|
943
|
+
await this.randomDelay();
|
|
944
|
+
}
|
|
945
|
+
try {
|
|
946
|
+
const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
|
|
947
|
+
method: "POST",
|
|
948
|
+
body: await logFile.getContent()
|
|
949
|
+
});
|
|
950
|
+
if (response.status === 402 && response.headers.has("Retry-After")) {
|
|
951
|
+
const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
|
|
952
|
+
if (retryAfter > 0) {
|
|
953
|
+
this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
|
|
954
|
+
this.requestLogger.clear();
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
if (!response.ok) {
|
|
959
|
+
throw new HTTPError(response);
|
|
960
|
+
}
|
|
961
|
+
logFile.delete();
|
|
962
|
+
} catch (error) {
|
|
963
|
+
this.requestLogger.retryFileLater(logFile);
|
|
964
|
+
break;
|
|
965
|
+
}
|
|
966
|
+
i++;
|
|
967
|
+
if (i >= 10) break;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
533
970
|
handleHubError(error) {
|
|
534
971
|
if (error instanceof HTTPError) {
|
|
535
972
|
if (error.response.status === 404) {
|
|
@@ -544,6 +981,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
544
981
|
}
|
|
545
982
|
return false;
|
|
546
983
|
}
|
|
984
|
+
async randomDelay() {
|
|
985
|
+
const delay = 100 + Math.random() * 200;
|
|
986
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
987
|
+
}
|
|
547
988
|
};
|
|
548
989
|
__name(_ApitallyClient, "ApitallyClient");
|
|
549
990
|
__publicField(_ApitallyClient, "instance");
|
|
@@ -596,10 +1037,7 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
|
|
|
596
1037
|
await client.handleShutdown();
|
|
597
1038
|
});
|
|
598
1039
|
fastify.addHook("onSend", (request, reply, payload, done) => {
|
|
599
|
-
|
|
600
|
-
reply.payload = JSON.parse(payload);
|
|
601
|
-
} catch (error) {
|
|
602
|
-
}
|
|
1040
|
+
reply.payload = payload;
|
|
603
1041
|
done();
|
|
604
1042
|
});
|
|
605
1043
|
fastify.addHook("onError", (request, reply, error, done) => {
|
|
@@ -609,6 +1047,7 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
|
|
|
609
1047
|
done();
|
|
610
1048
|
});
|
|
611
1049
|
fastify.addHook("onResponse", (request, reply, done) => {
|
|
1050
|
+
var _a2;
|
|
612
1051
|
if (request.method.toUpperCase() !== "OPTIONS") {
|
|
613
1052
|
const consumer = getConsumer(request);
|
|
614
1053
|
const path = "routeOptions" in request ? request.routeOptions.url : request.routerPath;
|
|
@@ -617,26 +1056,33 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
|
|
|
617
1056
|
if (Array.isArray(responseSize)) {
|
|
618
1057
|
responseSize = responseSize[0];
|
|
619
1058
|
}
|
|
1059
|
+
const responseTime = getResponseTime(reply);
|
|
620
1060
|
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
621
1061
|
client.requestCounter.addRequest({
|
|
622
1062
|
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
623
1063
|
method: request.method,
|
|
624
1064
|
path,
|
|
625
1065
|
statusCode: reply.statusCode,
|
|
626
|
-
responseTime
|
|
1066
|
+
responseTime,
|
|
627
1067
|
requestSize,
|
|
628
1068
|
responseSize
|
|
629
1069
|
});
|
|
630
|
-
if ((reply.statusCode === 400 || reply.statusCode === 422) && reply.payload
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
1070
|
+
if ((reply.statusCode === 400 || reply.statusCode === 422) && reply.payload) {
|
|
1071
|
+
try {
|
|
1072
|
+
const parsedPayload = JSON.parse(reply.payload);
|
|
1073
|
+
if ((!parsedPayload.code || parsedPayload.code === "FST_ERR_VALIDATION") && typeof parsedPayload.message === "string") {
|
|
1074
|
+
const validationErrors = extractAjvErrors(parsedPayload.message);
|
|
1075
|
+
validationErrors.forEach((error) => {
|
|
1076
|
+
client.validationErrorCounter.addValidationError({
|
|
1077
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1078
|
+
method: request.method,
|
|
1079
|
+
path,
|
|
1080
|
+
...error
|
|
1081
|
+
});
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
}
|
|
640
1086
|
}
|
|
641
1087
|
if (reply.statusCode === 500 && reply.serverError) {
|
|
642
1088
|
client.serverErrorCounter.addServerError({
|
|
@@ -648,6 +1094,24 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
|
|
|
648
1094
|
traceback: reply.serverError.stack || ""
|
|
649
1095
|
});
|
|
650
1096
|
}
|
|
1097
|
+
if (client.requestLogger.enabled) {
|
|
1098
|
+
client.requestLogger.logRequest({
|
|
1099
|
+
timestamp: Date.now() / 1e3,
|
|
1100
|
+
method: request.method,
|
|
1101
|
+
path,
|
|
1102
|
+
url: `${request.protocol}://${request.host ?? request.hostname}${request.originalUrl ?? request.url}`,
|
|
1103
|
+
headers: convertHeaders(request.headers),
|
|
1104
|
+
size: Number(requestSize),
|
|
1105
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1106
|
+
body: convertBody(request.body, request.headers["content-type"])
|
|
1107
|
+
}, {
|
|
1108
|
+
statusCode: reply.statusCode,
|
|
1109
|
+
responseTime: responseTime / 1e3,
|
|
1110
|
+
headers: convertHeaders(reply.getHeaders()),
|
|
1111
|
+
size: Number(responseSize),
|
|
1112
|
+
body: convertBody(reply.payload, (_a2 = reply.getHeader("content-type")) == null ? void 0 : _a2.toString())
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
651
1115
|
}
|
|
652
1116
|
done();
|
|
653
1117
|
});
|