UNPKG

apitally

Version:

Simple API monitoring & analytics for REST APIs built with Express, Fastify, NestJS, AdonisJS, Hono, H3, Elysia, Hapi, and Koa.

180 lines 7.98 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); import { AsyncLocalStorage } from "node:async_hooks"; import { performance } from "node:perf_hooks"; import { ApitallyClient } from "../common/client.js"; import { consumerFromStringOrObject } from "../common/consumerRegistry.js"; import { convertHeaders } from "../common/requestLogger.js"; import { handleHapiRequestEvent, patchConsole, patchPinoLogger, patchWinston } from "../loggers/index.js"; import { getAppInfo, isBoom } from "./utils.js"; const START_TIME_SYMBOL = Symbol("apitally.startTime"); const REQUEST_BODY_SYMBOL = Symbol("apitally.requestBody"); const REQUEST_SIZE_SYMBOL = Symbol("apitally.requestSize"); const RESPONSE_BODY_SYMBOL = Symbol("apitally.responseBody"); const RESPONSE_SIZE_SYMBOL = Symbol("apitally.responseSize"); const LOGS_SYMBOL = Symbol("apitally.logs"); function apitallyPlugin(config) { const client = new ApitallyClient(config); const logsContext = new AsyncLocalStorage(); let pinoLoggerPatched = false; if (client.requestLogger.enabled && client.requestLogger.config.captureLogs) { patchConsole(logsContext); patchWinston(logsContext); } return { name: "apitally", register: /* @__PURE__ */ __name(async function(server) { server.ext("onPostStart", async () => { if ("logger" in server && client.requestLogger.enabled && client.requestLogger.config.captureLogs) { pinoLoggerPatched = await patchPinoLogger(server.logger, logsContext); } }); server.ext("onPostStart", () => { const appInfo = getAppInfo(server, config.appVersion); client.setStartupData(appInfo); client.startSync(); }); server.ext("onPreStop", async () => { await client.handleShutdown(); }); server.events.on("request", (_, event) => { if (!pinoLoggerPatched && client.requestLogger.enabled && client.requestLogger.config.captureLogs && event.channel === "app") { handleHapiRequestEvent(event, logsContext); } }); server.ext("onRequest", async (request, h) => { if (!client.isEnabled() || request.method.toUpperCase() === "OPTIONS") { return h.continue; } request[START_TIME_SYMBOL] = performance.now(); const lifecycle = request._lifecycle.bind(request); const logs = []; request[LOGS_SYMBOL] = logs; request._lifecycle = () => logsContext.run(logs, lifecycle); const captureRequestBody = client.requestLogger.enabled && client.requestLogger.config.logRequestBody && client.requestLogger.isSupportedContentType(request.headers["content-type"]); const chunks = []; let size = 0; request.events.on("peek", (chunk, encoding) => { if (captureRequestBody) { chunks.push(Buffer.from(chunk, encoding)); } size += Buffer.byteLength(chunk, encoding); }); request.events.once("finish", () => { if (chunks.length > 0) { request[REQUEST_BODY_SYMBOL] = Buffer.concat(chunks); } request[REQUEST_SIZE_SYMBOL] = size; }); return h.continue; }); server.ext("onPreResponse", async (request, h) => { if (!client.isEnabled() || request.method.toUpperCase() === "OPTIONS" || isBoom(request.response)) { return h.continue; } const captureResponseBody = client.requestLogger.enabled && client.requestLogger.config.logResponseBody && client.requestLogger.isSupportedContentType(request.response.contentType); const chunks = []; let size = 0; request.response.events.on("peek", (chunk, encoding) => { if (captureResponseBody) { chunks.push(Buffer.from(chunk, encoding)); } size += Buffer.byteLength(chunk, encoding); }); request.response.events.once("finish", () => { if (chunks.length > 0) { request[RESPONSE_BODY_SYMBOL] = Buffer.concat(chunks); } request[RESPONSE_SIZE_SYMBOL] = size; }); return h.continue; }); server.ext("onPostResponse", async (request, h) => { var _a, _b, _c; if (!client.isEnabled() || request.method.toUpperCase() === "OPTIONS") { return h.continue; } const startTime = request[START_TIME_SYMBOL]; const responseTime = startTime ? performance.now() - startTime : 0; const timestamp = (Date.now() - responseTime) / 1e3; const requestBody = request[REQUEST_BODY_SYMBOL]; const requestSize = request[REQUEST_SIZE_SYMBOL]; const response = request.response; let statusCode; let responseHeaders; let responseBody = request[RESPONSE_BODY_SYMBOL]; let responseSize = request[RESPONSE_SIZE_SYMBOL]; const error = response._error instanceof Error ? response._error : void 0; if (isBoom(response)) { statusCode = ((_a = response.output) == null ? void 0 : _a.statusCode) ?? 500; responseHeaders = ((_b = response.output) == null ? void 0 : _b.headers) ?? {}; } else { statusCode = response.statusCode ?? 200; responseHeaders = response.headers ?? {}; } if (!responseBody && response._payload && client.requestLogger.enabled && client.requestLogger.config.logResponseBody && (isBoom(response) || client.requestLogger.isSupportedContentType(response.contentType))) { const responsePayload = response._payload; if (responsePayload && responsePayload._data && responsePayload._encoding && typeof responsePayload._data === "string" && typeof responsePayload._encoding === "string") { responseBody = Buffer.from(responsePayload._data, responsePayload._encoding); } } if (!responseSize && responseBody) { responseSize = responseBody.length; } const consumer = request.apitallyConsumer ? consumerFromStringOrObject(request.apitallyConsumer) : null; client.consumerRegistry.addOrUpdateConsumer(consumer); if (request.route.path) { client.requestCounter.addRequest({ consumer: consumer == null ? void 0 : consumer.identifier, method: request.method, path: request.route.path, statusCode, responseTime, requestSize, responseSize }); if (statusCode === 500 && error) { client.serverErrorCounter.addServerError({ consumer: consumer == null ? void 0 : consumer.identifier, method: request.method, path: request.route.path, type: error.name, msg: error.message, traceback: error.stack || "" }); } } if (client.requestLogger.enabled) { const logs = request[LOGS_SYMBOL]; client.requestLogger.logRequest({ timestamp, consumer: consumer == null ? void 0 : consumer.identifier, method: request.method, path: request.route.path, url: ((_c = request.url) == null ? void 0 : _c.href) || "", headers: convertHeaders(request.headers), size: requestSize, body: requestBody }, { statusCode, responseTime: responseTime / 1e3, headers: convertHeaders(responseHeaders), size: responseSize, body: responseBody }, error, logs); } return h.continue; }); }, "register") }; } __name(apitallyPlugin, "apitallyPlugin"); function setConsumer(request, consumer) { request.apitallyConsumer = consumer || void 0; } __name(setConsumer, "setConsumer"); export { apitallyPlugin as default, setConsumer }; //# sourceMappingURL=plugin.js.map