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/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,361 @@ 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?$/,
|
|
275
|
+
/\/_?health[_-]?checks?$/,
|
|
276
|
+
/\/_?heart[_-]?beats?$/,
|
|
277
|
+
/\/ping$/,
|
|
278
|
+
/\/ready$/,
|
|
279
|
+
/\/live$/
|
|
280
|
+
];
|
|
281
|
+
var MASK_QUERY_PARAM_PATTERNS = [
|
|
282
|
+
/auth/i,
|
|
283
|
+
/api-?key/i,
|
|
284
|
+
/secret/i,
|
|
285
|
+
/token/i,
|
|
286
|
+
/password/i,
|
|
287
|
+
/pwd/i
|
|
288
|
+
];
|
|
289
|
+
var MASK_HEADER_PATTERNS = [
|
|
290
|
+
/auth/i,
|
|
291
|
+
/api-?key/i,
|
|
292
|
+
/secret/i,
|
|
293
|
+
/token/i,
|
|
294
|
+
/cookie/i
|
|
295
|
+
];
|
|
296
|
+
var DEFAULT_CONFIG = {
|
|
297
|
+
enabled: false,
|
|
298
|
+
logQueryParams: true,
|
|
299
|
+
logRequestHeaders: false,
|
|
300
|
+
logRequestBody: false,
|
|
301
|
+
logResponseHeaders: true,
|
|
302
|
+
logResponseBody: false,
|
|
303
|
+
maskQueryParams: [],
|
|
304
|
+
maskHeaders: [],
|
|
305
|
+
excludePaths: []
|
|
306
|
+
};
|
|
307
|
+
var _RequestLogger = class _RequestLogger {
|
|
308
|
+
config;
|
|
309
|
+
enabled;
|
|
310
|
+
suspendUntil = null;
|
|
311
|
+
pendingWrites = [];
|
|
312
|
+
currentFile = null;
|
|
313
|
+
files = [];
|
|
314
|
+
maintainIntervalId;
|
|
315
|
+
constructor(config) {
|
|
316
|
+
this.config = {
|
|
317
|
+
...DEFAULT_CONFIG,
|
|
318
|
+
...config
|
|
319
|
+
};
|
|
320
|
+
this.enabled = this.config.enabled && checkWritableFs();
|
|
321
|
+
if (this.enabled) {
|
|
322
|
+
this.maintainIntervalId = setInterval(() => {
|
|
323
|
+
this.maintain();
|
|
324
|
+
}, 1e3);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
shouldExcludePath(urlPath) {
|
|
328
|
+
const patterns = [
|
|
329
|
+
...this.config.excludePaths,
|
|
330
|
+
...EXCLUDE_PATH_PATTERNS
|
|
331
|
+
];
|
|
332
|
+
return matchPatterns(urlPath, patterns);
|
|
333
|
+
}
|
|
334
|
+
shouldMaskQueryParam(name) {
|
|
335
|
+
const patterns = [
|
|
336
|
+
...this.config.maskQueryParams,
|
|
337
|
+
...MASK_QUERY_PARAM_PATTERNS
|
|
338
|
+
];
|
|
339
|
+
return matchPatterns(name, patterns);
|
|
340
|
+
}
|
|
341
|
+
shouldMaskHeader(name) {
|
|
342
|
+
const patterns = [
|
|
343
|
+
...this.config.maskHeaders,
|
|
344
|
+
...MASK_HEADER_PATTERNS
|
|
345
|
+
];
|
|
346
|
+
return matchPatterns(name, patterns);
|
|
347
|
+
}
|
|
348
|
+
hasSupportedContentType(headers) {
|
|
349
|
+
var _a2;
|
|
350
|
+
const contentType = (_a2 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a2[1];
|
|
351
|
+
return contentType !== void 0 && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
|
|
352
|
+
}
|
|
353
|
+
maskQueryParams(search) {
|
|
354
|
+
const params = new URLSearchParams(search);
|
|
355
|
+
for (const [key] of params) {
|
|
356
|
+
if (this.shouldMaskQueryParam(key)) {
|
|
357
|
+
params.set(key, MASKED);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return params.toString();
|
|
361
|
+
}
|
|
362
|
+
maskHeaders(headers) {
|
|
363
|
+
return headers.map(([k, v]) => [
|
|
364
|
+
k,
|
|
365
|
+
this.shouldMaskHeader(k) ? MASKED : v
|
|
366
|
+
]);
|
|
367
|
+
}
|
|
368
|
+
logRequest(request, response) {
|
|
369
|
+
var _a2, _b;
|
|
370
|
+
if (!this.enabled || this.suspendUntil !== null) return;
|
|
371
|
+
const url = new URL(request.url);
|
|
372
|
+
const path = request.path ?? url.pathname;
|
|
373
|
+
if (this.shouldExcludePath(path) || (((_b = (_a2 = this.config).excludeCallback) == null ? void 0 : _b.call(_a2, request, response)) ?? false)) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
|
|
377
|
+
request.url = url.toString();
|
|
378
|
+
request.headers = this.config.logRequestHeaders ? this.maskHeaders(request.headers) : [];
|
|
379
|
+
response.headers = this.config.logResponseHeaders ? this.maskHeaders(response.headers) : [];
|
|
380
|
+
if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
|
|
381
|
+
request.body = void 0;
|
|
382
|
+
} else if (request.body) {
|
|
383
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
384
|
+
request.body = BODY_TOO_LARGE;
|
|
385
|
+
} else if (this.config.maskRequestBodyCallback) {
|
|
386
|
+
try {
|
|
387
|
+
request.body = this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;
|
|
388
|
+
if (request.body.length > MAX_BODY_SIZE) {
|
|
389
|
+
request.body = BODY_TOO_LARGE;
|
|
390
|
+
}
|
|
391
|
+
} catch {
|
|
392
|
+
request.body = void 0;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
|
|
397
|
+
response.body = void 0;
|
|
398
|
+
} else if (response.body) {
|
|
399
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
400
|
+
response.body = BODY_TOO_LARGE;
|
|
401
|
+
} else if (this.config.maskResponseBodyCallback) {
|
|
402
|
+
try {
|
|
403
|
+
response.body = this.config.maskResponseBodyCallback(request, response) ?? BODY_MASKED;
|
|
404
|
+
if (response.body.length > MAX_BODY_SIZE) {
|
|
405
|
+
response.body = BODY_TOO_LARGE;
|
|
406
|
+
}
|
|
407
|
+
} catch {
|
|
408
|
+
response.body = void 0;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
const item = {
|
|
413
|
+
uuid: randomUUID2(),
|
|
414
|
+
request: skipEmptyValues(request),
|
|
415
|
+
response: skipEmptyValues(response)
|
|
416
|
+
};
|
|
417
|
+
[
|
|
418
|
+
item.request.body,
|
|
419
|
+
item.response.body
|
|
420
|
+
].forEach((body) => {
|
|
421
|
+
if (body) {
|
|
422
|
+
body.toJSON = function() {
|
|
423
|
+
return this.toString("base64");
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
this.pendingWrites.push(JSON.stringify(item));
|
|
428
|
+
if (this.pendingWrites.length > MAX_PENDING_WRITES) {
|
|
429
|
+
this.pendingWrites.shift();
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async writeToFile() {
|
|
433
|
+
if (!this.enabled || this.pendingWrites.length === 0) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (!this.currentFile) {
|
|
437
|
+
this.currentFile = new TempGzipFile();
|
|
438
|
+
}
|
|
439
|
+
while (this.pendingWrites.length > 0) {
|
|
440
|
+
const item = this.pendingWrites.shift();
|
|
441
|
+
if (item) {
|
|
442
|
+
await this.currentFile.writeLine(Buffer3.from(item));
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
getFile() {
|
|
447
|
+
return this.files.shift();
|
|
448
|
+
}
|
|
449
|
+
retryFileLater(file) {
|
|
450
|
+
this.files.unshift(file);
|
|
451
|
+
}
|
|
452
|
+
async rotateFile() {
|
|
453
|
+
if (this.currentFile) {
|
|
454
|
+
await this.currentFile.close();
|
|
455
|
+
this.files.push(this.currentFile);
|
|
456
|
+
this.currentFile = null;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
async maintain() {
|
|
460
|
+
await this.writeToFile();
|
|
461
|
+
if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
|
|
462
|
+
await this.rotateFile();
|
|
463
|
+
}
|
|
464
|
+
while (this.files.length > MAX_FILES) {
|
|
465
|
+
const file = this.files.shift();
|
|
466
|
+
file == null ? void 0 : file.delete();
|
|
467
|
+
}
|
|
468
|
+
if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
|
|
469
|
+
this.suspendUntil = null;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
async clear() {
|
|
473
|
+
this.pendingWrites = [];
|
|
474
|
+
await this.rotateFile();
|
|
475
|
+
this.files.forEach((file) => {
|
|
476
|
+
file.delete();
|
|
477
|
+
});
|
|
478
|
+
this.files = [];
|
|
479
|
+
}
|
|
480
|
+
async close() {
|
|
481
|
+
this.enabled = false;
|
|
482
|
+
await this.clear();
|
|
483
|
+
if (this.maintainIntervalId) {
|
|
484
|
+
clearInterval(this.maintainIntervalId);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
__name(_RequestLogger, "RequestLogger");
|
|
489
|
+
var RequestLogger = _RequestLogger;
|
|
490
|
+
function convertHeaders(headers) {
|
|
491
|
+
if (headers instanceof Headers) {
|
|
492
|
+
return Array.from(headers.entries());
|
|
493
|
+
}
|
|
494
|
+
return Object.entries(headers).flatMap(([key, value]) => {
|
|
495
|
+
if (value === void 0) {
|
|
496
|
+
return [];
|
|
497
|
+
}
|
|
498
|
+
if (Array.isArray(value)) {
|
|
499
|
+
return value.map((v) => [
|
|
500
|
+
key,
|
|
501
|
+
v
|
|
502
|
+
]);
|
|
503
|
+
}
|
|
504
|
+
return [
|
|
505
|
+
[
|
|
506
|
+
key,
|
|
507
|
+
value.toString()
|
|
508
|
+
]
|
|
509
|
+
];
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
__name(convertHeaders, "convertHeaders");
|
|
513
|
+
function matchPatterns(value, patterns) {
|
|
514
|
+
return patterns.some((pattern) => {
|
|
515
|
+
return pattern.test(value);
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
__name(matchPatterns, "matchPatterns");
|
|
519
|
+
function skipEmptyValues(data) {
|
|
520
|
+
return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
|
|
521
|
+
if (v == null || Number.isNaN(v)) return false;
|
|
522
|
+
if (Array.isArray(v) || Buffer3.isBuffer(v) || typeof v === "string") {
|
|
523
|
+
return v.length > 0;
|
|
524
|
+
}
|
|
525
|
+
return true;
|
|
526
|
+
}));
|
|
527
|
+
}
|
|
528
|
+
__name(skipEmptyValues, "skipEmptyValues");
|
|
529
|
+
function checkWritableFs() {
|
|
530
|
+
try {
|
|
531
|
+
const testPath = join2(tmpdir2(), `apitally-${randomUUID2()}`);
|
|
532
|
+
writeFileSync(testPath, "test");
|
|
533
|
+
unlinkSync2(testPath);
|
|
534
|
+
return true;
|
|
535
|
+
} catch (error) {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
__name(checkWritableFs, "checkWritableFs");
|
|
540
|
+
|
|
186
541
|
// src/common/serverErrorCounter.ts
|
|
187
542
|
import { createHash } from "crypto";
|
|
188
543
|
var MAX_MSG_LENGTH = 2048;
|
|
@@ -355,11 +710,12 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
355
710
|
startupData;
|
|
356
711
|
startupDataSent = false;
|
|
357
712
|
requestCounter;
|
|
713
|
+
requestLogger;
|
|
358
714
|
validationErrorCounter;
|
|
359
715
|
serverErrorCounter;
|
|
360
716
|
consumerRegistry;
|
|
361
717
|
logger;
|
|
362
|
-
constructor({ clientId, env = "dev", logger }) {
|
|
718
|
+
constructor({ clientId, env = "dev", requestLoggingConfig, logger }) {
|
|
363
719
|
if (_ApitallyClient.instance) {
|
|
364
720
|
throw new Error("Apitally client is already initialized");
|
|
365
721
|
}
|
|
@@ -372,9 +728,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
372
728
|
_ApitallyClient.instance = this;
|
|
373
729
|
this.clientId = clientId;
|
|
374
730
|
this.env = env;
|
|
375
|
-
this.instanceUuid =
|
|
731
|
+
this.instanceUuid = randomUUID3();
|
|
376
732
|
this.syncDataQueue = [];
|
|
377
733
|
this.requestCounter = new RequestCounter();
|
|
734
|
+
this.requestLogger = new RequestLogger(requestLoggingConfig);
|
|
378
735
|
this.validationErrorCounter = new ValidationErrorCounter();
|
|
379
736
|
this.serverErrorCounter = new ServerErrorCounter();
|
|
380
737
|
this.consumerRegistry = new ConsumerRegistry();
|
|
@@ -396,6 +753,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
396
753
|
async handleShutdown() {
|
|
397
754
|
this.stopSync();
|
|
398
755
|
await this.sendSyncData();
|
|
756
|
+
await this.sendLogData();
|
|
757
|
+
await this.requestLogger.close();
|
|
399
758
|
_ApitallyClient.instance = void 0;
|
|
400
759
|
}
|
|
401
760
|
getHubUrlPrefix() {
|
|
@@ -442,7 +801,8 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
442
801
|
async sync() {
|
|
443
802
|
try {
|
|
444
803
|
const promises = [
|
|
445
|
-
this.sendSyncData()
|
|
804
|
+
this.sendSyncData(),
|
|
805
|
+
this.sendLogData()
|
|
446
806
|
];
|
|
447
807
|
if (!this.startupDataSent) {
|
|
448
808
|
promises.push(this.sendStartupData());
|
|
@@ -470,7 +830,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
470
830
|
this.logger.debug("Sending startup data to Apitally Hub");
|
|
471
831
|
const payload = {
|
|
472
832
|
instance_uuid: this.instanceUuid,
|
|
473
|
-
message_uuid:
|
|
833
|
+
message_uuid: randomUUID3(),
|
|
474
834
|
...this.startupData
|
|
475
835
|
};
|
|
476
836
|
try {
|
|
@@ -492,7 +852,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
492
852
|
const newPayload = {
|
|
493
853
|
time_offset: 0,
|
|
494
854
|
instance_uuid: this.instanceUuid,
|
|
495
|
-
message_uuid:
|
|
855
|
+
message_uuid: randomUUID3(),
|
|
496
856
|
requests: this.requestCounter.getAndResetRequests(),
|
|
497
857
|
validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
|
|
498
858
|
server_errors: this.serverErrorCounter.getAndResetServerErrors(),
|
|
@@ -511,8 +871,7 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
511
871
|
const timeOffset = Date.now() - time;
|
|
512
872
|
if (timeOffset <= MAX_QUEUE_TIME) {
|
|
513
873
|
if (i > 0) {
|
|
514
|
-
|
|
515
|
-
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
874
|
+
await this.randomDelay();
|
|
516
875
|
}
|
|
517
876
|
payload.time_offset = timeOffset / 1e3;
|
|
518
877
|
await this.sendData("sync", payload);
|
|
@@ -531,6 +890,52 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
531
890
|
}
|
|
532
891
|
}
|
|
533
892
|
}
|
|
893
|
+
async sendLogData() {
|
|
894
|
+
this.logger.debug("Sending request log data to Apitally Hub");
|
|
895
|
+
await this.requestLogger.rotateFile();
|
|
896
|
+
const fetchWithRetry = fetchRetry(fetch, {
|
|
897
|
+
retries: 3,
|
|
898
|
+
retryDelay: 1e3,
|
|
899
|
+
retryOn: [
|
|
900
|
+
408,
|
|
901
|
+
429,
|
|
902
|
+
500,
|
|
903
|
+
502,
|
|
904
|
+
503,
|
|
905
|
+
504
|
|
906
|
+
]
|
|
907
|
+
});
|
|
908
|
+
let i = 0;
|
|
909
|
+
let logFile;
|
|
910
|
+
while (logFile = this.requestLogger.getFile()) {
|
|
911
|
+
if (i > 0) {
|
|
912
|
+
await this.randomDelay();
|
|
913
|
+
}
|
|
914
|
+
try {
|
|
915
|
+
const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
|
|
916
|
+
method: "POST",
|
|
917
|
+
body: await logFile.getContent()
|
|
918
|
+
});
|
|
919
|
+
if (response.status === 402 && response.headers.has("Retry-After")) {
|
|
920
|
+
const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
|
|
921
|
+
if (retryAfter > 0) {
|
|
922
|
+
this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
|
|
923
|
+
this.requestLogger.clear();
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
if (!response.ok) {
|
|
928
|
+
throw new HTTPError(response);
|
|
929
|
+
}
|
|
930
|
+
logFile.delete();
|
|
931
|
+
} catch (error) {
|
|
932
|
+
this.requestLogger.retryFileLater(logFile);
|
|
933
|
+
break;
|
|
934
|
+
}
|
|
935
|
+
i++;
|
|
936
|
+
if (i >= 10) break;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
534
939
|
handleHubError(error) {
|
|
535
940
|
if (error instanceof HTTPError) {
|
|
536
941
|
if (error.response.status === 404) {
|
|
@@ -545,6 +950,10 @@ var _ApitallyClient = class _ApitallyClient {
|
|
|
545
950
|
}
|
|
546
951
|
return false;
|
|
547
952
|
}
|
|
953
|
+
async randomDelay() {
|
|
954
|
+
const delay = 100 + Math.random() * 200;
|
|
955
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
956
|
+
}
|
|
548
957
|
};
|
|
549
958
|
__name(_ApitallyClient, "ApitallyClient");
|
|
550
959
|
__publicField(_ApitallyClient, "instance");
|
|
@@ -577,13 +986,13 @@ var useApitally = /* @__PURE__ */ __name((app, config) => {
|
|
|
577
986
|
}, 1e3);
|
|
578
987
|
}, "useApitally");
|
|
579
988
|
var getMiddleware = /* @__PURE__ */ __name((client) => {
|
|
580
|
-
const zodInstalled = getPackageVersion("zod") !== null;
|
|
581
989
|
return async (c, next) => {
|
|
582
990
|
const startTime = performance.now();
|
|
583
991
|
await next();
|
|
584
992
|
let response;
|
|
585
993
|
const responseTime = performance.now() - startTime;
|
|
586
994
|
const [responseSize, newResponse] = await measureResponseSize(c.res);
|
|
995
|
+
const requestSize = c.req.header("Content-Length");
|
|
587
996
|
const consumer = getConsumer(c);
|
|
588
997
|
client.consumerRegistry.addOrUpdateConsumer(consumer);
|
|
589
998
|
client.requestCounter.addRequest({
|
|
@@ -592,11 +1001,11 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
|
|
|
592
1001
|
path: c.req.routePath,
|
|
593
1002
|
statusCode: c.res.status,
|
|
594
1003
|
responseTime,
|
|
595
|
-
requestSize
|
|
1004
|
+
requestSize,
|
|
596
1005
|
responseSize
|
|
597
1006
|
});
|
|
598
1007
|
response = newResponse;
|
|
599
|
-
if (c.res.status === 400
|
|
1008
|
+
if (c.res.status === 400) {
|
|
600
1009
|
const [responseJson, newResponse2] = await getResponseJson(response);
|
|
601
1010
|
const validationErrors = extractZodErrors(responseJson);
|
|
602
1011
|
validationErrors.forEach((error) => {
|
|
@@ -619,6 +1028,34 @@ var getMiddleware = /* @__PURE__ */ __name((client) => {
|
|
|
619
1028
|
traceback: c.error.stack || ""
|
|
620
1029
|
});
|
|
621
1030
|
}
|
|
1031
|
+
if (client.requestLogger.enabled) {
|
|
1032
|
+
let requestBody;
|
|
1033
|
+
let responseBody;
|
|
1034
|
+
let newResponse2 = response;
|
|
1035
|
+
if (client.requestLogger.config.logRequestBody) {
|
|
1036
|
+
requestBody = Buffer.from(await c.req.arrayBuffer());
|
|
1037
|
+
}
|
|
1038
|
+
if (client.requestLogger.config.logResponseBody) {
|
|
1039
|
+
[responseBody, newResponse2] = await getResponseBody(response);
|
|
1040
|
+
response = newResponse2;
|
|
1041
|
+
}
|
|
1042
|
+
client.requestLogger.logRequest({
|
|
1043
|
+
timestamp: Date.now() / 1e3,
|
|
1044
|
+
method: c.req.method,
|
|
1045
|
+
path: c.req.routePath,
|
|
1046
|
+
url: c.req.url,
|
|
1047
|
+
headers: convertHeaders(c.req.header()),
|
|
1048
|
+
size: Number(requestSize),
|
|
1049
|
+
consumer: consumer == null ? void 0 : consumer.identifier,
|
|
1050
|
+
body: requestBody
|
|
1051
|
+
}, {
|
|
1052
|
+
statusCode: c.res.status,
|
|
1053
|
+
responseTime: responseTime / 1e3,
|
|
1054
|
+
headers: convertHeaders(c.res.headers),
|
|
1055
|
+
size: responseSize,
|
|
1056
|
+
body: responseBody
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
622
1059
|
c.res = response;
|
|
623
1060
|
};
|
|
624
1061
|
}, "getMiddleware");
|
|
@@ -648,6 +1085,14 @@ var measureResponseSize = /* @__PURE__ */ __name(async (response) => {
|
|
|
648
1085
|
newResponse1
|
|
649
1086
|
];
|
|
650
1087
|
}, "measureResponseSize");
|
|
1088
|
+
var getResponseBody = /* @__PURE__ */ __name(async (response) => {
|
|
1089
|
+
const [newResponse1, newResponse2] = await teeResponse(response);
|
|
1090
|
+
const responseBuffer = Buffer.from(await newResponse2.arrayBuffer());
|
|
1091
|
+
return [
|
|
1092
|
+
responseBuffer,
|
|
1093
|
+
newResponse1
|
|
1094
|
+
];
|
|
1095
|
+
}, "getResponseBody");
|
|
651
1096
|
var getResponseJson = /* @__PURE__ */ __name(async (response) => {
|
|
652
1097
|
const contentType = response.headers.get("content-type");
|
|
653
1098
|
if (contentType && contentType.includes("application/json")) {
|