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/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,403 @@ 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?$/i,
|
|
274
|
+
/\/_?health[_-]?checks?$/i,
|
|
275
|
+
/\/_?heart[_-]?beats?$/i,
|
|
276
|
+
/\/ping$/i,
|
|
277
|
+
/\/ready$/i,
|
|
278
|
+
/\/live$/i
|
|
279
|
+
];
|
|
280
|
+
var EXCLUDE_USER_AGENT_PATTERNS = [
|
|
281
|
+
/health[-_ ]?check/i,
|
|
282
|
+
/microsoft-azure-application-lb/i,
|
|
283
|
+
/googlehc/i,
|
|
284
|
+
/kube-probe/i
|
|
285
|
+
];
|
|
286
|
+
var MASK_QUERY_PARAM_PATTERNS = [
|
|
287
|
+
/auth/i,
|
|
288
|
+
/api-?key/i,
|
|
289
|
+
/secret/i,
|
|
290
|
+
/token/i,
|
|
291
|
+
/password/i,
|
|
292
|
+
/pwd/i
|
|
293
|
+
];
|
|
294
|
+
var MASK_HEADER_PATTERNS = [
|
|
295
|
+
/auth/i,
|
|
296
|
+
/api-?key/i,
|
|
297
|
+
/secret/i,
|
|
298
|
+
/token/i,
|
|
299
|
+
/cookie/i
|
|
300
|
+
];
|
|
301
|
+
var DEFAULT_CONFIG = {
|
|
302
|
+
enabled: false,
|
|
303
|
+
logQueryParams: true,
|
|
304
|
+
logRequestHeaders: false,
|
|
305
|
+
logRequestBody: false,
|
|
306
|
+
logResponseHeaders: true,
|
|
307
|
+
logResponseBody: false,
|
|
308
|
+
maskQueryParams: [],
|
|
309
|
+
maskHeaders: [],
|
|
310
|
+
excludePaths: []
|
|
311
|
+
};
|
|
312
|
+
var _RequestLogger = class _RequestLogger {
|
|
313
|
+
config;
|
|
314
|
+
enabled;
|
|
315
|
+
suspendUntil = null;
|
|
316
|
+
pendingWrites = [];
|
|
317
|
+
currentFile = null;
|
|
318
|
+
files = [];
|
|
319
|
+
maintainIntervalId;
|
|
320
|
+
constructor(config) {
|
|
321
|
+
this.config = {
|
|
322
|
+
...DEFAULT_CONFIG,
|
|
323
|
+
...config
|
|
324
|
+
};
|
|
325
|
+
this.enabled = this.config.enabled && checkWritableFs();
|
|
326
|
+
if (this.enabled) {
|
|
327
|
+
this.maintainIntervalId = setInterval(() => {
|
|
328
|
+
this.maintain();
|
|
329
|
+
}, 1e3);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
shouldExcludePath(urlPath) {
|
|
333
|
+
const patterns = [
|
|
334
|
+
...this.config.excludePaths,
|
|
335
|
+
...EXCLUDE_PATH_PATTERNS
|
|
336
|
+
];
|
|
337
|
+
return matchPatterns(urlPath, patterns);
|
|
338
|
+
}
|
|
339
|
+
shouldExcludeUserAgent(userAgent) {
|
|
340
|
+
return userAgent ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS) : false;
|
|
341
|
+
}
|
|
342
|
+
shouldMaskQueryParam(name) {
|
|
343
|
+
const patterns = [
|
|
344
|
+
...this.config.maskQueryParams,
|
|
345
|
+
...MASK_QUERY_PARAM_PATTERNS
|
|
346
|
+
];
|
|
347
|
+
return matchPatterns(name, patterns);
|
|
348
|
+
}
|
|
349
|
+
shouldMaskHeader(name) {
|
|
350
|
+
const patterns = [
|
|
351
|
+
...this.config.maskHeaders,
|
|
352
|
+
...MASK_HEADER_PATTERNS
|
|
353
|
+
];
|
|
354
|
+
return matchPatterns(name, patterns);
|
|
355
|
+
}
|
|
356
|
+
hasSupportedContentType(headers) {
|
|
357
|
+
var _a2;
|
|
358
|
+
const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
|
|
359
|
+
return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
|
|
360
|
+
}
|
|
361
|
+
maskQueryParams(search) {
|
|
362
|
+
const params = new URLSearchParams(search);
|
|
363
|
+
for (const [key] of params) {
|
|
364
|
+
if (this.shouldMaskQueryParam(key)) {
|
|
365
|
+
params.set(key, MASKED);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return params.toString();
|
|
369
|
+
}
|
|
370
|
+
maskHeaders(headers) {
|
|
371
|
+
return headers.map(([k, v]) => [
|
|
372
|
+
k,
|
|
373
|
+
this.shouldMaskHeader(k) ? MASKED : v
|
|
374
|
+
]);
|
|
375
|
+
}
|
|
376
|
+
logRequest(request, response) {
|
|
377
|
+
var _a2, _b, _c;
|
|
378
|
+
if (!this.enabled || this.suspendUntil !== null) return;
|
|
379
|
+
const url = new URL(request.url);
|
|
380
|
+
const path = request.path ?? url.pathname;
|
|
381
|
+
const userAgent = (_a2 = request.headers.find(([k]) => k.toLowerCase() === "user-agent")) == null ? void 0 : _a2[1];
|
|
382
|
+
if (this.shouldExcludePath(path) || this.shouldExcludeUserAgent(userAgent) || (((_c = (_b = this.config).excludeCallback) == null ? void 0 : _c.call(_b, request, response)) ?? false)) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
|
|
386
|
+
request.url = url.toString();
|
|
387
|
+
request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
|
|
388
|
+
response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
|
|
389
|
+
if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
|
|
390
|
+
request.body = void 0;
|
|
391
|
+
} else if (request.body) {
|
|
392
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
393
|
+
request.body = BODY_TOO_LARGE;
|
|
394
|
+
} else if (this.config.maskRequestBodyCallback) {
|
|
395
|
+
try {
|
|
396
|
+
request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
|
|
397
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
398
|
+
request.body = BODY_TOO_LARGE;
|
|
399
|
+
}
|
|
400
|
+
} catch {
|
|
401
|
+
request.body = void 0;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
|
|
406
|
+
response.body = void 0;
|
|
407
|
+
} else if (response.body) {
|
|
408
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
409
|
+
response.body = BODY_TOO_LARGE;
|
|
410
|
+
} else if (this.config.maskResponseBodyCallback) {
|
|
411
|
+
try {
|
|
412
|
+
response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
|
|
413
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
414
|
+
response.body = BODY_TOO_LARGE;
|
|
415
|
+
}
|
|
416
|
+
} catch {
|
|
417
|
+
response.body = void 0;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const item = {
|
|
422
|
+
uuid: randomUUID2(),
|
|
423
|
+
request: skipEmptyValues(request),
|
|
424
|
+
response: skipEmptyValues(response)
|
|
425
|
+
};
|
|
426
|
+
[
|
|
427
|
+
item.request.body,
|
|
428
|
+
item.response.body
|
|
429
|
+
].forEach((body) => {
|
|
430
|
+
if (body) {
|
|
431
|
+
body.toJSON = function() {
|
|
432
|
+
return this.toString("base64");
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
this.pendingWrites.push(JSON.stringify(item));
|
|
437
|
+
if (this.pendingWrites.length > MAX_PENDING_WRITES) {
|
|
438
|
+
this.pendingWrites.shift();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async writeToFile() {
|
|
442
|
+
if (!this.enabled || this.pendingWrites.length === 0) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (!this.currentFile) {
|
|
446
|
+
this.currentFile = new TempGzipFile();
|
|
447
|
+
}
|
|
448
|
+
while (this.pendingWrites.length > 0) {
|
|
449
|
+
const item = this.pendingWrites.shift();
|
|
450
|
+
if (item) {
|
|
451
|
+
await this.currentFile.writeLine(Buffer3.from(item));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
getFile() {
|
|
456
|
+
return this.files.shift();
|
|
457
|
+
}
|
|
458
|
+
retryFileLater(file) {
|
|
459
|
+
this.files.unshift(file);
|
|
460
|
+
}
|
|
461
|
+
async rotateFile() {
|
|
462
|
+
if (this.currentFile) {
|
|
463
|
+
await this.currentFile.close();
|
|
464
|
+
this.files.push(this.currentFile);
|
|
465
|
+
this.currentFile = null;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
async maintain() {
|
|
469
|
+
await this.writeToFile();
|
|
470
|
+
if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
|
|
471
|
+
await this.rotateFile();
|
|
472
|
+
}
|
|
473
|
+
while (this.files.length > MAX_FILES) {
|
|
474
|
+
const file = this.files.shift();
|
|
475
|
+
file == null ? void 0 : file.delete();
|
|
476
|
+
}
|
|
477
|
+
if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
|
|
478
|
+
this.suspendUntil = null;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
async clear() {
|
|
482
|
+
this.pendingWrites = [];
|
|
483
|
+
await this.rotateFile();
|
|
484
|
+
this.files.forEach((file) => {
|
|
485
|
+
file.delete();
|
|
486
|
+
});
|
|
487
|
+
this.files = [];
|
|
488
|
+
}
|
|
489
|
+
async close() {
|
|
490
|
+
this.enabled = false;
|
|
491
|
+
await this.clear();
|
|
492
|
+
if (this.maintainIntervalId) {
|
|
493
|
+
clearInterval(this.maintainIntervalId);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
__name(_RequestLogger, "RequestLogger");
|
|
498
|
+
var RequestLogger = _RequestLogger;
|
|
499
|
+
function convertHeaders(headers) {
|
|
500
|
+
if (headers instanceof Headers) {
|
|
501
|
+
return Array.from(headers.entries());
|
|
502
|
+
}
|
|
503
|
+
return Object.entries(headers).flatMap(([key, value]) => {
|
|
504
|
+
if (value === void 0) {
|
|
505
|
+
return [];
|
|
506
|
+
}
|
|
507
|
+
if (Array.isArray(value)) {
|
|
508
|
+
return value.map((v) => [
|
|
509
|
+
key,
|
|
510
|
+
v
|
|
511
|
+
]);
|
|
512
|
+
}
|
|
513
|
+
return [
|
|
514
|
+
[
|
|
515
|
+
key,
|
|
516
|
+
value.toString()
|
|
517
|
+
]
|
|
518
|
+
];
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
__name(convertHeaders, "convertHeaders");
|
|
522
|
+
function convertBody(body, contentType) {
|
|
523
|
+
if (!body || !contentType) {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
try {
|
|
527
|
+
if (contentType.startsWith("application/json")) {
|
|
528
|
+
if (isValidJsonString(body)) {
|
|
529
|
+
return Buffer3.from(body);
|
|
530
|
+
} else {
|
|
531
|
+
return Buffer3.from(JSON.stringify(body));
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (contentType.startsWith("text/") && typeof body === "string") {
|
|
535
|
+
return Buffer3.from(body);
|
|
536
|
+
}
|
|
537
|
+
} catch (error) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
__name(convertBody, "convertBody");
|
|
542
|
+
function isValidJsonString(body) {
|
|
543
|
+
if (typeof body !== "string") {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
try {
|
|
547
|
+
JSON.parse(body);
|
|
548
|
+
return true;
|
|
549
|
+
} catch {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
__name(isValidJsonString, "isValidJsonString");
|
|
554
|
+
function matchPatterns(value, patterns) {
|
|
555
|
+
return patterns.some((pattern) => {
|
|
556
|
+
return pattern.test(value);
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
__name(matchPatterns, "matchPatterns");
|
|
560
|
+
function skipEmptyValues(data) {
|
|
561
|
+
return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
|
|
562
|
+
if (v == null || Number.isNaN(v)) return false;
|
|
563
|
+
if (Array.isArray(v) || Buffer3.isBuffer(v) || typeof v === "string") {
|
|
564
|
+
return v.length > 0;
|
|
565
|
+
}
|
|
566
|
+
return true;
|
|
567
|
+
}));
|
|
568
|
+
}
|
|
569
|
+
__name(skipEmptyValues, "skipEmptyValues");
|
|
570
|
+
function checkWritableFs() {
|
|
571
|
+
try {
|
|
572
|
+
const testPath = join2(tmpdir2(), `apitally-${randomUUID2()}`);
|
|
573
|
+
writeFileSync(testPath, "test");
|
|
574
|
+
unlinkSync2(testPath);
|
|
575
|
+
return true;
|
|
576
|
+
} catch (error) {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
__name(checkWritableFs, "checkWritableFs");
|
|
581
|
+
|
|
185
582
|
// src/common/serverErrorCounter.ts
|
|
186
583
|
import { createHash } from "crypto";
|
|
187
584
|
var MAX_MSG_LENGTH = 2048;
|
|
@@ -354,11 +751,12 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
354
751
|
startupData;
|
|
355
752
|
startupDataSent = false;
|
|
356
753
|
requestCounter;
|
|
754
|
+
requestLogger;
|
|
357
755
|
validationErrorCounter;
|
|
358
756
|
serverErrorCounter;
|
|
359
757
|
consumerRegistry;
|
|
360
758
|
logger;
|
|
361
|
-
constructor({ clientId, env = "dev", logger }) {
|
|
759
|
+
constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
|
|
362
760
|
if (_ApitallyClient.instance) {
|
|
363
761
|
throw new Error("Apitally client is already initialized");
|
|
364
762
|
}
|
|
@@ -371,9 +769,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
371
769
|
_ApitallyClient.instance = this;
|
|
372
770
|
this.clientId = clientId;
|
|
373
771
|
this.env = env;
|
|
374
|
-
this.instanceUuid =
|
|
772
|
+
this.instanceUuid = randomUUID3();
|
|
375
773
|
this.syncDataQueue = [];
|
|
376
774
|
this.requestCounter = new RequestCounter();
|
|
775
|
+
this.requestLogger = new RequestLogger(requestLoggingConfig);
|
|
377
776
|
this.validationErrorCounter = new ValidationErrorCounter();
|
|
378
777
|
this.serverErrorCounter = new ServerErrorCounter();
|
|
379
778
|
this.consumerRegistry = new ConsumerRegistry();
|
|
@@ -395,6 +794,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
395
794
|
async handleShutdown() {
|
|
396
795
|
this.stopSync();
|
|
397
796
|
await this.sendSyncData();
|
|
797
|
+
await this.sendLogData();
|
|
798
|
+
await this.requestLogger.close();
|
|
398
799
|
_ApitallyClient.instance = void 0;
|
|
399
800
|
}
|
|
400
801
|
getHubUrlPrefix() {
|
|
@@ -441,7 +842,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
441
842
|
async sync() {
|
|
442
843
|
try {
|
|
443
844
|
const promises = [
|
|
444
|
-
this.sendSyncData()
|
|
845
|
+
this.sendSyncData(),
|
|
846
|
+
this.sendLogData()
|
|
445
847
|
];
|
|
446
848
|
if (!this.startupDataSent) {
|
|
447
849
|
promises.push(this.sendStartupData());
|
|
@@ -469,7 +871,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
469
871
|
this.logger.debug("Sending startup data to Apitally Hub");
|
|
470
872
|
const payload = {
|
|
471
873
|
instance_uuid: this.instanceUuid,
|
|
472
|
-
message_uuid:
|
|
874
|
+
message_uuid: randomUUID3(),
|
|
473
875
|
...this.startupData
|
|
474
876
|
};
|
|
475
877
|
try {
|
|
@@ -491,7 +893,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
491
893
|
const newPayload = {
|
|
492
894
|
time_offset: 0,
|
|
493
895
|
instance_uuid: this.instanceUuid,
|
|
494
|
-
message_uuid:
|
|
896
|
+
message_uuid: randomUUID3(),
|
|
495
897
|
requests: this.requestCounter.getAndResetRequests(),
|
|
496
898
|
validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
|
|
497
899
|
server_errors: this.serverErrorCounter.getAndResetServerErrors(),
|
|
@@ -510,8 +912,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
510
912
|
const timeOffset = Date.now() - time;
|
|
511
913
|
if (timeOffset <= MAX_QUEUE_TIME) {
|
|
512
914
|
if (i > 0) {
|
|
513
|
-
|
|
514
|
-
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
915
|
+
await this.randomDelay();
|
|
515
916
|
}
|
|
516
917
|
payload.time_offset = timeOffset / 1e3;
|
|
517
918
|
await this.sendData("sync", payload);
|
|
@@ -530,6 +931,52 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
530
931
|
}
|
|
531
932
|
}
|
|
532
933
|
}
|
|
934
|
+
async sendLogData() {
|
|
935
|
+
this.logger.debug("Sending request log data to Apitally Hub");
|
|
936
|
+
await this.requestLogger.rotateFile();
|
|
937
|
+
const fetchWithRetry = fetchRetry(fetch, {
|
|
938
|
+
retries: 3,
|
|
939
|
+
retryDelay: 1e3,
|
|
940
|
+
retryOn: [
|
|
941
|
+
408,
|
|
942
|
+
429,
|
|
943
|
+
500,
|
|
944
|
+
502,
|
|
945
|
+
503,
|
|
946
|
+
504
|
|
947
|
+
]
|
|
948
|
+
});
|
|
949
|
+
let i = 0;
|
|
950
|
+
let logFile;
|
|
951
|
+
while (logFile = this.requestLogger.getFile()) {
|
|
952
|
+
if (i > 0) {
|
|
953
|
+
await this.randomDelay();
|
|
954
|
+
}
|
|
955
|
+
try {
|
|
956
|
+
const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
|
|
957
|
+
method: "POST",
|
|
958
|
+
body: await logFile.getContent()
|
|
959
|
+
});
|
|
960
|
+
if (response.status === 402 && response.headers.has("Retry-After")) {
|
|
961
|
+
const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
|
|
962
|
+
if (retryAfter > 0) {
|
|
963
|
+
this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
|
|
964
|
+
this.requestLogger.clear();
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
if (!response.ok) {
|
|
969
|
+
throw new HTTPError(response);
|
|
970
|
+
}
|
|
971
|
+
logFile.delete();
|
|
972
|
+
} catch (error) {
|
|
973
|
+
this.requestLogger.retryFileLater(logFile);
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
976
|
+
i++;
|
|
977
|
+
if (i >= 10) break;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
533
980
|
handleHubError(error) {
|
|
534
981
|
if (error instanceof HTTPError) {
|
|
535
982
|
if (error.response.status === 404) {
|
|
@@ -544,6 +991,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
544
991
|
}
|
|
545
992
|
return false;
|
|
546
993
|
}
|
|
994
|
+
async randomDelay() {
|
|
995
|
+
const delay = 100 + Math.random() * 200;
|
|
996
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
997
|
+
}
|
|
547
998
|
};
|
|
548
999
|
__name(_ApitallyClient, "ApitallyClient");
|
|
549
1000
|
__publicField(_ApitallyClient, "instance");
|
|
@@ -596,10 +1047,7 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
|
|
|
596
1047
|
await client.handleShutdown();
|
|
597
1048
|
});
|
|
598
1049
|
fastify.addHook("onSend", (request, reply, payload, done) => {
|
|
599
|
-
|
|
600
|
-
reply.payload = JSON.parse(payload);
|
|
601
|
-
} catch (error) {
|
|
602
|
-
}
|
|
1050
|
+
reply.payload = payload;
|
|
603
1051
|
done();
|
|
604
1052
|
});
|
|
605
1053
|
fastify.addHook("onError", (request, reply, error, done) => {
|
|
@@ -609,6 +1057,7 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
|
|
|
609
1057
|
done();
|
|
610
1058
|
});
|
|
611
1059
|
fastify.addHook("onResponse", (request, reply, done) => {
|
|
1060
|
+
var _a2;
|
|
612
1061
|
if (request.method.toUpperCase() !== "OPTIONS") {
|
|
613
1062
|
const consumer = getConsumer(request);
|
|
614
1063
|
const path = "routeOptions" in request ? request.routeOptions.url : request.routerPath;
|
|
@@ -617,26 +1066,33 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
|
|
|
617
1066
|
if (Array.isArray(responseSize)) {
|
|
618
1067
|
responseSize = responseSize[0];
|
|
619
1068
|
}
|
|
1069
|
+
const responseTime = getResponseTime(reply);
|
|
620
1070
|
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
621
1071
|
client.requestCounter.addRequest({
|
|
622
1072
|
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
623
1073
|
method: request.method,
|
|
624
1074
|
path,
|
|
625
1075
|
statusCode: reply.statusCode,
|
|
626
|
-
responseTime
|
|
1076
|
+
responseTime,
|
|
627
1077
|
requestSize,
|
|
628
1078
|
responseSize
|
|
629
1079
|
});
|
|
630
|
-
if ((reply.statusCode === 400 || reply.statusCode === 422) && reply.payload
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
1080
|
+
if ((reply.statusCode === 400 || reply.statusCode === 422) && reply.payload) {
|
|
1081
|
+
try {
|
|
1082
|
+
const parsedPayload = JSON.parse(reply.payload);
|
|
1083
|
+
if ((!parsedPayload.code || parsedPayload.code === "FST_ERR_VALIDATION") && typeof parsedPayload.message === "string") {
|
|
1084
|
+
const validationErrors = extractAjvErrors(parsedPayload.message);
|
|
1085
|
+
validationErrors.forEach((error) => {
|
|
1086
|
+
client.validationErrorCounter.addValidationError({
|
|
1087
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1088
|
+
method: request.method,
|
|
1089
|
+
path,
|
|
1090
|
+
...error
|
|
1091
|
+
});
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
}
|
|
640
1096
|
}
|
|
641
1097
|
if (reply.statusCode === 500 && reply.serverError) {
|
|
642
1098
|
client.serverErrorCounter.addServerError({
|
|
@@ -648,6 +1104,24 @@ var apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
|
|
|
648
1104
|
traceback: reply.serverError.stack || ""
|
|
649
1105
|
});
|
|
650
1106
|
}
|
|
1107
|
+
if (client.requestLogger.enabled) {
|
|
1108
|
+
client.requestLogger.logRequest({
|
|
1109
|
+
timestamp: Date.now() / 1e3,
|
|
1110
|
+
method: request.method,
|
|
1111
|
+
path,
|
|
1112
|
+
url: `${request.protocol}://${request.host ?? request.hostname}${request.originalUrl ?? request.url}`,
|
|
1113
|
+
headers: convertHeaders(request.headers),
|
|
1114
|
+
size: Number(requestSize),
|
|
1115
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1116
|
+
body: convertBody(request.body, request.headers["content-type"])
|
|
1117
|
+
}, {
|
|
1118
|
+
statusCode: reply.statusCode,
|
|
1119
|
+
responseTime: responseTime / 1e3,
|
|
1120
|
+
headers: convertHeaders(reply.getHeaders()),
|
|
1121
|
+
size: Number(responseSize),
|
|
1122
|
+
body: convertBody(reply.payload, (_a2 = reply.getHeader("content-type")) == null ? void 0 : _a2.toString())
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
651
1125
|
}
|
|
652
1126
|
done();
|
|
653
1127
|
});
|