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/nestjs/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import { BaseExceptionFilter } from "@nestjs/core";
|
|
|
17
17
|
import { performance } from "perf_hooks";
|
|
18
18
|
|
|
19
19
|
// src/common/client.ts
|
|
20
|
-
import { randomUUID } from "crypto";
|
|
20
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
21
21
|
import fetchRetry from "fetch-retry";
|
|
22
22
|
|
|
23
23
|
// src/common/consumerRegistry.ts
|
|
@@ -186,6 +186,403 @@ var _RequestCounter = class _RequestCounter {
|
|
|
186
186
|
__name(_RequestCounter, "RequestCounter");
|
|
187
187
|
var RequestCounter = _RequestCounter;
|
|
188
188
|
|
|
189
|
+
// src/common/requestLogger.ts
|
|
190
|
+
import { Buffer as Buffer3 } from "buffer";
|
|
191
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
192
|
+
import { unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
193
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
194
|
+
import { join as join2 } from "path";
|
|
195
|
+
|
|
196
|
+
// src/common/tempGzipFile.ts
|
|
197
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
198
|
+
import { randomUUID } from "crypto";
|
|
199
|
+
import { createWriteStream, readFile, unlinkSync } from "fs";
|
|
200
|
+
import { tmpdir } from "os";
|
|
201
|
+
import { join } from "path";
|
|
202
|
+
import { createGzip } from "zlib";
|
|
203
|
+
var _TempGzipFile = class _TempGzipFile {
|
|
204
|
+
uuid;
|
|
205
|
+
filePath;
|
|
206
|
+
gzip;
|
|
207
|
+
writeStream;
|
|
208
|
+
readyPromise;
|
|
209
|
+
constructor() {
|
|
210
|
+
this.uuid = randomUUID();
|
|
211
|
+
this.filePath = join(tmpdir(), `apitally-${this.uuid}.gz`);
|
|
212
|
+
this.writeStream = createWriteStream(this.filePath);
|
|
213
|
+
this.readyPromise = new Promise((resolve, reject) => {
|
|
214
|
+
this.writeStream.once("ready", resolve);
|
|
215
|
+
this.writeStream.once("error", reject);
|
|
216
|
+
});
|
|
217
|
+
this.gzip = createGzip();
|
|
218
|
+
this.gzip.pipe(this.writeStream);
|
|
219
|
+
}
|
|
220
|
+
get size() {
|
|
221
|
+
return this.writeStream.bytesWritten;
|
|
222
|
+
}
|
|
223
|
+
async writeLine(data) {
|
|
224
|
+
await this.readyPromise;
|
|
225
|
+
return new Promise((resolve, reject) => {
|
|
226
|
+
this.gzip.write(Buffer2.concat([
|
|
227
|
+
data,
|
|
228
|
+
Buffer2.from("\n")
|
|
229
|
+
]), (error) => {
|
|
230
|
+
if (error) {
|
|
231
|
+
reject(error);
|
|
232
|
+
} else {
|
|
233
|
+
resolve();
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
async getContent() {
|
|
239
|
+
return new Promise((resolve, reject) => {
|
|
240
|
+
readFile(this.filePath, (error, data) => {
|
|
241
|
+
if (error) {
|
|
242
|
+
reject(error);
|
|
243
|
+
} else {
|
|
244
|
+
resolve(data);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
async close() {
|
|
250
|
+
return new Promise((resolve) => {
|
|
251
|
+
this.gzip.end(() => {
|
|
252
|
+
resolve();
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
async delete() {
|
|
257
|
+
await this.close();
|
|
258
|
+
unlinkSync(this.filePath);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
__name(_TempGzipFile, "TempGzipFile");
|
|
262
|
+
var TempGzipFile = _TempGzipFile;
|
|
263
|
+
|
|
264
|
+
// src/common/requestLogger.ts
|
|
265
|
+
var MAX_BODY_SIZE = 5e4;
|
|
266
|
+
var MAX_FILE_SIZE = 1e6;
|
|
267
|
+
var MAX_FILES = 50;
|
|
268
|
+
var MAX_PENDING_WRITES = 100;
|
|
269
|
+
var BODY_TOO_LARGE = Buffer3.from("<body too large>");
|
|
270
|
+
var BODY_MASKED = Buffer3.from("<masked>");
|
|
271
|
+
var MASKED = "******";
|
|
272
|
+
var ALLOWED_CONTENT_TYPES = [
|
|
273
|
+
"application/json",
|
|
274
|
+
"text/plain"
|
|
275
|
+
];
|
|
276
|
+
var EXCLUDE_PATH_PATTERNS = [
|
|
277
|
+
/\/_?healthz?$/i,
|
|
278
|
+
/\/_?health[_-]?checks?$/i,
|
|
279
|
+
/\/_?heart[_-]?beats?$/i,
|
|
280
|
+
/\/ping$/i,
|
|
281
|
+
/\/ready$/i,
|
|
282
|
+
/\/live$/i
|
|
283
|
+
];
|
|
284
|
+
var EXCLUDE_USER_AGENT_PATTERNS = [
|
|
285
|
+
/health[-_ ]?check/i,
|
|
286
|
+
/microsoft-azure-application-lb/i,
|
|
287
|
+
/googlehc/i,
|
|
288
|
+
/kube-probe/i
|
|
289
|
+
];
|
|
290
|
+
var MASK_QUERY_PARAM_PATTERNS = [
|
|
291
|
+
/auth/i,
|
|
292
|
+
/api-?key/i,
|
|
293
|
+
/secret/i,
|
|
294
|
+
/token/i,
|
|
295
|
+
/password/i,
|
|
296
|
+
/pwd/i
|
|
297
|
+
];
|
|
298
|
+
var MASK_HEADER_PATTERNS = [
|
|
299
|
+
/auth/i,
|
|
300
|
+
/api-?key/i,
|
|
301
|
+
/secret/i,
|
|
302
|
+
/token/i,
|
|
303
|
+
/cookie/i
|
|
304
|
+
];
|
|
305
|
+
var DEFAULT_CONFIG = {
|
|
306
|
+
enabled: false,
|
|
307
|
+
logQueryParams: true,
|
|
308
|
+
logRequestHeaders: false,
|
|
309
|
+
logRequestBody: false,
|
|
310
|
+
logResponseHeaders: true,
|
|
311
|
+
logResponseBody: false,
|
|
312
|
+
maskQueryParams: [],
|
|
313
|
+
maskHeaders: [],
|
|
314
|
+
excludePaths: []
|
|
315
|
+
};
|
|
316
|
+
var _RequestLogger = class _RequestLogger {
|
|
317
|
+
config;
|
|
318
|
+
enabled;
|
|
319
|
+
suspendUntil = null;
|
|
320
|
+
pendingWrites = [];
|
|
321
|
+
currentFile = null;
|
|
322
|
+
files = [];
|
|
323
|
+
maintainIntervalId;
|
|
324
|
+
constructor(config) {
|
|
325
|
+
this.config = {
|
|
326
|
+
...DEFAULT_CONFIG,
|
|
327
|
+
...config
|
|
328
|
+
};
|
|
329
|
+
this.enabled = this.config.enabled && checkWritableFs();
|
|
330
|
+
if (this.enabled) {
|
|
331
|
+
this.maintainIntervalId = setInterval(() => {
|
|
332
|
+
this.maintain();
|
|
333
|
+
}, 1e3);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
shouldExcludePath(urlPath) {
|
|
337
|
+
const patterns = [
|
|
338
|
+
...this.config.excludePaths,
|
|
339
|
+
...EXCLUDE_PATH_PATTERNS
|
|
340
|
+
];
|
|
341
|
+
return matchPatterns(urlPath, patterns);
|
|
342
|
+
}
|
|
343
|
+
shouldExcludeUserAgent(userAgent) {
|
|
344
|
+
return userAgent ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS) : false;
|
|
345
|
+
}
|
|
346
|
+
shouldMaskQueryParam(name) {
|
|
347
|
+
const patterns = [
|
|
348
|
+
...this.config.maskQueryParams,
|
|
349
|
+
...MASK_QUERY_PARAM_PATTERNS
|
|
350
|
+
];
|
|
351
|
+
return matchPatterns(name, patterns);
|
|
352
|
+
}
|
|
353
|
+
shouldMaskHeader(name) {
|
|
354
|
+
const patterns = [
|
|
355
|
+
...this.config.maskHeaders,
|
|
356
|
+
...MASK_HEADER_PATTERNS
|
|
357
|
+
];
|
|
358
|
+
return matchPatterns(name, patterns);
|
|
359
|
+
}
|
|
360
|
+
hasSupportedContentType(headers) {
|
|
361
|
+
var _a3;
|
|
362
|
+
const contentType = (_a3 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a3[1];
|
|
363
|
+
return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
|
|
364
|
+
}
|
|
365
|
+
maskQueryParams(search) {
|
|
366
|
+
const params = new URLSearchParams(search);
|
|
367
|
+
for (const [key] of params) {
|
|
368
|
+
if (this.shouldMaskQueryParam(key)) {
|
|
369
|
+
params.set(key, MASKED);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return params.toString();
|
|
373
|
+
}
|
|
374
|
+
maskHeaders(headers) {
|
|
375
|
+
return headers.map(([k, v]) => [
|
|
376
|
+
k,
|
|
377
|
+
this.shouldMaskHeader(k) ? MASKED : v
|
|
378
|
+
]);
|
|
379
|
+
}
|
|
380
|
+
logRequest(request, response) {
|
|
381
|
+
var _a3, _b, _c;
|
|
382
|
+
if (!this.enabled || this.suspendUntil !== null) return;
|
|
383
|
+
const url = new URL(request.url);
|
|
384
|
+
const path = request.path ?? url.pathname;
|
|
385
|
+
const userAgent = (_a3 = request.headers.find(([k]) => k.toLowerCase() === "user-agent")) == null ? void 0 : _a3[1];
|
|
386
|
+
if (this.shouldExcludePath(path) || this.shouldExcludeUserAgent(userAgent) || (((_c = (_b = this.config).excludeCallback) == null ? void 0 : _c.call(_b, request, response)) ?? false)) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
|
|
390
|
+
request.url = url.toString();
|
|
391
|
+
request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
|
|
392
|
+
response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
|
|
393
|
+
if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
|
|
394
|
+
request.body = void 0;
|
|
395
|
+
} else if (request.body) {
|
|
396
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
397
|
+
request.body = BODY_TOO_LARGE;
|
|
398
|
+
} else if (this.config.maskRequestBodyCallback) {
|
|
399
|
+
try {
|
|
400
|
+
request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
|
|
401
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
402
|
+
request.body = BODY_TOO_LARGE;
|
|
403
|
+
}
|
|
404
|
+
} catch {
|
|
405
|
+
request.body = void 0;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
|
|
410
|
+
response.body = void 0;
|
|
411
|
+
} else if (response.body) {
|
|
412
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
413
|
+
response.body = BODY_TOO_LARGE;
|
|
414
|
+
} else if (this.config.maskResponseBodyCallback) {
|
|
415
|
+
try {
|
|
416
|
+
response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
|
|
417
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
418
|
+
response.body = BODY_TOO_LARGE;
|
|
419
|
+
}
|
|
420
|
+
} catch {
|
|
421
|
+
response.body = void 0;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const item = {
|
|
426
|
+
uuid: randomUUID2(),
|
|
427
|
+
request: skipEmptyValues(request),
|
|
428
|
+
response: skipEmptyValues(response)
|
|
429
|
+
};
|
|
430
|
+
[
|
|
431
|
+
item.request.body,
|
|
432
|
+
item.response.body
|
|
433
|
+
].forEach((body) => {
|
|
434
|
+
if (body) {
|
|
435
|
+
body.toJSON = function() {
|
|
436
|
+
return this.toString("base64");
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
this.pendingWrites.push(JSON.stringify(item));
|
|
441
|
+
if (this.pendingWrites.length > MAX_PENDING_WRITES) {
|
|
442
|
+
this.pendingWrites.shift();
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async writeToFile() {
|
|
446
|
+
if (!this.enabled || this.pendingWrites.length === 0) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (!this.currentFile) {
|
|
450
|
+
this.currentFile = new TempGzipFile();
|
|
451
|
+
}
|
|
452
|
+
while (this.pendingWrites.length > 0) {
|
|
453
|
+
const item = this.pendingWrites.shift();
|
|
454
|
+
if (item) {
|
|
455
|
+
await this.currentFile.writeLine(Buffer3.from(item));
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
getFile() {
|
|
460
|
+
return this.files.shift();
|
|
461
|
+
}
|
|
462
|
+
retryFileLater(file) {
|
|
463
|
+
this.files.unshift(file);
|
|
464
|
+
}
|
|
465
|
+
async rotateFile() {
|
|
466
|
+
if (this.currentFile) {
|
|
467
|
+
await this.currentFile.close();
|
|
468
|
+
this.files.push(this.currentFile);
|
|
469
|
+
this.currentFile = null;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
async maintain() {
|
|
473
|
+
await this.writeToFile();
|
|
474
|
+
if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
|
|
475
|
+
await this.rotateFile();
|
|
476
|
+
}
|
|
477
|
+
while (this.files.length > MAX_FILES) {
|
|
478
|
+
const file = this.files.shift();
|
|
479
|
+
file == null ? void 0 : file.delete();
|
|
480
|
+
}
|
|
481
|
+
if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
|
|
482
|
+
this.suspendUntil = null;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
async clear() {
|
|
486
|
+
this.pendingWrites = [];
|
|
487
|
+
await this.rotateFile();
|
|
488
|
+
this.files.forEach((file) => {
|
|
489
|
+
file.delete();
|
|
490
|
+
});
|
|
491
|
+
this.files = [];
|
|
492
|
+
}
|
|
493
|
+
async close() {
|
|
494
|
+
this.enabled = false;
|
|
495
|
+
await this.clear();
|
|
496
|
+
if (this.maintainIntervalId) {
|
|
497
|
+
clearInterval(this.maintainIntervalId);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
__name(_RequestLogger, "RequestLogger");
|
|
502
|
+
var RequestLogger = _RequestLogger;
|
|
503
|
+
function convertHeaders(headers) {
|
|
504
|
+
if (headers instanceof Headers) {
|
|
505
|
+
return Array.from(headers.entries());
|
|
506
|
+
}
|
|
507
|
+
return Object.entries(headers).flatMap(([key, value]) => {
|
|
508
|
+
if (value === void 0) {
|
|
509
|
+
return [];
|
|
510
|
+
}
|
|
511
|
+
if (Array.isArray(value)) {
|
|
512
|
+
return value.map((v) => [
|
|
513
|
+
key,
|
|
514
|
+
v
|
|
515
|
+
]);
|
|
516
|
+
}
|
|
517
|
+
return [
|
|
518
|
+
[
|
|
519
|
+
key,
|
|
520
|
+
value.toString()
|
|
521
|
+
]
|
|
522
|
+
];
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
__name(convertHeaders, "convertHeaders");
|
|
526
|
+
function convertBody(body, contentType) {
|
|
527
|
+
if (!body || !contentType) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
try {
|
|
531
|
+
if (contentType.startsWith("application/json")) {
|
|
532
|
+
if (isValidJsonString(body)) {
|
|
533
|
+
return Buffer3.from(body);
|
|
534
|
+
} else {
|
|
535
|
+
return Buffer3.from(JSON.stringify(body));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (contentType.startsWith("text/") && typeof body === "string") {
|
|
539
|
+
return Buffer3.from(body);
|
|
540
|
+
}
|
|
541
|
+
} catch (error) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
__name(convertBody, "convertBody");
|
|
546
|
+
function isValidJsonString(body) {
|
|
547
|
+
if (typeof body !== "string") {
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
try {
|
|
551
|
+
JSON.parse(body);
|
|
552
|
+
return true;
|
|
553
|
+
} catch {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
__name(isValidJsonString, "isValidJsonString");
|
|
558
|
+
function matchPatterns(value, patterns) {
|
|
559
|
+
return patterns.some((pattern) => {
|
|
560
|
+
return pattern.test(value);
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
__name(matchPatterns, "matchPatterns");
|
|
564
|
+
function skipEmptyValues(data) {
|
|
565
|
+
return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
|
|
566
|
+
if (v == null || Number.isNaN(v)) return false;
|
|
567
|
+
if (Array.isArray(v) || Buffer3.isBuffer(v) || typeof v === "string") {
|
|
568
|
+
return v.length > 0;
|
|
569
|
+
}
|
|
570
|
+
return true;
|
|
571
|
+
}));
|
|
572
|
+
}
|
|
573
|
+
__name(skipEmptyValues, "skipEmptyValues");
|
|
574
|
+
function checkWritableFs() {
|
|
575
|
+
try {
|
|
576
|
+
const testPath = join2(tmpdir2(), `apitally-${randomUUID2()}`);
|
|
577
|
+
writeFileSync(testPath, "test");
|
|
578
|
+
unlinkSync2(testPath);
|
|
579
|
+
return true;
|
|
580
|
+
} catch (error) {
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
__name(checkWritableFs, "checkWritableFs");
|
|
585
|
+
|
|
189
586
|
// src/common/serverErrorCounter.ts
|
|
190
587
|
import { createHash } from "crypto";
|
|
191
588
|
var MAX_MSG_LENGTH = 2048;
|
|
@@ -358,11 +755,12 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
358
755
|
startupData;
|
|
359
756
|
startupDataSent = false;
|
|
360
757
|
requestCounter;
|
|
758
|
+
requestLogger;
|
|
361
759
|
validationErrorCounter;
|
|
362
760
|
serverErrorCounter;
|
|
363
761
|
consumerRegistry;
|
|
364
762
|
logger;
|
|
365
|
-
constructor({ clientId, env = "dev", logger }) {
|
|
763
|
+
constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
|
|
366
764
|
if (_ApitallyClient.instance) {
|
|
367
765
|
throw new Error("Apitally client is already initialized");
|
|
368
766
|
}
|
|
@@ -375,9 +773,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
375
773
|
_ApitallyClient.instance = this;
|
|
376
774
|
this.clientId = clientId;
|
|
377
775
|
this.env = env;
|
|
378
|
-
this.instanceUuid =
|
|
776
|
+
this.instanceUuid = randomUUID3();
|
|
379
777
|
this.syncDataQueue = [];
|
|
380
778
|
this.requestCounter = new RequestCounter();
|
|
779
|
+
this.requestLogger = new RequestLogger(requestLoggingConfig);
|
|
381
780
|
this.validationErrorCounter = new ValidationErrorCounter();
|
|
382
781
|
this.serverErrorCounter = new ServerErrorCounter();
|
|
383
782
|
this.consumerRegistry = new ConsumerRegistry();
|
|
@@ -399,6 +798,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
399
798
|
async handleShutdown() {
|
|
400
799
|
this.stopSync();
|
|
401
800
|
await this.sendSyncData();
|
|
801
|
+
await this.sendLogData();
|
|
802
|
+
await this.requestLogger.close();
|
|
402
803
|
_ApitallyClient.instance = void 0;
|
|
403
804
|
}
|
|
404
805
|
getHubUrlPrefix() {
|
|
@@ -445,7 +846,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
445
846
|
async sync() {
|
|
446
847
|
try {
|
|
447
848
|
const promises = [
|
|
448
|
-
this.sendSyncData()
|
|
849
|
+
this.sendSyncData(),
|
|
850
|
+
this.sendLogData()
|
|
449
851
|
];
|
|
450
852
|
if (!this.startupDataSent) {
|
|
451
853
|
promises.push(this.sendStartupData());
|
|
@@ -473,7 +875,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
473
875
|
this.logger.debug("Sending startup data to Apitally Hub");
|
|
474
876
|
const payload = {
|
|
475
877
|
instance_uuid: this.instanceUuid,
|
|
476
|
-
message_uuid:
|
|
878
|
+
message_uuid: randomUUID3(),
|
|
477
879
|
...this.startupData
|
|
478
880
|
};
|
|
479
881
|
try {
|
|
@@ -495,7 +897,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
495
897
|
const newPayload = {
|
|
496
898
|
time_offset: 0,
|
|
497
899
|
instance_uuid: this.instanceUuid,
|
|
498
|
-
message_uuid:
|
|
900
|
+
message_uuid: randomUUID3(),
|
|
499
901
|
requests: this.requestCounter.getAndResetRequests(),
|
|
500
902
|
validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
|
|
501
903
|
server_errors: this.serverErrorCounter.getAndResetServerErrors(),
|
|
@@ -514,8 +916,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
514
916
|
const timeOffset = Date.now() - time;
|
|
515
917
|
if (timeOffset <= MAX_QUEUE_TIME) {
|
|
516
918
|
if (i > 0) {
|
|
517
|
-
|
|
518
|
-
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
919
|
+
await this.randomDelay();
|
|
519
920
|
}
|
|
520
921
|
payload.time_offset = timeOffset / 1e3;
|
|
521
922
|
await this.sendData("sync", payload);
|
|
@@ -534,6 +935,52 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
534
935
|
}
|
|
535
936
|
}
|
|
536
937
|
}
|
|
938
|
+
async sendLogData() {
|
|
939
|
+
this.logger.debug("Sending request log data to Apitally Hub");
|
|
940
|
+
await this.requestLogger.rotateFile();
|
|
941
|
+
const fetchWithRetry = fetchRetry(fetch, {
|
|
942
|
+
retries: 3,
|
|
943
|
+
retryDelay: 1e3,
|
|
944
|
+
retryOn: [
|
|
945
|
+
408,
|
|
946
|
+
429,
|
|
947
|
+
500,
|
|
948
|
+
502,
|
|
949
|
+
503,
|
|
950
|
+
504
|
|
951
|
+
]
|
|
952
|
+
});
|
|
953
|
+
let i = 0;
|
|
954
|
+
let logFile;
|
|
955
|
+
while (logFile = this.requestLogger.getFile()) {
|
|
956
|
+
if (i > 0) {
|
|
957
|
+
await this.randomDelay();
|
|
958
|
+
}
|
|
959
|
+
try {
|
|
960
|
+
const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
|
|
961
|
+
method: "POST",
|
|
962
|
+
body: await logFile.getContent()
|
|
963
|
+
});
|
|
964
|
+
if (response.status === 402 && response.headers.has("Retry-After")) {
|
|
965
|
+
const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
|
|
966
|
+
if (retryAfter > 0) {
|
|
967
|
+
this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
|
|
968
|
+
this.requestLogger.clear();
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
if (!response.ok) {
|
|
973
|
+
throw new HTTPError(response);
|
|
974
|
+
}
|
|
975
|
+
logFile.delete();
|
|
976
|
+
} catch (error) {
|
|
977
|
+
this.requestLogger.retryFileLater(logFile);
|
|
978
|
+
break;
|
|
979
|
+
}
|
|
980
|
+
i++;
|
|
981
|
+
if (i >= 10) break;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
537
984
|
handleHubError(error) {
|
|
538
985
|
if (error instanceof HTTPError) {
|
|
539
986
|
if (error.response.status === 404) {
|
|
@@ -548,6 +995,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
548
995
|
}
|
|
549
996
|
return false;
|
|
550
997
|
}
|
|
998
|
+
async randomDelay() {
|
|
999
|
+
const delay = 100 + Math.random() * 200;
|
|
1000
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1001
|
+
}
|
|
551
1002
|
};
|
|
552
1003
|
__name(_ApitallyClient, "ApitallyClient");
|
|
553
1004
|
__publicField(_ApitallyClient, "instance");
|
|
@@ -717,18 +1168,18 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
717
1168
|
}
|
|
718
1169
|
try {
|
|
719
1170
|
const startTime = performance.now();
|
|
720
|
-
const
|
|
721
|
-
res.
|
|
1171
|
+
const originalSend = res.send;
|
|
1172
|
+
res.send = (body) => {
|
|
722
1173
|
res.locals.body = body;
|
|
723
|
-
return
|
|
1174
|
+
return originalSend.call(res, body);
|
|
724
1175
|
};
|
|
725
1176
|
res.on("finish", () => {
|
|
726
1177
|
try {
|
|
1178
|
+
const responseTime = performance.now() - startTime;
|
|
727
1179
|
const path = getRoutePath(req);
|
|
1180
|
+
const consumer = getConsumer(req);
|
|
1181
|
+
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
728
1182
|
if (path) {
|
|
729
|
-
const responseTime = performance.now() - startTime;
|
|
730
|
-
const consumer = getConsumer(req);
|
|
731
|
-
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
732
1183
|
client.requestCounter.addRequest({
|
|
733
1184
|
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
734
1185
|
method: req.method,
|
|
@@ -739,24 +1190,31 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
739
1190
|
responseSize: res.get("content-length")
|
|
740
1191
|
});
|
|
741
1192
|
if ((res.statusCode === 400 || res.statusCode === 422) && res.locals.body) {
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
}
|
|
746
|
-
if (validationErrors.length === 0) {
|
|
747
|
-
validationErrors.push(...extractCelebrateErrors(res.locals.body));
|
|
1193
|
+
let jsonBody;
|
|
1194
|
+
try {
|
|
1195
|
+
jsonBody = JSON.parse(res.locals.body);
|
|
1196
|
+
} catch {
|
|
748
1197
|
}
|
|
749
|
-
if (
|
|
750
|
-
validationErrors
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
1198
|
+
if (jsonBody) {
|
|
1199
|
+
const validationErrors = [];
|
|
1200
|
+
if (validationErrors.length === 0) {
|
|
1201
|
+
validationErrors.push(...extractExpressValidatorErrors(jsonBody));
|
|
1202
|
+
}
|
|
1203
|
+
if (validationErrors.length === 0) {
|
|
1204
|
+
validationErrors.push(...extractCelebrateErrors(jsonBody));
|
|
1205
|
+
}
|
|
1206
|
+
if (validationErrors.length === 0) {
|
|
1207
|
+
validationErrors.push(...extractNestValidationErrors(jsonBody));
|
|
1208
|
+
}
|
|
1209
|
+
validationErrors.forEach((error) => {
|
|
1210
|
+
client.validationErrorCounter.addValidationError({
|
|
1211
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1212
|
+
method: req.method,
|
|
1213
|
+
path: req.route.path,
|
|
1214
|
+
...error
|
|
1215
|
+
});
|
|
758
1216
|
});
|
|
759
|
-
}
|
|
1217
|
+
}
|
|
760
1218
|
}
|
|
761
1219
|
if (res.statusCode === 500 && res.locals.serverError) {
|
|
762
1220
|
const serverError = res.locals.serverError;
|
|
@@ -770,6 +1228,24 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
770
1228
|
});
|
|
771
1229
|
}
|
|
772
1230
|
}
|
|
1231
|
+
if (client.requestLogger.enabled) {
|
|
1232
|
+
client.requestLogger.logRequest({
|
|
1233
|
+
timestamp: Date.now() / 1e3,
|
|
1234
|
+
method: req.method,
|
|
1235
|
+
path,
|
|
1236
|
+
url: `${req.protocol}://${req.host}${req.originalUrl}`,
|
|
1237
|
+
headers: convertHeaders(req.headers),
|
|
1238
|
+
size: Number(req.get("content-length")),
|
|
1239
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1240
|
+
body: convertBody(req.body, req.get("content-type"))
|
|
1241
|
+
}, {
|
|
1242
|
+
statusCode: res.statusCode,
|
|
1243
|
+
responseTime: responseTime / 1e3,
|
|
1244
|
+
headers: convertHeaders(res.getHeaders()),
|
|
1245
|
+
size: Number(res.get("content-length")),
|
|
1246
|
+
body: convertBody(res.locals.body, res.get("content-type"))
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
773
1249
|
} catch (error) {
|
|
774
1250
|
client.logger.error("Error while logging request in Apitally middleware.", {
|
|
775
1251
|
request: req,
|