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/hono/middleware.js
CHANGED
|
@@ -14,7 +14,7 @@ import { isMiddleware } from "hono/utils/handler";
|
|
|
14
14
|
import { performance } from "perf_hooks";
|
|
15
15
|
|
|
16
16
|
// src/common/client.ts
|
|
17
|
-
import { randomUUID } from "crypto";
|
|
17
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
18
18
|
import fetchRetry from "fetch-retry";
|
|
19
19
|
|
|
20
20
|
// src/common/consumerRegistry.ts
|
|
@@ -183,6 +183,371 @@ var _RequestCounter = class _RequestCounter {
|
|
|
183
183
|
__name(_RequestCounter, "RequestCounter");
|
|
184
184
|
var RequestCounter = _RequestCounter;
|
|
185
185
|
|
|
186
|
+
// src/common/requestLogger.ts
|
|
187
|
+
import { Buffer as Buffer3 } from "buffer";
|
|
188
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
189
|
+
import { unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
190
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
191
|
+
import { join as join2 } from "path";
|
|
192
|
+
|
|
193
|
+
// src/common/tempGzipFile.ts
|
|
194
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
195
|
+
import { randomUUID } from "crypto";
|
|
196
|
+
import { createWriteStream, readFile, unlinkSync } from "fs";
|
|
197
|
+
import { tmpdir } from "os";
|
|
198
|
+
import { join } from "path";
|
|
199
|
+
import { createGzip } from "zlib";
|
|
200
|
+
var _TempGzipFile = class _TempGzipFile {
|
|
201
|
+
uuid;
|
|
202
|
+
filePath;
|
|
203
|
+
gzip;
|
|
204
|
+
writeStream;
|
|
205
|
+
readyPromise;
|
|
206
|
+
constructor() {
|
|
207
|
+
this.uuid = randomUUID();
|
|
208
|
+
this.filePath = join(tmpdir(), `apitally-${this.uuid}.gz`);
|
|
209
|
+
this.writeStream = createWriteStream(this.filePath);
|
|
210
|
+
this.readyPromise = new Promise((resolve, reject) => {
|
|
211
|
+
this.writeStream.once("ready", resolve);
|
|
212
|
+
this.writeStream.once("error", reject);
|
|
213
|
+
});
|
|
214
|
+
this.gzip = createGzip();
|
|
215
|
+
this.gzip.pipe(this.writeStream);
|
|
216
|
+
}
|
|
217
|
+
get size() {
|
|
218
|
+
return this.writeStream.bytesWritten;
|
|
219
|
+
}
|
|
220
|
+
async writeLine(data) {
|
|
221
|
+
await this.readyPromise;
|
|
222
|
+
return new Promise((resolve, reject) => {
|
|
223
|
+
this.gzip.write(Buffer2.concat([
|
|
224
|
+
data,
|
|
225
|
+
Buffer2.from("\n")
|
|
226
|
+
]), (error) => {
|
|
227
|
+
if (error) {
|
|
228
|
+
reject(error);
|
|
229
|
+
} else {
|
|
230
|
+
resolve();
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
async getContent() {
|
|
236
|
+
return new Promise((resolve, reject) => {
|
|
237
|
+
readFile(this.filePath, (error, data) => {
|
|
238
|
+
if (error) {
|
|
239
|
+
reject(error);
|
|
240
|
+
} else {
|
|
241
|
+
resolve(data);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
async close() {
|
|
247
|
+
return new Promise((resolve) => {
|
|
248
|
+
this.gzip.end(() => {
|
|
249
|
+
resolve();
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
async delete() {
|
|
254
|
+
await this.close();
|
|
255
|
+
unlinkSync(this.filePath);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
__name(_TempGzipFile, "TempGzipFile");
|
|
259
|
+
var TempGzipFile = _TempGzipFile;
|
|
260
|
+
|
|
261
|
+
// src/common/requestLogger.ts
|
|
262
|
+
var MAX_BODY_SIZE = 5e4;
|
|
263
|
+
var MAX_FILE_SIZE = 1e6;
|
|
264
|
+
var MAX_FILES = 50;
|
|
265
|
+
var MAX_PENDING_WRITES = 100;
|
|
266
|
+
var BODY_TOO_LARGE = Buffer3.from("<body too large>");
|
|
267
|
+
var BODY_MASKED = Buffer3.from("<masked>");
|
|
268
|
+
var MASKED = "******";
|
|
269
|
+
var ALLOWED_CONTENT_TYPES = [
|
|
270
|
+
"application/json",
|
|
271
|
+
"text/plain"
|
|
272
|
+
];
|
|
273
|
+
var EXCLUDE_PATH_PATTERNS = [
|
|
274
|
+
/\/_?healthz?$/i,
|
|
275
|
+
/\/_?health[_-]?checks?$/i,
|
|
276
|
+
/\/_?heart[_-]?beats?$/i,
|
|
277
|
+
/\/ping$/i,
|
|
278
|
+
/\/ready$/i,
|
|
279
|
+
/\/live$/i
|
|
280
|
+
];
|
|
281
|
+
var EXCLUDE_USER_AGENT_PATTERNS = [
|
|
282
|
+
/health[-_ ]?check/i,
|
|
283
|
+
/microsoft-azure-application-lb/i,
|
|
284
|
+
/googlehc/i,
|
|
285
|
+
/kube-probe/i
|
|
286
|
+
];
|
|
287
|
+
var MASK_QUERY_PARAM_PATTERNS = [
|
|
288
|
+
/auth/i,
|
|
289
|
+
/api-?key/i,
|
|
290
|
+
/secret/i,
|
|
291
|
+
/token/i,
|
|
292
|
+
/password/i,
|
|
293
|
+
/pwd/i
|
|
294
|
+
];
|
|
295
|
+
var MASK_HEADER_PATTERNS = [
|
|
296
|
+
/auth/i,
|
|
297
|
+
/api-?key/i,
|
|
298
|
+
/secret/i,
|
|
299
|
+
/token/i,
|
|
300
|
+
/cookie/i
|
|
301
|
+
];
|
|
302
|
+
var DEFAULT_CONFIG = {
|
|
303
|
+
enabled: false,
|
|
304
|
+
logQueryParams: true,
|
|
305
|
+
logRequestHeaders: false,
|
|
306
|
+
logRequestBody: false,
|
|
307
|
+
logResponseHeaders: true,
|
|
308
|
+
logResponseBody: false,
|
|
309
|
+
maskQueryParams: [],
|
|
310
|
+
maskHeaders: [],
|
|
311
|
+
excludePaths: []
|
|
312
|
+
};
|
|
313
|
+
var _RequestLogger = class _RequestLogger {
|
|
314
|
+
config;
|
|
315
|
+
enabled;
|
|
316
|
+
suspendUntil = null;
|
|
317
|
+
pendingWrites = [];
|
|
318
|
+
currentFile = null;
|
|
319
|
+
files = [];
|
|
320
|
+
maintainIntervalId;
|
|
321
|
+
constructor(config) {
|
|
322
|
+
this.config = {
|
|
323
|
+
...DEFAULT_CONFIG,
|
|
324
|
+
...config
|
|
325
|
+
};
|
|
326
|
+
this.enabled = this.config.enabled && checkWritableFs();
|
|
327
|
+
if (this.enabled) {
|
|
328
|
+
this.maintainIntervalId = setInterval(() => {
|
|
329
|
+
this.maintain();
|
|
330
|
+
}, 1e3);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
shouldExcludePath(urlPath) {
|
|
334
|
+
const patterns = [
|
|
335
|
+
...this.config.excludePaths,
|
|
336
|
+
...EXCLUDE_PATH_PATTERNS
|
|
337
|
+
];
|
|
338
|
+
return matchPatterns(urlPath, patterns);
|
|
339
|
+
}
|
|
340
|
+
shouldExcludeUserAgent(userAgent) {
|
|
341
|
+
return userAgent ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS) : false;
|
|
342
|
+
}
|
|
343
|
+
shouldMaskQueryParam(name) {
|
|
344
|
+
const patterns = [
|
|
345
|
+
...this.config.maskQueryParams,
|
|
346
|
+
...MASK_QUERY_PARAM_PATTERNS
|
|
347
|
+
];
|
|
348
|
+
return matchPatterns(name, patterns);
|
|
349
|
+
}
|
|
350
|
+
shouldMaskHeader(name) {
|
|
351
|
+
const patterns = [
|
|
352
|
+
...this.config.maskHeaders,
|
|
353
|
+
...MASK_HEADER_PATTERNS
|
|
354
|
+
];
|
|
355
|
+
return matchPatterns(name, patterns);
|
|
356
|
+
}
|
|
357
|
+
hasSupportedContentType(headers) {
|
|
358
|
+
var _a2;
|
|
359
|
+
const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
|
|
360
|
+
return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
|
|
361
|
+
}
|
|
362
|
+
maskQueryParams(search) {
|
|
363
|
+
const params = new URLSearchParams(search);
|
|
364
|
+
for (const [key] of params) {
|
|
365
|
+
if (this.shouldMaskQueryParam(key)) {
|
|
366
|
+
params.set(key, MASKED);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return params.toString();
|
|
370
|
+
}
|
|
371
|
+
maskHeaders(headers) {
|
|
372
|
+
return headers.map(([k, v]) => [
|
|
373
|
+
k,
|
|
374
|
+
this.shouldMaskHeader(k) ? MASKED : v
|
|
375
|
+
]);
|
|
376
|
+
}
|
|
377
|
+
logRequest(request, response) {
|
|
378
|
+
var _a2, _b, _c;
|
|
379
|
+
if (!this.enabled || this.suspendUntil !== null) return;
|
|
380
|
+
const url = new URL(request.url);
|
|
381
|
+
const path = request.path ?? url.pathname;
|
|
382
|
+
const userAgent = (_a2 = request.headers.find(([k]) => k.toLowerCase() === "user-agent")) == null ? void 0 : _a2[1];
|
|
383
|
+
if (this.shouldExcludePath(path) || this.shouldExcludeUserAgent(userAgent) || (((_c = (_b = this.config).excludeCallback) == null ? void 0 : _c.call(_b, request, response)) ?? false)) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
|
|
387
|
+
request.url = url.toString();
|
|
388
|
+
request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
|
|
389
|
+
response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
|
|
390
|
+
if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
|
|
391
|
+
request.body = void 0;
|
|
392
|
+
} else if (request.body) {
|
|
393
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
394
|
+
request.body = BODY_TOO_LARGE;
|
|
395
|
+
} else if (this.config.maskRequestBodyCallback) {
|
|
396
|
+
try {
|
|
397
|
+
request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
|
|
398
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
399
|
+
request.body = BODY_TOO_LARGE;
|
|
400
|
+
}
|
|
401
|
+
} catch {
|
|
402
|
+
request.body = void 0;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
|
|
407
|
+
response.body = void 0;
|
|
408
|
+
} else if (response.body) {
|
|
409
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
410
|
+
response.body = BODY_TOO_LARGE;
|
|
411
|
+
} else if (this.config.maskResponseBodyCallback) {
|
|
412
|
+
try {
|
|
413
|
+
response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
|
|
414
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
415
|
+
response.body = BODY_TOO_LARGE;
|
|
416
|
+
}
|
|
417
|
+
} catch {
|
|
418
|
+
response.body = void 0;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const item = {
|
|
423
|
+
uuid: randomUUID2(),
|
|
424
|
+
request: skipEmptyValues(request),
|
|
425
|
+
response: skipEmptyValues(response)
|
|
426
|
+
};
|
|
427
|
+
[
|
|
428
|
+
item.request.body,
|
|
429
|
+
item.response.body
|
|
430
|
+
].forEach((body) => {
|
|
431
|
+
if (body) {
|
|
432
|
+
body.toJSON = function() {
|
|
433
|
+
return this.toString("base64");
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
this.pendingWrites.push(JSON.stringify(item));
|
|
438
|
+
if (this.pendingWrites.length > MAX_PENDING_WRITES) {
|
|
439
|
+
this.pendingWrites.shift();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async writeToFile() {
|
|
443
|
+
if (!this.enabled || this.pendingWrites.length === 0) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (!this.currentFile) {
|
|
447
|
+
this.currentFile = new TempGzipFile();
|
|
448
|
+
}
|
|
449
|
+
while (this.pendingWrites.length > 0) {
|
|
450
|
+
const item = this.pendingWrites.shift();
|
|
451
|
+
if (item) {
|
|
452
|
+
await this.currentFile.writeLine(Buffer3.from(item));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
getFile() {
|
|
457
|
+
return this.files.shift();
|
|
458
|
+
}
|
|
459
|
+
retryFileLater(file) {
|
|
460
|
+
this.files.unshift(file);
|
|
461
|
+
}
|
|
462
|
+
async rotateFile() {
|
|
463
|
+
if (this.currentFile) {
|
|
464
|
+
await this.currentFile.close();
|
|
465
|
+
this.files.push(this.currentFile);
|
|
466
|
+
this.currentFile = null;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
async maintain() {
|
|
470
|
+
await this.writeToFile();
|
|
471
|
+
if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
|
|
472
|
+
await this.rotateFile();
|
|
473
|
+
}
|
|
474
|
+
while (this.files.length > MAX_FILES) {
|
|
475
|
+
const file = this.files.shift();
|
|
476
|
+
file == null ? void 0 : file.delete();
|
|
477
|
+
}
|
|
478
|
+
if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
|
|
479
|
+
this.suspendUntil = null;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
async clear() {
|
|
483
|
+
this.pendingWrites = [];
|
|
484
|
+
await this.rotateFile();
|
|
485
|
+
this.files.forEach((file) => {
|
|
486
|
+
file.delete();
|
|
487
|
+
});
|
|
488
|
+
this.files = [];
|
|
489
|
+
}
|
|
490
|
+
async close() {
|
|
491
|
+
this.enabled = false;
|
|
492
|
+
await this.clear();
|
|
493
|
+
if (this.maintainIntervalId) {
|
|
494
|
+
clearInterval(this.maintainIntervalId);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
__name(_RequestLogger, "RequestLogger");
|
|
499
|
+
var RequestLogger = _RequestLogger;
|
|
500
|
+
function convertHeaders(headers) {
|
|
501
|
+
if (headers instanceof Headers) {
|
|
502
|
+
return Array.from(headers.entries());
|
|
503
|
+
}
|
|
504
|
+
return Object.entries(headers).flatMap(([key, value]) => {
|
|
505
|
+
if (value === void 0) {
|
|
506
|
+
return [];
|
|
507
|
+
}
|
|
508
|
+
if (Array.isArray(value)) {
|
|
509
|
+
return value.map((v) => [
|
|
510
|
+
key,
|
|
511
|
+
v
|
|
512
|
+
]);
|
|
513
|
+
}
|
|
514
|
+
return [
|
|
515
|
+
[
|
|
516
|
+
key,
|
|
517
|
+
value.toString()
|
|
518
|
+
]
|
|
519
|
+
];
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
__name(convertHeaders, "convertHeaders");
|
|
523
|
+
function matchPatterns(value, patterns) {
|
|
524
|
+
return patterns.some((pattern) => {
|
|
525
|
+
return pattern.test(value);
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
__name(matchPatterns, "matchPatterns");
|
|
529
|
+
function skipEmptyValues(data) {
|
|
530
|
+
return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
|
|
531
|
+
if (v == null || Number.isNaN(v)) return false;
|
|
532
|
+
if (Array.isArray(v) || Buffer3.isBuffer(v) || typeof v === "string") {
|
|
533
|
+
return v.length > 0;
|
|
534
|
+
}
|
|
535
|
+
return true;
|
|
536
|
+
}));
|
|
537
|
+
}
|
|
538
|
+
__name(skipEmptyValues, "skipEmptyValues");
|
|
539
|
+
function checkWritableFs() {
|
|
540
|
+
try {
|
|
541
|
+
const testPath = join2(tmpdir2(), `apitally-${randomUUID2()}`);
|
|
542
|
+
writeFileSync(testPath, "test");
|
|
543
|
+
unlinkSync2(testPath);
|
|
544
|
+
return true;
|
|
545
|
+
} catch (error) {
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
__name(checkWritableFs, "checkWritableFs");
|
|
550
|
+
|
|
186
551
|
// src/common/serverErrorCounter.ts
|
|
187
552
|
import { createHash } from "crypto";
|
|
188
553
|
var MAX_MSG_LENGTH = 2048;
|
|
@@ -355,11 +720,12 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
355
720
|
startupData;
|
|
356
721
|
startupDataSent = false;
|
|
357
722
|
requestCounter;
|
|
723
|
+
requestLogger;
|
|
358
724
|
validationErrorCounter;
|
|
359
725
|
serverErrorCounter;
|
|
360
726
|
consumerRegistry;
|
|
361
727
|
logger;
|
|
362
|
-
constructor({ clientId, env = "dev", logger }) {
|
|
728
|
+
constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
|
|
363
729
|
if (_ApitallyClient.instance) {
|
|
364
730
|
throw new Error("Apitally client is already initialized");
|
|
365
731
|
}
|
|
@@ -372,9 +738,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
372
738
|
_ApitallyClient.instance = this;
|
|
373
739
|
this.clientId = clientId;
|
|
374
740
|
this.env = env;
|
|
375
|
-
this.instanceUuid =
|
|
741
|
+
this.instanceUuid = randomUUID3();
|
|
376
742
|
this.syncDataQueue = [];
|
|
377
743
|
this.requestCounter = new RequestCounter();
|
|
744
|
+
this.requestLogger = new RequestLogger(requestLoggingConfig);
|
|
378
745
|
this.validationErrorCounter = new ValidationErrorCounter();
|
|
379
746
|
this.serverErrorCounter = new ServerErrorCounter();
|
|
380
747
|
this.consumerRegistry = new ConsumerRegistry();
|
|
@@ -396,6 +763,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
396
763
|
async handleShutdown() {
|
|
397
764
|
this.stopSync();
|
|
398
765
|
await this.sendSyncData();
|
|
766
|
+
await this.sendLogData();
|
|
767
|
+
await this.requestLogger.close();
|
|
399
768
|
_ApitallyClient.instance = void 0;
|
|
400
769
|
}
|
|
401
770
|
getHubUrlPrefix() {
|
|
@@ -442,7 +811,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
442
811
|
async sync() {
|
|
443
812
|
try {
|
|
444
813
|
const promises = [
|
|
445
|
-
this.sendSyncData()
|
|
814
|
+
this.sendSyncData(),
|
|
815
|
+
this.sendLogData()
|
|
446
816
|
];
|
|
447
817
|
if (!this.startupDataSent) {
|
|
448
818
|
promises.push(this.sendStartupData());
|
|
@@ -470,7 +840,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
470
840
|
this.logger.debug("Sending startup data to Apitally Hub");
|
|
471
841
|
const payload = {
|
|
472
842
|
instance_uuid: this.instanceUuid,
|
|
473
|
-
message_uuid:
|
|
843
|
+
message_uuid: randomUUID3(),
|
|
474
844
|
...this.startupData
|
|
475
845
|
};
|
|
476
846
|
try {
|
|
@@ -492,7 +862,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
492
862
|
const newPayload = {
|
|
493
863
|
time_offset: 0,
|
|
494
864
|
instance_uuid: this.instanceUuid,
|
|
495
|
-
message_uuid:
|
|
865
|
+
message_uuid: randomUUID3(),
|
|
496
866
|
requests: this.requestCounter.getAndResetRequests(),
|
|
497
867
|
validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
|
|
498
868
|
server_errors: this.serverErrorCounter.getAndResetServerErrors(),
|
|
@@ -511,8 +881,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
511
881
|
const timeOffset = Date.now() - time;
|
|
512
882
|
if (timeOffset <= MAX_QUEUE_TIME) {
|
|
513
883
|
if (i > 0) {
|
|
514
|
-
|
|
515
|
-
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
884
|
+
await this.randomDelay();
|
|
516
885
|
}
|
|
517
886
|
payload.time_offset = timeOffset / 1e3;
|
|
518
887
|
await this.sendData("sync", payload);
|
|
@@ -531,6 +900,52 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
531
900
|
}
|
|
532
901
|
}
|
|
533
902
|
}
|
|
903
|
+
async sendLogData() {
|
|
904
|
+
this.logger.debug("Sending request log data to Apitally Hub");
|
|
905
|
+
await this.requestLogger.rotateFile();
|
|
906
|
+
const fetchWithRetry = fetchRetry(fetch, {
|
|
907
|
+
retries: 3,
|
|
908
|
+
retryDelay: 1e3,
|
|
909
|
+
retryOn: [
|
|
910
|
+
408,
|
|
911
|
+
429,
|
|
912
|
+
500,
|
|
913
|
+
502,
|
|
914
|
+
503,
|
|
915
|
+
504
|
|
916
|
+
]
|
|
917
|
+
});
|
|
918
|
+
let i = 0;
|
|
919
|
+
let logFile;
|
|
920
|
+
while (logFile = this.requestLogger.getFile()) {
|
|
921
|
+
if (i > 0) {
|
|
922
|
+
await this.randomDelay();
|
|
923
|
+
}
|
|
924
|
+
try {
|
|
925
|
+
const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
|
|
926
|
+
method: "POST",
|
|
927
|
+
body: await logFile.getContent()
|
|
928
|
+
});
|
|
929
|
+
if (response.status === 402 && response.headers.has("Retry-After")) {
|
|
930
|
+
const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
|
|
931
|
+
if (retryAfter > 0) {
|
|
932
|
+
this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
|
|
933
|
+
this.requestLogger.clear();
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
if (!response.ok) {
|
|
938
|
+
throw new HTTPError(response);
|
|
939
|
+
}
|
|
940
|
+
logFile.delete();
|
|
941
|
+
} catch (error) {
|
|
942
|
+
this.requestLogger.retryFileLater(logFile);
|
|
943
|
+
break;
|
|
944
|
+
}
|
|
945
|
+
i++;
|
|
946
|
+
if (i >= 10) break;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
534
949
|
handleHubError(error) {
|
|
535
950
|
if (error instanceof HTTPError) {
|
|
536
951
|
if (error.response.status === 404) {
|
|
@@ -545,6 +960,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
545
960
|
}
|
|
546
961
|
return false;
|
|
547
962
|
}
|
|
963
|
+
async randomDelay() {
|
|
964
|
+
const delay = 100 + Math.random() * 200;
|
|
965
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
966
|
+
}
|
|
548
967
|
};
|
|
549
968
|
__name(_ApitallyClient, "ApitallyClient");
|
|
550
969
|
__publicField(_ApitallyClient, "instance");
|
|
@@ -577,13 +996,13 @@ var useApitally = /* @__PURE__ */ __name((app, config) => {
|
|
|
577
996
|
}, 1e3);
|
|
578
997
|
}, "useApitally");
|
|
579
998
|
var getMiddleware = /* @__PURE__ */ __name((client) => {
|
|
580
|
-
const zodInstalled = getPackageVersion("zod") !== null;
|
|
581
999
|
return async (c, next) => {
|
|
582
1000
|
const startTime = performance.now();
|
|
583
1001
|
await next();
|
|
584
1002
|
let response;
|
|
585
1003
|
const responseTime = performance.now() - startTime;
|
|
586
1004
|
const [responseSize, newResponse] = await measureResponseSize(c.res);
|
|
1005
|
+
const requestSize = c.req.header("Content-Length");
|
|
587
1006
|
const consumer = getConsumer(c);
|
|
588
1007
|
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
589
1008
|
client.requestCounter.addRequest({
|
|
@@ -592,11 +1011,11 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
|
|
|
592
1011
|
path: c.req.routePath,
|
|
593
1012
|
statusCode: c.res.status,
|
|
594
1013
|
responseTime,
|
|
595
|
-
requestSize
|
|
1014
|
+
requestSize,
|
|
596
1015
|
responseSize
|
|
597
1016
|
});
|
|
598
1017
|
response = newResponse;
|
|
599
|
-
if (c.res.status === 400
|
|
1018
|
+
if (c.res.status === 400) {
|
|
600
1019
|
const [responseJson, newResponse2] = await getResponseJson(response);
|
|
601
1020
|
const validationErrors = extractZodErrors(responseJson);
|
|
602
1021
|
validationErrors.forEach((error) => {
|
|
@@ -619,6 +1038,34 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
|
|
|
619
1038
|
traceback: c.error.stack || ""
|
|
620
1039
|
});
|
|
621
1040
|
}
|
|
1041
|
+
if (client.requestLogger.enabled) {
|
|
1042
|
+
let requestBody;
|
|
1043
|
+
let responseBody;
|
|
1044
|
+
let newResponse2 = response;
|
|
1045
|
+
if (client.requestLogger.config.logRequestBody) {
|
|
1046
|
+
requestBody = Buffer.from(await c.req.arrayBuffer());
|
|
1047
|
+
}
|
|
1048
|
+
if (client.requestLogger.config.logResponseBody) {
|
|
1049
|
+
[responseBody, newResponse2] = await getResponseBody(response);
|
|
1050
|
+
response = newResponse2;
|
|
1051
|
+
}
|
|
1052
|
+
client.requestLogger.logRequest({
|
|
1053
|
+
timestamp: Date.now() / 1e3,
|
|
1054
|
+
method: c.req.method,
|
|
1055
|
+
path: c.req.routePath,
|
|
1056
|
+
url: c.req.url,
|
|
1057
|
+
headers: convertHeaders(c.req.header()),
|
|
1058
|
+
size: Number(requestSize),
|
|
1059
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1060
|
+
body: requestBody
|
|
1061
|
+
}, {
|
|
1062
|
+
statusCode: c.res.status,
|
|
1063
|
+
responseTime: responseTime / 1e3,
|
|
1064
|
+
headers: convertHeaders(c.res.headers),
|
|
1065
|
+
size: responseSize,
|
|
1066
|
+
body: responseBody
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
622
1069
|
c.res = response;
|
|
623
1070
|
};
|
|
624
1071
|
}, "getMiddleware");
|
|
@@ -648,6 +1095,14 @@ var measureResponseSize = /* @__PURE__ */ __name(async (response) => {
|
|
|
648
1095
|
newResponse1
|
|
649
1096
|
];
|
|
650
1097
|
}, "measureResponseSize");
|
|
1098
|
+
var getResponseBody = /* @__PURE__ */ __name(async (response) => {
|
|
1099
|
+
const [newResponse1, newResponse2] = await teeResponse(response);
|
|
1100
|
+
const responseBuffer = Buffer.from(await newResponse2.arrayBuffer());
|
|
1101
|
+
return [
|
|
1102
|
+
responseBuffer,
|
|
1103
|
+
newResponse1
|
|
1104
|
+
];
|
|
1105
|
+
}, "getResponseBody");
|
|
651
1106
|
var getResponseJson = /* @__PURE__ */ __name(async (response) => {
|
|
652
1107
|
const contentType = response.headers.get("content-type");
|
|
653
1108
|
if (contentType && contentType.includes("application/json")) {
|