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