apitally 0.9.5 → 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 +536 -41
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.d.cts +10 -2
- package/dist/nestjs/index.d.ts +10 -2
- package/dist/nestjs/index.js +534 -37
- package/dist/nestjs/index.js.map +1 -1
- package/package.json +8 -2
package/dist/nestjs/index.js
CHANGED
|
@@ -9,16 +9,20 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
9
9
|
});
|
|
10
10
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
11
11
|
|
|
12
|
+
// src/nestjs/index.ts
|
|
13
|
+
import { Catch } from "@nestjs/common";
|
|
14
|
+
import { BaseExceptionFilter } from "@nestjs/core";
|
|
15
|
+
|
|
12
16
|
// src/express/middleware.ts
|
|
13
17
|
import { performance } from "perf_hooks";
|
|
14
18
|
|
|
15
19
|
// src/common/client.ts
|
|
16
|
-
import { randomUUID } from "crypto";
|
|
20
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
17
21
|
import fetchRetry from "fetch-retry";
|
|
18
22
|
|
|
19
23
|
// src/common/consumerRegistry.ts
|
|
20
24
|
var consumerFromStringOrObject = /* @__PURE__ */ __name((consumer) => {
|
|
21
|
-
var
|
|
25
|
+
var _a3, _b;
|
|
22
26
|
if (typeof consumer === "string") {
|
|
23
27
|
consumer = String(consumer).trim().substring(0, 128);
|
|
24
28
|
return consumer ? {
|
|
@@ -26,7 +30,7 @@ var consumerFromStringOrObject = /* @__PURE__ */ __name((consumer) => {
|
|
|
26
30
|
} : null;
|
|
27
31
|
} else {
|
|
28
32
|
consumer.identifier = String(consumer.identifier).trim().substring(0, 128);
|
|
29
|
-
consumer.name = (
|
|
33
|
+
consumer.name = (_a3 = consumer.name) == null ? void 0 : _a3.trim().substring(0, 64);
|
|
30
34
|
consumer.group = (_b = consumer.group) == null ? void 0 : _b.trim().substring(0, 64);
|
|
31
35
|
return consumer.identifier ? consumer : null;
|
|
32
36
|
}
|
|
@@ -182,6 +186,393 @@ var _RequestCounter = class _RequestCounter {
|
|
|
182
186
|
__name(_RequestCounter, "RequestCounter");
|
|
183
187
|
var RequestCounter = _RequestCounter;
|
|
184
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?$/,
|
|
278
|
+
/\/_?health[_-]?checks?$/,
|
|
279
|
+
/\/_?heart[_-]?beats?$/,
|
|
280
|
+
/\/ping$/,
|
|
281
|
+
/\/ready$/,
|
|
282
|
+
/\/live$/
|
|
283
|
+
];
|
|
284
|
+
var MASK_QUERY_PARAM_PATTERNS = [
|
|
285
|
+
/auth/i,
|
|
286
|
+
/api-?key/i,
|
|
287
|
+
/secret/i,
|
|
288
|
+
/token/i,
|
|
289
|
+
/password/i,
|
|
290
|
+
/pwd/i
|
|
291
|
+
];
|
|
292
|
+
var MASK_HEADER_PATTERNS = [
|
|
293
|
+
/auth/i,
|
|
294
|
+
/api-?key/i,
|
|
295
|
+
/secret/i,
|
|
296
|
+
/token/i,
|
|
297
|
+
/cookie/i
|
|
298
|
+
];
|
|
299
|
+
var DEFAULT_CONFIG = {
|
|
300
|
+
enabled: false,
|
|
301
|
+
logQueryParams: true,
|
|
302
|
+
logRequestHeaders: false,
|
|
303
|
+
logRequestBody: false,
|
|
304
|
+
logResponseHeaders: true,
|
|
305
|
+
logResponseBody: false,
|
|
306
|
+
maskQueryParams: [],
|
|
307
|
+
maskHeaders: [],
|
|
308
|
+
excludePaths: []
|
|
309
|
+
};
|
|
310
|
+
var _RequestLogger = class _RequestLogger {
|
|
311
|
+
config;
|
|
312
|
+
enabled;
|
|
313
|
+
suspendUntil = null;
|
|
314
|
+
pendingWrites = [];
|
|
315
|
+
currentFile = null;
|
|
316
|
+
files = [];
|
|
317
|
+
maintainIntervalId;
|
|
318
|
+
constructor(config) {
|
|
319
|
+
this.config = {
|
|
320
|
+
...DEFAULT_CONFIG,
|
|
321
|
+
...config
|
|
322
|
+
};
|
|
323
|
+
this.enabled = this.config.enabled && checkWritableFs();
|
|
324
|
+
if (this.enabled) {
|
|
325
|
+
this.maintainIntervalId = setInterval(() => {
|
|
326
|
+
this.maintain();
|
|
327
|
+
}, 1e3);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
shouldExcludePath(urlPath) {
|
|
331
|
+
const patterns = [
|
|
332
|
+
...this.config.excludePaths,
|
|
333
|
+
...EXCLUDE_PATH_PATTERNS
|
|
334
|
+
];
|
|
335
|
+
return matchPatterns(urlPath, patterns);
|
|
336
|
+
}
|
|
337
|
+
shouldMaskQueryParam(name) {
|
|
338
|
+
const patterns = [
|
|
339
|
+
...this.config.maskQueryParams,
|
|
340
|
+
...MASK_QUERY_PARAM_PATTERNS
|
|
341
|
+
];
|
|
342
|
+
return matchPatterns(name, patterns);
|
|
343
|
+
}
|
|
344
|
+
shouldMaskHeader(name) {
|
|
345
|
+
const patterns = [
|
|
346
|
+
...this.config.maskHeaders,
|
|
347
|
+
...MASK_HEADER_PATTERNS
|
|
348
|
+
];
|
|
349
|
+
return matchPatterns(name, patterns);
|
|
350
|
+
}
|
|
351
|
+
hasSupportedContentType(headers) {
|
|
352
|
+
var _a3;
|
|
353
|
+
const contentType = (_a3 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a3[1];
|
|
354
|
+
return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
|
|
355
|
+
}
|
|
356
|
+
maskQueryParams(search) {
|
|
357
|
+
const params = new URLSearchParams(search);
|
|
358
|
+
for (const [key] of params) {
|
|
359
|
+
if (this.shouldMaskQueryParam(key)) {
|
|
360
|
+
params.set(key, MASKED);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return params.toString();
|
|
364
|
+
}
|
|
365
|
+
maskHeaders(headers) {
|
|
366
|
+
return headers.map(([k, v]) => [
|
|
367
|
+
k,
|
|
368
|
+
this.shouldMaskHeader(k) ? MASKED : v
|
|
369
|
+
]);
|
|
370
|
+
}
|
|
371
|
+
logRequest(request, response) {
|
|
372
|
+
var _a3, _b;
|
|
373
|
+
if (!this.enabled || this.suspendUntil !== null) return;
|
|
374
|
+
const url = new URL(request.url);
|
|
375
|
+
const path = request.path ?? url.pathname;
|
|
376
|
+
if (this.shouldExcludePath(path) || (((_b = (_a3 = this.config).excludeCallback) == null ? void 0 : _b.call(_a3, request, response)) ?? false)) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
|
|
380
|
+
request.url = url.toString();
|
|
381
|
+
request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
|
|
382
|
+
response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
|
|
383
|
+
if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
|
|
384
|
+
request.body = void 0;
|
|
385
|
+
} else if (request.body) {
|
|
386
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
387
|
+
request.body = BODY_TOO_LARGE;
|
|
388
|
+
} else if (this.config.maskRequestBodyCallback) {
|
|
389
|
+
try {
|
|
390
|
+
request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
|
|
391
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
392
|
+
request.body = BODY_TOO_LARGE;
|
|
393
|
+
}
|
|
394
|
+
} catch {
|
|
395
|
+
request.body = void 0;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
|
|
400
|
+
response.body = void 0;
|
|
401
|
+
} else if (response.body) {
|
|
402
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
403
|
+
response.body = BODY_TOO_LARGE;
|
|
404
|
+
} else if (this.config.maskResponseBodyCallback) {
|
|
405
|
+
try {
|
|
406
|
+
response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
|
|
407
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
408
|
+
response.body = BODY_TOO_LARGE;
|
|
409
|
+
}
|
|
410
|
+
} catch {
|
|
411
|
+
response.body = void 0;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const item = {
|
|
416
|
+
uuid: randomUUID2(),
|
|
417
|
+
request: skipEmptyValues(request),
|
|
418
|
+
response: skipEmptyValues(response)
|
|
419
|
+
};
|
|
420
|
+
[
|
|
421
|
+
item.request.body,
|
|
422
|
+
item.response.body
|
|
423
|
+
].forEach((body) => {
|
|
424
|
+
if (body) {
|
|
425
|
+
body.toJSON = function() {
|
|
426
|
+
return this.toString("base64");
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
this.pendingWrites.push(JSON.stringify(item));
|
|
431
|
+
if (this.pendingWrites.length > MAX_PENDING_WRITES) {
|
|
432
|
+
this.pendingWrites.shift();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
async writeToFile() {
|
|
436
|
+
if (!this.enabled || this.pendingWrites.length === 0) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (!this.currentFile) {
|
|
440
|
+
this.currentFile = new TempGzipFile();
|
|
441
|
+
}
|
|
442
|
+
while (this.pendingWrites.length > 0) {
|
|
443
|
+
const item = this.pendingWrites.shift();
|
|
444
|
+
if (item) {
|
|
445
|
+
await this.currentFile.writeLine(Buffer3.from(item));
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
getFile() {
|
|
450
|
+
return this.files.shift();
|
|
451
|
+
}
|
|
452
|
+
retryFileLater(file) {
|
|
453
|
+
this.files.unshift(file);
|
|
454
|
+
}
|
|
455
|
+
async rotateFile() {
|
|
456
|
+
if (this.currentFile) {
|
|
457
|
+
await this.currentFile.close();
|
|
458
|
+
this.files.push(this.currentFile);
|
|
459
|
+
this.currentFile = null;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
async maintain() {
|
|
463
|
+
await this.writeToFile();
|
|
464
|
+
if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
|
|
465
|
+
await this.rotateFile();
|
|
466
|
+
}
|
|
467
|
+
while (this.files.length > MAX_FILES) {
|
|
468
|
+
const file = this.files.shift();
|
|
469
|
+
file == null ? void 0 : file.delete();
|
|
470
|
+
}
|
|
471
|
+
if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
|
|
472
|
+
this.suspendUntil = null;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
async clear() {
|
|
476
|
+
this.pendingWrites = [];
|
|
477
|
+
await this.rotateFile();
|
|
478
|
+
this.files.forEach((file) => {
|
|
479
|
+
file.delete();
|
|
480
|
+
});
|
|
481
|
+
this.files = [];
|
|
482
|
+
}
|
|
483
|
+
async close() {
|
|
484
|
+
this.enabled = false;
|
|
485
|
+
await this.clear();
|
|
486
|
+
if (this.maintainIntervalId) {
|
|
487
|
+
clearInterval(this.maintainIntervalId);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
__name(_RequestLogger, "RequestLogger");
|
|
492
|
+
var RequestLogger = _RequestLogger;
|
|
493
|
+
function convertHeaders(headers) {
|
|
494
|
+
if (headers instanceof Headers) {
|
|
495
|
+
return Array.from(headers.entries());
|
|
496
|
+
}
|
|
497
|
+
return Object.entries(headers).flatMap(([key, value]) => {
|
|
498
|
+
if (value === void 0) {
|
|
499
|
+
return [];
|
|
500
|
+
}
|
|
501
|
+
if (Array.isArray(value)) {
|
|
502
|
+
return value.map((v) => [
|
|
503
|
+
key,
|
|
504
|
+
v
|
|
505
|
+
]);
|
|
506
|
+
}
|
|
507
|
+
return [
|
|
508
|
+
[
|
|
509
|
+
key,
|
|
510
|
+
value.toString()
|
|
511
|
+
]
|
|
512
|
+
];
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
__name(convertHeaders, "convertHeaders");
|
|
516
|
+
function convertBody(body, contentType) {
|
|
517
|
+
if (!body || !contentType) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
try {
|
|
521
|
+
if (contentType.startsWith("application/json")) {
|
|
522
|
+
if (isValidJsonString(body)) {
|
|
523
|
+
return Buffer3.from(body);
|
|
524
|
+
} else {
|
|
525
|
+
return Buffer3.from(JSON.stringify(body));
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (contentType.startsWith("text/") && typeof body === "string") {
|
|
529
|
+
return Buffer3.from(body);
|
|
530
|
+
}
|
|
531
|
+
} catch (error) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
__name(convertBody, "convertBody");
|
|
536
|
+
function isValidJsonString(body) {
|
|
537
|
+
if (typeof body !== "string") {
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
try {
|
|
541
|
+
JSON.parse(body);
|
|
542
|
+
return true;
|
|
543
|
+
} catch {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
__name(isValidJsonString, "isValidJsonString");
|
|
548
|
+
function matchPatterns(value, patterns) {
|
|
549
|
+
return patterns.some((pattern) => {
|
|
550
|
+
return pattern.test(value);
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
__name(matchPatterns, "matchPatterns");
|
|
554
|
+
function skipEmptyValues(data) {
|
|
555
|
+
return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
|
|
556
|
+
if (v == null || Number.isNaN(v)) return false;
|
|
557
|
+
if (Array.isArray(v) || Buffer3.isBuffer(v) || typeof v === "string") {
|
|
558
|
+
return v.length > 0;
|
|
559
|
+
}
|
|
560
|
+
return true;
|
|
561
|
+
}));
|
|
562
|
+
}
|
|
563
|
+
__name(skipEmptyValues, "skipEmptyValues");
|
|
564
|
+
function checkWritableFs() {
|
|
565
|
+
try {
|
|
566
|
+
const testPath = join2(tmpdir2(), `apitally-${randomUUID2()}`);
|
|
567
|
+
writeFileSync(testPath, "test");
|
|
568
|
+
unlinkSync2(testPath);
|
|
569
|
+
return true;
|
|
570
|
+
} catch (error) {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
__name(checkWritableFs, "checkWritableFs");
|
|
575
|
+
|
|
185
576
|
// src/common/serverErrorCounter.ts
|
|
186
577
|
import { createHash } from "crypto";
|
|
187
578
|
var MAX_MSG_LENGTH = 2048;
|
|
@@ -354,11 +745,12 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
354
745
|
startupData;
|
|
355
746
|
startupDataSent = false;
|
|
356
747
|
requestCounter;
|
|
748
|
+
requestLogger;
|
|
357
749
|
validationErrorCounter;
|
|
358
750
|
serverErrorCounter;
|
|
359
751
|
consumerRegistry;
|
|
360
752
|
logger;
|
|
361
|
-
constructor({ clientId, env = "dev", logger }) {
|
|
753
|
+
constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
|
|
362
754
|
if (_ApitallyClient.instance) {
|
|
363
755
|
throw new Error("Apitally client is already initialized");
|
|
364
756
|
}
|
|
@@ -371,9 +763,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
371
763
|
_ApitallyClient.instance = this;
|
|
372
764
|
this.clientId = clientId;
|
|
373
765
|
this.env = env;
|
|
374
|
-
this.instanceUuid =
|
|
766
|
+
this.instanceUuid = randomUUID3();
|
|
375
767
|
this.syncDataQueue = [];
|
|
376
768
|
this.requestCounter = new RequestCounter();
|
|
769
|
+
this.requestLogger = new RequestLogger(requestLoggingConfig);
|
|
377
770
|
this.validationErrorCounter = new ValidationErrorCounter();
|
|
378
771
|
this.serverErrorCounter = new ServerErrorCounter();
|
|
379
772
|
this.consumerRegistry = new ConsumerRegistry();
|
|
@@ -395,6 +788,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
395
788
|
async handleShutdown() {
|
|
396
789
|
this.stopSync();
|
|
397
790
|
await this.sendSyncData();
|
|
791
|
+
await this.sendLogData();
|
|
792
|
+
await this.requestLogger.close();
|
|
398
793
|
_ApitallyClient.instance = void 0;
|
|
399
794
|
}
|
|
400
795
|
getHubUrlPrefix() {
|
|
@@ -441,7 +836,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
441
836
|
async sync() {
|
|
442
837
|
try {
|
|
443
838
|
const promises = [
|
|
444
|
-
this.sendSyncData()
|
|
839
|
+
this.sendSyncData(),
|
|
840
|
+
this.sendLogData()
|
|
445
841
|
];
|
|
446
842
|
if (!this.startupDataSent) {
|
|
447
843
|
promises.push(this.sendStartupData());
|
|
@@ -469,7 +865,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
469
865
|
this.logger.debug("Sending startup data to Apitally Hub");
|
|
470
866
|
const payload = {
|
|
471
867
|
instance_uuid: this.instanceUuid,
|
|
472
|
-
message_uuid:
|
|
868
|
+
message_uuid: randomUUID3(),
|
|
473
869
|
...this.startupData
|
|
474
870
|
};
|
|
475
871
|
try {
|
|
@@ -491,7 +887,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
491
887
|
const newPayload = {
|
|
492
888
|
time_offset: 0,
|
|
493
889
|
instance_uuid: this.instanceUuid,
|
|
494
|
-
message_uuid:
|
|
890
|
+
message_uuid: randomUUID3(),
|
|
495
891
|
requests: this.requestCounter.getAndResetRequests(),
|
|
496
892
|
validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
|
|
497
893
|
server_errors: this.serverErrorCounter.getAndResetServerErrors(),
|
|
@@ -510,8 +906,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
510
906
|
const timeOffset = Date.now() - time;
|
|
511
907
|
if (timeOffset <= MAX_QUEUE_TIME) {
|
|
512
908
|
if (i > 0) {
|
|
513
|
-
|
|
514
|
-
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
909
|
+
await this.randomDelay();
|
|
515
910
|
}
|
|
516
911
|
payload.time_offset = timeOffset / 1e3;
|
|
517
912
|
await this.sendData("sync", payload);
|
|
@@ -530,6 +925,52 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
530
925
|
}
|
|
531
926
|
}
|
|
532
927
|
}
|
|
928
|
+
async sendLogData() {
|
|
929
|
+
this.logger.debug("Sending request log data to Apitally Hub");
|
|
930
|
+
await this.requestLogger.rotateFile();
|
|
931
|
+
const fetchWithRetry = fetchRetry(fetch, {
|
|
932
|
+
retries: 3,
|
|
933
|
+
retryDelay: 1e3,
|
|
934
|
+
retryOn: [
|
|
935
|
+
408,
|
|
936
|
+
429,
|
|
937
|
+
500,
|
|
938
|
+
502,
|
|
939
|
+
503,
|
|
940
|
+
504
|
|
941
|
+
]
|
|
942
|
+
});
|
|
943
|
+
let i = 0;
|
|
944
|
+
let logFile;
|
|
945
|
+
while (logFile = this.requestLogger.getFile()) {
|
|
946
|
+
if (i > 0) {
|
|
947
|
+
await this.randomDelay();
|
|
948
|
+
}
|
|
949
|
+
try {
|
|
950
|
+
const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
|
|
951
|
+
method: "POST",
|
|
952
|
+
body: await logFile.getContent()
|
|
953
|
+
});
|
|
954
|
+
if (response.status === 402 && response.headers.has("Retry-After")) {
|
|
955
|
+
const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
|
|
956
|
+
if (retryAfter > 0) {
|
|
957
|
+
this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
|
|
958
|
+
this.requestLogger.clear();
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (!response.ok) {
|
|
963
|
+
throw new HTTPError(response);
|
|
964
|
+
}
|
|
965
|
+
logFile.delete();
|
|
966
|
+
} catch (error) {
|
|
967
|
+
this.requestLogger.retryFileLater(logFile);
|
|
968
|
+
break;
|
|
969
|
+
}
|
|
970
|
+
i++;
|
|
971
|
+
if (i >= 10) break;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
533
974
|
handleHubError(error) {
|
|
534
975
|
if (error instanceof HTTPError) {
|
|
535
976
|
if (error.response.status === 404) {
|
|
@@ -544,6 +985,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
544
985
|
}
|
|
545
986
|
return false;
|
|
546
987
|
}
|
|
988
|
+
async randomDelay() {
|
|
989
|
+
const delay = 100 + Math.random() * 200;
|
|
990
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
991
|
+
}
|
|
547
992
|
};
|
|
548
993
|
__name(_ApitallyClient, "ApitallyClient");
|
|
549
994
|
__publicField(_ApitallyClient, "instance");
|
|
@@ -713,18 +1158,18 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
713
1158
|
}
|
|
714
1159
|
try {
|
|
715
1160
|
const startTime = performance.now();
|
|
716
|
-
const
|
|
717
|
-
res.
|
|
1161
|
+
const originalSend = res.send;
|
|
1162
|
+
res.send = (body) => {
|
|
718
1163
|
res.locals.body = body;
|
|
719
|
-
return
|
|
1164
|
+
return originalSend.call(res, body);
|
|
720
1165
|
};
|
|
721
1166
|
res.on("finish", () => {
|
|
722
1167
|
try {
|
|
1168
|
+
const responseTime = performance.now() - startTime;
|
|
723
1169
|
const path = getRoutePath(req);
|
|
1170
|
+
const consumer = getConsumer(req);
|
|
1171
|
+
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
724
1172
|
if (path) {
|
|
725
|
-
const responseTime = performance.now() - startTime;
|
|
726
|
-
const consumer = getConsumer(req);
|
|
727
|
-
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
728
1173
|
client.requestCounter.addRequest({
|
|
729
1174
|
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
730
1175
|
method: req.method,
|
|
@@ -735,24 +1180,31 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
735
1180
|
responseSize: res.get("content-length")
|
|
736
1181
|
});
|
|
737
1182
|
if ((res.statusCode === 400 || res.statusCode === 422) && res.locals.body) {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
}
|
|
742
|
-
if (validationErrors.length === 0) {
|
|
743
|
-
validationErrors.push(...extractCelebrateErrors(res.locals.body));
|
|
1183
|
+
let jsonBody;
|
|
1184
|
+
try {
|
|
1185
|
+
jsonBody = JSON.parse(res.locals.body);
|
|
1186
|
+
} catch {
|
|
744
1187
|
}
|
|
745
|
-
if (
|
|
746
|
-
validationErrors
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
1188
|
+
if (jsonBody) {
|
|
1189
|
+
const validationErrors = [];
|
|
1190
|
+
if (validationErrors.length === 0) {
|
|
1191
|
+
validationErrors.push(...extractExpressValidatorErrors(jsonBody));
|
|
1192
|
+
}
|
|
1193
|
+
if (validationErrors.length === 0) {
|
|
1194
|
+
validationErrors.push(...extractCelebrateErrors(jsonBody));
|
|
1195
|
+
}
|
|
1196
|
+
if (validationErrors.length === 0) {
|
|
1197
|
+
validationErrors.push(...extractNestValidationErrors(jsonBody));
|
|
1198
|
+
}
|
|
1199
|
+
validationErrors.forEach((error) => {
|
|
1200
|
+
client.validationErrorCounter.addValidationError({
|
|
1201
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1202
|
+
method: req.method,
|
|
1203
|
+
path: req.route.path,
|
|
1204
|
+
...error
|
|
1205
|
+
});
|
|
754
1206
|
});
|
|
755
|
-
}
|
|
1207
|
+
}
|
|
756
1208
|
}
|
|
757
1209
|
if (res.statusCode === 500 && res.locals.serverError) {
|
|
758
1210
|
const serverError = res.locals.serverError;
|
|
@@ -766,6 +1218,24 @@ var getMiddleware = /* @__PURE__ */ __name((app, client) => {
|
|
|
766
1218
|
});
|
|
767
1219
|
}
|
|
768
1220
|
}
|
|
1221
|
+
if (client.requestLogger.enabled) {
|
|
1222
|
+
client.requestLogger.logRequest({
|
|
1223
|
+
timestamp: Date.now() / 1e3,
|
|
1224
|
+
method: req.method,
|
|
1225
|
+
path,
|
|
1226
|
+
url: `${req.protocol}://${req.host}${req.originalUrl}`,
|
|
1227
|
+
headers: convertHeaders(req.headers),
|
|
1228
|
+
size: Number(req.get("content-length")),
|
|
1229
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1230
|
+
body: convertBody(req.body, req.get("content-type"))
|
|
1231
|
+
}, {
|
|
1232
|
+
statusCode: res.statusCode,
|
|
1233
|
+
responseTime: responseTime / 1e3,
|
|
1234
|
+
headers: convertHeaders(res.getHeaders()),
|
|
1235
|
+
size: Number(res.get("content-length")),
|
|
1236
|
+
body: convertBody(res.locals.body, res.get("content-type"))
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
769
1239
|
} catch (error) {
|
|
770
1240
|
client.logger.error("Error while logging request in Apitally middleware.", {
|
|
771
1241
|
request: req,
|
|
@@ -796,12 +1266,12 @@ var getRoutePath = /* @__PURE__ */ __name((req) => {
|
|
|
796
1266
|
return req.route.path;
|
|
797
1267
|
}, "getRoutePath");
|
|
798
1268
|
var getRouterPath = /* @__PURE__ */ __name((stack, baseUrl) => {
|
|
799
|
-
var
|
|
1269
|
+
var _a3;
|
|
800
1270
|
const routerPaths = [];
|
|
801
1271
|
while (stack && stack.length > 0) {
|
|
802
1272
|
const routerLayer = stack.find((layer) => {
|
|
803
|
-
var
|
|
804
|
-
return layer.name === "router" && layer.path && ((
|
|
1273
|
+
var _a4;
|
|
1274
|
+
return layer.name === "router" && layer.path && ((_a4 = layer.regexp) == null ? void 0 : _a4.test(baseUrl));
|
|
805
1275
|
});
|
|
806
1276
|
if (routerLayer) {
|
|
807
1277
|
if (routerLayer.keys.length > 0) {
|
|
@@ -810,7 +1280,7 @@ var getRouterPath = /* @__PURE__ */ __name((stack, baseUrl) => {
|
|
|
810
1280
|
} else {
|
|
811
1281
|
routerPaths.push(routerLayer.path);
|
|
812
1282
|
}
|
|
813
|
-
stack = (
|
|
1283
|
+
stack = (_a3 = routerLayer.handle) == null ? void 0 : _a3.stack;
|
|
814
1284
|
baseUrl = baseUrl.slice(routerLayer.path.length);
|
|
815
1285
|
} else {
|
|
816
1286
|
break;
|
|
@@ -928,7 +1398,34 @@ var getAppInfo = /* @__PURE__ */ __name((app, basePath, appVersion) => {
|
|
|
928
1398
|
client: "js:express"
|
|
929
1399
|
};
|
|
930
1400
|
}, "getAppInfo");
|
|
1401
|
+
|
|
1402
|
+
// src/nestjs/index.ts
|
|
1403
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
1404
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1405
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1406
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1407
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1408
|
+
}
|
|
1409
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
1410
|
+
var useApitally2 = /* @__PURE__ */ __name((app, config) => {
|
|
1411
|
+
const httpAdapter = app.getHttpAdapter();
|
|
1412
|
+
const expressInstance = httpAdapter.getInstance();
|
|
1413
|
+
useApitally(expressInstance, config);
|
|
1414
|
+
app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));
|
|
1415
|
+
}, "useApitally");
|
|
1416
|
+
var _a2;
|
|
1417
|
+
var AllExceptionsFilter = (_a2 = class extends BaseExceptionFilter {
|
|
1418
|
+
catch(exception, host) {
|
|
1419
|
+
const ctx = host.switchToHttp();
|
|
1420
|
+
const res = ctx.getResponse();
|
|
1421
|
+
res.locals.serverError = exception;
|
|
1422
|
+
super.catch(exception, host);
|
|
1423
|
+
}
|
|
1424
|
+
}, __name(_a2, "AllExceptionsFilter"), _a2);
|
|
1425
|
+
AllExceptionsFilter = _ts_decorate([
|
|
1426
|
+
Catch()
|
|
1427
|
+
], AllExceptionsFilter);
|
|
931
1428
|
export {
|
|
932
|
-
useApitally
|
|
1429
|
+
useApitally2 as useApitally
|
|
933
1430
|
};
|
|
934
1431
|
//# sourceMappingURL=index.js.map
|